本文介绍了GOT和GOTOFF之间的区别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是32位汇编的初学者,我试图将一个简单的C程序编译为Assembly.除了使用GOTOFF时,我了解其中的大部分内容.

  .file"main.c".文本.section .rodata.LC0:.string"Hello world".文本.globl主.type main,@功能主要的:.LFB0:.cfi_startproc第4张(%esp),%ecx.cfi_def_cfa 1,0andl $ -16,%esppushl -4(%ecx)Pushl%ebp.cfi_escape 0x10,0x5,0x2,0x75,0move%esp,%ebpPushl%ebxpushl%ecx.cfi_escape 0xf,0x3,0x75,0x78,0x6.cfi_escape 0x10,0x3,0x2,0x75,0x7c致电__x86.get_pc_thunk.ax地址$ _GLOBAL_OFFSET_TABLE_,%eaxsubl $ 12,%espleal .LC0 @ GOTOFF(%eax),%edx#<-在这里Pushl%edxmove%eax,%ebx呼叫puts @ PLTaddl $ 16,%espmovl $ 0,%eaxLeal -8(%ebp),%esppopl%ecx.cfi_restore 1.cfi_def_cfa 1,0popl%ebx.cfi_restore 3popl%ebp.cfi_restore 5leal -4(%ecx),%esp.cfi_def_cfa 4,4退回.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_startprocmovl(%esp),%eax退回.cfi_endproc.LFE1:.ident"GCC:(GNU)9.2.0".section .note.GNU-stack,",@ progbits 

为什么使用GOTOFF?%eax中是否已经加载了GOT的地址?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可执行文件,以通过 -fno-plt 将GOT直接用于共享库函数地址,而不是调用PLT并使其使用GOT.

  #include< stdio.h>无效foo(){putchar('\ n');} 

gcc9.2 -O3 -m32 -fno-plt on Godbolt ( -fno-pie 是Godbolt编译器资源管理器上的默认设置,与您的系统不同.)

  foo():sub esp,20#gcc喜欢浪费额外的16个字节的堆栈推送DWORD PTR stdout#[disp32]绝对地址推10致电[DWORD PTR _IO_putc @ GOT]加esp,28退回 

push call 都有一个使用32位绝对地址的内存操作数. push 正在从已知的(链接时间常数)地址加载 stdout FILE * 值.(没有文本重定位.)

调用正在从GOT加载由动态链接程序保存的函数指针.(并将其直接加载到EIP中.)

I'm a beginner to 32 bit assembly and I tried to compile a simple C program into Assembly. I understand most of it except when it uses 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

Why does it use GOTOFF? Isn't the address of GOT already loaded in %eax? What is the difference between GOT and GOTOFF?

解决方案

symbol@GOTOFF addresses the variable itself, relative to the GOT base (as a convenient but arbitrary choice of anchor). lea of that gives you symbol address, mov would give you data at the symbol. (The first few bytes of the string in this case.)

symbol@GOT gives you offset (within the GOT) of the GOT entry, for that symbol. A mov load from there gives you the address of the symbol. (GOT entries are filled in by the dynamic linker).

Why use the Global Offset Table for symbols defined in the shared library itself? has an example of accessing an extern variable that does result in getting its address from the GOT and then dereferencing that.


BTW, this is position-independent code. Your GCC is configured that way by default. If you used -fno-pie -no-pie to make a traditional position-dependent executable, you'd just get a normal efficient pushl $.LC0. (32-bit is missing RIP-relative addressing so it's quite inefficient.)

In a non-PIE (or in 64-bit PIE), the GOT barely gets used at all. The main executable defines space for symbols so it can access them without going through the GOT. libc code uses the GOT anyway (mostly because of symbol interposition in 64-bit code) so letting the main executable provide the symbol doesn't cost anything and makes the non-PIE executable faster.

We can get a non-PIE executable to use the GOT directly for shared library function addresses with -fno-plt, instead of calling into the PLT and having it use the GOT.

#include <stdio.h>
void foo() { putchar('\n'); }

gcc9.2 -O3 -m32 -fno-plt on Godbolt (-fno-pie is the default on the Godbolt compiler explorer, unlike your system.)

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

Both push and call have a memory operand using a 32-bit absolute address. push is loading the FILE* value of stdout from a known (link-time-constant) address. (There isn't a text relocation for it.)

call is loading the function pointer saved by the dynamic linker from the GOT. (And loading it directly into EIP.)

这篇关于GOT和GOTOFF之间的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-11 02:18