让动态库可以执行

让一个动态链接库既可以执行,又可以被动态链接,下面是linux x86_64示例代码

#define OUT_STR "hello world\n"

long _write(long fd, char *buf, unsigned long len)
{
    long ret;
    __asm__ volatile(
        "mov %0, %%rdi\n"
        "mov %1, %%rsi\n"
        "mov %2, %%rdx\n"
        "mov $1, %%rax\n"
        "syscall" : : "g"(fd), "g"(buf), "g"(len)
    );

    asm("mov %%rax, %0" : "=r"(ret));
    return ret;
}

void Exit(long status)
{
    __asm__ volatile("mov %0, %%rdi\n"
        "mov $60, %%rax\n"
        "syscall" : : "r"(status)
    );
}

int entry_point()
{
    _write(1, OUT_STR, sizeof(OUT_STR));

    Exit(-1);
}
[root@dldl tmp]# gcc main.c -fPIC -pie -nostdlib -o main -Wl,-e,entry_point
[root@dldl tmp]# ./main 
hello world

-fPIC 表示位置无关,-pie 表示生成elf类型为DYN但是可以执行的二进制,-nostdlib 表示不链接glibc库,-Wl,-e,entry_point (-e entry_point) 表示将entry_point函数设置为入口点


这里解释下为啥正常编译出来的动态库不能执行

gcc main.c -fPIC -shared -nostdlib -o main.so

上面是动态库的常规编译方法,通过这样编译出来的动态库 main.so 有两个不同点

所以,如果我们不加上 -pie 选项,我们也可以自己手动指定入口点和解释器

#define OUT_STR "hello world\n"
const char my_interp[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";


void Exit(long status);
long _write(long fd, char *buf, unsigned long len);
int entry_point()
{
    _write(1, OUT_STR, sizeof(OUT_STR));

    Exit(-1);
}

long _write(long fd, char *buf, unsigned long len)
{
    long ret;
    __asm__ volatile(
        "mov %0, %%rdi\n"
        "mov %1, %%rsi\n"
        "mov %2, %%rdx\n"
        "mov $1, %%rax\n"
        "syscall" : : "g"(fd), "g"(buf), "g"(len)
    );

    asm("mov %%rax, %0" : "=r"(ret));
    return ret;
}

void Exit(long status)
{
    __asm__ volatile("mov %0, %%rdi\n"
        "mov $60, %%rax\n"
        "syscall" : : "r"(status)
    );
}

上面代码我们手动指定了解释器,然后把入口点 entry_point 函数放在另外两个函数的前面,那么它即位于 .text 的起始位置,为默认入口点位置

此时,我们正常编译,就可以正常运行

[root@dldl tmp]# gcc main.c -fPIC -shared -nostdlib -o main.so
[root@dldl tmp]# ./main.so 
hello world


上面的做法,只是让一个类型为DYN的动态库可以被执行,但是里面的符号在链接时却找不到比如:(下面去掉 -nostdlib,即使用了glibc)

#include <stdio.h>

int add(int x, int y)
{
    return x+y;
}

int main(int argc, char **argv)
{
    printf("hello world\n");

    return 0;
}
[root@dldl tmp]# gcc main.c -fPIC -pie -o main.so
[root@dldl tmp]# ./main.so 
hello world

此时main.so已经为可执行的动态库,但是当我们其他的代码想要链接main.so时,发现找不到 add 符号,我们通过 readelf -s main.so 查看符号表,发现 .dynsym 节里面没有符号 add

所以按照上述方法,只能执行,但是不能被链接,解决办法很简单,编译时加上 -Wl,-E 即链接时,把所有的符号都导入到 .dynsym 动态符号表

gcc main.c -fPIC -pie -o main.so -Wl,-E

此时我们通过 readelf -s main.so 就能看到 .dynsym 里面有 add 符号了,就可以被正常链接了




上一篇: 进程内存分布-分析/proc/pid/maps段映射
下一篇: linux系统调用以及vsyscall、vdso
作者邮箱: 203328517@qq.com