Today I posted a stackoverflow question and answer on how to do arbitrary calculations on assembly language labels, How can I do arbitrary calculations on assembly language labels?. As a companion to that question, here is a collection of comments in which I have referred to label delta arithmetic before.
Your workaround is correct. A label that is segmented (not a scalar number) cannot be used for some calculations such as shifting, division, or masking. This is because a segmented label is passed to the linker, and the linker doesn't have relocation types for these calculations.
NASM's
-f binformat from the assembler's point of view is an object output format like any other, and theorgsetting is handled by the output format - not by the assembler. The-f binformat acts as a linker integrated into NASM. You can see from a simple example that NASM's listing output starts counting at00000000even if a nonzeroorgis given, and$$is (by design) a segmented label referring to the beginning of the section. That is, the point referenced as00000000in the listing file. The brackets indicate that a value is subject to relocation. Note how the relocated executable output evaluates the$$use to 0100h, not 0000h:test/20260206$ nasm test.asm -l /dev/stderr && podhex test 1 2 org 256 3 00000000 [0000] dw $$ 000000 00 01 >..< 000002To work around this, you have to work with label delta arithmetic. As you found,
label - $$can be used for masking operations. This is because a delta (difference between two labels in the same section) is a scalar value, allowing all sorts of calculations. As you further found,$$refers to the equivalent of a vstart of 100h (theorgvalue) when used in the first section. Therefore it is correct to add the (well-known)orgvalue to the label deltas to calculate the final relocated address corresponding to label.I make extensive use of label deltas in some of my source texts, sometimes chaining them like
origin + section1_end - section1_start + section2_label - section2_start. I'm also revising a draft that explains problems like these some more, which I can link here once it's up.2026-02-06 reply to a NASM issue, https://github.com/netwide-assembler/nasm/issues/197#issuecomment-3858298717
Instead of
<handler>use a delta, eg placestart:at beginning (beforecli) then(handler - start + 7C00h)can be calculated to allow masking and shifting.2025-12-24 comment, https://stackoverflow.com/questions/79854512/how-to-get-idt-handler-address-in-assembly#comment140916679_79854512
Guessing from my NASM/8086 experience, you may be right that the label address is resolved at link time. But there isn't necessarily an object file relocation format for such calculations, so the assembler cannot encode your desired instruction as it cannot communicate it to the linker.
2025-10-29 comment, https://stackoverflow.com/questions/79804147/how-to-access-global-c-variables-from-within-standalone-assembler-modules-with-t#comment140825649_79804147
@Joshua You can use
absolute $to switch to a nobits part after a progbits part. While this isn't documented it does work, and you can then subtract a label at the start of the progbits section from one at the end of the nobits part to get the size. (You do still need the label delta arithmetic I think. It just works as a single section for these purposes then.)2024-11-30 comment, https://stackoverflow.com/questions/79238530/overcoming-division-operator-may-only-be-applied-to-scalar-values/79238627#comment139728681_79238530
@PeterCordes
alignb 16in a nobits section wouldsectalignI believe. You don't want that because your deltas won't know about this padding. However, you can replacealignb 16by equivalent use oftimes … resb 1withsection .bss nobits align=1that aligns to a paragraph but takes into account the exact length of the prior section.2024-11-30 comment, https://stackoverflow.com/questions/79238530/overcoming-division-operator-may-only-be-applied-to-scalar-values/79238627#comment139728511_79238627
A label is a relocateable symbol within a given section. Relocateable just means that depending on the linker, the assembler's view of the section may be incomplete so the linker may apply an adjustment. (When you assemble with
nasm -f binthen a linker internal to NASM is used.) Other than the relocateable part, the label just represents an offset (into a section/segment). In NASM no type, size, or segment information is stored alongside the label's name, offset (in a section), and section (to make it relocateable). You can misuse a label with a "wrong" segment, NASM will be none the wiser.2024-09-03 comment, https://stackoverflow.com/questions/78945635/how-does-an-assembler-find-the-offset-of-a-label-without-knowing-the-value-of-th#comment139192947_78945635
Yeah, the
-f binformat of NASM is said to involve a "linker internal to NASM" which is why some relocations will be processed not by the assembler proper but rather the linker. So you get a[00000000]style relocation marker in the listing file because the listing part doesn't communicate with the linker. I mentioned some of this in yesterday's blog post: https://pushbx.org/ecm/dokuwiki/blog/pushbx/2024/0714_early_mid_july_work2024-07-15 comment, https://stackoverflow.com/questions/78747126/nasm-org-doesnt-like-section#comment138846008_78747126
I'm using NASM for 8086 code but perhaps my approach could work on a GNU toolchain as well. That is, you cannot do bitwise operations, shifts, or divides with relocatable addresses. But you can calculate deltas within a single assembly module, if they stay in the same section. These deltas need no relocation, ie they are simply scalar values, so you can do all sorts of calculations on them. However, you need to somehow pre-calculate where a section is placed and then manually add in this adjustment (as another scalar) to obtain the equivalent of doing calculations on a relocatable address.
2024-06-25 comment, https://stackoverflow.com/questions/78669648/ways-to-perform-bitwise-operations-with-symbols-inside-assembly-constants#comment138700367_78669648
I prepared examples at https://pushbx.org/ecm/test/20240212/ - 1.txt: Move data to behind code, assembles
mov dxagain to fill in the offset only known after the entire code is assembled. 2.txt: Put the data at a considerable distance from the code so we can predict during the assembly a valid offset that we can place the data at. 3.txt: Skip over data using ajmp(size of the jump + length of the data must be predicted or fixed up afterwards again). 4.txt: Input that works only with lDebug, using lDebug's variables to fix up themov dxinstruction with the correct address automatically.2024-02-12 comment, https://stackoverflow.com/questions/77979198/why-does-code-in-ms-dos-debug-does-not-run-and-makes-the-prompt-disappear#comment137475437_77979198
@PeterCordes The NASM
-f binformat is actually a linker internal to NASM. So the assembler doesn't actually know numeric values for symbols that are to be relocated. Still,a + bworks in NASM-f bintoo.2023-12-08 comment, https://stackoverflow.com/questions/77622288/why-does-pointer-addition-work-in-nasm-32-bits-programming#comment136855564_77622288
@PeterCordes Indeed, the
-f binlisting file doesn't use [] or () around the byte written for the instruction immediate. And yes, the relocations are not arbitrary, which is why I often work with deltas (subtract one label from another). Negative relocations are another bit that's not supported: https://sourceforge.net/p/nasm/feature-requests/176/2023-12-08 comment, https://stackoverflow.com/questions/77622288/why-does-pointer-addition-work-in-nasm-32-bits-programming#comment136856036_77622288
Assuming
ds= 0 is not the same as assumingds=csbecausecscan also be nonzero. Also, in the very next instruction after the one that changesssyou should also changesp. Furthermore, using two sections is possible with a padding line liketimes 510 - ($ - $$) - (text_end - text_start) db 0if the proper labels are placed in the.textsection and alignment is taken care of. (Either specifyalign=1as a section attribute, or place an appropriatealigndirective before the labeltext_end.)2023-08-10 comment, https://stackoverflow.com/questions/76872613/i-cannot-execute-my-bootloader-file-in-qemu-emulator/76872703#comment135520707_76872703
I'm only particularly well versed in NASM, but it appears to be similar to gas in this regard. What I do when I want to shift or divide values derived from labels at build time is I do label arithmetic so that the assembler can operate on deltas. This is perhaps only really useful in a format like NASM's
-f binwhere you can reconstruct all section start/vstart/size parameters using deltas, eg(data_label - data_start + text_end - text_start + 256 + 15) / 16where just(data_label + 15) / 16would not be valid.2023-06-02 comment, https://stackoverflow.com/questions/76389786/how-to-use-arbitrary-arithmentic-expressions-on-pointers-in-gas#comment134705598_76389786
You can take a delta between two labels (subtract one from the other) if they're in the same section. The result is a scalar numeric value, in NASM terms. Label arithmetic like that allows to divide or shift label deltas, which is not allowed with labels that are relocateable (non-scalar) values.
2023-02-06 comment, https://stackoverflow.com/questions/58082833/what-value-we-get-when-we-subtract-name-of-two-labels-and-store-it-in-a-variable/75359034#comment132974951_75359034
Do label arithmetic to gain a scalar value that NASM will allow to shift. For example, if your section starts with (say)
org 7C00hthen you can do arithmetic likedw (isr - $$ + 7C00h) » 16. In general, the delta between two labels that are in the same section results in a scalar value that you can then use in all kinds of calculations, including shifts and division.2022-10-02 comment, https://stackoverflow.com/questions/73927720/nasm-define-static-idt-at-compile-time-without-using-the-linker#comment130534145_73927720
NASM with the multi-section bin output format requires you to use proper label arithmetic to calculate things like this. This is required because NASM's internal linker doesn't allow for arbitrary calculations. For example, you need
(stack_end - stack_start + code_end - code_start + 256 + 15) / 16if the length you want to include has the PSP (org 256) plus a code section (stretching from offset 0100h up to the stack) plus this stack section. Here's some code using label arithmetic (in a single section).2022-01-04 comment, https://stackoverflow.com/questions/70573150/calculating-size-in-paragraphs-from-an-address-as-a-nasm-constant-expression#comment124755973_70573150
Here's another example, in this case all sections' sizes are paragraph aligned so we can just add up the individual sizes to get a valid length in paragraphs. Here's yet another example, this one uses several sections (data entry, data stack, code, init) and there's more label arithmetic done overall.
2022-01-04 comment, https://stackoverflow.com/questions/70573150/calculating-size-in-paragraphs-from-an-address-as-a-nasm-constant-expression#comment124756051_70573150
@Peter Cordes: You are right about using
addwith an immediate if you have a build time constant. However, do consider that back in the bad old days people often calculated the entire constant at run time, eg in the old callver before I changed it.2022-01-04 comment, https://stackoverflow.com/questions/70573150/calculating-size-in-paragraphs-from-an-address-as-a-nasm-constant-expression#comment124756229_70573150
@Peter Cordes: Yes, if you put it at the right spot and in the right section then what I called
code_end - code_startcan be replaced by$ - $$. In a single section the$$will evaluate to a symbol at the offset equal to theorgvalue, but (like a regular label) not as a scalar.2022-01-06 comment, https://stackoverflow.com/questions/70573150/calculating-size-in-paragraphs-from-an-address-as-a-nasm-constant-expression#comment124808875_70573150
In NASM I usually do label arithmetic, such as putting
label1at the start of a section thenlabel2later in the section. The differencelabel2 - label1can be computed by the assembler without involving the linker backend.2021-05-06 comment, https://stackoverflow.com/questions/67422867/load-low-byte-of-address-only-in-x86-assembly#comment119173048_67422867
I don't see
stringbeing defined anywhere. The other problem is that you wantmovzx ecx, byte [length]\shr ecx, 1to get the string length (memory access with brackets) then divide by two. Yourmov ecx, (length / 2)is wrong and would, if it was supported, just calculate half of the address of thelengthvariable, not the content. (If you really want to do arithmetic on addresses in NASM source, you need to calculate deltas likelabel2 - label1from two labels in the same section. These can be used for assemble-time division, shifting, etc.)2020-12-14 comment, https://stackoverflow.com/questions/65295702/checking-if-the-users-input-is-a-palindrome#comment115436806_65295702
"
$ - $$is a "scalar" (as is any difference between labels)" – Correction: Any difference between two labels in the same section is a scalar.2020-09-04 comment, https://stackoverflow.com/questions/14928741/whats-the-real-meaning-of-in-nasm/14933178#comment112709144_14933178
I use
align 16+section_endorsection_sizestyle labels after the data to emit, then sum all the respective deltas. Egtext_end - text_startto figure out the length of the "text" section. NASM allows some arithmetic on addresses like this, but none that crosses sections. So you need to list all sections separately, and put a label behind the end of the data. (NASM 2.10 was supposed to introduce%finalbut that was scrapped.)2020-07-13 comment, https://stackoverflow.com/questions/62877962/getting-section-size-with-nasm-f-bin#comment111202724_62877962
"Binary files could be an exception, but aren't" – In the NASM architecture, flat binary output is just another output format for the frontend, which is linked internally by the backend. That is, there is a linker and an intermediate object format there, albeit both internal to the assembler. That is why bin output doesn't allow fancy relocations which could be uniquely supported by it, eg https://sourceforge.net/p/nasm/feature-requests/176/
2019-11-24 comment, https://stackoverflow.com/questions/59010047/yasm-symbol-effective-address-is-too-complex-in-a-flat-binary/59012976#comment104284764_59012976
(Setup-Start+15)/16is incorrect, you need to add 256 for the PSP.2019-09-03 comment, https://stackoverflow.com/questions/56403333/how-do-i-properly-hook-interrupt-28h-in-assembly-for-dos-and-restore-it/56418301#comment101967158_56418301
There are two types of symbol in x86 architecture: 1. plain number (scalar) 2. address (vector of two components: segment:offset). While you can add plain numbers or offset parts of addresses, addition of two addresses is not permitted. You can only add/subtract a plain number to/from an address, or subtract two addresses if both belong to the same segment.
2015-01-18 comment by vitsoft, https://stackoverflow.com/questions/27996727/adding-together-labels-in-assembly-language#comment44406788_28000532
Well… as you probably know, Nasm will condescend to do a shift on the difference between two labels. The usual construct is something like:
dw (int3 - $$) » 16where
$$refers to the beginning of the section. This calculates the "file offset". This is probably not the value you want to shift.
dw (int3 - $$ + ORIGIN) » 16may do what you want… where
ORIGINis… well, what we told Nasm fororg, if we were using flat binary. I ASSume you're assembling to-f elf32or-f elf64, telling ld–oformat=binary, and telling ld either in a linker script or on the command line where you want.textto be (?). This seems to work. I made an interesting discovery: if you tell ld-oformat=binary(one hyphen) instead of–oformat=binary(two hyphens), ld silently outputs nothing! Don't do this - you waste a lot of time!2013-05-03 answer by Frank Kotler, https://stackoverflow.com/questions/16351889/nasm-emit-msw-of-non-scalar-link-time-value/16355350#16355350