这是“可执行”共享库的一个最小示例(假定文件名: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

甚至更好的是,完全自动检测适当的路径,以确保该路径适用于在其上编译库的系统。

最佳答案



当您运行共享库时,argcargv将被传递到堆栈上的输入函数。

问题在于,在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.asmentry32.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/

10-13 09:29