GOT 和 GOTOFF 的区别
Difference between GOT and 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 , %esp
leal .LC0@GOTOFF(%eax), %edx # <- Here
pushl %edx
movl %eax, %ebx
call puts@PLT
addl , %esp
movl [=10=], %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? %eax 中不是已经载入了 GOT 的地址吗? GOT 和 GOTOFF 有什么区别?
symbol@GOTOFF 寻址变量本身,相对于 GOT 基(作为一个方便但任意选择的锚点)。 lea
of that gives you symbol address, mov
would give you data at the symbol. (在本例中为字符串的前几个字节。)
symbol@GOT 为您提供该符号的 GOT 条目的偏移量(在 GOT 内)。从那里加载 mov
会为您提供符号的地址。 (GOT条目由动态linker填写)。
有一个访问 extern
变量的示例,该变量确实会导致从 GOT 获取其地址,然后取消引用该地址。
顺便说一句,这是与位置无关的代码。默认情况下,您的 GCC 就是这样配置的。如果你使用 -fno-pie -no-pie
来制作一个传统的 position-dependent 可执行文件,你只会得到一个正常的高效 pushl $.LC0
。 (32 位缺少 RIP 相对寻址,因此效率很低。)
在非 PIE(或 64 位 PIE)中,几乎不使用 GOT。主要的可执行文件为符号定义了 space,因此它可以在不通过 GOT 的情况下访问它们。 libc 代码无论如何都使用 GOT(主要是因为 64 位代码中的符号插入)所以让主要可执行文件提供符号不会花费任何费用并使非 PIE 可执行文件更快。
我们可以让非 PIE 可执行文件使用 GOT 直接用于共享库函数地址 -fno-plt
,而不是调用 PLT 并让它使用 GOT。
#include <stdio.h>
void foo() { putchar('\n'); }
gcc9.2 -O3 -m32 -fno-plt
on Godbolt(-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
正在从已知(link-时间常数)地址加载 stdout
的 FILE*
值。 (没有文本重定位。)
call
正在从GOT中加载动态linker保存的函数指针。 (并直接加载到 EIP 中。)
我是 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 , %esp
leal .LC0@GOTOFF(%eax), %edx # <- Here
pushl %edx
movl %eax, %ebx
call puts@PLT
addl , %esp
movl [=10=], %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? %eax 中不是已经载入了 GOT 的地址吗? GOT 和 GOTOFF 有什么区别?
symbol@GOTOFF 寻址变量本身,相对于 GOT 基(作为一个方便但任意选择的锚点)。 lea
of that gives you symbol address, mov
would give you data at the symbol. (在本例中为字符串的前几个字节。)
symbol@GOT 为您提供该符号的 GOT 条目的偏移量(在 GOT 内)。从那里加载 mov
会为您提供符号的地址。 (GOT条目由动态linker填写)。
extern
变量的示例,该变量确实会导致从 GOT 获取其地址,然后取消引用该地址。
顺便说一句,这是与位置无关的代码。默认情况下,您的 GCC 就是这样配置的。如果你使用 -fno-pie -no-pie
来制作一个传统的 position-dependent 可执行文件,你只会得到一个正常的高效 pushl $.LC0
。 (32 位缺少 RIP 相对寻址,因此效率很低。)
在非 PIE(或 64 位 PIE)中,几乎不使用 GOT。主要的可执行文件为符号定义了 space,因此它可以在不通过 GOT 的情况下访问它们。 libc 代码无论如何都使用 GOT(主要是因为 64 位代码中的符号插入)所以让主要可执行文件提供符号不会花费任何费用并使非 PIE 可执行文件更快。
我们可以让非 PIE 可执行文件使用 GOT 直接用于共享库函数地址 -fno-plt
,而不是调用 PLT 并让它使用 GOT。
#include <stdio.h>
void foo() { putchar('\n'); }
gcc9.2 -O3 -m32 -fno-plt
on Godbolt(-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
正在从已知(link-时间常数)地址加载 stdout
的 FILE*
值。 (没有文本重定位。)
call
正在从GOT中加载动态linker保存的函数指针。 (并直接加载到 EIP 中。)