看起来您(或TCC)截断了一个指向32位的指针,并将其符号扩展到64位.它对存储使用了[disp32]
寻址模式,因为这是mov
而不是movabs moffs, rax
.
64-位模式意味着CS/DS/ES/SS段基数都为零,主流OS下的32位代码已经做到了这一点.ds:0x...
是GAS .intel_syntax noprefix
反汇编语法(如objdump -drwC -Mintel
)如何显示[disp32]
个寻址模式以将其与立即数区分开来,而不是仅使用方括号(这在asm源代码中确实适用于裸数字,而不像在实际的MASM中).例如add rax, 1
添加常数1
,而不是来自绝对地址1
的加载.
movabs rax, 0x7ffff5e1b020
是地址的立即移位,mov rax, [rax]
也使用DS作为段基数,只是不会在反汇编中显示它.
请注意,TCC已经很旧了,而且x86-64支持可能是在它被设计为为32位x86编译之后添加的.This is probably a TCC bug,如果该代码(在mov rcx
之前)都来自*((volatile uint64_t*)(0x7ffff5e1b020) += 1
,因为它从正确的地址加载,但截断存储地址.32位x86可以使用任何有效地址作为绝对寻址模式.X86-64只有在加载/存储累加器时才能做到这一点,具有mov moffs
又称为movabs
操作码(https://www.felixcloutier.com/x86/mov).
对于静态存储,您通常需要RIP相对寻址模式,例如mov rax, [RIP + rel32]
,因为这是一个7字节指令,而不是10用于movab,并且更有效地适合uop缓存. (或者RIP相关的lea
到不同的寄存器中,以便它可以在加载和存储之间重用地址,同时仍然将+=
结果留在寄存器中作为同一表达式的一部分进行比较.
TCC正在使用可能最糟糕的策略,先将64位立即数写入寄存器,然后再用值覆盖地址.但它需要将+=
存储回相同的地址,所以如果它只是使用了其他寄存器中的任何一个,它仍然有mov [rdx], rax
或其他地址可用.为了生成正确的代码,它需要另一个movabs rcx, imm64
来重新实现mov [rcx], rax
之前或更早的寄存器中的地址.
或者因为这是累加器,movabs rax, ds:0x7ffff5e1b020
/ inc rax
/ movabs ds:0x7ffff5e1b020, rax
将是可编码的. (mov-imm 64到任何寄存器都是可用的,但是从64位绝对地址加载/存储仅适用于AL/AX/EAX/RAX. 但这是TCC,所以它不会寻找RAX的特殊情况.)
不知道为什么它知道如何将mov-imm64
写入寄存器以加载而不是存储.也许是因为加载不需要任何额外的寄存器,因为它已经加载到一个寄存器中,因此该寄存器可以作为地址的暂存空间.截断store 的64位地址显然是一个问题.
非PIE可执行文件的静态存储在虚拟地址空间的低32位,mov [disp32], reg
可以对其进行寻址,但mov [RIP+rel32]
仍然更高效,这就是为什么像GCC和clang这样的主流编译器即使在非PIE可执行文件中也使用RIP相对寻址来处理全局/静态变量.100(但mov r32, imm32
用于非PIE中的静态地址与PIE或其他共享对象中的RIP相对LEA.101)
顺便说一句,它可能会对<=
比较的另一边造成同样的低效混乱,因为你有一个相似的表达式.这只是一个加载,但在您的例子中,b
的地址非常接近a
的地址,所以好的代码生成程序将地址放入+=
的寄存器一次,就可以使用具有小偏移量的地址,例如
movabs rcx, 0x7ffff5e1b020
mov rax, [rcx]
add rax, 0x1
mov [rcx], rax
cmp rax, [rcx+8] # 0x7ffff5e1b020+8 = 0x7ffff5e1b028
这是一个低效的10字节指令,其余的每个都是3或4字节.
如果您将TCC用作带有64位地址常量的JIT(使用&a
的printf %p
使C for TCC编译成您dlopen的库),它将不能使用RIP相对寻址.使用像uint64_t *anchor = 0x7ffff5e1b020;
这样的本地变量将为您的全局变量提供一个具有+-2GiB范围的参考点.(或者char*
或uint32_t*
,然后在指针数学之后对其进行强制转换.)例如,如果将anchor[1]
定义为uint64_t*
,则在您的例子中anchor[1]
就是b
.
TCC可能必须在每个使用它的表达式中至少从堆栈加载一次,但是mov rax, [rbp+8]
只有4字节长,并且可以在现有CPU上高效运行.(每个时钟周期加载2次,或者在Alder Lake上加载3次,标量-整数加载使用Zen 3或更高版本.)如果 Select movabs r64, imm64
,那么movabs
-imm64可能会更好,即使movabs
-imm64可以在任何ALU端口上运行,而不是大量运行.
我希望TCC会将指针本地变量加载到寄存器中,然后在C源代码看起来像anchor[0]
或anchor[1]
时使用像[rcx]
或[rcx+8]
这样的寻址模式,但如果它浪费add
上的指令,情况可能会更糟.