在 asm 中定义变量

Dav*_*542 1 variables x86 assembly gnu-assembler att

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

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

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

main:
    mov     $0,         %eax
    add     a(%rip),    %eax
    add     b(%rip),    %eax
    add     c(%rip),    %eax
    add     d(%rip),    %eax
    ret
Run Code Online (Sandbox Code Playgroud)

例如,是否需要/建议有一个.TEXT部分?究竟如何a(%rip)解析 的值$4?这对我来说几乎就像魔术一样。

Pet*_*des 5

默认部分是.text; 任何节指令之前的行都组装到.text节中。所以你确实有一个,实际上你把所有东西都放进去了,包括你的数据。(或只读常量。)通常,出于性能原因,您应该将静态常量放在.rodata(或.rdata在 Windows 上),而不是放在 中.text。(混合代码和数据会浪费 I-cache 和 D-cache 以及 TLB 中的空间。)


它不会$4在汇编时解析为立即数,而是解析为地址。在这种情况下,使用 RIP 相对寻址模式。看看“mov offset(%rip), %rax”有什么作用?/在 x86-64 GAS Intel 语法中,像“[RIP + _a]”这样的 RIP 相关变量引用是如何工作的?有关它的意思是“a相对于 RIP的符号地址”这一事实的更多信息,而不是实际上 RIP + 符号的绝对地址。

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

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

您将常量数据.text与代码一起放入 中,出于性能原因,这通常不是您想要的。但这意味着汇编器可以在汇编时解析 RIP 相对寻址,而不是仅使用链接器必须填写的重定位。 尽管 GAS 似乎选择解析引用并将其留给链接器:

$ 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     $0x0,%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    $0x0,%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   
Run Code Online (Sandbox Code Playgroud)

(尝试将您的数据作为指令进行反汇编,因为您将它们放入.text. objdump -d仅反汇编.text,并且非立即常量通常放置在.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    $0x0,%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   
Run Code Online (Sandbox Code Playgroud)

请注意 RIP+rel32 寻址模式中相对偏移量的 32 位小端 2 补码编码。(以及带有绝对地址的注释,由 objdump 在此反汇编输出中添加方便。)


顺便说一句,包括 GAS 在内的大多数汇编器都有宏工具,因此您可以使用a = 4.equ a, 4将其定义为汇编时间常数,而不是将数据发送到那里的输出中。然后你将它用作add $a, %eax,它将组装成一个add $sign_extended_imm8, r/m32操作码。


此外,您的所有负载都是 dword 大小(由寄存器操作数确定),因此其中只有 1 个与您使用的数据指令的大小匹配。 单步执行您的代码并查看 EAX 的高位。

汇编语言并没有真正的变量。它具有可用于实现变量的高级语言概念的工具,包括具有静态存储类的变量。(一个标签和一些空格在.dataor 中.bss。或.rodata用于const“变量”。)

但是,如果您以不同的方式使用这些工具,您可以执行诸如加载 4 个字节的操作.byte,这些字节跨越 . 、.value(16 位)和.long. 所以在第一条指令之后,你会得到 EAX += 0x0c000704 (因为 x86 是小端)。这在汇编程序中是完全合法的,并且没有任何检查来强制变量的概念在下一个标签之前结束。

(除非你使用MASM,里面确实有变量;在这种情况下,你不得不写add eax, dword ptr [a];没有替代大小MASM会抱怨一个DWORD寄存器和一个字节变量之间的不匹配ASM语法的其他口味,像NASM和。 AT&T,假设你知道你在做什么,不要试图“有帮助”。)