Android C语言_init函数和constructor属性及.init/.init_array节探索

丈夫志四海,万里犹比邻。这篇文章主要讲述Android C语言_init函数和constructor属性及.init/.init_array节探索相关的知识,希望能为你提供帮助。
本篇文章主要介绍了"android C语言_init函数和constructor属性及.init/.init_array节探索",主要涉及到Android C语言_init函数和constructor属性及.init/.init_array节探索方面的内容,对于Android C语言_init函数和constructor属性及.init/.init_array节探索感兴趣的同学可以参考一下。
了解C语言的程序猿都知道有两种方法可以让一部分代码在so或可执行文件被加载的时候先于其它任何函数执行,一种是定义一个void _init(void)函数,另一种是在函数后面声明constructor属性。那么这两种方式在执行的时候有什么区别吗?先后顺序呢?了解ELF文件格式的人又会问它们在文件中的位置又有什么差别呢?这篇文章就来解答这些问题。
首先你需要了解一下ELF文件格式了,这里就不啰嗦了,不了解的人可以搜一下看看。
下面是一个例子,在你的Android工程中的C/C++代码中加入下面几行:
 

........#ifdef __cplusplus extern "C" { #endifvoid _init(void){mlog_info("_init enter"); }#ifdef __cplusplus } #endifvoid __attribute__((constructor)) myConstructor(void){mlog_info("myConstructor enter\n"); }........


我这边编译出来是libcheckcert.so文件,放到手机上去运行的结果是:
 
 
........12-13 11:04:46.603: I/BRIAN(12203): _init enter 12-13 11:04:46.603: I/BRIAN(12203): myConstructor enter........

【Android C语言_init函数和constructor属性及.init/.init_array节探索】_init函数是最先运行的,为什么会这样呢?了解ELF文件的人都知道有.init和.init_array这两个节,它们是ELF文件在加载的时候用来做初始化的,那么它们和_init函数及constructor属性有什么关系呢?下面我们需要借助readelf和IDA pro来查看,首先readelf -d libcheckcert.so来查看ELF的dynamic段:
 
 
BriansdeMacBook-Pro:armeabi-v7a brian$ arm-linux-androideabi-readelf -d libcheckcert.so Dynamic section at offset 0x19b80 contains 27 entries: TagTypeName/Value 0x00000003 (PLTGOT)0x1ad84 0x00000002 (PLTRELSZ)1248 (bytes) 0x00000017 (JMPREL)0x4200 0x00000014 (PLTREL)REL 0x00000011 (REL)0x31a8 0x00000012 (RELSZ)4184 (bytes) 0x00000013 (RELENT)8 (bytes) 0x6ffffffa (RELCOUNT)390 0x00000006 (SYMTAB)0x148 0x0000000b (SYMENT)16 (bytes) 0x00000005 (STRTAB)0x1028 0x0000000a (STRSZ)6825 (bytes) 0x00000004 (HASH)0x2ad4 0x00000001 (NEEDED)Shared library: [liblog.so] 0x00000001 (NEEDED)Shared library: [libc.so] 0x00000001 (NEEDED)Shared library: [libm.so] 0x00000001 (NEEDED)Shared library: [libstdc++.so] 0x00000001 (NEEDED)Shared library: [libdl.so] 0x0000000e (SONAME)Library soname: [libcheckcert.so] 0x0000000c (INIT)0x4f9c 0x0000001a (FINI_ARRAY)0x1a658 0x0000001c (FINI_ARRAYSZ)8 (bytes) 0x00000019 (INIT_ARRAY)0x1a660 0x0000001b (INIT_ARRAYSZ)20 (bytes) 0x0000001e (FLAGS)BIND_NOW 0x6ffffffb (FLAGS_1)Flags: NOW 0x00000000 (NULL)0x0

可以看到INIT和INIT_ARRAY节的地址分别为0x4f9c和0x1a660,打开IDA pro来查看相应位置的代码:
 
 
.text:00004F9C ; =============== S U B R O U T I N E ======================================= .text:00004F9C .text:00004F9C ; Attributes: bp-based frame .text:00004F9C .text:00004F9CEXPORT _init .text:00004F9C _init .text:00004F9C .text:00004F9C var_8= -8 .text:00004F9C var_4= -4 .text:00004F9C .text:00004F9CSTMFDSP!, {R11,LR} .text:00004FA0MOVR11, SP .text:00004FA4SUBSP, SP, #8 .text:00004FA8LDRR0, =(_GLOBAL_OFFSET_TABLE_ - 0x4FB4) .text:00004FACADDR0, PC, R0 ; _GLOBAL_OFFSET_TABLE_ .text:00004FB0MOVR1, #4 .text:00004FB4LDRR2, =(aBrian_1 - 0x1AD84) .text:00004FB8ADDR2, R2, R0 ; "BRIAN" .text:00004FBCLDRR3, =(a_initEnter - 0x1AD84) .text:00004FC0ADDR0, R3, R0 ; "_init enter" .text:00004FC4STRR0, [SP,#8+var_4] .text:00004FC8MOVR0, R1 .text:00004FCCMOVR1, R2 .text:00004FD0LDRR2, [SP,#8+var_4] .text:00004FD4BL__android_log_print .text:00004FD8STRR0, [SP,#8+var_8] .text:00004FDCMOVSP, R11 .text:00004FE0LDMFDSP!, {R11,PC} .text:00004FE0 ; End of function _init

 
 
 
init_array:0001A660 ; =========================================================================== .init_array:0001A660 .init_array:0001A660 ; Segment type: Pure data .init_array:0001A660AREA .init_array, DATA .init_array:0001A660; ORG 0x1A660 .init_array:0001A660DCD _Z13myConstructorv; myConstructor(void) .init_array:0001A664DCD sub_4E90 .init_array:0001A668DCD sub_4EA8 .init_array:0001A66CDCD sub_4F04 .init_array:0001A670DCB0 .init_array:0001A671DCB0 .init_array:0001A672DCB0 .init_array:0001A673DCB0 .init_array:0001A673 ; .init_arrayends


可以看到上面的代码中执行了我们所定义的函数,.init节就是_init函数的代码,而.init_array节是一个指针数组,每一项对应的是一块代码,可以做一系列的初始化操作。那么为什么.init节的代码先于.init_array节的代码执行呢?这个要看linker的代码了,位置在AOSP的bionic/linker目录下,这里只摘录里面的一小段代码:
 
 
void soinfo::CallConstructors() {........// DT_INIT should be called before DT_INIT_ARRAY if both are present. CallFunction("DT_INIT", init_func); CallArray("DT_INIT_ARRAY", init_array, init_array_count, false); }

可以看到先执行.init节中的代码,然后在顺序执行.init_array中的各个代码块。
 
到这里大家应该对_init函数、constructor属性及.init节和.init_array节的对应情况了解的很清楚了吧。
下面说一个我不太清楚的地方,用readelf查看ELF中的所有符号信息,可以看到在.rel.dyn和.rel.plt都有myConstructor符号,一个类型是R_ARM_ABS32一个是R_ARM_JUMP_SLOT。另外在IDA pro中查看myConstructor可以发现它的代码主体是在.text节,但是也可以发现在.plt和.got节中也有myConstructor的定义。这样的话每次显式调用myConstructor的时候都需要通过PLT来跳转然后从GOT表中来找到myConstructor在TEXT节中的真正地址才能执行。但在.init_array中的地址是它在TEXT节中的真正地址,初始化的时候调用myConstructor并不需要通过PLT和GOT表。不明白这是为什么?留待以后解决吧。
更新:上面这个问题是因为编译器的问题,不同的编译器编译出来的ELF文件是不太一样的,上面我说的这种情况是LLVM编译器编译出来的,而如果用arm-linux-androideabi-*的话myConstructor符号是只有在.text节中才有,不会出现在.rel.dyn和.rel.plt中。

    推荐阅读