如何更改解释器路径并将命令行参数传递给Linux上的"可执行"共享库?

con*_*use 8 c linux linker compilation shared-libraries

以下是"可执行"共享库的最小示例(假定文件名:)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);
}
Run Code Online (Sandbox Code Playgroud)

如果用例如:编译它gcc -fPIC -o mini.so -shared -Wl,-e,entry mini.c."运行"结果.so将如下所示:

confus@confusion:~$ ./mini.so
WooFoo!
Run Code Online (Sandbox Code Playgroud)

我现在的问题是:
如何更改上述程序以将命令行参数传递给.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!
Run Code Online (Sandbox Code Playgroud)

在编译时检测也是很好的,目标是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
Run Code Online (Sandbox Code Playgroud)

或者甚至更好,完全自动检测适当的路径,以确保编译库的系统是正确的.

tux*_*ux3 5

我如何更改上述程序以将命令行参数传递给 .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
Run Code Online (Sandbox Code Playgroud)

该代码将参数从堆栈复制到适当的寄存器中,然后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);
}
Run Code Online (Sandbox Code Playgroud)

这就是您编译库的方式:

nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o
Run Code Online (Sandbox Code Playgroud)

优点是您不会将内联 asm 语句与您的 C 代码混合在一起,而是您的真正入口点在起始文件中被清晰地抽象出来。

在编译时检测也是很好的,目标是 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);
}
Run Code Online (Sandbox Code Playgroud)

并且有两个不同的启动文件。
一个 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
Run Code Online (Sandbox Code Playgroud)

一个是 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
Run Code Online (Sandbox Code Playgroud)

这意味着您现在有两种稍微不同的方法来为每个目标编译您的库。

对于 64 位:

nasm entry.asm -f elf64
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64
Run Code Online (Sandbox Code Playgroud)

对于 32 位:

nasm entry32.asm -f elf32
gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32
Run Code Online (Sandbox Code Playgroud)

总而言之,您现在有两个启动文件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
Run Code Online (Sandbox Code Playgroud)

我们已经完成了。只需运行make即可构建您的库,让奇迹发生。