x86-64编译指令与栈偏移选择的技术疑问咨询
原始C代码
char *name[2]; name[0] = "Name"; name[1] = "null";
编译器生成的汇编代码(函数序言后)
mov %fs:0x28,%rax mov %rax,-0x8(%rbp) xor %eax,%eax lea 0x9689d(%rip),%rax mov %rax,-0x20(%rbp) lea 0x9689a(%rip),%rax mov %rax,-0x18(%rbp)
问题解答
你对LEA指令的推测不正确。LEA(Load Effective Address)的作用是计算内存有效地址并写入目标寄存器,这里是通过RIP相对寻址获取静态字符串的地址:
0x9689d(%rip)指当前RIP的值加上偏移量0x9689d,得到的就是字符串"Name"在数据段的地址,再将该地址存入RAX。编译器选择LEA是为了生成位置无关代码(PIC),这样程序可以在内存任意地址加载,无需修改指令中的绝对地址,这是现代操作系统程序加载的标准要求。第一条指令
mov %fs:0x28,%rax和字符串地址复制无关——这是栈金丝雀(Stack Canary)的初始化操作,用于检测栈溢出:%fs段寄存器指向线程本地存储,0x28位置存储随机金丝雀值,将其存入栈上的-0x8(%rbp)位置,函数结束时会对比该值是否被篡改,以此判断栈是否溢出。而静态字符串的地址在数据段,必须通过RIP相对寻址获取(LEA是实现该寻址的合适指令),无法用访问FS段的方式获取字符串地址。你混淆了栈上的存储位置:
-0x8(%rbp)存储的是栈金丝雀值,并非第一个字符串的地址。第一个字符串的地址存在-0x20(%rbp),第二个在-0x18(%rbp),两者间隔正好是8字节(x86-64架构下指针的大小),完全符合char*数组的存储规则。这里存储的是指向字符串的指针,而非字符串内容本身,因此栈上不存在'\0'——字符串"Name"和"null"的内容(包括末尾的'\0')都存储在程序的数据段中,栈上仅保存指向这些数据的指针。
内容的提问来源于stack exchange,提问作者clancy

