ELF格式文件是UNIX系统常见二进制文件格式
常见有三种文件类型:
可执行文件
可重定位文件(就是.o,链接起来就是静态链接库)
共享目标文件(.so)
ELF文件格式从链接视图角度分为三部分:
ELF文件头、节区(section)以及节区表(section table)
从装载视图角度也分为三部分:
ELF文件头、段区(segment table)表以及段区
左边链接视图。右边装载视图
左边就是所有的section节表,从节表头取索引,右边段表,从段表头取索引。链接头可以忽略。
ELF文件头
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT]; //magic等
Elf32_Half e_type; //格式类型,binary还是so
Elf32_Half e_machine; //平台架构,arm或者x86
Elf32_Word e_version; //格式版本,没动过
Elf32_Addr e_entry; //入口点
Elf32_Off e_phoff; //段表偏移
Elf32_Off e_shoff; //节表偏移
Elf32_Word e_flags; //平台特定标志
Elf32_Half e_ehsize; //Elf_Ehdr大小
Elf32_Half e_phentsize; //段表结构大小
Elf32_Half e_phnum; //段表数量
Elf32_Half e_shentsize; //节表结构大小
Elf32_Half e_shnum; //解表数量
Elf32_Half e_shstrndx; //节表字符串表索引,节表名字在这
} Elf32_Ehdr;
ELF文件节表
typedef struct {
Elf32_Word sh_name; //指向节表字符串表的偏移
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr; //虚地址
Elf32_Off sh_offset; //文件内偏移
Elf32_Word sh_size; //大小
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
ELF文件段表
typedef struct elf32_phdr{
Elf32_Word p_type; //段类型LOAD、DYNAMIC等
Elf32_Off p_offset; //段文件偏移,跟节表文件偏移一直
Elf32_Addr p_vaddr; //段虚地址,跟节表也一样
Elf32_Addr p_paddr; //段物理地址,嵌入式有区别
Elf32_Word p_filesz; //段文件中大小
Elf32_Word p_memsz; //段占用内存大小,比如全局没有初始化的数组在data和非初始化在bss,不写在文件里,进入内存就要占空间?
Elf32_Word p_flags; //段的权限属性
Elf32_Word p_align; //段对齐
} Elf32_Phdr;
Linker加载流程
加载so部分代码
观察find_library,就是对dlopen(char* name,flag)的封装,就是打开一个动态链接库。
// /data/data/***/libtest.so
soinfo *find_library(const char *name)
{
soinfo *si;
const char *bname;
#if ALLOW_SYMBOLS_FROM_MAIN
if (name == NULL)
return somain;
#else
if (name == NULL)
return NULL;
#endif
//因为so路径可能是个名字,或者全路径。所以操作是字符串逆置去找反斜杠。如果有反斜杠,加一就是名字,如果没找到,传的name就是直接是名字。
bname = strrchr(name, '/');
bname = bname ? bname + 1 : name;
//下面就是dlopen只能加载一次,乳沟已经加载了就返回原来的,实现就是dlopen返回void*,底层是返回的是soinfo*一个指针,linker会把所有so加载完生成一个soinfo结果,里面有个next指针,所以遍历去找名字如果找到就说明已经加载好了,dlopen结束,如果没找到就加载。所以后面函数是一个load_library和init_library。
for(si = solist; si != 0; si = si->next){
if(!strcmp(bname, si->name)) {
if(si->flags & FLAG_ERROR) {
DL_ERR("%5d '%s' failed to load previously", pid, bname);
return NULL;
}
if(si->flags & FLAG_LINKED) return si;
DL_ERR("OOPS: %5d recursive link to '%s'", pid, si->name);
return NULL;
}
}
TRACE("[ %5d '%s' has not been loaded yet. Locating...]\n", pid, name);
si = load_library(name);
if(si == NULL)
return NULL;
return init_library(si);
}
然后我们看load_library
load_library
tatic soinfo *
load_library(const char *name)
{
int fd = open_library(name);//打开函数在下面
int cnt;
unsigned ext_sz;
unsigned req_base;
const char *bname;
soinfo *si = NULL;
Elf32_Ehdr *hdr;
if(fd == -1) {
DL_ERR("Library '%s' not found", name);
return NULL;
}
/* We have to read the ELF header to figure out what to do with this image
*/
if (lseek(fd, 0, SEEK_SET) < 0) {//从0开始读
DL_ERR("lseek() failed!");
goto fail;
}
if ((cnt = read(fd, &__header[0], PAGE_SIZE)) < 0) {//读一页放到header
DL_ERR("read() failed!");
goto fail;
}
/* Parse the ELF header and get the size of the memory footprint for
* the library 解析头部计算出文件所需要占用的内存大小,放在ext_sz*/
req_base = get_lib_extents(fd, name, &__header[0], &ext_sz);
if (req_base == (unsigned)-1)
goto fail;
TRACE("[ %5d - '%s' (%s) wants base=0x%08x sz=0x%08x ]\n", pid, name,
(req_base ? "prelinked" : "not pre-linked"), req_base, ext_sz);
/* Now configure the soinfo struct where we'll store all of our data
* for the ELF object. If the loading fails, we waste the entry, but
* same thing would happen if we failed during linking. Configuring the
* soinfo struct here is a lot more convenient.
*就是把所有段表串起来放在soinfo链表里/
bname = strrchr(name, '/');
si = alloc_info(bname ? bname + 1 : name);
if (si == NULL)
goto fail;
/* Carve out a chunk of memory where we will map in the individual
* segments */
si->base = req_base;
si->size = ext_sz;
si->flags = 0;
si->entry = 0;
si->dynamic = (unsigned *)-1;
if (alloc_mem_region(si) < 0)//在下面,分配文件
goto fail;
TRACE("[ %5d allocated memory for %s @ %p (0x%08x) ]\n",
pid, name, (void *)si->base, (unsigned) ext_sz);
/* Now actually load the library's segments into right places in memory */
if (load_segments(fd, &__header[0], si) < 0) {//把文件加载到内存,在下面
goto fail;
}
/* this might not be right. Technically, we don't even need this info
* once we go through 'load_segments'. */
hdr = (Elf32_Ehdr *)si->base;//把内存中的开始给hdr,下面是关掉文件
si->phdr = (Elf32_Phdr *)((unsigned char *)si->base + hdr->e_phoff);
si->phnum = hdr->e_phnum;
/**/
close(fd);
return si;
fail:
if (si) free_info(si);
close(fd);
return NULL;
}
open_library,总之就是打开文件,返回fd
static int open_library(const char *name)
{
int fd;
char buf[512];
const char **path;
int n;
TRACE("[ %5d opening %s ]\n", pid, name);
if(name == 0) return -1;
if(strlen(name) > 256) return -1;//校验长度
if ((name[0] == '/') && ((fd = _open_lib(name)) >= 0))//对文件在不在,有没有访问权限,就是调open去打开的封装
return fd;
//如果没打开,就去搜索环境变量之类里面的路径去打开
for (path = ldpaths; *path; path++) {
n = format_buffer(buf, sizeof(buf), "%s/%s", *path, name);
if (n < 0 || n >= (int)sizeof(buf)) {
WARN("Ignoring very long library path: %s/%s\n", *path, name);
continue;
}
if ((fd = _open_lib(buf)) >= 0)
return fd;
}
for (path = sopaths; *path; path++) {
n = format_buffer(buf, sizeof(buf), "%s/%s", *path, name);
if (n < 0 || n >= (int)sizeof(buf)) {
WARN("Ignoring very long library path: %s/%s\n", *path, name);
continue;
}
if ((fd = _open_lib(buf)) >= 0)
return fd;
}
return -1;
}
get_lib_extents,传进来名字,和第一个页读的内容。计算出所需要占用的内存大小
static unsigned
get_lib_extents(int fd, const char *name, void *__hdr, unsigned *total_sz)
{
unsigned req_base;
unsigned min_vaddr = 0xffffffff;
unsigned max_vaddr = 0;
unsigned char *_hdr = (unsigned char *)__hdr;
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)_hdr;
Elf32_Phdr *phdr;
int cnt;
TRACE("[ %5d Computing extents for '%s'. ]\n", pid, name);
if (verify_elf_object(_hdr, name) < 0) {//检验头elf头是否正确,,magic
DL_ERR("%5d - %s is not a valid ELF object", pid, name);
return (unsigned)-1;
}
req_base = (unsigned) is_prelinked(fd, name);//
if (req_base == (unsigned)-1)
return -1;
else if (req_base != 0) {
TRACE("[ %5d - Prelinked library '%s' requesting base @ 0x%08x ]\n",
pid, name, req_base);
} else {
TRACE("[ %5d - Non-prelinked library '%s' found. ]\n", pid, name);
}
phdr = (Elf32_Phdr *)(_hdr + ehdr->e_phoff);//取得段表偏移,得到段表开始,然后去迭代
/* find the min/max p_vaddrs from all the PT_LOAD segments so we can
* get the range. 找所有段的中的最小地址和最大地址*/
for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
if (phdr->p_type == PT_LOAD) {
if ((phdr->p_vaddr + phdr->p_memsz) > max_vaddr)
max_vaddr = phdr->p_vaddr + phdr->p_memsz;
if (phdr->p_vaddr < min_vaddr)
min_vaddr = phdr->p_vaddr;
}
}
//找到之后取整,计算出所占的最大内存
if ((min_vaddr == 0xffffffff) && (max_vaddr == 0)) {
DL_ERR("%5d - No loadable segments found in %s.", pid, name);
return (unsigned)-1;
}
/* truncate min_vaddr down to page boundary *///按页对齐
min_vaddr &= ~PAGE_MASK;
/* round max_vaddr up to the next page */
max_vaddr = (max_vaddr + PAGE_SIZE - 1) & ~PAGE_MASK;
*total_sz = (max_vaddr - min_vaddr);
return (unsigned)req_base;
}
alloc_mem_region,顾名思义,分配内存
static int
alloc_mem_region(soinfo *si)
{
if (si->base) {
/* Attempt to mmap a prelinked library. */
return reserve_mem_region(si);
}
/* This is not a prelinked library, so we use the kernel's default
allocator.
*/
//分配一个si->size就是前面计算的extern_zs的可读可执行的私有匿名共享内存
void *base = mmap(NULL, si->size, PROT_READ | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (base == MAP_FAILED) {
DL_ERR("%5d mmap of library '%s' failed: %d (%s)\n",
pid, si->name,
errno, strerror(errno));
goto err;
}
si->base = (unsigned) base;//把内存首地址给他
PRINT("%5d mapped library '%s' to %08x via kernel allocator.\n",
pid, si->name, si->base);
return 0;
err:
DL_ERR("OOPS: %5d cannot map library '%s'. no vspace available.",
pid, si->name);
return -1;
}
load_segments
static int
load_segments(int fd, void *header, soinfo *si)//把前面预读的header,内存,文件指针传进来
{
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)header;
Elf32_Phdr *phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff);
unsigned char *base = (unsigned char *)si->base;
int cnt;
unsigned len;
unsigned char *tmp;
unsigned char *pbase;
unsigned char *extra_base;
unsigned extra_len;
unsigned total_sz = 0;
si->wrprotect_start = 0xffffffff;
si->wrprotect_end = 0;
TRACE("[ %5d - Begin loading segments for '%s' @ 0x%08x ]\n",
pid, si->name, (unsigned)si->base);
/* Now go through all the PT_LOAD segments and map them into memory
* at the appropriate locations.这个大循环就是把文件加载到内存 */
for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
if (phdr->p_type == PT_LOAD) {
DEBUG_DUMP_PHDR(phdr, "PT_LOAD", pid);
/* we want to map in the segment on a page boundary */
tmp = base + (phdr->p_vaddr & (~PAGE_MASK));//基地址加上虚地址按页向下取整,因为linux的mmp需要按页对齐的。
/* add the # of bytes we masked off above to the total length. 就是占的大小*/
len = phdr->p_filesz + (phdr->p_vaddr & PAGE_MASK);
TRACE("[ %d - Trying to load segment from '%s' @ 0x%08x "
"(0x%08x). p_vaddr=0x%08x p_offset=0x%08x ]\n", pid, si->name,
(unsigned)tmp, len, phdr->p_vaddr, phdr->p_offset);
pbase = mmap(tmp, len, PFLAGS_TO_PROT(phdr->p_flags),//这个参数是通过这个宏把elf文件权限属性转换成外部的系统调用?
MAP_PRIVATE | MAP_FIXED, fd,//fd就是打开的handle,这里直接文件映射到内存,不需要什么read,和文件偏移按页对齐,直接把文件映射到内存,在内核中直接把文件的页链接到虚地址上,比其他方法高效,一般load的2个,test代码rx,data数据,rw,所以要load两次因为属性不同,而且一般test和data也会按页隔离,中间插一页,不会完全挨着
phdr->p_offset & (~PAGE_MASK));
if (pbase == MAP_FAILED) {
DL_ERR("%d failed to map segment from '%s' @ 0x%08x (0x%08x). "
"p_vaddr=0x%08x p_offset=0x%08x", pid, si->name,
(unsigned)tmp, len, phdr->p_vaddr, phdr->p_offset);
goto fail;
}
/* If 'len' didn't end on page boundary, and it's a writable
* segment, zero-fill the rest. */
if ((len & PAGE_MASK) && (phdr->p_flags & PF_W))
memset((void *)(pbase + len), 0, PAGE_SIZE - (len & PAGE_MASK));
/* Check to see if we need to extend the map for this segment to
* cover the diff between filesz and memsz (i.e. for bss).
*
* base _+---------------------+ page boundary
* . .
* | |
* . .
* pbase _+---------------------+ page boundary
* | |
* . .
* base + p_vaddr _| |
* . \ \ .
* . | filesz | .
* pbase + len _| / | |
* <0 pad> . . .
* extra_base _+------------|--------+ page boundary
* / . . .
* | . . .
* | +------------|--------+ page boundary
* extra_len-> | | | |
* | . | memsz .
* | . | .
* \ _| / |
* . .
* | |
* _+---------------------+ page boundary
*/
tmp = (unsigned char *)(((unsigned)pbase + len + PAGE_SIZE - 1) &
(~PAGE_MASK));
if (tmp < (base + phdr->p_vaddr + phdr->p_memsz)) {
extra_len = base + phdr->p_vaddr + phdr->p_memsz - tmp;
TRACE("[ %5d - Need to extend segment from '%s' @ 0x%08x "
"(0x%08x) ]\n", pid, si->name, (unsigned)tmp, extra_len);
/* map in the extra page(s) as anonymous into the range.
* This is probably not necessary as we already mapped in
* the entire region previously, but we just want to be
* sure. This will also set the right flags on the region
* (though we can probably accomplish the same thing with
* mprotect).
*/
extra_base = mmap((void *)tmp, extra_len,
PFLAGS_TO_PROT(phdr->p_flags),
MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS,
-1, 0);
if (extra_base == MAP_FAILED) {
DL_ERR("[ %5d - failed to extend segment from '%s' @ 0x%08x"
" (0x%08x) ]", pid, si->name, (unsigned)tmp,
extra_len);
goto fail;
}
/* TODO: Check if we need to memset-0 this region.
* Anonymous mappings are zero-filled copy-on-writes, so we
* shouldn't need to. */
TRACE("[ %5d - Segment from '%s' extended @ 0x%08x "
"(0x%08x)\n", pid, si->name, (unsigned)extra_base,
extra_len);
}
/* set the len here to show the full extent of the segment we
* just loaded, mostly for debugging */
len = (((unsigned)base + phdr->p_vaddr + phdr->p_memsz +
PAGE_SIZE - 1) & (~PAGE_MASK)) - (unsigned)pbase;
TRACE("[ %5d - Successfully loaded segment from '%s' @ 0x%08x "
"(0x%08x). p_vaddr=0x%08x p_offset=0x%08x\n", pid, si->name,
(unsigned)pbase, len, phdr->p_vaddr, phdr->p_offset);
total_sz += len;
/* Make the section writable just in case we'll have to write to
* it during relocation (i.e. text segment). However, we will
* remember what range of addresses should be write protected.
*
*/
if (!(phdr->p_flags & PF_W)) {
if ((unsigned)pbase < si->wrprotect_start)
si->wrprotect_start = (unsigned)pbase;
if (((unsigned)pbase + len) > si->wrprotect_end)
si->wrprotect_end = (unsigned)pbase + len;
mprotect(pbase, len,
PFLAGS_TO_PROT(phdr->p_flags) | PROT_WRITE);
}
} else if (phdr->p_type == PT_DYNAMIC) {
DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC", pid);
/* this segment contains the dynamic linking information */
si->dynamic = (unsigned *)(base + phdr->p_vaddr);//上面已经加载到内存了,这里主意上面只是读个个head
} else {
#ifdef ANDROID_ARM_LINKER
if (phdr->p_type == PT_ARM_EXIDX) {
DEBUG_DUMP_PHDR(phdr, "PT_ARM_EXIDX", pid);
/* exidx entries (used for stack unwinding) are 8 bytes each.
*/
si->ARM_exidx = (unsigned *)phdr->p_vaddr;
si->ARM_exidx_count = phdr->p_memsz / 8;
}
#endif
}
}
/* Sanity check */
if (total_sz > si->size) {
DL_ERR("%5d - Total length (0x%08x) of mapped segments from '%s' is "
"greater than what was allocated (0x%08x). THIS IS BAD!",
pid, total_sz, si->name, si->size);
goto fail;
}
TRACE("[ %5d - Finish loading segments for '%s' @ 0x%08x. "
"Total memory footprint: 0x%08x bytes ]\n", pid, si->name,
(unsigned)si->base, si->size);
return 0;
fail:
/* We can just blindly unmap the entire region even though some things
* were mapped in originally with anonymous and others could have been
* been mapped in from the file before we failed. The kernel will unmap
* all the pages in the range, irrespective of how they got there.
*/
munmap((void *)si->base, si->size);
si->flags |= FLAG_ERROR;
return -1;
}
/* TODO: Implement this to take care of the fact that Android ARM
* ELF objects shove everything into a single loadable segment that has the
* write bit set. wr_offset is then used to set non-(data|bss) pages to be
* non-writable.
*/
#if 0
static unsigned
get_wr_offset(int fd, const char *name, Elf32_Ehdr *ehdr)
{
Elf32_Shdr *shdr_start;
Elf32_Shdr *shdr;
int shdr_sz = ehdr->e_shnum * sizeof(Elf32_Shdr);
int cnt;
unsigned wr_offset = 0xffffffff;
shdr_start = mmap(0, shdr_sz, PROT_READ, MAP_PRIVATE, fd,
ehdr->e_shoff & (~PAGE_MASK));
if (shdr_start == MAP_FAILED) {
WARN("%5d - Could not read section header info from '%s'. Will not "
"not be able to determine write-protect offset.\n", pid, name);
return (unsigned)-1;
}
for(cnt = 0, shdr = shdr_start; cnt < ehdr->e_shnum; ++cnt, ++shdr) {
if ((shdr->sh_type != SHT_NULL) && (shdr->sh_flags & SHF_WRITE) &&
(shdr->sh_addr < wr_offset)) {
wr_offset = shdr->sh_addr;
}
}
munmap(shdr_start, shdr_sz);
return wr_offset;
}
#endif
下面就是init_library
static soinfo *
init_library(soinfo *si)
{
unsigned wr_offset = 0xffffffff;
/* At this point we know that whatever is loaded @ base is a valid ELF
* shared library whose segments are properly mapped in. */
TRACE("[ %5d init_library base=0x%08x sz=0x%08x name='%s') ]\n",
pid, si->base, si->size, si->name);
if(link_image(si, wr_offset)) {//这个函数做链接相关的事情
/* We failed to link. However, we can only restore libbase
** if no additional libraries have moved it since we updated it.
*/
munmap((void *)si->base, si->size);
return NULL;
}
return si;
}
link_image这和函数就是解析处理前面dynamic段读取的内容。 解析elf的内容。
static int link_image(soinfo *si, unsigned wr_offset)
{
unsigned *d;
Elf32_Phdr *phdr = si->phdr;
int phnum = si->phnum;
INFO("[ %5d linking %s ]\n", pid, si->name);
DEBUG("%5d si->base = 0x%08x si->flags = 0x%08x\n", pid,
si->base, si->flags);
if (si->flags & FLAG_EXE) {
/* Locate the needed program segments (DYNAMIC/ARM_EXIDX) for
* linkage info if this is the executable. If this was a
* dynamic lib, that would have been done at load time.
*
* TODO: It's unfortunate that small pieces of this are
* repeated from the load_library routine. Refactor this just
* slightly to reuse these bits.
*/
si->size = 0;
for(; phnum > 0; --phnum, ++phdr) {
#ifdef ANDROID_ARM_LINKER
if(phdr->p_type == PT_ARM_EXIDX) {
/* exidx entries (used for stack unwinding) are 8 bytes each.
*/
si->ARM_exidx = (unsigned *)phdr->p_vaddr;
si->ARM_exidx_count = phdr->p_memsz / 8;
}
#endif
if (phdr->p_type == PT_LOAD) {
/* For the executable, we use the si->size field only in
dl_unwind_find_exidx(), so the meaning of si->size
is not the size of the executable; it is the last
virtual address of the loadable part of the executable;
since si->base == 0 for an executable, we use the
range [0, si->size) to determine whether a PC value
falls within the executable section. Of course, if
a value is below phdr->p_vaddr, it's not in the
executable section, but a) we shouldn't be asking for
such a value anyway, and b) if we have to provide
an EXIDX for such a value, then the executable's
EXIDX is probably the better choice.
*/
DEBUG_DUMP_PHDR(phdr, "PT_LOAD", pid);
if (phdr->p_vaddr + phdr->p_memsz > si->size)
si->size = phdr->p_vaddr + phdr->p_memsz;
/* try to remember what range of addresses should be write
* protected */
if (!(phdr->p_flags & PF_W)) {
unsigned _end;
if (phdr->p_vaddr < si->wrprotect_start)
si->wrprotect_start = phdr->p_vaddr;
_end = (((phdr->p_vaddr + phdr->p_memsz + PAGE_SIZE - 1) &
(~PAGE_MASK)));
if (_end > si->wrprotect_end)
si->wrprotect_end = _end;
}
} else if (phdr->p_type == PT_DYNAMIC) {
if (si->dynamic != (unsigned *)-1) {
DL_ERR("%5d multiple PT_DYNAMIC segments found in '%s'. "
"Segment at 0x%08x, previously one found at 0x%08x",
pid, si->name, si->base + phdr->p_vaddr,
(unsigned)si->dynamic);
goto fail;
}
DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC", pid);
si->dynamic = (unsigned *) (si->base + phdr->p_vaddr);
}
}
}
if (si->dynamic == (unsigned *)-1) {
DL_ERR("%5d missing PT_DYNAMIC?!", pid);
goto fail;
}
DEBUG("%5d dynamic = %p\n", pid, si->dynamic);
/* extract useful information from dynamic section 比如引用一个elf.h,这里面tag,value*/
for(d = si->dynamic; *d; d++){
DEBUG("%5d d = %p, d[0] = 0x%08x d[1] = 0x%08x\n", pid, d, d[0], d[1]);
switch(*d++){
case DT_HASH://说明这个是hash表,用来快速查找用的。下面这说那个用来解决比如一个libc里面一个函数,函数实现不在这里。我们要把这个符号找到放到这里面来,这个符号名字内容就在下面,方便查找生成了这个hash表,然后重定位的位置写在位置就是下面,而且还有比如函数指针这种自己的重定位
si->nbucket = ((unsigned *) (si->base + *d))[0];
si->nchain = ((unsigned *) (si->base + *d))[1];
si->bucket = (unsigned *) (si->base + *d + 8);
si->chain = (unsigned *) (si->base + *d + 8 + si->nbucket * 4);
break;
case DT_STRTAB://字符串在内存的位置
si->strtab = (const char *) (si->base + *d);
break;
case DT_SYMTAB://符号表在内存的位置
si->symtab = (Elf32_Sym *) (si->base + *d);
break;
#if !defined(ANDROID_SH_LINKER)
case DT_PLTREL:
if(*d != DT_REL) {
DL_ERR("DT_RELA not supported");
goto fail;
}
break;
#endif
#ifdef ANDROID_SH_LINKER
case DT_JMPREL:
si->plt_rela = (Elf32_Rela*) (si->base + *d);
break;
case DT_PLTRELSZ:
si->plt_rela_count = *d / sizeof(Elf32_Rela);
break;
#else
case DT_JMPREL:
si->plt_rel = (Elf32_Rel*) (si->base + *d);//重定位表
break;
case DT_PLTRELSZ:
si->plt_rel_count = *d / 8;//表大小
break;
#endif
case DT_REL:
si->rel = (Elf32_Rel*) (si->base + *d);
break;
case DT_RELSZ:
si->rel_count = *d / 8;
break;
#ifdef ANDROID_SH_LINKER
case DT_RELASZ:
si->rela_count = *d / sizeof(Elf32_Rela);
break;
#endif
case DT_PLTGOT://没有使用
/* Save this in case we decide to do lazy binding. We don't yet. */
si->plt_got = (unsigned *)(si->base + *d);
break;
case DT_DEBUG:
// Set the DT_DEBUG entry to the addres of _r_debug for GDB
*d = (int) &_r_debug;
break;
#ifdef ANDROID_SH_LINKER
case DT_RELA:
si->rela = (Elf32_Rela *) (si->base + *d);
break;
#else
case DT_RELA:
DL_ERR("%5d DT_RELA not supported", pid);
goto fail;
#endif//下面内容跟初始化有关。
case DT_INIT:
si->init_func = (void (*)(void))(si->base + *d);
DEBUG("%5d %s constructors (init func) found at %p\n",
pid, si->name, si->init_func);
break;
case DT_FINI:
si->fini_func = (void (*)(void))(si->base + *d);
DEBUG("%5d %s destructors (fini func) found at %p\n",
pid, si->name, si->fini_func);
break;
case DT_INIT_ARRAY:
si->init_array = (unsigned *)(si->base + *d);
DEBUG("%5d %s constructors (init_array) found at %p\n",
pid, si->name, si->init_array);
break;
case DT_INIT_ARRAYSZ:
si->init_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
break;
case DT_FINI_ARRAY:
si->fini_array = (unsigned *)(si->base + *d);
DEBUG("%5d %s destructors (fini_array) found at %p\n",
pid, si->name, si->fini_array);
break;
case DT_FINI_ARRAYSZ:
si->fini_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
break;
case DT_PREINIT_ARRAY:
si->preinit_array = (unsigned *)(si->base + *d);
DEBUG("%5d %s constructors (preinit_array) found at %p\n",
pid, si->name, si->preinit_array);
break;
case DT_PREINIT_ARRAYSZ:
si->preinit_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
break;
case DT_TEXTREL:
/* TODO: make use of this. */
/* this means that we might have to write into where the text
* segment was loaded during relocation... Do something with
* it.
*/
DEBUG("%5d Text segment should be writable during relocation.\n",
pid);
break;
}
}
DEBUG("%5d si->base = 0x%08x, si->strtab = %p, si->symtab = %p\n",
pid, si->base, si->strtab, si->symtab);
if((si->strtab == 0) || (si->symtab == 0)) {
DL_ERR("%5d missing essential tables", pid);
goto fail;
}
/* if this is the main executable, then load all of the preloads now */
if(si->flags & FLAG_EXE) {
int i;
memset(preloads, 0, sizeof(preloads));
for(i = 0; ldpreload_names[i] != NULL; i++) {
soinfo *lsi = find_library(ldpreload_names[i]);
if(lsi == 0) {
strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));
DL_ERR("%5d could not load needed library '%s' for '%s' (%s)",
pid, ldpreload_names[i], si->name, tmp_err_buf);
goto fail;
}
lsi->refcount++;
preloads[i] = lsi;
}
}
for(d = si->dynamic; *d; d += 2) {//这部分是链接部分,文件的符号有肯能依靠外部符号,外部符号在的库要加载起来,需要知道哪些库要加载,就在dynamic有个dt_needed的tag,每个tag的value值指到一个字符串,遍历一遍所有可能依赖的外部文件,都find_library,找到加载起来库
if(d[0] == DT_NEEDED){
DEBUG("%5d %s needs %s\n", pid, si->name, si->strtab + d[1]);
soinfo *lsi = find_library(si->strtab + d[1]);
if(lsi == 0) {
strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));
DL_ERR("%5d could not load needed library '%s' for '%s' (%s)",
pid, si->strtab + d[1], si->name, tmp_err_buf);
goto fail;
}
/* Save the soinfo of the loaded DT_NEEDED library in the payload
of the DT_NEEDED entry itself, so that we can retrieve the
soinfo directly later from the dynamic segment. This is a hack,
but it allows us to map from DT_NEEDED to soinfo efficiently
later on when we resolve relocations, trying to look up a symgol
with dlsym().
*/
d[1] = (unsigned)lsi;
lsi->refcount++;
}
}
if(si->plt_rel) {//这里一般存放函数指针重定位符号
DEBUG("[ %5d relocating %s plt ]\n", pid, si->name );
if(reloc_library(si, si->plt_rel, si->plt_rel_count))
goto fail;
}
if(si->rel) {
DEBUG("[ %5d relocating %s ]\n", pid, si->name );
if(reloc_library(si, si->rel, si->rel_count))
goto fail;
}
#ifdef ANDROID_SH_LINKER
if(si->plt_rela) {//这里一般是数据重定位
DEBUG("[ %5d relocating %s plt ]\n", pid, si->name );
if(reloc_library_a(si, si->plt_rela, si->plt_rela_count))
goto fail;
}
if(si->rela) {
DEBUG("[ %5d relocating %s ]\n", pid, si->name );
if(reloc_library_a(si, si->rela, si->rela_count))
goto fail;
}
#endif /* ANDROID_SH_LINKER */
si->flags |= FLAG_LINKED;//就是说明当前so已经被链接起来了。
DEBUG("[ %5d finished linking %s ]\n", pid, si->name);
#if 0
/* This is the way that the old dynamic linker did protection of
* non-writable areas. It would scan section headers and find where
* .text ended (rather where .data/.bss began) and assume that this is
* the upper range of the non-writable area. This is too coarse,
* and is kept here for reference until we fully move away from single
* segment elf objects. See the code in get_wr_offset (also #if'd 0)
* that made this possible.
*/
if(wr_offset < 0xffffffff){
mprotect((void*) si->base, wr_offset, PROT_READ | PROT_EXEC);
}
#else
/* TODO: Verify that this does the right thing in all cases, as it
* presently probably does not. It is possible that an ELF image will
* come with multiple read-only segments. What we ought to do is scan
* the program headers again and mprotect all the read-only segments.
* To prevent re-scanning the program header, we would have to build a
* list of loadable segments in si, and then scan that instead. */
if (si->wrprotect_start != 0xffffffff && si->wrprotect_end != 0) {
mprotect((void *)si->wrprotect_start,
si->wrprotect_end - si->wrprotect_start,
PROT_READ | PROT_EXEC);
}
#endif
/* If this is a SET?ID program, dup /dev/null to opened stdin,
stdout and stderr to close a security hole described in:
ftp://ftp.freebsd.org/pub/FreeBSD/CERT/advisories/FreeBSD-SA-02:23.stdio.asc
*/
if (program_is_setuid)
nullify_closed_stdio ();
notify_gdb_of_load(si);//这里就是从linker角度看有没被gdb调试?
call_constructors(si);//最后一步,解决比如类函数调用前一定要初始化的操作,代码在下面
return 0;
fail:
ERROR("failed to link %s\n", si->name);
si->flags |= FLAG_ERROR;
return -1;
}
下面是重定位函数reloc_library
static int reloc_library(soinfo *si, Elf32_Rel *rel, unsigned count)
{
Elf32_Sym *symtab = si->symtab;
const char *strtab = si->strtab;
Elf32_Sym *s;
unsigned base;
Elf32_Rel *start = rel;
unsigned idx;
for (idx = 0; idx < count; ++idx) {
unsigned type = ELF32_R_TYPE(rel->r_info);
unsigned sym = ELF32_R_SYM(rel->r_info);//取得符号
unsigned reloc = (unsigned)(rel->r_offset + si->base);//重定位的位置,需要被修改的信息
unsigned sym_addr = 0;
char *sym_name = NULL;
DEBUG("%5d Processing '%s' relocation at index %d\n", pid,
si->name, idx);
if(sym != 0) {
sym_name = (char *)(strtab + symtab[sym].st_name);//拿到需要被重定位的名字,通过sym
s = _do_lookup(si, sym_name, &base);//查找,函数在下面,找到之后通过他当前的base和value找到这个符号的地址。
if(s == NULL) {
/* We only allow an undefined symbol if this is a weak
reference.. */
s = &symtab[sym];
if (ELF32_ST_BIND(s->st_info) != STB_WEAK) {
DL_ERR("%5d cannot locate '%s'...\n", pid, sym_name);
return -1;
}
/* IHI0044C AAELF 4.5.1.1:
Libraries are not searched to resolve weak references.
It is not an error for a weak reference to remain
unsatisfied.
During linking, the value of an undefined weak reference is:
- Zero if the relocation type is absolute
- The address of the place if the relocation is pc-relative
- The address of nominial base address if the relocation
type is base-relative.
*/
switch (type) {
#if defined(ANDROID_ARM_LINKER)
case R_ARM_JUMP_SLOT:
case R_ARM_GLOB_DAT:
case R_ARM_ABS32:
case R_ARM_RELATIVE: /* Don't care. */
case R_ARM_NONE: /* Don't care. */
#elif defined(ANDROID_X86_LINKER)
case R_386_JUMP_SLOT:
case R_386_GLOB_DAT:
case R_386_32:
case R_386_RELATIVE: /* Dont' care. */
#endif /* ANDROID_*_LINKER */
/* sym_addr was initialized to be zero above or relocation
code below does not care about value of sym_addr.
No need to do anything. */
break;
#if defined(ANDROID_X86_LINKER)
case R_386_PC32:
sym_addr = reloc;
break;
#endif /* ANDROID_X86_LINKER */
#if defined(ANDROID_ARM_LINKER)
case R_ARM_COPY:
/* Fall through. Can't really copy if weak symbol is
not found in run-time. */
#endif /* ANDROID_ARM_LINKER */
default:
DL_ERR("%5d unknown weak reloc type %d @ %p (%d)\n",
pid, type, rel, (int) (rel - start));
return -1;
}
} else {
/* We got a definition. */
#if 0
if((base == 0) && (si->base != 0)){
/* linking from libraries to main image is bad */
DL_ERR("%5d cannot locate '%s'...",
pid, strtab + symtab[sym].st_name);
return -1;
}
#endif
sym_addr = (unsigned)(s->st_value + base);//找到之后通过他当前的base和value找到这个符号的地址。
}
COUNT_RELOC(RELOC_SYMBOL);
} else {
s = NULL;
}
/* TODO: This is ugly. Split up the relocations by arch into
* different files.
*/
switch(type){
#if defined(ANDROID_ARM_LINKER)
case R_ARM_JUMP_SLOT://如果是jump类型,直接把上面值给这里
COUNT_RELOC(RELOC_ABSOLUTE);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO JMP_SLOT %08x <- %08x %s\n", pid,
reloc, sym_addr, sym_name);
*((unsigned*)reloc) = sym_addr;
break;
case R_ARM_GLOB_DAT:
COUNT_RELOC(RELOC_ABSOLUTE);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO GLOB_DAT %08x <- %08x %s\n", pid,
reloc, sym_addr, sym_name);
*((unsigned*)reloc) = sym_addr;
break;
case R_ARM_ABS32:
COUNT_RELOC(RELOC_ABSOLUTE);//绝对重定位
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO ABS %08x <- %08x %s\n", pid,
reloc, sym_addr, sym_name);
*((unsigned*)reloc) += sym_addr;
break;
case R_ARM_REL32:
COUNT_RELOC(RELOC_RELATIVE);//相对减offset
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO REL32 %08x <- %08x - %08x %s\n", pid,
reloc, sym_addr, rel->r_offset, sym_name);
*((unsigned*)reloc) += sym_addr - rel->r_offset;
break;
#elif defined(ANDROID_X86_LINKER)
case R_386_JUMP_SLOT:
COUNT_RELOC(RELOC_ABSOLUTE);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO JMP_SLOT %08x <- %08x %s\n", pid,
reloc, sym_addr, sym_name);
*((unsigned*)reloc) = sym_addr;
break;
case R_386_GLOB_DAT:
COUNT_RELOC(RELOC_ABSOLUTE);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO GLOB_DAT %08x <- %08x %s\n", pid,
reloc, sym_addr, sym_name);
*((unsigned*)reloc) = sym_addr;
break;
#endif /* ANDROID_*_LINKER */
#if defined(ANDROID_ARM_LINKER)
case R_ARM_RELATIVE:
#elif defined(ANDROID_X86_LINKER)
case R_386_RELATIVE:
#endif /* ANDROID_*_LINKER */
COUNT_RELOC(RELOC_RELATIVE);
MARK(rel->r_offset);
if(sym){
DL_ERR("%5d odd RELATIVE form...", pid);
return -1;
}
TRACE_TYPE(RELO, "%5d RELO RELATIVE %08x <- +%08x\n", pid,
reloc, si->base);
*((unsigned*)reloc) += si->base;
break;
#if defined(ANDROID_X86_LINKER)
case R_386_32:
COUNT_RELOC(RELOC_RELATIVE);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO R_386_32 %08x <- +%08x %s\n", pid,
reloc, sym_addr, sym_name);
*((unsigned *)reloc) += (unsigned)sym_addr;
break;
case R_386_PC32:
COUNT_RELOC(RELOC_RELATIVE);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO R_386_PC32 %08x <- "
"+%08x (%08x - %08x) %s\n", pid, reloc,
(sym_addr - reloc), sym_addr, reloc, sym_name);
*((unsigned *)reloc) += (unsigned)(sym_addr - reloc);
break;
#endif /* ANDROID_X86_LINKER */
#ifdef ANDROID_ARM_LINKER
case R_ARM_COPY:
COUNT_RELOC(RELOC_COPY);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "%5d RELO %08x <- %d @ %08x %s\n", pid,
reloc, s->st_size, sym_addr, sym_name);
memcpy((void*)reloc, (void*)sym_addr, s->st_size);
break;
case R_ARM_NONE:
break;
#endif /* ANDROID_ARM_LINKER */
default:
DL_ERR("%5d unknown reloc type %d @ %p (%d)",
pid, type, rel, (int) (rel - start));
return -1;
}
rel++;
}
return 0;
}
do_lookup
static unsigned elfhash(const char *_name)
{
const unsigned char *name = (const unsigned char *) _name;
unsigned h = 0, g;
while(*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static Elf32_Sym *
_do_lookup(soinfo *si, const char *name, unsigned *base)
{
unsigned elf_hash = elfhash(name);//首先把名字做一个hash,算法如上
Elf32_Sym *s;
unsigned *d;
soinfo *lsi = si;
int i;
/* Look for symbols in the local scope first (the object who is
* searching). This happens with C++ templates on i386 for some
* reason.
*
* Notes on weak symbols:
* The ELF specs are ambigious about treatment of weak definitions in
* dynamic linking. Some systems return the first definition found
* and some the first non-weak definition. This is system dependent.
* Here we return the first definition found for simplicity. */
s = _elf_lookup(si, elf_hash, name);//函数如下,就是去某个soinfo找到那个符号,这里使我们自己文件,如果没找到,就去找我们依赖的库,DT_NEED
if(s != NULL)
goto done;
/* Next, look for it in the preloads list */
for(i = 0; preloads[i] != NULL; i++) {
lsi = preloads[i];
s = _elf_lookup(lsi, elf_hash, name);
if(s != NULL)
goto done;
}
for(d = si->dynamic; *d; d += 2) {
if(d[0] == DT_NEEDED){
lsi = (soinfo *)d[1];
if (!validate_soinfo(lsi)) {
DL_ERR("%5d bad DT_NEEDED pointer in %s",
pid, si->name);
return NULL;
}
DEBUG("%5d %s: looking up %s in %s\n",
pid, si->name, name, lsi->name);
s = _elf_lookup(lsi, elf_hash, name);
if ((s != NULL) && (s->st_shndx != SHN_UNDEF))
goto done;
}
}
#if ALLOW_SYMBOLS_FROM_MAIN
/* If we are resolving relocations while dlopen()ing a library, it's OK for
* the library to resolve a symbol that's defined in the executable itself,
* although this is rare and is generally a bad idea.
*/
if (somain) {
lsi = somain;
DEBUG("%5d %s: looking up %s in executable %s\n",
pid, si->name, name, lsi->name);
s = _elf_lookup(lsi, elf_hash, name);
}
#endif
done:
if(s != NULL) {
TRACE_TYPE(LOOKUP, "%5d si %s sym %s s->st_value = 0x%08x, "
"found in %s, base = 0x%08x\n",
pid, si->name, name, s->st_value, lsi->name, lsi->base);
*base = lsi->base;
return s;
}
return NULL;
}
elf_lookup
static Elf32_Sym *_elf_lookup(soinfo *si, unsigned hash, const char *name)
{
Elf32_Sym *s;
Elf32_Sym *symtab = si->symtab;
const char *strtab = si->strtab;
unsigned n;
TRACE_TYPE(LOOKUP, "%5d SEARCH %s in %s@0x%08x %08x %d\n", pid,
name, si->name, si->base, hash, hash % si->nbucket);
n = hash % si->nbucket;
for(n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]){
s = symtab + n;
if(strcmp(strtab + s->st_name, name)) continue;
/* only concern ourselves with global and weak symbol definitions */
switch(ELF32_ST_BIND(s->st_info)){
case STB_GLOBAL:
case STB_WEAK:
/* no section == undefined */
if(s->st_shndx == 0) continue;
TRACE_TYPE(LOOKUP, "%5d FOUND %s in %s (%08x) %d\n", pid,
name, si->name, s->st_value, s->st_size);
return s;
}
}
return NULL;
}
call_constructors
static void call_constructors(soinfo *si)
{
if (si->flags & FLAG_EXE) {
TRACE("[ %5d Calling preinit_array @ 0x%08x [%d] for '%s' ]\n",
pid, (unsigned)si->preinit_array, si->preinit_array_count,
si->name);
call_array(si->preinit_array, si->preinit_array_count, 0);
TRACE("[ %5d Done calling preinit_array for '%s' ]\n", pid, si->name);
} else {
if (si->preinit_array) {
DL_ERR("%5d Shared library '%s' has a preinit_array table @ 0x%08x."
" This is INVALID.", pid, si->name,
(unsigned)si->preinit_array);
}
}
if (si->init_func) {//先看看有没有init_func,有就调用,就是说如果我们的so函数里面有init_func和init_array,在dlopen返回之前,这里就可以在这个函数里面把jni_onload解密,把那些apk函数,native在这里实现。
TRACE("[ %5d Calling init_func @ 0x%08x for '%s' ]\n", pid,
(unsigned)si->init_func, si->name);
si->init_func();
TRACE("[ %5d Done calling init_func for '%s' ]\n", pid, si->name);
}
if (si->init_array) {
TRACE("[ %5d Calling init_array @ 0x%08x [%d] for '%s' ]\n", pid,
(unsigned)si->init_array, si->init_array_count, si->name);
call_array(si->init_array, si->init_array_count, 0);
TRACE("[ %5d Done calling init_array for '%s' ]\n", pid, si->name);
}
}
这样就完了,linker总结一下就是分3步,1文件解析段表需要的内容,分配内存,加载映射到内存,2解析dynamic段,在里面找到符号表,hash表,为了解决符号链接,3,然后去解决初始化问题。
然后像上面分析的,so库文件有两个入口点,init和ini_array,编写方式如下
void _init(){
printf("init func called\n");
}
void __attribute__((constructor)) init_array(){
printf("init_array func called\n");
}
运行结果就如下
root@hammerhead:/data/local/tmp # ./test
init func called
init_array func called
然后讲so拖进ida查看
export里面看不到init_array,ctrl+s可以看到然后点过去
查看这个85AD
sub_85AC
LDR R0, =(aInitArrayFuncC - 0x85B2)
ADD R0, PC ; "init_array func called"
B.W j_puts
; End of function sub_85AC
这里看不到_init,需要利用linux工具readel.exe -d查看。
所以可以利用init加密文件里的内容。