Skip to content

Instantly share code, notes, and snippets.

@pts
Last active March 14, 2026 20:58
Show Gist options
  • Select an option

  • Save pts/5f496de8caabe375e5bd480ffe0d70fa to your computer and use it in GitHub Desktop.

Select an option

Save pts/5f496de8caabe375e5bd480ffe0d70fa to your computer and use it in GitHub Desktop.
drload.nasm: port of FreeDOS and DR-DOS loader for ldebug payload to NASM 0.98.39
;
; drload.nasm: FreeDOS and DR-DOS loader for ldebug payload
; adapted to NASM 0.98.39 without lmacros by pts@fazekas.hu at Sat Mar 14 12:38:44 CET 2026
; based on drload.asm https://hg.pushbx.org/ecm/ldosboot/file/1d5b71d420de/drload.asm
;
; Preparation: wget -nv -O ldebug10.zip https://pushbx.org/ecm/download/ldebug/ldebug10.zip && TZ=GMT unzip -o ldebug10.zip bin/ldebug.com
; Compile with: nasm -O0 -w+orphan-labels -f bin -D_PAYLOAD_FILE='bin/ldebug.com', 0x1070' -o ldebug.bin drload.nasm
; Minimum NASM version required: 0.98.39
; To try it, replace kernel.sys with the contents of ldebug.bin on a bootable FreeDOS filesystem. (As a backup, rename kernel.sys to fdkernel.sys first.)
;
%if 0
Loader for Enhanced DR-DOS single-file load
by E. C. Masloch, 2024
Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
%endif
; --- Configuration (from the NASM command-line).
%ifndef _PAYLOAD_FILE
%define _PAYLOAD_FILE 'edrdos.bin'
%endif
%ifdef _RPL
%assign _RPL _RPL
%else
%define _RPL 1 ; support RPL and do not overwrite it
%endif
%ifdef _QUERY_PATCH
%assign _QUERY_PATCH _QUERY_PATCH
%else
%define _QUERY_PATCH 1 ; use new style patch of CHS/LBA/geometry
%endif
%ifdef _QUERY_DEFAULT
%assign _QUERY_DEFAULT _QUERY_DEFAULT
%else
%define _QUERY_DEFAULT 0
%endif
%ifdef _EXEC_OFFSET
%assign _EXEC_OFFSET _EXEC_OFFSET
%else
%define _EXEC_OFFSET 0
%endif
%ifdef _EXEC_SEGMENT
%assign _EXEC_SEGMENT _EXEC_SEGMENT
%else
%define _EXEC_SEGMENT 0
%endif
%ifdef _STACKSIZE
%assign _STACKSIZE _STACKSIZE
%else
%define _STACKSIZE 2048
%endif
%if _STACKSIZE<256
%error Too small stack size.
times -1 nop ; Make sure NASM 0.98.39 compilation fails.
%elif _STACKSIZE>3*1024
; Note that we use 8 KiB for SectorSeg, 8 KiB for FATSeg,
; 512 bytes + (ebpbNew - bpbNew) for the boot sector,
; and a few paragraphs left for MCBs and headers. As the
; protocol is implemented with a 20 KiB reserved area (below
; EBDA / RPL / end of low memory), this results in a maximum
; stack size around 3 KiB (substantially below 4 KiB).
%error Too large stack size.
times -1 nop ; Make sure NASM 0.98.39 compilation fails.
%endif
; ---
bits 16
cpu 8086
;org 42 ; Output is the same with any org.
;lsvefNoDataStart equ 1 ; Unused.
;lsvefPartitionNumber equ 2 ; Unused.
lsvclSignature equ 'CL'
lsvclBufferLength equ 256
struc BS
bsJump: resb 3
bsOEM: resb 8
bsBPB:
BS.size: equ $
endstruc
struc EBPB ; BPB sec
bpbBytesPerSector: resw 1 ; offset 00h 0Bh
bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh
bpbReservedSectors: resw 1 ; offset 03h 0Eh
bpbNumFATs: resb 1 ; offset 05h 10h
bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32
bpbTotalSectors: resw 1 ; offset 08h 13h
bpbMediaID: resb 1 ; offset 0Ah 15h
bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32
bpbCHSSectors: resw 1 ; offset 0Dh 18h
bpbCHSHeads: resw 1 ; offset 0Fh 1Ah
bpbHiddenSectors: resd 1 ; offset 11h 1Ch
bpbTotalSectorsLarge: resd 1 ; offset 15h 20h
bpbNew: ; offset 19h 24h
ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h
ebpbFSFlags: resw 1 ; offset 1Dh 28h
ebpbFSVersion: resw 1 ; offset 1Fh 2Ah
ebpbRootCluster: resd 1 ; offset 21h 2Ch
ebpbFSINFOSector: resw 1 ; offset 25h 30h
ebpbBackupSector: resw 1 ; offset 27h 32h
ebpbReserved: resb 12 ; offset 29h 34h
ebpbNew: ; offset 35h 40h
EBPB.size: equ $
endstruc
struc BPBN ; ofs B16 S16 B32 S32
bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h
resb 1 ; 01h 1Ah 25h 36h 41h
bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN
bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h
bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h
bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h
BPBN.size: equ $
endstruc ; 1Ah 33h 3Eh 4Fh 5Ah
struc LOADSTACKVARS ; , -0x10 ; NASM 0.98.39 doesn't support this 2nd argument.
;lsvFirstCluster: resd 1
lsvFirstCluster: equ $-0x10+0
;lsvFATSector: resd 1
lsvFATSector: equ $-0x10+4
;lsvFATSeg: resw 1
lsvFATSeg: equ $-0x10+8
;lsvLoadSeg: resw 1
lsvLoadSeg: equ $-0x10+0xa
;lsvDataStart: resd 1
lsvDataStart: equ $-0x10+0xc
.size equ 0x10
endstruc
struc LOADDATA ; , LOADSTACKVARS-0x10 ; NASM 0.98.39 doesn't support this 2nd argument.
;ldMemoryTop: resw 1
ldMemoryTop: equ $-0x20+0
;ldLoadTop: resw 1
ldLoadTop: equ $-0x20+2
;ldSectorSeg: resw 1
ldSectorSeg: equ $-0x20+4
;ldFATType: resb 1
ldFATType: equ $-0x20+6
;ldHasLBA: resb 1
ldHasLBA: equ $-0x20+7
;ldClusterSize: resw 1
ldClusterSize: equ $-0x20+8
;ldParaPerSector: resw 1
ldParaPerSector: equ $-0x20+0xa
;ldLoadingSeg: ; word
ldLoadingSeg: equ $-0x20+0xc
;ldQueryPatchValue: ; word
ldQueryPatchValue: equ $-0x20+0xc
;lsvCommandLine: ; word
lsvCommandLine: equ $-0x20+0xc
;.start: equ $-lsvclBufferLength
lsvCommandLine.start: equ $-0x20+0xc-lsvclBufferLength
;.signature: resw 1
lsvCommandLine.signature: equ $-0x20+0xc
;ldLoadUntilSeg: ; word
ldLoadUntilSeg: equ $-0x20+0xe
;lsvExtra: ; word
lsvExtra: equ $-0x20+0xe
;.partition: resb 1 ; byte
lsvExtra.partition: equ $-0x20+0xe
;.flags: resb 1 ; byte
lsvExtra.flags: equ $-0x20+0xf
.size equ 0x10
endstruc
struc LOADCMDLINE ; , LOADDATA-lsvclBufferLength ; NASM 0.98.39 doesn't support this 2nd argument.
ldCommandLine: equ $-0x20-lsvclBufferLength
;.start: resb lsvclBufferLength
ldCommandLine.start: equ $-0x20-lsvclBufferLength
.size: equ lsvclBufferLength
endstruc
; This is the FreeDOS compatible entry point.
; Supports FAT32 too.
; cs:ip = 60h:0 (or 70h:0 for EDR-DOS entry)
; whole load file loaded
; first cluster of load file: not given!
; first data sector: not given!
; int 1Eh not modified, original address: not given!
; bl = load unit (or dl for EDR-DOS entry) (not used by us)
; ss:bp -> boot sector with (E)BPB,
; load unit field set, hidden sectors set
; (usually at 1FE0h:7C00h)
; NEW: word [ss:bp - 14h] = "CL" to indicate command line
; then ss:bp - 114h -> 256 byte ASCIZ string
freedos_entry:
lea bx, [bp+lsvCommandLine.start] ; ss:bx -> command line buffer, if any
cmp bp, -lsvCommandLine.start ; enough data below bp to hold buffer ?
jb short .2 ; no -->
cmp sp, bx ; sp below-or-equal would-be buffer ?
jbe short .canbevalid ; yes, can be valid --> (and word access valid)
.2:
cmp bp, byte -lsvCommandLine.signature ; enough data below bp to hold our lsv ?
jae short .3 ; yes -->
test bp, 1 ; valid to access even-aligned words ?
jz short .3 ; yes -->
; maybe not
.error:
call error
db 'Invalid base pointer in FreeDOS entrypoint.', 0
.3:
and word [bp+lsvCommandLine.signature], byte 0 ; invalidate signature
.canbevalid:
cmp word [bp+lsvCommandLine.signature], lsvclSignature ; valid signature ?
je short .4 ; yes, keep bx pointing at buffer
lea bx, [bp+lsvCommandLine.signature] ; no, ss:bx -> lsv with signature
.4:
cmp sp, bx ; sp below-or-equal needed stack frame ?
jbe short .5 ; yes -->
and bl, ~1 ; make even-aligned stack (rounding down)
mov sp, bx ; change sp
.5:
init_memory: ; Get conventional memory size and store it.
int 0x12
mov cl, 6
shl ax, cl
%if _RPL
xor si, si
xchg ax, dx
mov ds, si
lds si, [0x2f << 2]
add si, byte 3
lodsb
cmp al, 'R'
jne short .no_rpl
lodsb
cmp al, 'P'
jne short .no_rpl
lodsb
cmp al, 'L'
jne short .no_rpl
mov ax, 0x4a06
int 0x2f
.no_rpl:
xchg ax, dx
%endif
push ax
mov cx, ax
.gotsegs:
; +_STACKSIZE = stack space
; -ldCommandLine = load cmd line + data + lsv space
; 512 = boot sector (allows finding filename)
; (ebpbNew - bpbNew) = additional space for BPBN moving
; 32 = leave space for lower buffer MCB + header
sub cx, (+_STACKSIZE-ldCommandLine+512+(ebpbNew-bpbNew)+32+0xf)>>4 ; cx = stack seg
mov dx, cs
add dx, (payload.end-freedos_entry+0xf)>>4
cmp cx, dx
jnb short .6
.error_outofmemory:
call error
db 'Out of memory.', 0
.6:
mov dx, ss
mov ax, bp
add ax, 512+0xf
jnc short .0
mov ax, 0x10000>>1
db 0xa9 ; Opcode byte of `test ax, strict word ...'. This jumps to .1.
.0:
shr ax, 1
.1:
times 3 shr ax, 1
add dx, ax
cmp dx, cx
ja short .error_outofmemory
pop dx ; top of memory (=> start of RPL, EBDA, video memory)
push ss
pop ds
mov es, cx
push cx ; top of memory below buffers
xor cx, cx
lea si, [bp+lsvCommandLine.start]
cmp bp, si ; can have command line ? (also makes sure movsw and lodsw never run with si = 0FFFFh which'd cause a fault.)
jb short .no_cmdline
mov di, _STACKSIZE-ldCommandLine+ldCommandLine.start ; -> cmd line target
mov cl, (LOADCMDLINE.size+1)>>1 ; copy cmd line
%if lsvCommandLine.start+((LOADCMDLINE.size+1)&~1)!=lsvCommandLine.signature
%error Unexpected structure layout.
times -1 nop ; Make sure NASM 0.98.39 compilation fails.
%endif
rep movsw
lodsw
cmp ax, lsvclSignature
je short .7 ; if command line given -->
.no_cmdline:
mov [es:_STACKSIZE-ldCommandLine+ldCommandLine.start], cl ; truncate as if empty line given
dec cx ; cl = 0FFh
.7:
mov [es:_STACKSIZE-ldCommandLine+ldCommandLine.start+((LOADCMDLINE.size+1)&~1)-1], cl ; remember whether command line given. = 0 if given (also truncates if too long). = 0FFh if not given
; si happens to be already correct here if we didn't
; branch to .no_cmdline, however make sure to set
; it here to support this case.
lea si, [bp+lsvExtra] ; ds:si -> lsv + BPB
mov di, _STACKSIZE-ldCommandLine+lsvExtra ; es:di -> where to place lsv
mov cx, (-lsvExtra+512+1)>>1
rep movsw ; copy lsv (including lsvExtra) and BPB
xor ax, ax
mov cx, ((ebpbNew-bpbNew+0xf)&~0xf)>>1
rep stosw ; initialise area behind sector (left so for FAT32)
pop cx
mov ss, cx
mov sp, _STACKSIZE ; -> above end of stack space
mov bp, _STACKSIZE-ldCommandLine ; -> BPB, above end of lsv
sti
; cx => above end of memory available for load
; dx => above end of memory used by us
mov [bp+ldMemoryTop], dx
mov [bp+ldLoadTop], cx
push ss
pop es
push ss
pop ds
mov bx, [bp+bsBPB+bpbSectorsPerFAT]
test bx, bx
jz short .fat32
%if 0 ; Longer.
lea si, [bp+510] ; -> last source word
%else
mov si, _STACKSIZE-ldCommandLine+510 ; -> last source word
%endif
lea di, [si+(ebpbNew-bpbNew)] ; -> last dest word
mov cx, (512-bsBPB-bpbNew+1)>>1 ; move sector up, except common BPB start part
%if ((512-bsBPB-bpbNew+1)>>1)<=20
%error Need AMD erratum 109 workaround.
times -1 nop
%endif
std ; AMD erratum 109 handling not needed
rep movsw
cld
mov [bp+bsBPB+ebpbSectorsPerFATLarge], bx
mov [bp+bsBPB+ebpbSectorsPerFATLarge+2], cx
mov [bp+bsBPB+ebpbFSFlags], cx
xchg ax, cx ; ax = 0
push ss
pop es
lea di, [bp+bsBPB+ebpbFSFlags]
mov cx, (EBPB.size-ebpbFSFlags)/2
rep stosw ; initialise ebpbFSFlags (reinit), ebpbFSVersion, ebpbRootCluster, ebpbFSINFOSector, ebpbBackupSector, ebpbReserved
.fat32:
push ss
pop es
lea di, [bp+ldCommandLine.start]
mov cx, lsvclBufferLength
xor ax, ax
push word [bp+ldCommandLine.start+lsvclBufferLength-1] ; get sentinel (whether command line given)
repne scasb ; scan for terminator
pop ax ; al = 0FFh if no command line given. al = 0 else
rep stosb ; clear remainder of buffer
mov [bp+ldFATType], cx ; clear ldFATType and ldHasLBA
mov [bp+lsvFATSeg], cx ; clear lsvFATSeg
dec cx
mov [bp+lsvFATSector], cx ; clear low word of lsvFATSector
mov [bp+lsvFATSector+2], cx ; clear high word of lsvFATSector
%if _QUERY_PATCH
mov ax, [cs:..@query_patch_site-freedos_entry] ; Omit the -freedos_entry, depending on the entry offset.
%else
mov ax, _QUERY_DEFAULT
%endif
mov [bp+ldQueryPatchValue], ax
mov ax, cs
%if 0x1b+_EXEC_SEGMENT>0x7f
add ax, strict word ((payload-freedos_entry)>>4)+_EXEC_SEGMENT
%else
add ax, byte ((payload-freedos_entry)>>4)+_EXEC_SEGMENT
%endif
push ax
%if _EXEC_OFFSET
mov ax, _EXEC_OFFSET
%else
xor ax, ax
%endif
push ax
; cs:ip = xxxxh:_EXEC_OFFSET
; entire payload loaded (payload -- payload.actual_end)
; LOADSTACKVARS and LOADDATA and EBPB and ebpbNew BPBN set
; ldCommandLine set (ASCIZ, up to 255 bytes + 1 byte terminator)
; word [ldCommandLine.start] = 0FF00h if had invalid signature
retf
%if _QUERY_PATCH
query_geometry: ; NOT A FUNCTION. only included as magic bytes
; magic bytes start
mov dl, [bp+bsBPB+ebpbNew+bpbnBootUnit]
mov ax, _QUERY_DEFAULT ; magic bytes, checked by patch script
..@query_patch_site equ $-2
test dl, dl ; hard disk unit ?
jns short error ; no -->
dw 0xc486 ; xchg al, ah ; get high byte into al. Hardcode opcode bytes to avoid differences between NASM versions. (NASM 2.16.03 is different from NASM 2.13.02 == 0.98.39.)
; magic bytes end
%if ($-freedos_entry)>1536
%error Query patch site should be in first 1536 bytes.
times -1 nop ; Make sure NASM 0.98.39 compilation fails.
%endif
%endif
error:
push cs
pop ds
mov si, msg.error-freedos_entry ; Omit the -freedos_entry, depending on the entry offset.
call disp_error
pop si
call disp_error
xor ax, ax
int 0x16
int 0x19
disp_error.loop:
mov ah, 0xe
mov bx, 7
;push bp
int 0x10 ; (call may change bp, but it is not used here any longer.)
;pop bp
disp_error:
lodsb
test al, al
jnz short disp_error.loop
ret
msg.error: db 'Load error: ', 0
times ($$-$)&0xf db '&'
payload:
incbin _PAYLOAD_FILE
times ($$-$)&0xf db '&'
.actual_end:
.end:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment