在 gcc 32 位代码中未定义对“_GLOBAL_OFFSET_TABLE_”的引用,用于一个简单的函数,独立 OS
undefined reference to `_GLOBAL_OFFSET_TABLE_' in gcc 32-bit code for a trivial function, freestanding OS
我有一个小的c代码文件(function.c):
int function()
{
return 0x1234abce;
}
我使用的是 64 位机器。但是,我想写一个小的32位OS。我想将代码编译成 'pure' assembly/binary 文件。
我用以下代码编译我的代码:
gcc function.c -c -m32 -o file.o -ffreestanding # This gives you the object file
我link它与:
ld -o function.bin -m elf_i386 -Ttext 0x0 --oformat binary function.o
我收到以下错误:
function.o: In function `function':
function.c:(.text+0x9): undefined reference to `_GLOBAL_OFFSET_TABLE_'
默认情况下,您的编译器会创建一个 position-independant executable.
您可以通过传递选项 -fno-pie
.
强制您的编译器构建非 pie 可执行文件
你需要-fno-pie
; default (in most modern distros) is -fpie
: generate code for a position-independent executable。这是一个独立于 -pie
链接器选项(gcc 也默认通过)的代码生成选项,并且独立于 -ffreestanding
。 -fpie -ffreestanding
意味着您想要一个使用 GOT 的独立 PIE,这就是 GCC 的目标。
-fpie
仅在 64 位代码中花费一点速度(其中 RIP 相对寻址是可能的)但对于 32 位代码来说非常糟糕;编译器在其中一个整数寄存器中获取指向 GOT 的指针(绑定 8 个寄存器中的另一个)并使用 [reg + disp32]
寻址模式(如 [eax + foo@GOTOFF]
访问相对于该地址的静态数据
禁用优化后,gcc -fpie -m32
会在寄存器 中生成 GOT 地址,即使该函数不访问任何静态数据。如果您查看编译器输出(在您正在编译的机器上使用 gcc -S
而不是 -c
),您会看到这一点。
On 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
"thunk" return 是它的 return 地址。即 call
之后的指令地址。 .ax
名称在 EAX 中的意思是 return。现代 GCC 可以选择任何寄存器;传统上,32 位 PIC 基址寄存器始终是 EBX,但现代 GCC 在避免额外的 save/restore EBX 时选择调用破坏寄存器。
有趣的事实:call +0; pop eax
会更有效率,而且每个调用点只多 1 个字节。您可能认为这会使 return 地址预测器堆栈失衡,但实际上 call +0
在大多数 CPU 上都是特殊情况,不会这样做。 http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0。 (call +0
表示 rel32 = 0,因此它调用下一条指令。但这不是 NASM 解释该语法的方式。)
clang 不会生成 GOT 指针,除非它需要一个,即使在 -O0
。但是 call +0
;pop %eax
: https://godbolt.org/z/GFY9Ht
我有一个小的c代码文件(function.c):
int function()
{
return 0x1234abce;
}
我使用的是 64 位机器。但是,我想写一个小的32位OS。我想将代码编译成 'pure' assembly/binary 文件。
我用以下代码编译我的代码:
gcc function.c -c -m32 -o file.o -ffreestanding # This gives you the object file
我link它与:
ld -o function.bin -m elf_i386 -Ttext 0x0 --oformat binary function.o
我收到以下错误:
function.o: In function `function':
function.c:(.text+0x9): undefined reference to `_GLOBAL_OFFSET_TABLE_'
默认情况下,您的编译器会创建一个 position-independant executable.
您可以通过传递选项 -fno-pie
.
你需要-fno-pie
; default (in most modern distros) is -fpie
: generate code for a position-independent executable。这是一个独立于 -pie
链接器选项(gcc 也默认通过)的代码生成选项,并且独立于 -ffreestanding
。 -fpie -ffreestanding
意味着您想要一个使用 GOT 的独立 PIE,这就是 GCC 的目标。
-fpie
仅在 64 位代码中花费一点速度(其中 RIP 相对寻址是可能的)但对于 32 位代码来说非常糟糕;编译器在其中一个整数寄存器中获取指向 GOT 的指针(绑定 8 个寄存器中的另一个)并使用 [reg + disp32]
寻址模式(如 [eax + foo@GOTOFF]
禁用优化后,gcc -fpie -m32
会在寄存器 中生成 GOT 地址,即使该函数不访问任何静态数据。如果您查看编译器输出(在您正在编译的机器上使用 gcc -S
而不是 -c
),您会看到这一点。
On Godbolt 我们可以使用 -m32 -fpie
来获得与配置了 --enable-default-pie
:
# 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
"thunk" return 是它的 return 地址。即 call
之后的指令地址。 .ax
名称在 EAX 中的意思是 return。现代 GCC 可以选择任何寄存器;传统上,32 位 PIC 基址寄存器始终是 EBX,但现代 GCC 在避免额外的 save/restore EBX 时选择调用破坏寄存器。
有趣的事实:call +0; pop eax
会更有效率,而且每个调用点只多 1 个字节。您可能认为这会使 return 地址预测器堆栈失衡,但实际上 call +0
在大多数 CPU 上都是特殊情况,不会这样做。 http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/#call0。 (call +0
表示 rel32 = 0,因此它调用下一条指令。但这不是 NASM 解释该语法的方式。)
clang 不会生成 GOT 指针,除非它需要一个,即使在 -O0
。但是 call +0
;pop %eax
: https://godbolt.org/z/GFY9Ht