如果我编译这个程序:
#include <stdio.h> int main(int argc, char** argv) { printf("hello world!\n"); return 0; }
对于x86-64,asm输出使用movl $.LC0, %edi
/ movl $.LC0, %edi
call puts
。 ( 请参阅godbolt上的完整输出/编译选项 。)
我的问题是:GCC如何知道string的地址可以放在一个32位立即数操作数中? 为什么不需要使用movabs $.LC0, %rdi
(即mov r64, imm64
,不是零或符号扩展的imm32
)。
AFAIK,没有什么说加载器必须决定加载数据部分在任何特定的地址。 如果string被存储在1ULL << 32
以上的某个地址,那么movl将忽略高位。 我用clang得到类似的行为,所以我不认为这是GCC独一无二的。
我关心的原因是我想创build我自己的数据段,它存放在我select的任意地址的内存中(可能高于2 ^ 32)。
在GCC手册中:
https://gcc.gnu.org/onlinedocs/gcc-4.5.3/gcc/i386-and-x86_002d64-Options.html
3.17.15 Intel 386和AMD x86-64选项
-mcmodel =小
为小代码模型生成代码:程序及其符号必须在地址空间的低2 GB中链接。 指针是64位。 程序可以静态或动态链接。 这是 默认的代码模型。
-mcmodel =内核为内核代码模型生成代码。 内核运行在负2 GB的地址空间中。 这个模型必须用于Linux内核代码。
-mcmodel =中等
为中型号生成代码:程序链接在地址空间的下方2 GB处。 小的符号也放在那里。 尺寸大于-mlarge-data-threshold的符号被放入大数据或bss段,并且可以位于2GB以上。 程序可以静态或动态链接。
-mcmodel =大
为大型模型生成代码:此模型不会对地址和区域大小做任何假设。
https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html
3.18.1 AArch64选项
-mcmodel =微小
为微小的代码模型生成代码。 程序及其静态定义的符号必须在1GB以内。 指针是64位。 程序可以静态或动态链接。 这个模式并没有完全实施,大多被视为“小”。
-mcmodel =小
为小代码模型生成代码。 程序及其静态定义的符号必须在4GB以内。 指针是64位。 程序可以静态或动态链接。 这是 默认的代码模型 。
-mcmodel =大
为大代码模型生成代码。 这没有任何关于地址和部分大小的假设。 指针是64位。 程序只能静态链接。
我可以确认这发生在64位编译上:
gcc -O1 foo.c
然后objdump -d a.out
(注意printf("%s\n")
可以优化到puts
! ):
0000000000400536 <main>: 400536: 48 83 ec 08 sub $0x8,%rsp 40053a: bf d4 05 40 00 mov $0x4005d4,%edi 40053f: e8 cc fe ff ff callq 400410 <puts@plt> 400544: b8 00 00 00 00 mov $0x0,%eax 400549: 48 83 c4 08 add $0x8,%rsp 40054d: c3 retq 40054e: 66 90 xchg %ax,%ax
原因在于GCC默认为-mcmodel=small
,静态数据在地址空间的底部2G链接。
请注意,字符串常量不会进入数据段,但是它们在代码段内,除非-fwritable-strings
。 另外,如果你想在内存中自由移动目标代码,你可能想用-fpic
进行编译,使得代码RIP相对而不是放在任何地方的64位地址。