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

pushcall都有一个使用32位绝对地址的内存操作数。 push 正在从已知(link-时间常数)地址加载 stdoutFILE* 值。 (没有文本重定位。)

call正在从GOT中加载动态linker保存的函数指针。 (并直接加载到 EIP 中。)