在 asm 中定义变量

Defining variables in asm

以下是在 asm 中定义变量的有效方法吗?

.globl main
.globl a, b, c, d

a: .byte    4
b: .value   7
c: .long    0x0C    # 11
d: .quad    9

main:
    mov     [=10=],         %eax
    add     a(%rip),    %eax
    add     b(%rip),    %eax
    add     c(%rip),    %eax
    add     d(%rip),    %eax
    ret

比如required/suggested有一个.TEXT节吗? a(%rip) 究竟如何解析为 的值?这对我来说就像魔法一样。

默认部分是.text;任何部分指令 assemble 之前的行进入 .text 部分。所以你确实有一个,事实上你把所有的东西都放在里面,包括你的数据。 (或 read-only 常量。)出于性能原因,通常您应该将静态常量放在 .rodata 中(或 Windows 上的 .rdata),而不是 .text。 (在 I-cache 和 D-cache 以及 TLB 中混合代码和数据浪费 space。)


它不会在 assemble 时解析为立即 </code>,而是解析为一个地址。在这种情况下,使用 RIP-relative 寻址方式。请参阅 <a href="">what does "mov offset(%rip), %rax" do?</a> / <a href="">How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work?</a> 了解更多关于它的意思是“相对于 RIP 的符号地址 <code>a”,而不是实际的 RIP + 符号的绝对地址。

在其他情况下,符号 a 在用作 add $a, %rdi 或其他东西时通常会解析为其(32 位)绝对地址。

只有 在运行时 CPU 从静态加载数据(你用像 .long 这样的指令放在那里)存储。如果您在执行 add c(%rip), %eax 之前更改了内存中的内容(例如使用调试器,或通过 运行 其他指令),它将加载不同的值。

您将常量数据与代码一起放在 .text 中,出于性能原因,这通常不是您想要的。但这意味着 assembler 可以在 assemble 时解析 RIP-relative 寻址,而不是仅使用链接器必须填写的重定位。虽然看起来 GAS 选择 not 来解析引用并将其留给链接器:

$ gcc -c foo.s
$ objdump -drwC -Matt foo.o

foo.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <a>:
   0:   04                      .byte 0x4

0000000000000001 <b>:
   1:   07                      (bad)  
        ...

0000000000000003 <c>:
   3:   0c 00                   or     [=10=]x0,%al
        ...

0000000000000007 <d>:
   7:   09 00                   or     %eax,(%rax)
   9:   00 00                   add    %al,(%rax)
   b:   00 00                   add    %al,(%rax)
        ...

000000000000000f <main>:
   f:   b8 00 00 00 00          mov    [=10=]x0,%eax
  14:   03 05 00 00 00 00       add    0x0(%rip),%eax        # 1a <main+0xb>    16: R_X86_64_PC32       a-0x4
  1a:   03 05 00 00 00 00       add    0x0(%rip),%eax        # 20 <main+0x11>   1c: R_X86_64_PC32       b-0x4
  20:   03 05 00 00 00 00       add    0x0(%rip),%eax        # 26 <main+0x17>   22: R_X86_64_PC32       c-0x4
  26:   03 05 00 00 00 00       add    0x0(%rip),%eax        # 2c <main+0x1d>   28: R_X86_64_PC32       d-0x4
  2c:   c3                      retq   

(尝试按照指令反汇编数据是因为您将它们放入 .textobjdump -d 仅显示 assembles .text 和 non-immediate常量通常放在 .rodata.)

将其链接到可执行文件可解析这些符号引用:

$ gcc -nostdlib -static foo.s    # not a working executable, just link it without extra stuff
$ objdump -drwC -Matt a.out
... (bogus data omitted)

000000000040100f <main>:
  40100f:       b8 00 00 00 00          mov    [=11=]x0,%eax
  401014:       03 05 e6 ff ff ff       add    -0x1a(%rip),%eax        # 401000 <a>
  40101a:       03 05 e1 ff ff ff       add    -0x1f(%rip),%eax        # 401001 <b>
  401020:       03 05 dd ff ff ff       add    -0x23(%rip),%eax        # 401003 <c>
  401026:       03 05 db ff ff ff       add    -0x25(%rip),%eax        # 401007 <d>
  40102c:       c3                      retq   

注意RIP+rel32寻址方式下相对偏移量的32位little-endian2的补码编码。 (以及带有绝对地址的注释,由 objdump 在此反汇编输出中为方便起见添加的。)


顺便说一句,包括 GAS 在内的大多数 assemblers 都有宏功能,因此您可以使用 a = 4.equ a, 4 将其定义为 assemble-time 常量,而不是将数据发射到那里的输出中。然后你将它用作 add $a, %eax,这将 assemble 到 add $sign_extended_imm8, r/m32 操作码。


此外,您所有的加载都是双字大小的(由寄存器操作数决定),因此其中只有 1 个与您使用的数据指令的大小相匹配。 Single-step 通过你的代码并查看 EAX 的高位。

汇编语言并没有真正的变量。它具有可用于实现 high-level-language 变量概念的工具,包括具有静态存储的变量 class。 (标签和 .data.bss 中的一些 space。或 const“变量”的 .rodata。)

但是如果您以不同的方式使用这些工具,您可以执行一些操作,例如加载跨越 .byte.value(16 位)和 [= 的第一个字节的 4 个字节21=]。所以在第一条指令之后,您将得到 EAX += 0x0c000704(因为 x86 是 little-endian)。这在 assembler 中写入是完全合法的,并且没有检查以强制变量的概念在下一个标签之前结束。

(除非你使用 MASM,它确实有变量;在那种情况下你不得不写 add eax, dword ptr [a];如果没有大小覆盖,MASM 会抱怨双字寄存器和字节变量之间的不匹配. 其他风格的 asm 语法,比如 NASM 和 AT&T,假设你知道自己在做什么,不要试图“提供帮助”。)