Application startup code looks like this:
CPU 8086
_start:
cld
mov ax, ds
dec ax
mov es, ax
mov ax, [es:3]
cmp ax, word (_endbss + 768) / 16
jae .mem
; Code would be here to emit an out of memory error
.mem:
mov ax, 4C00h
int 21h
section .bss
_bss:
big_array resb 16384
_endbss:
What's it supposed to be doing? Checking of we actually have enough RAM to run the program or not.
What it's actually doing? Not assembling.
The general idea is we need so much RAM, so we set up Code + BSS + Stack in the initial segment and the slab above it (which will exceed 64K by itself so there's no point putting it below the stack..). I'm convinced the assembler should be able to do this but I cannot figure out what to type in to make it work.
How do I get the expression that would be (_endbss + 768) / 16
to assemble to the constant it most definitely is? I'd really rather not have to figure it at runtime.
NASM's -f bin
unfortunately mostly works like a simple linker built-in to NASM. It doesn't override the usual rules that any math on symbol addresses must be representable with relocation entries, and isn't known at assemble time (only link time).
But you probably still need separate sections so resb
can be in .bss
and not assemble to bytes in the output file.
You can add up the lengths of your two sections, on the assumption that there's no padding for alignment between sections. (You can make that a reality with section .bss align=1
if that isn't the default, I think.) So we add an _endtext
label after the last byte of .text
, and the problematic instruction becomes:
cmp ax, word (_endbss - _bss + _endtext-_start + 768) / 16
Listing (nasm -l /dev/stdout -f bin foo.asm
) of a working example:
1 CPU 8086
2 _start:
3 00000000 FC cld
4 00000001 8CD8 mov ax, ds
5 00000003 48 dec ax
6 00000004 8EC0 mov es, ax
7 00000006 26A10300 mov ax, [es:3]
8 0000000A 3D3104 cmp ax, word (_endbss-_bss + _endtext-_start + 768) / 16
9 0000000D 7300 jae .mem
10 ; Code would be here to emit an out of memory error
11
12 .mem:
13 0000000F B8004C mov ax, 4C00h
14 00000012 CD21 int 21h
15
16 00000014 B8[0040] mov ax, _endbss ; for testing to see what value it puts here
17 00000017 01000000 dd 1 ; see what happens as the sign approaches 32...
18 _endtext:
19 section .bss
20
21 _bss:
22 00000000 <res 4000h> big_array resb 16384
23
24 _endbss:
25
In the actual output file foo
, mov ax, _endbss
assembled to b8 1c 40
(not b8 00 40
like the listing shows as a placeholder), so 1c 40
(little-endian) = 0x401c
is the offset part of the full address, and is what you wanted _endbss
to evaluate to.
The compare immediate is little-endian 31 04
which is 0x0431
.
Plugging that _endbss
value into your expression to check the results:
(0x401c + 768) / 16
rounds down to 1073, which is 0x431
, the value my expression got for cmp
.
I haven't tested extensively with extra times x db 1
in .ext or non-power-of-2 .bss
sizes to make sure there's no off-by-one at a cutoff between the next size, or cutoff for the next level of alignment padding if there is any.