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" {
#endif

void _init(void){mlog_info("_init enter");}

#ifdef __cplusplus
}
#endif

void __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

........

_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:
  Tag        Type                         Name/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:00004F9C                 EXPORT _init
.text:00004F9C _init
.text:00004F9C
.text:00004F9C var_8           = -8
.text:00004F9C var_4           = -4
.text:00004F9C
.text:00004F9C                 STMFD           SP!, {R11,LR}
.text:00004FA0                 MOV             R11, SP
.text:00004FA4                 SUB             SP, SP, #8
.text:00004FA8                 LDR             R0, =(_GLOBAL_OFFSET_TABLE_ - 0x4FB4)
.text:00004FAC                 ADD             R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00004FB0                 MOV             R1, #4
.text:00004FB4                 LDR             R2, =(aBrian_1 - 0x1AD84)
.text:00004FB8                 ADD             R2, R2, R0 ; "BRIAN"
.text:00004FBC                 LDR             R3, =(a_initEnter - 0x1AD84)
.text:00004FC0                 ADD             R0, R3, R0 ; "_init enter"
.text:00004FC4                 STR             R0, [SP,#8+var_4]
.text:00004FC8                 MOV             R0, R1
.text:00004FCC                 MOV             R1, R2
.text:00004FD0                 LDR             R2, [SP,#8+var_4]
.text:00004FD4                 BL              __android_log_print
.text:00004FD8                 STR             R0, [SP,#8+var_8]
.text:00004FDC                 MOV             SP, R11
.text:00004FE0                 LDMFD           SP!, {R11,PC}
.text:00004FE0 ; End of function _init
init_array:0001A660 ; ===========================================================================
.init_array:0001A660
.init_array:0001A660 ; Segment type: Pure data
.init_array:0001A660                 AREA .init_array, DATA
.init_array:0001A660                 ; ORG 0x1A660
.init_array:0001A660                 DCD _Z13myConstructorv  ; myConstructor(void)
.init_array:0001A664                 DCD sub_4E90
.init_array:0001A668                 DCD sub_4EA8
.init_array:0001A66C                 DCD sub_4F04
.init_array:0001A670                 DCB    0
.init_array:0001A671                 DCB    0
.init_array:0001A672                 DCB    0
.init_array:0001A673                 DCB    0
.init_array:0001A673 ; .init_array   ends

可以看到上面的代码中执行了我们所定义的函数,.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中。

时间: 2024-10-27 11:20:27

Android C语言_init函数和constructor属性及.init/.init_array节探索的相关文章

android 多语言版本开发

最近项目中用用到语言切换功能,第一想到的就是资源文件,没错. 在资源文件中新建一个文件夹values-en,en表示英语,有一些还细化到地区,如values-en-rUS 即美国地区的英语,r是必需的.在里面新建一个strings.xml,把默认values文件夹中的strings.xml中的内容Copy至values-en中的strings.xml,再把该文件夹中的中文内容转换为英文,例如<string name="set">设置</string>转换为<

Android初始化语言

init.rc 文件并不是普通的配置文件,而是由一种被称为"Android初始化语言"(Android Init Language,这里简称为AIL)的脚本写成的文件. AIL由如下4部分组成: 1.  动作(Actions) 2.  命令(Commands) 3.服务(Services) 4.  选项(Options) 这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符.而每一行的代码由多个符号(Tokens)表示.可以使用反斜杠转义符在 Token中插入空格.双引

Android 初始化语言 --init.rc

p.p1 { margin: 0.0px 0.0px 12.9px 0.0px; line-height: 24.0px; font: 21.0px Helvetica; color: #000000; background-color: #ffffff } p.p2 { margin: 0.0px 0.0px 10.0px 0.0px; line-height: 16.0px; font: 14.0px "PingFang SC"; color: #000000; backgroun

javascript Array对象的constructor属性

定义和用法 constructor 属性返回对创建此对象的数组函数的引用. 语法 object.constructor 例子: <script type="text/javascript"> var test=new Array(); if (test.constructor==Array) { document.write("This is an Array"); } if (test.constructor==Boolean) { document.

【C语言】函数指针与回调函数

在C语言中:指针是C语言的特色,有着各种各样的指针,普通的变量指针,常量指针,数组指针,指针数组,函数指针,指针函数.我们就讲一下函数指针与回调函数吧 首先关于函数指针,其实很简单. 对于一个函数指针来说,顾名思义,就是一个指向函数的指针,需要知道的是,对于指针而言,他总是存储一块地址,地址里面有着一个,一组,或者一块数据,在函数中,函数的存储是放在代码段的,每个函数都有着一个函数首地址,调用了这个地址相当于调用的这个函数. 具体的可以观看我的这篇博客,其中就通过在内存阶段改变栈帧返回值,成功的

Android 开发小工具之:Tools 属性 (转)

Android 开发小工具之:Tools 属性 http://blog.chengyunfeng.com/?p=755#ixzz4apLZhfmi 今天来介绍一些 Android 开发过程中比较有用但是大家又不常用的小工具.这些小工具可以提高 Android 应用开发的效率.还可以提高代码质量.所以还是有必要使用的. 首先介绍布局文件中的 tools 属性. 如果你用 Android Studio 创建一个简单的示例项目,在生成的布局文件中会有这么一行内容: xmlns:tools="http:

JavaScript对象中的constructor属性

constructor属性始终指向创建当前对象的构造函数. 比如下面的例子: 1 // 等价于 var foo = new Array(1, 56, 34, 12); 2 var arr = [1, 56, 34, 12]; 3 console.log(arr.constructor === Array); // true 4 // 等价于 var foo = new Function(); 5 var Foo = function() { }; 6 console.log(Foo.constr

prototype 以及 constructor 属性的理解

1 为什么 xx.constructor.prototype 可以访问到当前对象的原型. 'str'.constructor.prototype 'str'.constructor 指向当前对象的构造函数 (构造函数).prototype:即函数的prototype 属性 1 函数有prototype属性,所以可以访问到 2 函数的prototype属性会在创建实例的时候作为实例的原型而存在. 所以 'str'.constructor.prototype 就可以访问到当前对象实例的原型. 1 '

Android初始化语言(init.rc语法)

本文为 ANDROID_SOURCE/system/core/init/readme.txt 的译文. 安卓初始化语言 安卓初始化语言包括四种类型的语句,它们是: 动作 Action 命令 Command 服务 Service 选项 Option 所有语句都是面向行的,以空格分割每行包含的若干token.C风格的反斜杠可以用于token中插入空格,双引号同样可以避免空格将文本分为多个token.反斜杠是一行的最后一个字符时,将用于续行(PS:下一行也属于该句). 以#开头的行(前面有空格也是允许