Question Details

No question body available.

Tags

assembly nasm x86-16 dos

Answers (1)

March 28, 2026 Score: 4 Rep: 3,146 Quality: Medium Completeness: 100%

There are four ways to solve this:

  1. Use label arithmetic involving label deltas
  2. Postprocess object file or binary file at build time
  3. Postprocess executable at run time
  4. Do the calculation at run time

Label arithmetic involving label deltas

Background

NASM labels in non-absolute sections are relocatable, meaning that the assembler stores a name, an offset, and a section for every label. The section is used by the linker to evaluate the final relocated offset for a label's use. Generally only additions can be expressed in the object format, so arbitrary calculations are not allowed. A relocatable label is as opposed to a scalar, where a scalar is a plain number not subject to relocation.

(The default NASM output format -f bin appears to not involve a linker, so does it support more than addition of relocatable labels? The answer is no. This is because the -f bin format actually is implemented as a linker internal to the assembler, which is given input as an internal object format. This is also why the listing file with -f bin format doesn't always list the final binary contents of all instructions, emitting relocations surrounded by square brackets [...] or round parenthesis (...) same as for other output formats.)

The solution

NASM allows to calculate the difference, or delta, between two labels that are in the same section. This is, crucially, a scalar value to the assembler. It evaluates to a plain number at assemble time. That means the assembler can do arbitrary arithmetic on it, including shifting, division, and subtracting the delta.

This itself does not solve all our problems, but label arithmetic that may involve several deltas makes this a more powerful tool. For instance, consider the following code that could be used to build a 86-DOS flat-format .COM executable program:

cpu 8086
org 256

section code codestart: jmp init ...

align 16 codeend:

section data follows=code datastart: ...

dataend:

How do we calculate the size of the program needed to hold both the code section and data section, in paragraphs (16-byte chunks)? The naive solution would be mov bx, (dataend + 15) / 16 but that doesn't work due to the lack of division relocations.

The delta solution is mov bx, (256 + codeend - codestart + dataend - datastart + 15) / 16 where the 256 is to account for the org and the other two parts to the addition are deltas giving the code and data section sizes. Note that for this calculation we need to know the offsets that the linker will find for codestart and datastart which depend on the origin specified with org and the size of all prior sections, if any.

Postprocess at build time

This solution can be achieved by extending the toolchain, either by modifying the assembler, or the linker, or by adding an additional tool to do the postprocessing. It requires indicating in the source where to apply the postprocessing, and what calculation to use.

I modified the WarpLink OMF linker to support an extension, called wlcalc, allowing to do some kinds of calculations in the linker program during a "post-link" pass over the final executable file. The feature is enabled by passing the /XC switch, allowing the linker to detect public (global) symbols starting with the phrase wlcalc.

Here's an example to create a simple flat-format .COM-style DOS program:

    cpu 8086

group programgroup code data

section code times 256 db 0 ..start: jmp init

db "Test",13,10,26

init: mov sp, stack.top

mov ah, 4Ah mov bx, dataend + 15 global wlcalcwordshr4 wlcalcwordshr4: equ $ - 2 int 21h

mov ah, 09h mov dx, msg int 21h

mov ax, 4C00h int 21h

section data align 16 msg: db "Test Message",13,10,36

align 2 stack: times 512 db '^' align 2 .top: dataend:

Build as follows (using NASM, WarpLink, and x2b2):

nasm -f obj test.asm
warplink /xc /mx test.obj,test.exe,test.map;
x2b2 test.exe test.com

Postprocess at run time

To do postprocessing at run time, a linker of sorts could run when a program is being loaded. It could use link tables to find where to patch the program. These tables have to be set up at build time.

As an example, our debugger's Extensions for lDebug (ELDs) use a run time linker that runs when an ELD is loading to resolve both internal code and data relocations and external linkage with code and data links of the debugger application. This case is not related to arbitrary calculations on labels but the same general design could be used to do so. The linker is described in the manual at https://pushbx.org/ecm/doc/ldebug.htm#eldlinker

Do the calculation at run time

This solution was common during the heydays of 86-DOS application development. Taking the same example as before, it would look like:

  mov bx, dataend + 15
  mov cl, 4
  shr bx, cl

The addition also could be done at run time rather than at build time. The obvious downside compared to using deltas arithmetic or the linker extension is that the code doing this calculation both increases the file and memory footprint of the application and takes longer to run. It also may use more registers than needed for the other solutions.