$ 究竟是如何在 NASM 中工作的?

Posted

技术标签:

【中文标题】$ 究竟是如何在 NASM 中工作的?【英文标题】:How does $ work in NASM, exactly? 【发布时间】:2018-05-09 17:40:02 【问题描述】:
message db "Enter a digit ", 0xA,0xD
Length equ $- message

是用来获取字符串长度的吗? 它在内部是如何工作的?

【问题讨论】:

你试过reading the documentation吗? 我试过了,但看不懂! $ 通常被称为“位置计数器”。 (在其他汇编器中可以是*. 相关:如果您将Length equ $- message 放在错误的位置会发生什么可能会帮助您了解它是如何工作的:***.com/questions/26897633/… (I tried but could not understand! 失败并没有错。但是记录和展示你尝试过的东西并没有什么坏处——即使不言而喻你尝试了某事 在寻求帮助之前。) 【参考方案1】:

这会让汇编器在汇编时为你计算字符串长度

$ 是当前位置的地址之前 发射它出现的行的字节(如果有的话)。 Section 3.5 of the manual 没有详细说明。

$ - msg 类似于here - msg,即当前位置(字符串末尾)和字符串开头之间的字节距离。 (See also this tutorial NASM 标签和指令,如resb

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


为了更好地理解它,看看当你弄错时会发生什么可能会有所帮助:In NASM labels next to each other in memory are printing both strings instead of first one。这个人用过

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

导致hlen 包括两个字符串的长度。

EQU 立即将右侧计算为常数值。 (在一些像 FASM 这样的汇编程序中,equ 是一个文本替换,你必须在这个位置使用glen = $ - GOODBYE_MSG 来评估$,而不是在以后的mov ecx, glen 指令或其他东西中评估$。但是 NASM 的equ 现场评估;使用%define 进行文本替换)


使用$ 完全等同于在行首放置一个标签并使用它来代替$

对象大小示例也可以使用常规标签来完成:

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

您可以将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  

手动编码跳转位移:

一个普通的直接call is E8 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,例如。前 2 个调用指令中的相对位移相差 5,因为 call rel32 的长度为 5 个字节,并且它们具有相同的实际目的地,相同的相对位移。请注意,反汇编程序负责将 rel32 添加到调用指令的地址中,以显示绝对目标地址。

您可以使用db target - ($+1) 将偏移量编码为短的jmpjcc。 (但请注意:db 0xEB, target - ($+1) 是不对的,因为当您将操作码和位移都作为同一 db 伪指令的多个 args 时,指令的结尾实际上是 $+2。)


相关:$$ 是当前部分的开始,所以$ - $$ 是您进入当前部分的距离。但这仅在当前文件中,因此链接两个将内容放入 .rodata 的文件不同于在同一源文件中拥有两个 section .rodata 块。见What's the real meaning of $$ in nasm。

到目前为止,最常见的用法是times 510-($-$$) db 0 / dw 0xAA55 将引导扇区(使用db 0)填充到 510 字节,然后添加引导扇区签名以生成 512 字节。 (The NASM manual explains how this works)

【讨论】:

很好的例子!您也许可以添加另一个使用dbdd$ 手动创建call rel32 指令的指令。 @fuz:感谢您的建议。终于有时间完成这个编辑了。 有趣的事实:MMIX 汇编器使用@ 而不是$,因为@ 清楚地表示了我们所在的位置。 @PeterCordes 我们可以认为 $ 等于 RIP,我正在阅读一些代码,我发现了这个 lea rsi, [rel $ +0xffffffffffffffff ],这真的让我很困惑很多关于这应该是什么意思? @zerocool: 不,RIP 相对寻址是相对于 next 指令的开始,但$this 的开始操作说明。 [rel $ - 1] 将是该 LEA 指令之前的字节,使用 RIP 相对寻址模式寻址。 (我没有检查机器代码以查看 NASM 是否实际组装它)。 [rip - 1] 将是 LEA 指令的最后一个字节,即 rel32 的高字节。

以上是关于$ 究竟是如何在 NASM 中工作的?的主要内容,如果未能解决你的问题,请参考以下文章

关于库如何在 C 中工作的问题

如何在 iPhone 模拟器中工作的 UIAutomation 中获取 captureScreenWithName?

关于 sprintf 函数如何在 C 中工作的困惑

如何定义在 Laravel 数据库迁移中工作的非 id 主键?

Android究竟是如何判断是不是在线的?

关于 connection.end() 如何在 node.js mySQL 模块中工作的困惑