Question Details

No question body available.

Tags

assembly linker nasm x86-16 dos

Answers (2)

February 9, 2026 Score: 1 Rep: 2,990 Quality: Low Completeness: 80%

Found a workable solution: Use the absolute $ trick to get the assembler into a NASM-internal nobits mode at a position based on a relocatable label/position. (I reported this surprising feature to NASM's old bugzilla a while ago, asking for it to be supported and documented. But unfortunately that's down more often than not recently so I'm unable to link to that page.)

To demonstrate, I extended the example a little. This is test.asm:

[map all test.map]

org 256 section ENTRY start=100h entry: mov ax, init mov bx, entryend mov sp, stack.top mov ax, 4C00h int 21h db "entry " align 16

db "more entry " align 16 entry
end:

section STACK follows=ENTRY nobits alignb 16 stack: resb 512 .top:

section INIT follows=ENTRY vstart=0 init: db "init " align 16 initend:

Then, we split the source text into two assembly language files, just to prove that the absolute $ thing does what we want. This is test1.asm:

    section ENTRY
    times 256 db 0
..start:
entry:
    mov ax, init
 extern entryend
    mov bx, entryend
 extern stack.top
    mov sp, stack.top
    mov ax, 4C00h
    int 21h
    db "entry "
    align 16

section INIT init: db "init " align 16 init
end:

And this is test2.asm:

 global entryend
 global stack.top

section ENTRY align 16

db "more entry " align 16 entry
end:

absolute $ alignb 16 stack: resb 512 .top:

This is how to build the first example:

nasm -f bin test.asm -l test.lst

And that's the build commands for the second example:

nasm -f obj test1.asm
nasm -f obj test2.asm
~/proj/ldos/warplink.sh /c /mx test1.obj test2.obj,testwl.exe,testwl.map\;

(Using a script from the lDOS kernel repo to run WarpLink from the Linux shell. Needs dosemu2, a DOS kernel and shell, and the warplink.exe in the DOS path.)

Now check the results, using bdiff:

test$ bdiff test testwl.exe
File: test
Files are identical.
test$ dos 'ldebug testwl.exe'
About to Execute : ldebug testwl.exe
lDebug (2026-02-01)
-u cs:ip l #14
2B5F:0100 B80000            mov     ax, 0000
2B5F:0103 BB3001            mov     bx, 0130
2B5F:0106 BC3003            mov     sp, 0330
2B5F:0109 B8004C            mov     ax, 4C00
2B5F:010C CD21              int     21
-

Note how mov sp gets the intended offset, 100h (org) plus 30h (size of ENTRY) plus 200h (size of the stack).

Do beware that in the stack "section" you must be careful using macros that will change into an absolute section of their own. The NASM struc related macros do use absolute internally. They try to restore the prior section context in endstruc by using ?SECT? which won't do what we want for our absolute section.

Instead, place a label before the struc construct, and use absolute label to restore the original absolute section:

 global entryend
 global stack.top

section ENTRY align 16

db "more entry " align 16 entry
end:

absolute $ alignb 16 stack: resb 256

restore:

struc FOO quux: resd 1 endstruc

absolute restore

resb 256 stack.top:
February 9, 2026 Score: 1 Rep: 2,990 Quality: Low Completeness: 80%

I found another way to create the file that I want. In this case, we emit the desired nobits section into the linked executable (so it actually becomes progbits backed data filled with all-zeroes) but then postprocess the file to cut out that section. This obviously requires some adjustments to the source texts, if the segment location of eg the INIT section is used somewhere in the code.

The base example is the same as for the first (absolute $) answer. These are the files for this answer, test1.asm:

group GROUP ENTRY STACK

section ENTRY times 256 db 0 ..start: entry: mov ax, init extern entryend mov bx, entryend extern stack.top mov sp, stack.top mov ax, 4C00h int 21h db "entry " align 16

section STACK

section INIT init: db "init " align 16 initend:

And test2.asm:

group GROUP ENTRY STACK

global entryend global stack.top

section ENTRY align 16

db "more entry " align 16 entry_end:

section STACK alignb 16 stack: resb 512 .top:

And here's the postprocessing script, done in a Perl script, postproc.pl. (I originally wrote this as a scriptlet but it got too unwieldy.)

#! /usr/bin/perl

use strict; use warnings;

my $seen = 0; while () { /^\sStart\s+Stop\s+Length\s+Name\s+/ and $seen = 1; if ($seen and /^\s([0-9A-Fa-f]+)[Hh]? \s+[0-9A-Fa-fHh]+ \s+([0-9A-Fa-f]+)[Hh]? \s+STACK\b/ix) { my $start = hex($1); my $length = hex($2); $start -= 256; printf ("%04XH %04Xh\n", $start, $length); open my $input, "", "testwlp.bin"; binmode($output); my $buffer; read($input, $buffer, $start); print { $output } ($buffer); seek($input, 0, 2); my $filesize = tell($input); seek($input, $start + $length, 0); my $trailsize = $filesize - ($start + $length); read($input, $buffer, $trailsize); print { $output } ($buffer); last; } }

I hardcoded the binary filenames here, and this doesn't do much error checking. It takes as its command line parameter the filename of the WarpLink .map file.

These are the commands to build this example:

nasm test1.asm -f obj nasm test2.asm -f obj ~/proj/ldos/warplink.sh /c /mx test1.obj test2.obj,testwlp.exe,testwlp.map\; ./postproc.pl testwlp.map

And checking again that they match the desired results:

test$ bdiff testwlp.bin ../absolute/testwl.exe File: testwlp.bin Files are identical. test$ dos 'ldebug testwlp.bin' About to Execute : ldebug testwlp.bin lDebug (2026-02-01) -u cs:ip l #14 2B5F:0100 B80000 mov ax, 0000 2B5F:0103 BB3001 mov bx, 0130 2B5F:0106 BC3003 mov sp, 0330 2B5F:0109 B8004C mov ax, 4C00 2B5F:010C CD21 int 21 -q test$