在 asm 中定义变量

Posted

技术标签:

【中文标题】在 asm 中定义变量【英文标题】:Defining variables in asm 【发布时间】:2020-08-05 02:19:20 【问题描述】:

以下是在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

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

【问题讨论】:

【参考方案1】:

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


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

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

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

您将常量数据连同代码一起放在.text 中,出于性能原因,这通常不是您想要的。但这意味着汇编器可以在汇编时解析 RIP 相对寻址,而不是仅使用链接器必须填写的重定位。尽管 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     $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   

(由于您将它们放入.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   

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


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


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

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

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

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

【讨论】:

以上是关于在 asm 中定义变量的主要内容,如果未能解决你的问题,请参考以下文章

ASM 代码中对 C++ 变量的未定义引用 [重复]

Inline ASM C ++中的DB ASM变量

为啥要在 ASM 中指定变量的地址而不是将其复制到寄存器中?

asm x86中的变量声明顺序?

GCC 内联汇编错误:变量 '%al' 的 asm 说明符与 asm clobber 列表冲突

如何在没有扩展内联 asm 的情况下在 gcc 内联汇编中声明和初始化局部变量?