1

我是 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 有什么区别?

4

1 回答 1

1

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 中。)

于 2019-10-11T22:45:48.390 回答