在 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
(尝试按照指令反汇编数据是因为您将它们放入 .text
。objdump -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,假设你知道自己在做什么,不要试图“提供帮助”。)
以下是在 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="
”,而不是实际的 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
(尝试按照指令反汇编数据是因为您将它们放入 .text
。objdump -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,假设你知道自己在做什么,不要试图“提供帮助”。)