鉴于main.c
:
#include <stdio.h>
void (*fn_ptr)(void);
void foo(void);
void bar(void) {
printf("bar\n");
}
int main(void) {
fn_ptr = bar;
foo();
}
和foo.s
:
extern fn_ptr
global foo
foo:
mov rax, fn_ptr
call [rax]
ret
我们可以像这样运行并编译它:
clear &&
nasm foo.s -f elf64 -O0 &&
clang main.c foo.o -z noexecstack &&
./a.out
成功打印bar
,但同时也打印了一条警告:
/usr/bin/ld: foo.o: warning: relocation against `fn_ptr' in read-only section `.text'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
bar
我找不到在 NASM 中调用全局函数指针的任何在线示例,并将其更改为mov rax, fn_ptr wrt ..plt
或call [fn_ptr wrt ..plt]
打印此错误:
foo.s:7: error: ELF format cannot produce non-PC-relative PLT references
-z noexecstack
(如果有人想知道消除了什么警告):
/usr/bin/ld: warning: foo.o: missing .note.GNU-stack section implies executable stack
/usr/bin/ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
与访问链接到同一个 ELF 对象(PIE 可执行文件)的静态数据的任何其他情况相同:直接使用 RIP 相对寻址模式。
您不需要也不想通过 PLT,除非您想使用
LD_PRELOAD
定义不同全局变量的库进行符号插入技巧。但我不确定这是否可以对可执行文件(不是另一个共享库)执行任何操作,而且由于这是一个变量而不是函数,它最多可以将初始值更改为除以外的其他值0
。mov rbx, [fn_ptr]
如果我想使用同一个函数指针进行多次调用,而不想每次都重新加载函数指针,那么我也可以这样做,call rbx
这样还可以节省代码大小。或者,如果我只想节省一点代码大小,但仍然每次都重新加载函数指针,那么我也可以这样做。call rbx
lea rbx, [fn_ptr]
call [rbx]
call [rbx]
这是一个变量,所以它没有 PLT 条目;那些是用于函数的。如果需要,它将有一个 GOT 条目,例如,如果您正在创建共享库并且想要
fn_ptr
ELF 可见性global
而不是hidden
.(除非您使用编译器选项来更改它,否则默认为全局。)这类似于使用 GOT 条目调用库函数的方式,如 gcc/clang
-fno-plt
所做的那样:无法从汇编(yasm)代码在 64 位 Linux 上调用 C 标准库函数如果您不需要共享库之外的代码来读取和修改此函数指针,则
__attribute__((hidden))
您可以直接使用来访问它[rel fn_ptr]
,而无需不必要的间接级别。正如 Joshua 的回答所示,您实际上可以使用
wrt ..plt
,R_X86_64_PLT32
而不是 来进行重定位R_X86_64_PC32
,但它仍然解析为直接寻址全局变量,而不是 GOT 或 PLT 条目。我不知道PLT32
重定位是做什么用的。没有不同的机器指令,它仍然call qword [rel32]
使用 RIP 相对寻址模式,我猜只是让链接器计算从该指令末尾到全局变量的正确偏移量的另一种方法。也许这种区别对于没有 RIP 相对寻址的 32 位代码很重要,因此需要手动添加来自 PLT 而不是其自身的偏移量。是拆卸自
在没有
default rel
和/或[rel
寻址模式中,参见mov reg, imm64
使用绝对地址需要在 PIE 可执行文件或共享对象中进行文本重定位,因此会出现警告。来自评论;我现在可以输入答案了。
以下组装说明不起作用:
但这些确实如此:
ELF 格式错误是真的。我很惊讶它无法处理,
mov rax, fn_ptr wrt ..plt
但call [fn_ptr wrt ..plt]
肯定无法工作。没有这样的指令,call [qword]
但只有call rel dword
和call [rel dword]
。令我惊讶的是,它不起作用的指令
mov
无论如何也不会让您摆脱问题;它要求在文本段中进行绝对修复,以通过绝对地址引用 plt。因此,如果它确实有效,那仍然是一个警告。您想要在其他地方访问函数总是带有某些东西,
rel
因此您会获得对修复区域的 PIC 相对引用。基本形式: