我目前正在 NASM x64 中重新编码标准 C 库中的一些函数。目前我只有两个功能:
strlen:
bits 64
section .text
global strlen
strlen:
xor rbx, rbx
jmp strlen_loop
strlen_loop:
cmp byte [rdi + rbx], 0
je strlen_ret
inc rbx
jmp strlen_loop
strlen_ret:
mov rax, rbx
ret
putstr:
bits 64
extern strlen
STDOUT equ 1
section .text
global putstr
putstr:
call strlen
mov rdx, rax
xor rax, rax
mov rdi, STDOUT
syscall
ret
这是 makefile:
CC = nasm
CFLAGS = -f elf64
SRC = $(wildcard src/*.asm)
OBJ = $(SRC:.asm=.o)
NAME = minilibc.so
all: $(NAME)
$(NAME): $(OBJ)
ld -fPIC -shared -o $(NAME) $(OBJ) -nostdlib
%.o: %.asm
$(CC) $(CFLAGS) $< -o $@
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean all
test: re
gcc -o test/test test/main.c
LD_PRELOAD=./minilibc.so ./test/test
.PHONY: all clean fclean re
和测试代码:
#include <stdio.h>
extern size_t putstr(const char *str);
int main(void)
{
const char *str = "Hello, World!\n";
putstr(str);
return (0);
}
这是我得到的错误:
rm -f src/putstr.o src/strlen.o rm -f minilibc.so nasm -f elf64 src/putstr.asm -o src/putstr.o nasm -f elf64 src/strlen.asm -o src/strlen.o ld -fPIC -shared -o minilibc.so src/putstr.o src/strlen.o -nostdlib ld: src/putstr.o: attention: readdress on "strlen" in read-only ".text" section ld: src/putstr.o: readdressing R_X86_64_PC32 to symbol "strlen" cannot be used when creating a shared object; recompile with -fPIC ld: final link edit failed: wrong value make: *** [Makefile:10: minilibc.so] Error 1
LD似乎告诉我要在PIC模式下重新编译,我已经这样做了,那么问题是我的函数与GLIBC函数同名吗?但是,我在没有标准库的情况下进行编译。
使用
global strlen:function hidden
而不是 global strlen
使符号对链接器可见,以便其他目标文件可以引用它,但 not 从我们正在创建的 .so
共享对象导出,因此它不参与在符号插入中。
您的
global strlen
使符号完全全局化,参与符号插入。 链接器不会让您用 call rel32
引用该符号,因为在动态链接时使用的实际定义可能位于不同的共享中库(例如 libc.so
,因为您使用了标准 C 函数的名称),并且它可能会从该共享库加载超过 +- 2GiB。
在 32 位代码中,rel32 可以到达虚拟地址空间中的任何位置,因此“重新寻址”(如文本重定位)可以通过需要在只读页面中重写机器代码的警告来处理它(在
.text
)。
在这种情况下,如果动态链接器已经在不同的库中看到了
strlen
,您不希望让您的库调用更快的版本。您的 putstr
取决于 ABI 不需要的 strlen
的自定义行为(例如,不修改 RDI 指针 arg,以便在找到长度后可以使用它,而不需要保存/恢复堆栈上的任何内容)。
您的
strlen
也与编译器生成的代码不兼容 ABI;它修改 RBX,这是一个被调用破坏的寄存器。但即使从 putstr
调用它也是一个问题,因为它最终返回到 C 调用者。只需首先使用 rax
,而不是稍后复制到返回值的单独寄存器。或者计入 RDX,因为这个私有版本的 strlen
仅作为 putstr
的助手存在。 (也许也可以叫它不同的名字。)
顺便说一句,你的
putstr
也完全坏了;它不会将 void*
arg 的 RDI 复制到 write
,并且实际上进行 read
系统调用而不是 write
。 (系统调用号与标准文件描述符匹配,例如 x86-64 Linux 的 __NR_write = STDOUT_FD = 1
中的 unistd_64.h
,这是记住它们的有用选择。)想必您会注意到,如果您不在共享库中尝试此操作这样你就可以让它工作链接到可执行文件中。或者,如果您将 strlen
放在与 .asm
相同的 putstr
中,并且使其不是 global
。
相关:
https://nasm.us/doc/nasmdoc8.html#section-8.9.5 -
elf
GLOBAL
指令的扩展记录了这一点。
在共享库中不使用 PLT 的情况下调用另一个目标文件中的函数? 同一问题的 GAS 版本。在 GAS 中,您可以执行
.globl strlen
和 .hidden strlen
。还有 IIRC,.type strlen, @function
无法在 64 位 Linux 上从汇编 (yasm) 代码调用 C 标准库函数 - 调用 other 共享库中的函数的三种方法:PLT、
gcc -fno-plt
样式或制作非 PIE 可执行文件因此链接器通过 PLT 为您发明了间接。链接器底部的示例将间接/PLT 调用“放松”为直接,提到了 NASM 的 hidden
标志。
NASM Linux 共享对象错误:针对“.data”重新定位 R_X86_64_32S 还提到了 NASM 的
hidden
指令的 global
标志。