简体   繁体   English

在 gcc 32 位代码中未定义对“_GLOBAL_OFFSET_TABLE_”的引用,用于简单的功能,独立的操作系统

[英]undefined reference to `_GLOBAL_OFFSET_TABLE_' in gcc 32-bit code for a trivial function, freestanding OS

I have a small c code file(function.c):我有一个小的 C 代码文件(function.c):

int function()
{
    return 0x1234abce;
}

I am using a 64 bit machine.我正在使用 64 位机器。 However, I want to write a small 32 bit OS.但是,我想编写一个小型 32 位操作系统。 I want to compile the code into a 'pure' assembly/binary file.我想将代码编译成“纯”程序集/二进制文件。

I compile my code with:我编译我的代码:

gcc function.c -c -m32 -o file.o -ffreestanding # This gives you the object file

I link it with:我将它链接到:

ld -o function.bin -m elf_i386 -Ttext 0x0 --oformat binary function.o

I am getting the following error:我收到以下错误:

function.o: In function `function':
function.c:(.text+0x9): undefined reference to `_GLOBAL_OFFSET_TABLE_'

You need -fno-pie ;你需要-fno-pie the default (in most modern distros) is -fpie : generate code for a position-independent executable . 默认值(在大多数现代发行版中)是-fpie :为与位置无关的可执行文件生成代码 This is a code-gen option separate from the -pie linker option (which gcc also passes by default), and is independent of -ffreestanding .这是一个代码生成选项,与-pie链接器选项(gcc 默认情况下也通过)分开,并且独立于-ffreestanding -fpie -ffreestanding implies you want a freestanding PIE that uses a GOT, so that's what GCC targets. -fpie -ffreestanding意味着你想要一个使用 GOT 的独立 PIE,这就是 GCC 的目标。

-fpie only costs a bit of speed in 64-bit code (where RIP-relative addressing is possible) but is quite bad for 32-bit code; -fpie在 64 位代码(可以使用 RIP 相对寻址)中只需要一点速度,但对于 32 位代码来说非常糟糕; compilers get a pointer to the GOT in one of the integer registers (tying up another one of the 8) and access static data relative to that address with [reg + disp32] addressing modes like [eax + foo@GOTOFF]编译器在其中一个整数寄存器中获取指向 GOT 的指针(绑定 8 个寄存器中的另一个),并使用[reg + disp32]寻址模式(如[eax + foo@GOTOFF] )访问相对于该地址的静态数据


With optimization disabled, gcc -fpie -m32 generates the address of the GOT in a register even though the function doesn't access any static data.禁用优化后, gcc -fpie -m32会在寄存器中生成 GOT 的地址,即使该函数不访问任何静态数据也是如此。 You'd can see this if you look at your compiler output (with gcc -S instead of -c on the machine you're compiling on).如果您查看编译器输出(在您正在编译的机器上使用gcc -S而不是-c ),您就会看到这一点。

On Godbolt we can use -m32 -fpie to give the same effect as a GCC configured with --enable-default-pie : 在 Godbolt 上,我们可以使用-m32 -fpie来提供与配置了--enable-default-pie的 GCC 相同的效果:

# gcc9.2 -O0 -m32 -fpie
function():
        push    ebp
        mov     ebp, esp                        # frame pointer
        call    __x86.get_pc_thunk.ax
        add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_  # EAX points to the GOT
        mov     eax, 305441742                  # overwrite with the return value
        pop     ebp
        ret

__x86.get_pc_thunk.ax:          # this is the helper function gcc calls
        mov     eax, DWORD PTR [esp]
        ret

The "thunk" returns its return address. “thunk”返回它的返回地址。 ie the address of the instruction after the call .call后指令的地址。 The .ax name means to return in EAX. .ax名称表示在 EAX 中返回。 Modern GCC can choose any register;现代 GCC 可以选择任何寄存器; traditionally the 32-bit PIC base register was always EBX but modern GCC chooses a call-clobbered register when that avoids an extra save/restore of EBX.传统上,32 位 PIC 基址寄存器始终是 EBX,但现代 GCC 在避免额外保存/恢复 EBX 时选择调用破坏寄存器。

Fun fact: call +0; pop eax有趣的事实: call +0; pop eax call +0; pop eax would be more efficient, and only 1 byte larger at each call site. call +0; pop eax会更有效率,并且在每个调用点只多 1 个字节。 You might think that would unbalance the return-address predictor stack, but in fact call +0 is special-cased on most CPUs to not do that.您可能认为这会使返回地址预测器堆栈失衡,但实际上call +0在大多数 CPU 上都是特殊情况,不会这样做。 http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0 . http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0 ( call +0 means the rel32 = 0, so it calls the next instruction. That's not how NASM would interpret that syntax, though.) call +0意味着 rel32 = 0,所以它调用下一条指令。但这不是 NASM 解释该语法的方式。)

clang doesn't generate a GOT pointer unless it needs one, even at -O0 . clang 不会生成 GOT 指针,除非它需要一个指针,即使在-O0处也是如此。 But it does so with call +0 ;但它是通过call +0这样做的; pop %eax : https://godbolt.org/z/GFY9Ht pop %eax : https://godbolt.org/z/GFY9Ht

By default, your compiler creates a position-independant executable .默认情况下,您的编译器会创建一个与位置无关的可执行文件

You can force your compiler to build a non-pie executable by passing the option -fno-pie .您可以通过传递选项-fno-pie强制您的编译器构建非 pie 可执行文件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM