这是“可执行”共享库(假定文件名:)的最小示例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会话可能例如如下所示:
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
甚至更好的是,完全自动检测适当的路径,以确保该路径适用于编译库的系统。
如何更改上面的程序以将命令行参数传递给.so文件的调用?
当你运行你的共享库,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语句,而是将真正的入口点清晰地抽象到了起始文件中。
如果目标是32位或64位二进制文件以相应地更改解释器字符串,则在编译时进行检测也将很不错。
不幸的是,没有完全干净,可靠的方法可以做到这一点。最好的办法是依靠具有正确定义的首选编译器。
由于您使用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
即可构建您的库并让魔术发生。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句