在 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 中指定变量的地址而不是将其复制到寄存器中?