$在NASM中如何工作? [英] How does $ work in NASM, exactly?

查看:81
本文介绍了$在NASM中如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

message db "Enter a digit ", 0xA,0xD
Length equ $- message

是否用于获取字符串的长度?
它在内部如何运作?

解决方案

这将使汇编器在汇编时为您计算字符串长度

$是当前位置之前的地址,发出它出现的行的字节(如果有). 手册的第3.5节没有涉及太多细节. /p>

$ - msg类似于here - msg,即当前位置(在字符串的末尾)和字符串的开头之间的距离(以字节为单位). (另请参阅本教程有关NASM标签和指令,例如)

(相关:除使用.(句点)的GAS外,大多数其他x86汇编器也以相同的方式使用$. MMIX汇编器使用具有正确语义的@.


要更好地理解它,可能会有助于您了解错误时会发生的情况:

您可以将Length equ msgend - msg放置在任何位置,或直接将mov ecx, msgend - msg放置. (有时,在某事物的末尾贴上标签会很有用,例如在循环底部的cmp rsi, msgend/jb .loop.

顺便说一句,通常是CR LF,而不是LF CR.


很少见的例子:

times 4  dd $

与此组装相同(但不创建符号表条目或与现有名称冲突):

here:    times 4 dd here

times 4 dd $中,$不会针对每个dword更新到它自己的地址,它仍然是行首的地址. (单独尝试在文件中进行尝试,并将转储的二进制文件进行十六进制转储:全为零.)


但是%rep块在$之前展开,所以

%rep 4
    dd $
%endrep

产生0、4、8、12(对于此示例,从0的输出位置以平面二进制形式开始).

$ nasm -o foo  rep.asm  && hd foo
00000000  00 00 00 00 04 00 00 00  08 00 00 00 0c 00 00 00  


手动编码跳跃位移:

正常的直接 callE8 rel32 ,相对于指令的 end 计算的位移. (即相对于指令执行时的EIP/RIP,因为RIP保留下一条指令的地址.RIP相对寻址模式也以这种方式工作.)dword是4个字节,因此在dd伪指令中一个操作数的末尾地址为$+4.您当然可以只在 next 行上贴上标签并使用它.

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

反汇编输出(来自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.

如果偏移错误,例如,objdump会将符号部分放置为func+8.前两个调用指令中的相对位移相差5,因为call rel32的长度为5个字节,并且它们具有相同的实际目的地,而 not 却没有相同的相对位移.请注意,反汇编程序负责将rel32添加到调用指令的地址中,以向您显示绝对目标地址.

您可以使用db target - ($+1)编码较短的jmpjcc的偏移量. (但是要注意:db 0xEB, target - ($+1)是不正确的,因为当您将操作码和置换作为同一db伪指令的多个args放置时,指令的末尾实际上是$+2.)


相关: $$是当前部分 的开始,所以$ - $$是您到当前部分的距离.但这仅在当前文件中,因此链接将内容放入.rodata的两个文件与在同一源文件中具有两个section .rodata块不同.参见> $$在nasm中的真正含义是什么.

到目前为止,最常见的用法是times 510-($-$$) db 0/dw 0xAA55将启动扇区填充(使用db 0)到510字节,然后添加启动扇区签名以形成512字节. ( NASM手册介绍了其工作原理)

message db "Enter a digit ", 0xA,0xD
Length equ $- message

Is it used to get the length of a string?
How does it work internally?

解决方案

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 causing printing issues. This person used

HELLO_MSG db 'Hello, World!',0
GOODBYE_MSG db 'Goodbye!',0

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)

这篇关于$在NASM中如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆