在 windows 中,exe 与 dll 只是一个标志位的差别。而在 linux 中则更为复杂,尽管 linux 中.so (sharedobject) 与 executable 文件同为 elf,但是实际上 executable 文件是无法直接被 dlopen。
如果真的使用如下代码加载 pie 文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
int main(int argc, char** argv)
{
void *handle;
void (*func_print_name)(const char*);
handle = dlopen("./pie", RTLD_LAZY);
if(!handle)
{
printf("%s\n",dlerror());
}
dlclose(handle);
return EXIT_SUCCESS;
}
则会报出错误:./pie: cannot dynamically load position-independent executable
于是就去看了下 glibc dlopen 的代码,发现是因为 glibc 在 dlopen 的代码里做了限制 dl-load.c - elf/dl-load.c - Glibc source code (glibc-2.30) - Bootlin
if ((__glibc_unlikely (l->l_flags_1 & DF_1_NOOPEN)
&& (mode & __RTLD_DLOPEN))
|| (__glibc_unlikely (l->l_flags_1 & DF_1_PIE)
&& __glibc_unlikely ((mode & __RTLD_OPENEXEC) == 0)))
{
/* We are not supposed to load this object. Free all resources. */
_dl_unmap_segments (l);
if (!l->l_libname->dont_free)
free (l->l_libname);
if (l->l_phdr_allocated)
free ((void *) l->l_phdr);
if (l->l_flags_1 & DF_1_PIE)
errstring
= N_("cannot dynamically load position-independent executable");
else
errstring = N_("shared object cannot be dlopen()ed");
goto call_lose;
}
当 .dynamic section 的 FLAGS_1 tag 具有 DF_1_NOOPEN 或 DF_1_PIE 标志位时,则拒绝加载该 elf 文件。
解决: 处理这两个标志位,pie 文件就可以被 dlopen 加载
反过来,如何让一个 sharedobject 可以直接执行?
如果直接执行一个.so 文件,我们会看到 Segmentation fault (core dumped) 。观察.so 文件,首先会看到.so 文件是没有.interp 这个 section 的,因此程序执行的时候不会有动态链接器为程序做动态链接。再看入口点位置,发现指向 deregister_tm_clones 这个函数,这个函数很明显不是我们要的入口函数,因此导致程序无法执行。
解决: 首先在代码中加入.interp 这个区段,为程序加入要使用的动态链接器的名字。然后在编译时指定程序入口点,即可使程序正常运行。
但光这样实际上是不完美的,熟悉 linux 程序运行流程的都知道,程序在执行 main 函数前还有 libc 的初始化流程,如果不进行这个流程,那么一些函数则无法使用。最开始我想在编译的时候将入口点相关的代码编译进.so 文件中,但是 gcc 在编译的时候报错: __init_array_start can not be used when making a shared object
,看来在动态库中没法链接入口点相关的代码,因此只好自己手动定义入口点,动态调用__libc_start_main 为 libc 进行初始化。
代码供参考:
const char interp_path[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";
int _start(void *a1, void *a2, void (*a3)(void))
{
void *stack;
asm(" .intel_syntax noprefix\n\
and rsp,0x0fffffffffffffff0 \n\
mov %0,rsp;\n\
.att_syntax prefix "
: "=r"(stack));
pfn__libc_start_main libc_start_main = dlsym(0, "__libc_start_main");
libc_start_main(main, 0, 0, 0, 0, 0, &stack);
}