I wanted to make a few examples of what I mentioned in a comment. That is, to create an MZ .EXE header or to manually implement relocations in a flat format binary, all using only NASM's multi-section `-f bin` format. To save on work, I actually created a single example with multiple build options to select what layout to use exactly. Some macros used in this example are from my 8086 macro collection found at https://hg.pushbx.org/ecm/lmacros/ and all other files are in https://pushbx.org/ecm/test/20230702/ This is the main sources, `test.asm`: ``` ; Public Domain %include "lmacros3.mac" numdef DOSEXIT, 0 numdef DOSENTRY, 0 numdef LARGEFILL, 0 numdef RELOC, 1 numdef BASESEGMENT, 0 numdef RELOCEXE, 0 cpu 8086 %if _RELOCEXE %assign ORIGIN 0 org ORIGIN addsection HEADER, start=0 header_start: db "MZ" ; exeSignature FILESIZE equ fromparas(header_size_p + BEHINDSEGMENT) dw FILESIZE % 512 ; exeExtraBytes dw (FILESIZE + 511) / 512 ; exePages dw relocationtable.amount ; exeRelocItems dw header_size_p ; exeHeaderSize dw paras(512) ; exeMinAlloc dw paras(512) ; exeMaxAlloc dw +BEHINDSEGMENT ; exeInitSS dw 512 ; exeInitSP dw 0 ; exeChecksum dw 0, +0 ; exeInitCSIP dw relocationtable ; exeRelocTable addsection FIRST, vstart=0 align=16 follows=HEADER %elif _DOSENTRY %assign ORIGIN 256 org ORIGIN addsection FIRST, start=ORIGIN %else %assign ORIGIN 0 org ORIGIN addsection FIRST, start=ORIGIN %endif first_start: addsection SECOND, vstart=0 align=16 follows=FIRST second_start: addsection THIRD, vstart=0 align=16 follows=SECOND third_start: first_size equ first_end - first_start endarea first, 1 second_size equ second_end - second_start endarea second, 1 third_size equ third_end - third_start endarea third, 1 %if _RELOCEXE header_size equ header_end - header_start endarea header, 1 %endif FIRSTSEGMENT equ _BASESEGMENT + 0 SECONDSEGMENT equ _BASESEGMENT + paras(ORIGIN) + first_size_p THIRDSEGMENT equ _BASESEGMENT + paras(ORIGIN) + first_size_p + second_size_p BEHINDSEGMENT equ _BASESEGMENT + paras(ORIGIN) + first_size_p + second_size_p + third_size_p %define RELOCATIONFROMFIRST "" %define RELOCATIONFROMSECOND "" %define RELOCATIONFROMTHIRD "" %imacro relocation 0-1.nolist -2 %%reloc equ $ + %1 %ifidn _CURRENT_SECTION, FIRST %xdefine RELOCATIONFROMFIRST RELOCATIONFROMFIRST, %%reloc, FIRSTSEGMENT %elifidn _CURRENT_SECTION, SECOND %xdefine RELOCATIONFROMFIRST RELOCATIONFROMFIRST, %%reloc, SECONDSEGMENT %elifidn _CURRENT_SECTION, THIRD %xdefine RELOCATIONFROMFIRST RELOCATIONFROMFIRST, %%reloc, THIRDSEGMENT %else %error Unknown section for relocation %endif %endmacro usesection FIRST start: mov dx, cs mov ds, dx %if !_RELOCEXE && _RELOC mov si, relocationtable mov cx, relocationtable.amount jcxz .noreloc @@: lodsw xchg bx, ax lodsw add ax, dx mov es, ax add word [es:bx], dx loop @B .noreloc: %endif displayfirst: mov si, firstmsg mov bx, 7 mov ah, 0Eh db __TEST_IMM16 ; (skip int 10h) @@: int 10h lodsb test al, al jnz @B call SECONDSEGMENT:secondentry relocation exit: %if _DOSEXIT mov ax, 4C00h int 21h %else xor ax, ax int 16h int 19h %endif bouncetothird: jmp THIRDSEGMENT:bounced relocation firstmsg: asciz "Hello from first!",13,10 usesection SECOND secondmsg: asciz "Hello from second!",13,10 secondentry: displaysecond: mov si, secondmsg mov bx, 7 mov ah, 0Eh db __TEST_IMM16 ; (skip int 10h) @@: int 10h cs lodsb test al, al jnz @B jmp THIRDSEGMENT:thirdentry relocation usesection THIRD align 2, db 0 indirect_to_bounce: dw bouncetothird relocation 0 dw FIRSTSEGMENT thirdmsg: asciz "Hello from third!",13,10 thirdentry: jmp far [cs:indirect_to_bounce] bounced: displaythird: mov si, thirdmsg mov bx, 7 mov ah, 0Eh db __TEST_IMM16 ; (skip int 10h) @@: int 10h cs lodsb test al, al jnz @B retf %ifn _RELOCEXE usesection FIRST %else usesection HEADER %endif %if _RELOCEXE || _RELOC align 4, db 0 relocationtable: .: dw RELOCATIONFROMFIRST dw RELOCATIONFROMSECOND dw RELOCATIONFROMTHIRD .end: .amount: equ (.end - .) / 4 %endif %if _RELOCEXE align 16 header_end: %endif %if _LARGEFILL usesection SECOND _fill fromkib(64), 0CCh, second_start %endif usesection FIRST align 16 first_end: usesection SECOND align 16 second_end: usesection THIRD align 16 third_end: ``` How does it work? * NASM allows to create far jumps and calls with a hardcoded immediate address, in the format `jmp SEGMENT:OFFSET` (no size keyword, two colon-separated immediate numbers). * The `relocation` macro defaults to creating an equate for `$ - 2` (current output address minus 2), which points into the segment immediate of a preceding instruction. This reference is used to make Self-Modifying Code. * The macro can be used as `relocation 0` to create a relocation table entry for the next word. * The relocation entries are emitted into a table. For simplicity this table is here always in the same format as used by the MZ .EXE header. * The segment values (`FIRSTSEGMENT` and so on) are defined using a base segment plus a displacement, in paragraphs, to the particular segment in our program image. The displacements are calculated from the label deltas that give the length of each segment in the program image. * The base segment can be nonzero to support loading at a fixed address. Here are the commands I used to create three different builds of this program: `nasm -I ~/proj/lmacros/ test.asm -o test.com -l testcom.lst -D_DOSENTRY -D_DOSEXIT && dosemu -K "$PWD" -E "ldebug test.com" -dumb -td -kt` Build a flat format .COM file with DOS entry (origin = 256) and DOS termination call. (All I/O is done using the ROM-BIOS interfaces, unconditionally.) `nasm -I ~/proj/lmacros/ test.asm -o test.exe -l testexe.lst -D_DOSENTRY -D_DOSEXIT -D_RELOCEXE -D_LARGEFILL && dosemu -K "$PWD" -E "ldebug test.exe" -dumb -td -kt` Build an MZ .EXE file placing and using the relocation table within the MZ .EXE header. Still use DOS termination call. For fun, fill the SECOND segment to 64 KiB. This requires an MZ .EXE file under DOS, or alternatively what I call a .BIG file. (.BIG files are flat format files similar to .COM files but may exceed 64 KiB and the initial stack is set up in another segment. They're used internally by my debugger's build process.) `nasm -I ~/proj/lmacros/ test.asm -o test.bin -l testbin.lst -D_DOSENTRY=0 -D_RELOC=0 -D_BASESEGMENT=3000h -D_DOSEXIT=0 -D_RELOCEXE=0 -D_LARGEFILL && dosemu -K "$PWD" -E "ldebug /crcsip=3000_0000;lcs:ip /f test.bin" -dumb -td -kt` Build a flat format binary file, no DOS entry, use ROM-BIOS termination calls, and do not relocate at run time. The segment base is instead fixed to a particular address. The debugger command loads the file to that address. (Must be free!) For fun, do also fill the SECOND segment to 64 KiB.