代码重定位——ELF分段、API/ABI、指定函数(变量)地址、分散加载、函数调用过程、动态加载
小狼@http://blog.csdn.net/xiaolangyangyang
Linux读取elf命令:readelf
Linux反汇编命令:objdump
Linux查看可执行程序动态链接依赖命令:ldd
静态链接库:.a .lib;动态链接库:.so .dll
一、采用分段结构的优点
1、分段易于管理,对于代码段,是只读的,而数据段可读写;
2、提高cache命中率,Icache与Dcache分开,分别读取代码段和数据段的数据;
3、未初始化数据放在.bss段,生成bin文件时不包含该部分,只在startup.s的指令中保存.bss段的__bss_start和__bss_end地址,减小bin文件大小;
4、同一进程的线程共享代码段,节省内存。
二、目标文件分段结构
以uboot为例,以下是uboot的链接文件,代码在board/smdk2410/U-Boot.lds里
根据上面的链接文件编译生成的分段文件如下图所示:
1、目标文件(可执行文件)包含.text、.data和.bss段:
.text段:代码段,保存的是指令,只读
.data段:已初始化的全局变量、已初始化的局部静态变量,可读写
.bss段:未初始化的全局变量、未初始化的局部静态变量,可读写
2、stack地址在uboot中使用宏定义CONFIG_SYS_INIT_SP_ADDR定义,以下是初始化sp指针的代码:
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
3、bin文件中只包含.text和.data段,不包括.bss段。
三、问题解答
1、bin文件中,没有.bss段,那么程序运行时是怎么知道.bss段的地址的?
bin文件头部是初始化程序startup.s,该程序为汇编程序,启动时会清空.bss段,其中使用到的符号表__bss_start和__bss_end就是.bss段的起始和结束地址,编译后__bss_start和__bss_end就是实际的物理地址,所以.bss段的起始地址其实已经被编译到程序里面了,实际值应该是存储在.data段中。
clear_bss: /*将bss段数据清零*/
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
2、生成bin文件时,只保留.text和.data段,那么.data段的结束地址是怎么定义的?
从board/smdk2410/U-Boot.lds文件可以看出,.bss段是放在程序的最后,前面就是.data段,所以__bss_start就是.data段的结束地址。
ABI与API之间的区别
ABI:解决的是bin文件在CPU上怎么运行,指定了文件格式、数据类型、寄存器使用、堆积组织优化和在一个嵌入式软件中的参数的标准约定;
API:解决的是bin文件在CPU运行起来后,怎么实现各层次软件之间的调用问题。
指定某个函数或变量存放的地址
1、将一个全局变量放到0x20000000处
int value __attribute__((section(".ARM.__at_0x20000000"))) = 0x33;
2、将一个const常量放置到0x00001000处
const char ziku[] __attribute__((section(".ARM.__at_0x00001000"))) = {0x1, 0x2, 0x3};
3、将func函数放置到0x00000100起始处
void func (void) __attribute__((section(".ARM.__at_0x00000100")));
对于变量,在其后边加修饰;对于函数,在声明处加修饰
STM32分散加载
STM32的分散加载就是使用链接文件,将代码和数据各段分配到不同的存储空间,如内部SDRAM、外挂SDRAM等;与DSP或GCC使用的链接文件是同样的道理。
ARM体系架构函数调用过程
1、栈底在高地址,栈向下增长;
2、ARM压栈的顺序为:当前函数指针PC -> 返回指针LR -> 栈指针SP -> 栈基址FP -> 传入参数个数及指针 -> 本地变量和临时变量;
3、调用函数前,都将做一些准备工作,主要是将参数存入r0~r3寄存器中,当参数多于4个时,r0~r3寄存器也将马上被压入栈中,而参数少于4个时就不压栈;
4、当进入被调用函数后,首先会将FP,LR等寄存器值压栈,保存目前(调用函数)栈帧状态,随后FP指向当前栈顶,栈顶下偏一定量,FP、SP的差值即表示为被调用函数开辟的栈空间。
Linux动态链接库原理
1、静态链接库在加载时机是进程启动,而动态加载库则由程序自行决定加载时机;
2、代码编译后链接之前,.o文件中符号表没有被重定位,以重定位表(relocatioin table)的形式存在,静态链接时,由链接程序直接进行符号表重定位,确定每个符号的地址;动态链接库是在程序需要使用动态链接库时,由动态链接器进行加载(加载位置不固定)以及进行符号表的重定位。
3、STM32一般没有动态链接库的说法,实现方式有以下几种:
RTOS下:由支持动态链接的RTOS实现;
裸机程序下:由应用程序实现动态链接库的加载(加载地址不固定),然后由应用程序实现重定位(如借助寄存器进行重定位),实现比较复杂且意义不太大;
裸机程序下:动态链接库对外接口函数地址固定,可实现库文件的更换(严格意义上说这不属于动态链接库)。
参考链接