在x86-64 AT&T汇编中,地址前面的星号*代表什么意思?

32

以下一行代码是什么意思:

...
401147: ff 24 c5 80 26 40 00    jmpq   *0x402680(,%rax,8)
...

在内存地址前面的星号代表什么意思?此外,当内存访问方法缺少第一个寄存器值时,这是什么意思?

通常是类似于(“%register”,%rax,8),但在这种情况下,它没有第一个寄存器。

有什么提示吗?

7个回答

22
这是AT&T汇编语法:
  • 源在目标之前
  • 助记符后缀指示操作数的大小(q表示四元组等)
  • 寄存器以 % 为前缀,立即值以 $ 为前缀
  • 有效地址采用 DISP(BASE, INDEX, SCALE) 的形式(DISP + BASE + INDEX * SCALE)
  • 间接跳转 / 调用操作数用 * 表示(与直接操作数相对应)。
因此,您可以使用 jmpq 跳转到存储在 %rax * 8 + 0x402680 中的绝对地址,其长度为四元组。
AT&T语法需要一种方式来区分 RIP = foo (jmp foo) 和 RIP = 从某个符号地址装载(jmp *foo)。请记住,movl $1, foo 是一个对绝对地址 foo 进行存储的操作。
对于其他寻址模式,没有什么跳转/调用的类型是有歧义的,任何不是裸标签的东西都必须是间接的。(如果你输入jmp %raxjmp 24(%rax) 或者其他任何不是裸符号名称的东西,GAS会推断并警告关于没有 * 的间接跳转。)。
(在64位模式下,您通常会实际使用 jmp *foo(%rip) 将全局变量加载到RIP中,而不是像 jmp *foo 这样使用32位绝对地址。但这种可能性存在,并且在AT&T语法设计之前,在x86-64之前,这是正常的做事方式。)

但是在前面加上星号有什么区别吗?我明白你说的话在其他方面是有道理的。我认为首先星号将从内存位置0x402680中取出数据。因此,基本上它将变成%rax*8+mem[0x402680]。 - de1337ed
2
星号只是指定了一个绝对跳转。无论如何,jmp都会取出指定内存位置的数据。 - Michael Foukarakis
1
*实际上意味着间接引用,就像C语言的解引用运算符一样。考虑jmp foo(RIP=foo)和jmp *foo(RIP=foo所指向的内存内容)。当然,通常使用RIP相对寻址来访问代码指针变量foo,例如jmp *foo(%rip),但是foo*foo很好地展示了为什么AT&T语法需要用*来消除歧义。 - Peter Cordes

18

实际上这是计算表跳转,其中0x402680是表的地址,rax是8字节(qword)指针的索引。


3
在汇编代码中,当C语言代码完成if-else或switch语句时,经常使用跳转表(Jump tables)。跳转表使得控制可以在固定时间内传递,而不是必须检查很多个单独的相等性检查。 - Eagle

10

将东西转换为英特尔语法总是可以使事情更清晰:

FF24C5 80264000  JMP QWORD PTR [RAX*8+402680]

谁给我点了踩,能解释一下为什么吗?仅仅因为反对智能语法这个理由是不足够的... - Necrolis
4
除了逐字地重复英语中的数学公式,当使用英特尔语法时并没有太多需要添加的内容。 - Necrolis
不回答问题。 - Michael O
@MichaelO 其实这是你可以给出的最简洁和直接的答案。如果你看一下这里评分最高的回复,它实际上是一个从左到右阅读的英特尔助记符的项目列表... - Necrolis

5
jmpq 是一个无条件跳转到指定地址的指令。'q' 表示处理的是 quad words (64 位长)。 *0x402680(,%rax,8) : 这是在x-86汇编中表示地址的一种方式。您说得没错,通常在第一个逗号前面有一个寄存器,但如果未指定寄存器,则仍遵循相同的规则。
格式如下: D(reg1, reg2, scalingFactor),其中 D 代表位移(displacement)。位移基本上就是一个整数。reg1 是第一个或基础寄存器。 reg2 是第二个寄存器,scalingFactor 是 2、4、8 中的一个(也许还有 1,但我不确定)。现在,您可以按照以下方式通过简单相加来获得地址:位移 + (reg1 中的值)+ scalingFactor *(reg2 中的值)。
我不完全确定地址前面的星号是什么意思,但我猜测它表示位移值存储在该地址处。
希望这有所帮助。

*表示间接跳转,从内存或寄存器中设置RIP的值。例如,jmp foo将设置RIP为foo。jmp *foo将设置RIP为从该内存地址加载的结果。(当然,在64位代码中,您通常使用RIP相对寻址来访问静态存储,因此jmp *foo(%rip)会解引用全局变量foo,假设它保存了一个函数指针,并且这是一个尾调用。 - Peter Cordes

5

这是一个跳转到存储在内存中的地址的指令。该地址存储在内存地址rax*8+0x402680处,其中rax是当前rax值(当此指令执行时)。


5
作为Necrolis所写,Intel语法使它更加明显,但是RTN确实更清晰。该行代码的含义是将一个寄存器的值传输到另一个寄存器中,具体解释请参考RTN
jmpq   *0x402680(,%rax,8)

将在RTN中描述为:
RIP <- M[0x402680 + (8 * RAX)]

其中M是系统内存。

因此,我们可以编写通用格式jmpq *c(r1, r2, k),其中c是立即常量,r1r2是通用寄存器,k可以是1(默认)、2、4或8:

RIP <- M[c + r1 + (k * r2)]

4

最简单的例子

为了让事情更清晰:

.data
    # Store he address of the label in the data section.
    symbol: .int label   
.text   
    # Jumps to label.
    jmp *symbol
    label:

GitHub上游

如果没有*,它会跳转到.data部分中symbol的地址并且崩溃。

我觉得这个语法有点不一致,因为对于大多数指令:

mov symbol, %eax
mov label, %eax

已经移动了地址为symbol的数据,$symbol用于该地址。在这一点上,Intel语法更加一致,因为它总是使用[]进行解引用。

*当然是C中解引用操作符*ptr的助记符。


关于“不一致性”:跳转“访问”标签处的代码。jmp foo会导致CPU将foo处的字节作为代码加载(通过设置RIP)。而mov foo,%eax则将foo处的字节加载到寄存器中。我不确定使用jmp $foo进行直接相对跳转的语法设计是否好,因为通常$修饰符仅适用于(绝对)立即数,而不是相对编码。.long foo发出符号的地址。我们真的不希望jmp foo成为内存间接RIP = load(foo)的语法;那是一个可怕的失败模式。 - Peter Cordes
此外,jmp 指令与其他指令的参数处理方式不同;它仅支持内存间接跳转的寻址模式,因此没有必要在没有 * 的情况下消除立即数和其他内容之间的歧义。 - Peter Cordes
参见警告:间接调用没有`*' - 我在那里的答案提供了类似的推理,解释了为什么AT&T需要一个*来消除歧义。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接