android linker 浅析
Android 的加載/鏈接器linker 主要用于實現(xiàn)共享庫的加載與鏈接。它支持應用程序對庫函數(shù)的隱式和顯式調用。對于隱式調用,應用程序的編譯與靜態(tài)庫大致相同,只是在靜態(tài)鏈接的時候通過--dynamic-linker /system/bin/linker 指定動態(tài)鏈接器,(該信息將被存放在ELF文件的.interp節(jié)中,內核執(zhí)行目標映像文件前將通過該信息加載并運行相應的解釋器程序linker.)并鏈接相應的共享庫。與ld.so不同的是,Linker目前沒有提供Lazy Binding機制,所有外部過程引用都在映像執(zhí)行之前解析。對于顯式調用,可以同過linker中提供的接口dlopen,dlsym,dlerror和dlclose來動態(tài)加載和鏈接共享庫。
Android中的共享庫和可執(zhí)行映像都默認采用ELF格式的文件,其基本格式如下:
?????? 每個ELF文件的開始部分都包含一個ELF頭,其中包含了整個文件的基本信息,包括目標代碼的格式,體系結構,各程序頭或節(jié)頭的偏移和大小,組織結構和訪問權限等信息。
?????? 程序頭表包含了加載到內存中的各種段的索引及屬性信息,它將告訴加載器如何加載映像。每個段中有包含了一個或幾個節(jié)區(qū),每個節(jié)區(qū)應是唯一的。無論是可執(zhí)行程序還是共享庫都包含以下幾個的節(jié)區(qū):
1. GOT表和PLT表:
?????? 不同映像間的函數(shù)和數(shù)據引用都是通過它們實現(xiàn)的。GOT(全局偏移表)給出了映像中所有被引用符號(函數(shù)或變量)的值。每個普通PLT表項相當于一個函數(shù)的樁函數(shù)(stub),支持懶綁定的情況下,當發(fā)生對外部函數(shù)的調用時,程序會通過PLT表將控制交給動態(tài)連接器,后者解析出函數(shù)的絕對地址,修改GOT中相應的值,之后的調用將不再需要連接器的綁定。由于linker是不支持懶綁定的,所以在進程初始化時,動態(tài)鏈接器首先解析出外部過程引用的絕對地址,一次性的修改所有相應的GOT表項。對共享對象來說,由于GOT,PLT節(jié)以及代碼段和數(shù)據段之間的相對位置是固定的,所有引用都是基于一個固定地址(GOT)的偏移量,所以實現(xiàn)了PIC代碼,重定位時只需要修改可寫段中的GOT表。而可執(zhí)行程序在連接過程中則可能發(fā)生對不可寫段的修改。如果只讀段和可寫段不是以固定的相對位置加載的,那么在重定位是還需要修改所有指向GOT的指針。???
?? ? ? ? ? ? ? ????? ??????????????????
2. dynamic節(jié):
?????? 與重定位有關的基本目錄結構,例如:
Dynamic section at offset 0x61014 contains 20 entries:
? Tag??????? Type???????????????????????? Name/Value
?0x00000001 (NEEDED)?? ??????????????????Shared library: [libc.so.6]
?0x0000000c (INIT)????????????????????? ?????? 0xb8a8
?0x0000000d (FINI)?????????????????????? ??? 0x555c4
?0x00000004 (HASH)?????????????????????? 0x8128
?0x00000005 (STRTAB)???????????????????? ? 0xa004
?0x00000006 (SYMTAB)???????????????????? 0x8aa4
?0x0000000a (STRSZ)????????????????????? ?? 2902 (bytes)
?0x0000000b (SYMENT)???????????????????? 16 (bytes)
?0x00000015 (DEBUG)????????????????????? ????????? 0x0
?0x00000003 (PLTGOT)???????????????????? 0x710dc
?0x00000002 (PLTRELSZ)?????????????????? ? 2464 (bytes)
?0x00000014 (PLTREL)???????????????????? ? REL
?0x00000017 (JMPREL)??????????????????? ?? 0xaf08
?0x00000011 (REL)??????????????????????? ?? 0xae98
?0x00000012 (RELSZ)????????????????????? ? 112 (bytes)
?0x00000013 (RELENT)?? ?????????????????? 8 (bytes)
3. dynsym和dynstr節(jié):
?????? 與重定位有關的符號表和字符串表:
Symbol table '.dynsym' contains 69 entries:
?? Num:??? Value? ?Size ?Type??? Bind?? ????Vis????? ??Ndx ??Name
???? 0:? ?00000000? ?0 ?NOTYPE? LOCAL? ?DEFAULT? UND
???? ……
???? 6: ?00002568??? 28 ?FUNC??? GLOBAL ?DEFAULT??? 7? ?__ashldi3
???? 7: ?00000001??? 58 ?FUNC??? GLOBAL ?DEFAULT? UND ?_ZNK7android7RefBase9decS
???? 8: ?00000001??? 32 ?FUNC??? GLOBAL ?DEFAULT? UND ?ioctl
???? 9: ?00000001??? 18 ?FUNC??? GLOBAL ?DEFAULT? UND ?_ZN7android7String8D1Ev
??? 10: ?00000001??? 16 ?FUNC??? GLOBAL? DEFAULT? UND ?_ZNK7android8EventHub16ge
??? 11: ?00000001??? 32 ?FUNC??? GLOBAL ?DEFAULT? UND? strerror
??? 12: ?00003024???? 0 ?NOTYPE? GLOBAL ?DEFAULT? ABS ?__exidx_end
4. .rel.dyn和.rel.plt節(jié):
?????? .rel.dyn節(jié)的表項對應了出外部過程調用的符號以外的所有重定位對象,.rel.plt則對應所有外部過程調用的重定位信息。每個重定位項記錄了符號的符號表索引,重定位的操作地址,重定位類型的信息(見3.3節(jié))。重定位所在的節(jié)區(qū)往往與重定位類型有關,例如:
Relocation section '.rel.plt' at offset 0x2f08 contains 308 entries:
?Offset???? Info??? ??Type??????????? ??????Sym.Value? Sym. Name
000710e8? 00000116 ??R_ARM_JUMP_SLOT?? 0000b8d0?? fileno
000710ec? 00000216 ??R_ARM_JUMP_SLOT?? 0000b8dc?? getpagesize
000710f0? 00000316 ??R_ARM_JUMP_SLOT?? 0000b8e8?? fputs
000710f4? 00000416 ??R_ARM_JUMP_SLOT?? 0000b8f4?? abort
000710f8? 00000516 ??R_ARM_JUMP_SLOT?? 0000b900?? __errno_location
?
Relocation section '.rel.dyn' at offset 0x2e98 contains 14 entries:
?Offset???? Info??? ??Type??????????? Sym.Value? Sym. Name
000715b8? 00001e15 ?R_ARM_GLOB_DAT??? 00071000?? __fini_array_end
000715bc? 00002f15 ?R_ARM_GLOB_DAT??? 00000000?? __gmon_start__
000715c8? 0000f515 ?R_ARM_GLOB_DAT??? 00071000?? __fini_array_start
000715cc? 00010015 ?R_ARM_GLOB_DAT??? 00071000?? __init_array_end
000715d0? 00012e15 ?R_ARM_GLOB_DAT??? 00071000?? __init_array_start
00072a00? 00002714 ?R_ARM_COPY??????? ?00072a00?? __timezone
00072a04? 00005514 ?R_ARM_COPY??????? ?00072a04?? __daylight
?
?????? R_ARM_JUMP_SLOT和R_ARM_GLOB_DAT屬性的重定位地址一般位于GOT表,R_ARM_COPY和R_ARM_ABS32屬性的重定位一般位于.data節(jié)或.text節(jié)中。
Linker的加載與啟動
?????? Linker是共享庫的加載/鏈接器,也可以稱為解釋器(interpreter)。共享庫以ELF文件的形式保存在文件系統(tǒng)中,核心的load_elf_binary會首先將其映像文件映射到內存,然后映射并執(zhí)行其解釋器也就是linker的代碼。linker的代碼段是進程間共享的,但數(shù)據段為各進程私有。
linker執(zhí)行完后會自動跳轉到目標映像的入口地址。
?????? /*in sys_execve->do_execve->search_binary_handler->load_elf_binary*/
?????? elf_entry = load_elf_interp(&loc->interp_elf_ex,interpreter,&interp_map_addr, load_bias);
?????? ………..
?????? start_thread(regs, elf_entry, bprm->p);?????? //start to execute linker
?????? 在android中,linker代碼的運行域由地址0xb0000100開始(see /bionic/linker/Android.mk),直接從_start開始執(zhí)行。do_execve會預先將應用程序參數(shù)(argc,argv[],envc和envp[]還有一些"輔助向量(Auxiliary Vector)"等(see load_elf_binary>create_elf_tables))存放在分配好的用戶空間堆棧中,通過堆棧將這些參數(shù)和指針(位于linux_binprm結構體bprm中)傳遞給用戶空間的目標進程。
?????? Linker會提取出它所需要的信息,例如目標映像中程序頭表在用戶空間的地址以及應用程序入口等。
?????? /*in __linker_init()*/
?????? ??????? /* extract information passed from the kernel */
??? while(vecs[0] != 0){
??????? switch(vecs[0]){
??????? case AT_PHDR:
??????????? si->phdr = (Elf32_Phdr*) vecs[1];
??????????? break;
??????? case AT_PHNUM:
??????????? si->phnum = (int) vecs[1];
??????????? break;
??????? case AT_ENTRY:
??????????? si->entry = vecs[1];??????????????? /*entry of the executable image.*/
??????????? break;
??????? }
??????? vecs += 2;
}
加載依賴的共享庫
Linker會首先調用__linker_init執(zhí)行一段自舉代碼,完成其自身的初始化,初始化與目標映像相關的數(shù)據結構。Linker會首先調用alloc_info為目標映像分配一個soinfo結構體,它用于存放與映像文件有關的所有信息,這樣可以使可執(zhí)行映像與共享對象共享連接與重定位函數(shù),后面的程序將通過soinfo的flags域判斷目標映像是共享庫還是可執(zhí)行程序。
??? si = alloc_info(argv[0]);????????????????? /*name of exe */
??? if(si == 0) {
??????? exit(-1);
??? }
????? ?………
??? si->flags |= FLAG_EXE;???????????????? /*exe not share library*/
與共享庫的鏈接操作通過函數(shù)link_image調用其它函數(shù)執(zhí)行。Link_image會對ELF文件進行解析,根據DYNAMIC段確定目標映像(可能是可執(zhí)行程序或共享庫)依賴的共享庫,調用find_library函數(shù)在soinfo鏈表中搜索并加載這些共享庫。Soinfo鏈表是進程私有的全局變量,無論其它進程是否已將某一共享庫加載至內存,依賴它的進程都需要調用mmap來建立其虛擬內存到實際物理內存的映射,這是因為每個進程都有它自己的mm_struct內存描述符和vm_area_struct結構體鏈表(每個vm_area_struct對應了該進程虛擬地址空間的一個區(qū)域(VMA)),同一個物理內存中的映射文件在不同的進程中會被映射到不同的虛擬地址空間。在linux下可以使用pmap(pid)或cat /proc/(pid)/maps查看相應進程的地址空間分布,會發(fā)現(xiàn)同一個庫(如libc.so)被放到了不同的地址上。實際的從文件到內存頁的拷貝發(fā)生在程序對相應的虛擬內存進行讀寫操作的時候,系統(tǒng)發(fā)生缺頁異常,從而產生一次調頁請求,內核根據操作的不同創(chuàng)建后援文件頁或COW頁。
如果在搜索鏈表的過程中發(fā)現(xiàn)該庫已經存在,則find_library直接返回該庫的soinfo結構,以防止發(fā)生重復的加載甚至進入無限遞歸,否則會調用load_library進行實際的加載操作,庫的加載地址均位于0x80000000到0x90000000之間(prelink的庫除外),庫與庫之間以1MB對齊,庫的代碼段和數(shù)據段都是頁對齊的。
??? for(d = si->dynamic; *d; d += 2) {
??? ????? f(d[0] == DT_NEEDED){???????????? //it ‘s a needed share library.
??????? ????? soinfo *lsi = find_library(si->strtab + d[1]);? //get soinfo by name?????????
??????? ????? if(lsi == 0) {
?????????? ??????? goto fail;
?? ?????????}
??????? lsi->refcount++;????????????????? // ?Increment it’s referenced count
??????? }
??? }
load_library的具體加載過程是:
1.?????? 讀取共享庫的文件頭和程序頭表到指定的頁中。
2.?????? 調用get_lib_extents分析ELF頭表,并獲取文件中的地址信息。如果該庫不是prelink的,則庫加載的起始地址為零。同時計算出加載該庫所需總的內存空間大小。
3.?????? 根據已獲得的起始地址和總空間大小,調用alloc_mem_region預先為共享庫分配一段內存空間。這段空間是通過系統(tǒng)調用mmap實現(xiàn)的,其訪問屬性是PROT_READ | PROT_EXEC和 MAP_PRIVATE | MAP_ANONYMOUS,文件描述符為-1,匿名私有的內存映射意味著,內核將為該庫分配虛擬線性區(qū),保留給后邊進行內存映射文件的操作。
?????? ??? while(libbase < LIBLAST) {? //LIBLAST=0x90000000
??????? base = mmap((void*) libbase, sz, PROT_READ | PROT_EXEC,
??????????????????? MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
??????? if(((unsigned)base) == libbase) {
??????????? return base;??????
??????? }
??????? if(base != MAP_FAILED)
??????? ?????? munmap(base, sz);
??????? libbase += LIBINC;??? // LIBINC = 0x00100000,再進行一次映射。
??? }
4.?????? 調用alloc_info為該庫在共享庫鏈表中分配一個soinfo節(jié)點,初始化其數(shù)據?? 結構。
5.?????? 調用load_segments將所有的PT_LOAD屬性的段加載至合適的地址空間,代碼段與數(shù)據段的相對位置與文件的運行域一致。??
?????? pbase = mmap(tmp, len, PFLAGS_TO_PROT(phdr->p_flags),MAP_PRIVATE | ?? MAP_FIXED, fd,phdr->p_offset & (~PAGE_MASK));
?????? 如果該段是只讀的,則核心將其映射致內存中唯一的拷貝,如果該段是可寫的,??? 則NAP_PRIVATE意味著該段是寫時拷貝的,只有在寫操作時核心才會將相應的??? 頁面拷貝至內存。由于需要對可執(zhí)行映像中位于只讀段的代碼進行重定位,所?????? 以調用mprotect??? 將只讀段的屬性暫時更改為R/W/E的。?
if (si->flags & FLAG_EXE) {
?????? ……????????
??????????????????? 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);
??????????? }
??????????????????? ……
???????????? }
???????????? ?????? 共享庫代碼由于是位置無關的,所以只需要可寫段中的.got段(COW的),???????????????? 所以不需要只讀段的內存保護。
???????????? ?????? 如果bss段的區(qū)間中包含頁邊界,則對超出的部分作另外的匿名映射,????????????????????????? private anonymous mappings意味著當這些內存映射被取消映射時,內存會真的???????????????????? 將其釋放給系統(tǒng)。
?????? if (tmp < (base + phdr->p_vaddr + phdr->p_memsz)) {
?????? extra_base = mmap((void *)tmp, extra_len,PFLAGS_TO_PROT(phdr->p_flags), MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
加載完成后load_library會調用init_library進行庫的初始化操作,init_library又會調用link_image,鏈接共享庫的映像文件,共享對象的鏈接與重定位過程參考3.3節(jié)。
可執(zhí)行映像的重定位
加載目標映像的所有依賴庫后,link_image調用reloc_library函數(shù)根據DYNAMIC段中的DT_REL和DT_JMPREL屬性節(jié)區(qū)對目標映像進行重訂位,兩種屬性的節(jié)區(qū)都是Elf32_Rel結構體的鏈表。
typedef struct elf32_rel {
? Elf32_Addr?????? r_offset;?????????? //重定位偏移量,以目標文件加載地址為基準
? Elf32_Word????? r_info;????????????????? ? //包含了重定位類型和在符號表中的索引????
} Elf32_Rel;
DT_JMPREL包含了與PLT表相關的重定位信息,把它與主重定位表分離是為了支持懶綁定,以便讓連接器在進程初始化時跳過這些重定位,而在運行時發(fā)生外部過程調用時通過PLT表調用連接器函數(shù)實現(xiàn)綁定。由于linker不支持懶綁定,所以對該表的重定位也需要提前到進程初始化時進行。
/*in myandroid/build/core/armelf.xsc*/
? .rel.dyn??????? :
??? {
????? *(.rel.init)
????? *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
????? *(.rel.fini)
????? *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
????? *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
????? *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
????? *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
????? *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
????? *(.rel.ctors)
????? *(.rel.dtors)
????? *(.rel.got)
????? *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
??? }
? .rel.plt??????? : { *(.rel.plt) }
屬性在重定位的過程中,linker會調用_do_lookup在soinfo結構體鏈表中的所有映像文件的符號表中查找該符號的實際地址,然后修改.rel.plt表項所指向的該符號在映像(位于.got段)中的地址。對.rel.plt段的重定位將解析所有外部過程引用(符號屬性st_shndx為STN_UNDEF),完成與共享庫的鏈接。
/*in link_image()*/
??? if(si->plt_rel) {????????????????????????? /*it ‘s a .rel.plt (DT_JMPREL) section*/
????? if(reloc_library(si, si->plt_rel, si->plt_rel_count))//binding all the external func
?????? ?????goto fail;
??? }
??? if(si->rel) {???????????????????????????? /*it ‘s a .rel.dyn (DT_REL)section*/
??????? if(reloc_library(si, si->rel, si->rel_count))
??????????? goto fail;
??? }
重定位的過程也是解析和綁定符號的過程,主要要解決的兩個問題是:
1.? 如何找出有哪些符號需要重定位。
2.? 這些符號的重定位類型(R_ARM_GLOB_DAT,R_ARM_JUMP_SLOT…)及相應的重定位操作。
????
符號綁定示意圖
?
上圖給出了linker進行符號綁定過程涉及到的主要數(shù)據對象及其關系,Dyn段中給出了所有與重定位有關的數(shù)據結構的組成分布。Rel代表了所有重定位表項。每個重定位表項對應一個Symtab表項和一個strtab表項。Symtab和strtab也一一對應,但strtab中只包含了字符串,并不能指向其它的表。Hash表與symtab表中的索引項相對應,通過它可以加快符號查找的速度。同一個符號在“對象層”可能出現(xiàn)多次。
?
?????? Linker中符號的綁定過程大致如下:
首先在reloc_library中隊重定位節(jié)的每個Rel表項的rel->r_info成員依次進行解析,得到該重定位符號在本地符號表中的索引,該符號的重定位類型以及應進行修改的位置。根據符號索引在字符串表中找到相應的符號字符串,調用_do_lookup函數(shù)在soinfo鏈表對應的共享庫中查找該符號。
?? /*in reloc_library ()*/
??? for (idx = 0; idx < count; ++idx) {????????? //count = si->plt_rel_count.
??????? unsigned type = ELF32_R_TYPE(rel->r_info);
??????? unsigned sym = ELF32_R_SYM(rel->r_info);
??????? unsigned reloc = (unsigned)(rel->r_offset + si->base);
??????? if(sym != 0) {
????????? ??/*search symbol within solist*/
??????????????????? s = _do_lookup(si, strtab + symtab[sym].st_name, &base);
??????????? if ((s->st_shndx == SHN_UNDEF) && (s->st_value != 0)) {return -1;}
}
_do_lookup首先會調用_do_lookup_in_so在目標映像本地的符號表中查找該符號,這個過程會解析出本地的重定位符號,對于未定義的外部符號,_do_lookup_in_so會返回0,然后
_do_lookup開始進入一個for循環(huán),遍歷整個soinfo鏈表。為每個soinfo調用一次_do_lookup_in_so。
?????? /* in _do_lookup()*/
??? for(si = solist; (s == NULL) && (si != NULL); si = si->next)
??? {
??????? if((si->flags & FLAG_ERROR) || (si == user_si))
??????????? continue;
??????? s = _do_lookup_in_so(si, name, &elf_hash);
??????? if (s != NULL) {
??????????? *base = si->base;
??????????? break;
??????? }
}
_do_lookup_in_so首先會調用elfhash計算出未定義字符串的hash值,將該值作為參數(shù)傳遞給_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;
??? 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 symbols */?????
??????? switch(ELF32_ST_BIND(s->st_info)){
??????? case STB_GLOBAL:
??????????? if(s->st_shndx == 0) continue;? /* no section == undefined */
??????? case STB_WEAK:
??????????? return s;
??????? }
??? }
??? return 0;
}
程序中的參數(shù)hash是針對目標符號字符串計算出的hash值,bucket[hash % si->nbucket]
對應于符號表中的一個索引,根據這個索引找到相應的符號,與目標符號比較,相同則返回該符號的值(s->st_shndx == 0除外,說明該符號不在本文件定義),否則繼續(xù)查找,n = si->chain[n]將給出相同hash值的另一個符號索引。
?????? 符號若找到,其對應的地址被返回給reloc_library.reloc_library會根據之前得到的重定位類型,用該值進行相應的重定位操作。
?????? /*in reloc_library*/
???????????????????? ???? ……..
??????????? sym_addr = (unsigned)(s->st_value + base);?? /*get the actual address.*/
??????????? sym_name = (char *)(strtab + symtab[sym].st_name);
??????? }
??????? switch(type){
??????? case R_ARM_JUMP_SLOT:
??????? case R_ARM_GLOB_DAT:
??????? case R_ARM_ABS32:
??????????? *((unsigned*)reloc) = sym_addr;
??????????? break;
??????? case R_ARM_RELATIVE:
??????????? if(sym){return -1; }
??????????? *((unsigned*)reloc) += si->base;
??????????? break;
??????? case R_ARM_COPY:
??????????? memcpy((void*)reloc, (void*)sym_addr, s->st_size);? /*object in RW seg*/
??????????? break;
??????? default: return -1;
??????? }
??????? rel++;
??? }
?? ?return 0;
}
?
整個加載與鏈接的過程通過link_image遞歸進行,最終所有相關映像文件均會被加載并連接,為避免遞歸式的加載與重定位過程導致進程啟動的時間開銷過大,可以使用mklibs工具控制共享庫的數(shù)量。
可執(zhí)行映像的重定位完成后link_image之后會調用mprotect將代碼段的權限改回可讀可執(zhí)行。
??? if (si->wrprotect_start != 0xffffffff && si->wrprotect_end != 0) {
??????? mprotect((void *)si->wrprotect_start, si->wrprotect_end - ?????????????????????????????????? ????si->wrprotect_start, PROT_READ | PROT_EXEC);
??? }
然后調用call_destructors,執(zhí)行映像的初始化隊列,最后返回映像的入口地址。Linker將直接跳入可執(zhí)行映像并開始執(zhí)行。
/* begin.S */
_start:
????? ……….
????? bl??? __linker_init?? /* linker init returns the _entry address in the main image */
????? mov pc, r0
?
linker中定義了dl_unwind_find_exidx函數(shù),該函數(shù)將通過dl.so導出給libc.so的__gnu_Unwind_Find_exidx函數(shù),該函數(shù)可以根據PC計數(shù)器的值返回相應共享庫中指向ARM_EXIDX段的指針(位于soinfo結構中),該段用于棧退回(stack unwinding)機制,確保C++在異常被拋出、捕獲并處理后,所有生命期已結束的對象都會被正確地析構,它們所占用的空間會被正確地回收??蓤?zhí)行映像及共享庫的soinfo結構用于棧退回的數(shù)據結構分別在link_image和load_library中被賦值。
Unload_library用于卸載指定的共享庫并卸載其依賴庫中可以被卸載的庫。
?????? 如果采用顯式調用的方法動態(tài)鏈接使用共享庫的例程,應用程序需要使用linker提供的外部接口,libdl.so中包含了所有這些接口,鏈接時在命令行加入-ldl,這樣可執(zhí)行映像的依賴庫中將只包含libdl.so。dlopen是加載共享庫的接口,它會調用find_library找到并加載共享庫。dlsym會調用_do_lookup返回符號地址,dlerror用于錯誤檢查,dlclose調用unload_library動態(tài)卸載共享庫(進程退出時不會自動卸載不用的共享庫)。用戶也可以通過系統(tǒng)調用sys_uselib在核心態(tài)加載共享庫,但該函數(shù)只支持固定地址加載。
?????? 生成共享庫的基本方法與linux相同。在編譯鏈接時在命令行加入
?-shared 和 –fPIC,android共享庫鏈接腳本為armelf.xcs。例如:
?????? $ arm-none-linux-gnueabi-gcc -fpic -nostdlib -Wl,-T,armelf.xsc, -shared, -Bsymbolic -o libhello.so ?hello.c???? /*create shared object*/
????? 在編譯動態(tài)鏈接的可執(zhí)行文件時使用--dynamic-linker ,-nostdlib, -rpath ,–L…. 指定解釋器,共享庫及搜索路徑。例如:
????? $ arm-none-linux-gnueabi-gcc -c start.c
?????? $ arm-none-linux-gnueabi-gcc -c main.c
????? $ arm-none-linux-gnueabi-ld --dynamic-linker /system/bin/linker -nostdlib /
?? -rpath /system/lib -rpath ~/tmp/android/system/lib -L . /
????? -L ~/tmp/android/system/lib -lc -lhello -o hello2 start.o main.o
????? 另外有兩個特別的工具mklibs和apriori。mklib可用于查找并復制程序用到的最小的共享庫集,apriori可以預先為若干共享庫確定加載地址,并為有依賴關系的共享庫做靜態(tài)重定位和連接,解釋器會在共享庫加載時(see load_library)調用is_prelinked查看該庫是否時prelink的并在alloc_mem_region中檢查目的地址是否被占用。
Reference
[1] ?漫談兼容內核之八: ELF映像的裝入 毛德操
[2]? Linkers and loaders.
[3]? How to write shared libraries.
[4]? C++異常機制的實現(xiàn)方式和開銷分析
?
原文地址:?http://blog.csdn.net/dinuliang/article/details/5509009
總結
以上是生活随笔為你收集整理的android linker 浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ELF文件装载链接过程及hook原理
- 下一篇: 爬取微博对话语料制作聊天机器人