ARM Architecture C语言寻址解析——
从U-Boot relocation所展开的探索(二)
by蔡于清
ARMArchitecture C语言PIC寻址方式解析
承前文所述,可不可以产生一种可以运行在任意地址段的代码呢?可以,这种代码被称之为Position-IndependentCode,简称PIC(windows DLL,Linux ShareObject,这两者就是典型的PIC文件)。那么如何产生PIC呢?可以通过为编译器指定编译选项产生,比如:
arm-none-eabi-gcc -c -o
-fpic main.o main.c
又比如:
arm-none-eabi-gcc -c -o
-fpie main.o main.c
这样编译产生的目标文件包含了PIC所需要的信息,
-fpic,-fpie是gcc的PIC编译选项。ld也有PIC连接选项
-pie,要获得一个完整的PIC可运行文件,连接目标文件时必须为ld指定-pie选项,比如:
arm-none-eabi-ld -Tarm_pic.lds main.o -o arm_pic
-pie
PIC可运行文件的一个最重要特点就是——这种文件里包含一个Global OffsetTable,简称GOT。每一个GOT Entry记录了一个对象的地址(对象可以是全局变量或函数),CPU从GOT中读取GOTEntry从而获得全局变量的地址。
下面讨论指定了
-fpic编译选项和
-pie连接选项所产生的代码是如何寻址的。
命令行进入arm_pic目录,make GCC_PIC=-fpicLD_PIC=-pie,得到以下文件:arm_pic(elf格式文件)
- arm_pic.bin(二进制镜像文件)
- arm_pic.dump(反汇编文件)
- arm_pic.map(Memory Map文件)
从arm_pic.dump可见,出现了.got数据段,这一份GOT包含6个Entry,基地址为0x402001d0,6个Entry标示出6个全局变量的地址。
接下来通过main函数分析PIC寻址,main函数反汇编代码如下图所示:
与上一篇文章分析的汇编代码不同,现在Lable里面存放的已经不是地址值,而是偏移量(offset)。global_var1的寻址经过如下3个步骤(global_str的寻址方式也是如此):
- r3通过Lable1取得GOT Base相对pc的offset,从而确定GOT Base的地址;
- r2通过Lable2取得global_var1 GTO Entry相对于GOT Base的offset;
- r2 + r3累加(Base + offset)得到global_var1 GTOEntry的地址,从而取得global_var1的地址;
每一个变量的地址最终都是从GOT中获得,这就是PIC的寻址方式,也是它的核心,得到GOT的基地址,就能修改变量的地址从而对变量进行relocation(重定位)。
OK,跟上一篇文章所讨论一样,我们把arm_pic整体copy到0x80000000,同时,将GOT中每一个entry的内容累加上偏移量0x3fe00000,这样所有C全局变量的地址都被调整到新的地址(修改变量地址的这一操作被称之为relocation),main函数和foo函数中的变量寻址不会出错了,那么程序是不是就能正常运行了呢?还不行,为什么?GOT中保存的仅仅是C的变量地址和函数地址,但不要忘了,我们的工程不仅仅有C代码,还有汇编代码!那么汇编代码的对象是如何relocation呢?我们如何得到汇编代码中需要relocation的对象信息呢?答案在.rel.dyn数据段和.dynsym数据段,这部分将在下一篇文章中分析,同时也将具体分析U-Boot2011.12如何relocation。