assemblynasmcompile-time-constant

Overcoming "division operator may only be applied to scalar values"


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.


Solution

  • 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.