How does $ work in NASM, exactly?
Solution 1:
This gets the assembler to calculate the string length for you at assemble time
$
is the address of the current position before emitting the bytes (if any) for the line it appears on. Section 3.5 of the manual doesn't go into much detail.
$ - msg
is like doing here - msg
, i.e. the distance in bytes between the current position (at the end of the string) and the start of the string. (See also this tutorial on NASM labels and directives like resb
)
(Related: Most other x86 assemblers also use $
the same way, except for GAS which uses .
(period). MMIX assembler uses @
, which has the right semantic meaning).
To understand it better, it may help to see what happens when you get it wrong: In NASM labels next to each other in memory are printing both strings instead of first one. This person used
HELLO_MSG db 'Hello, World!',0 ; normally you don't want ,0
GOODBYE_MSG db 'Goodbye!',0 ; in explicit-length strings, unless it also needs to be a C-string
hlen equ $ - HELLO_MSG
glen equ $ - GOODBYE_MSG
resulting in hlen
including the length of both strings.
EQU
evaluates the right hand side right away, to a constant value. (In some assemblers like FASM, equ
is a text substitution and you have to use glen = $ - GOODBYE_MSG
to evaluate with $
at this position, instead of evaluating $
in a later mov ecx, glen
instruction or something. But NASM's equ
evaluates on the spot; use %define
for text substitutions)
Using $
is exactly equivalent to putting a label at the start of the line and using it instead of $
.
The object-size example can also be done using regular labels:
msg: db "Enter a digit "
msgend:
Length equ msgend - msg
Length2 equ $ - msg ; Length2 = Length
newline: db 0xA,0xD
Length3 equ $ - msg ; Length3 includes the \n\r LF CR sequence as well.
; sometimes that *is* what you want
You can put Length equ msgend - msg
anywhere, or mov ecx, msgend - msg
directly. (It's sometimes useful to have a label on the end of something, e.g. cmp rsi, msgend
/ jb .loop
at the bottom of a loop.
BTW, it's usually CR LF, not LF CR.
Less obvious examples:
times 4 dd $
assembles the same as this (but without creating a symbol table entry or clashing with an existing name):
here: times 4 dd here
In times 4 dd $
, $
doesn't update to its own address for each dword, it's still the address of the start of the line. (Try it in a file by itself and hexdump the flat binary: it's all zeros.)
But a %rep
block is expanded before $
, so
%rep 4
dd $
%endrep
does produce 0, 4, 8, 12 (starting from an output position of 0
in a flat binary for this example.)
$ nasm -o foo rep.asm && hd foo
00000000 00 00 00 00 04 00 00 00 08 00 00 00 0c 00 00 00
Manually encoding jump displacements:
A normal direct call
is E8 rel32
, with the displacement calculated relative to the end of the instruction. (i.e. relative to EIP/RIP while the instruction is executing, because RIP holds the address of the next instruction. RIP-relative addressing modes work this way, too.) A dword is 4 bytes, so in a dd
pseudo-instruction with one operand, the address of the end is $+4
. You could of course just put a label on the next line and use that.
earlyfunc: ; before the call
call func ; let NASM calculate the offset
db 0xE8
dd func - ($ + 4) ; or do it ourselves
db 0xE8
dd earlyfunc - ($ + 4) ; and it still works for negative offsets
...
func: ; after the call
disassembly output (from objdump -drwC -Mintel
):
0000000000400080 <earlyfunc>:
400080: e8 34 00 00 00 call 4000b9 <func> # encoded by NASM
400085: e8 2f 00 00 00 call 4000b9 <func> # encoded manually
40008a: e8 f1 ff ff ff call 400080 <earlyfunc> # and backwards works too.
If you get the offset wrong, objdump will put the symbolic part as func+8
, for example. The relative displacement in the first 2 call instructions differs by 5 because call rel32
is 5 bytes long and they have the same actual destination, not the same relative displacement. Note that the disassembler takes care of adding the rel32 to the address of the call instructions to show you absolute destination addresses.
You can use db target - ($+1)
to encode the offset for a short jmp
or jcc
. (But beware: db 0xEB, target - ($+1)
isn't right, because the end of the instruction is actually $+2
when you put both the opcode and displacement as multiple args for the same db
pseudo-instruction.)
Related: $$
is the start of the current section, so $ - $$
is how far into the current section you are. But this is only within the current file, so linking two files that put stuff in .rodata
is different from having two section .rodata
blocks in the same source file. See What's the real meaning of $$ in nasm.
By far the most common use is times 510-($-$$) db 0
/ dw 0xAA55
to pad (with db 0
) a boot sector out to 510 bytes, and then add the boot sector signature to make 512 bytes. (The NASM manual explains how this works)