这是“可执行”共享库的一个最小示例(假定文件名:mini.c
):
// Interpreter path is different on some systems
//+definitely different for 32-Bit machines
const char my_interp[] __attribute__((section(".interp")))
= "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#include <stdio.h>
#include <stdlib.h>
int entry() {
printf("WooFoo!\n");
exit (0);
}
如果使用以下格式进行编译:
gcc -fPIC -o mini.so -shared -Wl,-e,entry mini.c
。 “运行”生成的.so
将如下所示:confus@confusion:~$ ./mini.so
WooFoo!
我的问题是:
如何更改上面的程序以将命令行参数传递给
.so
-file的调用? 更改后的示例shell session 可能例如看起来像这样:confus@confusion:~$ ./mini.so 2 bar
1: WooFoo! bar!
2: WooFoo! bar!
confus@confusion:~$ ./mini.so 3 bla
1: WooFoo! bla!
2: WooFoo! bla!
3: WooFoo! bla!
5: WooFoo! Bar!
如果目标是32位或64位二进制,则在编译时进行检测也很好,可以相应地更改解释器字符串。否则,将收到“正在访问损坏的共享库”警告。就像是:
#ifdef SIXTY_FOUR_BIT
const char my_interp[] __attribute__((section(".interp"))) = "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#else
const char my_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.2";
#endif
甚至更好的是,完全自动检测适当的路径,以确保该路径适用于在其上编译库的系统。
最佳答案
当您运行共享库时,argc
和argv
将被传递到堆栈上的输入函数。
问题在于,在x86_64 linux上编译共享库时使用的调用约定将是System V AMD64 ABI的调用约定,它不在堆栈上而是在寄存器中接受参数。
您将需要一些ASM粘合代码,该代码从堆栈中获取参数并将其放入正确的寄存器中。
这是一个简单的.asm文件,您可以将其另存为entry.asm并仅链接以下内容:
global _entry
extern entry, _GLOBAL_OFFSET_TABLE_
section .text
BITS 64
_entry:
mov rdi, [rsp]
mov rsi, rsp
add rsi, 8
call .getGOT
.getGOT:
pop rbx
add rbx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc
jmp entry wrt ..plt
该代码将参数从堆栈复制到适当的寄存器中,然后以与位置无关的方式调用
entry
函数。然后,您可以像编写常规
entry
函数一样直接编写main
:// Interpreter path is different on some systems
//+definitely different for 32-Bit machines
const char my_interp[] __attribute__((section(".interp")))
= "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#include <stdio.h>
#include <stdlib.h>
int entry(int argc, char* argv[]) {
printf("WooFoo! Got %d args!\n", argc);
exit (0);
}
这就是您随后编译库的方式:
nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o
优点是您不会在C代码中混入内联asm语句,而是将真正的入口点清晰地抽象到了起始文件中。
不幸的是,有no completely clean, reliable way to do that。最好的办法是依靠具有正确定义的首选编译器。
由于您使用的是GCC,因此您可以这样编写C代码:
#if defined(__x86_64__)
const char my_interp[] __attribute__((section(".interp")))
= "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2";
#elif defined(__i386__)
const char my_interp[] __attribute__((section(".interp")))
= "/lib/ld-linux.so.2";
#else
#error Architecture or compiler not supported
#endif
#include <stdio.h>
#include <stdlib.h>
int entry(int argc, char* argv[]) {
printf("%d: WooFoo!\n", argc);
exit (0);
}
并且有两个不同的开始文件。
一种用于64位:
global _entry
extern entry, _GLOBAL_OFFSET_TABLE_
section .text
BITS 64
_entry:
mov rdi, [rsp]
mov rsi, rsp
add rsi, 8
call .getGOT
.getGOT:
pop rbx
add rbx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc
jmp entry wrt ..plt
还有一个32位的:
global _entry
extern entry, _GLOBAL_OFFSET_TABLE_
section .text
BITS 32
_entry:
mov edi, [esp]
mov esi, esp
add esi, 4
call .getGOT
.getGOT:
pop ebx
add ebx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc
push edi
push esi
jmp entry wrt ..plt
这意味着您现在有两种稍微不同的方式为每个目标编译库。
对于64位:
nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64
对于32位:
nasm entry32.asm -f elf32
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32
综上所述,您现在有两个开始文件
entry.asm
和entry32.asm
,在mini.c
中有一组定义,它们会自动选择正确的解释器,以及根据目标编译库的两种略有不同的方式。因此,如果我们真的想一路走下去,剩下的就是创建一个Makefile来检测正确的目标并相应地构建您的库。让我们这样做:
ARCH := $(shell getconf LONG_BIT)
all: build_$(ARCH)
build_32:
nasm entry32.asm -f elf32
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32
build_64:
nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64
我们在这里完成了。只需运行
make
来构建您的库并让魔术发生。关于c - 如何在Linux上更改解释器路径并将命令行参数传递给 "executable"共享库?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29652446/