Last active
March 14, 2026 20:58
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ; | |
| ; 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