代码重定位——ELF分段、API/ABI、指定函数(变量)地址、分散加载、函数调用过程、动态加载

代码重定位——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实现;

                裸机程序下:由应用程序实现动态链接库的加载(加载地址不固定),然后由应用程序实现重定位(如借助寄存器进行重定位),实现比较复杂且意义不太大;

                裸机程序下:动态链接库对外接口函数地址固定,可实现库文件的更换(严格意义上说这不属于动态链接库)。

        linux 下动态链接实现原理

        单片机高阶技能之动态链接库技术实现的讨论


 参考链接

程序 目标文件结构及bss段分析

对uboot中BSS段的理解

start.S 代码学习 C语言内存布局

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值