assembly - GOT和GOTOFF之间的区别
问题描述
我是 32 位汇编的初学者,我尝试将一个简单的 C 程序编译成汇编。我了解其中的大部分内容,除非它使用 GOTOFF。
.file "main.c"
.text
.section .rodata
.LC0:
.string "Hello world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
leal 4(%esp), %ecx
.cfi_def_cfa 1, 0
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl %esp, %ebp
pushl %ebx
pushl %ecx
.cfi_escape 0xf,0x3,0x75,0x78,0x6
.cfi_escape 0x10,0x3,0x2,0x75,0x7c
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
subl $12, %esp
leal .LC0@GOTOFF(%eax), %edx # <- Here
pushl %edx
movl %eax, %ebx
call puts@PLT
addl $16, %esp
movl $0, %eax
leal -8(%ebp), %esp
popl %ecx
.cfi_restore 1
.cfi_def_cfa 1, 0
popl %ebx
.cfi_restore 3
popl %ebp
.cfi_restore 5
leal -4(%ecx), %esp
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB1:
.cfi_startproc
movl (%esp), %eax
ret
.cfi_endproc
.LFE1:
.ident "GCC: (GNU) 9.2.0"
.section .note.GNU-stack,"",@progbits
为什么它使用 GOTOFF?GOT的地址不是已经加载到%eax了吗?GOT 和 GOTOFF 有什么区别?
解决方案
symbol@GOTOFF 处理变量本身,相对于 GOT 基数(作为锚点的方便但任意选择)。 lea
其中给你符号地址,mov
会给你符号上的数据。(在这种情况下,字符串的前几个字节。)
symbol@GOT 为您提供该符号的 GOT 条目的偏移量(在 GOT 内)。从mov
那里加载会为您提供符号的地址。(GOT 条目由动态链接器填写)。
为什么要对共享库本身中定义的符号使用全局偏移表?有一个访问变量的示例,该extern
变量确实会导致从 GOT 获取其地址,然后取消引用它。
顺便说一句,这是与位置无关的代码。默认情况下,您的 GCC 是这样配置的。如果您曾经-fno-pie -no-pie
制作过传统的位置相关可执行文件,那么您只会得到一个正常高效的pushl $.LC0
. (32 位缺少 RIP 相对寻址,因此效率非常低。)
在非 PIE(或 64 位 PIE)中,GOT 几乎没有被使用。主可执行文件为符号定义了空间,因此它可以在不通过 GOT 的情况下访问它们。libc 代码无论如何都使用 GOT(主要是因为 64 位代码中的符号插入),因此让主可执行文件提供符号不会花费任何成本,并使非 PIE 可执行文件更快。
我们可以获得一个非 PIE 可执行文件,以直接将 GOT 用于共享库函数地址-fno-plt
,而不是调用 PLT 并让它使用 GOT。
#include <stdio.h>
void foo() { putchar('\n'); }
Godbolt-O3 -m32 -fno-plt
上的 gcc9.2 (-fno-pie
与您的系统不同,它是 Godbolt 编译器资源管理器的默认设置。)
foo():
sub esp, 20 # gcc loves to waste an extra 16 bytes of stack
push DWORD PTR stdout # [disp32] absolute address
push 10
call [DWORD PTR _IO_putc@GOT]
add esp, 28
ret
两者push
都有call
一个使用 32 位绝对地址的内存操作数。正在从已知(链接时间常数)地址push
加载 的FILE*
值。stdout
(没有文本重定位。)
call
正在从 GOT 加载由动态链接器保存的函数指针。(并将其直接加载到 EIP 中。)
推荐阅读
- java - 无法从 KSQL UDF 返回结构
- c# - 以错误轨迹生成的弹丸
- reactjs - Axios 发布问题和 .Net Core
- angularjs - 是否可以在 Angular 中过滤多个用户选择的值的 Firestore 数据
- arrays - 是否可以从终端定义一个常量值?
- python - “数组索引过多”尝试定义一列值时
- javascript - 使页面循环显示 10 秒
- c# - 在 Windows 剪贴板中存储图像宽度 alpha 通道
- javascript - 使用 JS 更改 PHP 包含在 HTML 页面中
- python - KeyError:'password1':password1 = self.cleaned_data['password1']