代码重定位(2.编程实现代码重定位)

1.引入链接脚本

我们上一节讲述了为什么要重定位代码,那么怎么去重定位代码呢?

上一节我们发现"arm-linux-ld -Ttext 0 -Tdata 0x30000000"这种方式编译出来的bin文件有800多M,这肯定是不行的,那么需要怎么把.data段重定位到sdram呢?

可以通过AT参数指定.data段在编译时的存放位置,我们发现这样指定太不方便了,而且不好确定要放在bin文件的哪个位置。这里就要引入链接脚本,它可以帮我们解决这个不必要的麻烦。

链接脚本格式

格式如下图:

我们来看一个具体的例子:

SECTIONS
{
. = 0x00000000; //表示当前地址为0 . = ALIGN(4); //设置当前位置让4字节对齐
.text :
{
cpu/arm920t/start.o (.text)
board/lyb2440/boot_init.o (.text)
*(.text)
} //表示.text段从0x4开始存放,其中可以手动调整代码段的位置,
//比如让start.o,boot_init.o中的函数放在最前面,然后存放剩余的代码段 . = ALIGN(4); //设置当前位置让4字节对齐
.rodata : { *(.rodata) } //从该位置开始存放所有的.rodata段 . = ALIGN(4); //设置当前位置让4字节对齐
.data : 0x30000000 : AT(0x800) { *(.data) } //从该位置开始存放所有的.data段 设置运行 __bss_start = .; //设置.bss段的起始位置
.bss : { *(.bss) } //从该位置开始存放所有的.bss段
_end = .;//设置.bss段的结束位置(也就是整个链接脚本的结束为止)
}

这个是我从uboot中裁剪过来的链接脚本,从注释我已把链接脚本的结构讲解的差不多了。这里.data段指定了程序的运行(链接)地址为sdram的base_addr(0x30000000),通过AT指定加载(在bin文件的存放)地址0x800

2.如何重定位代码

我们先编写一个链接脚本如下所示:

SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800) { *(.data) }
.bss : { *(.bss) *(.COMMON) }
}

我们上一节分析了nor启动时无法写全局变量,那么现在有两种重定位方法:

  1. 只重定位数据段

    只重定位数据段的过程用下图更直观:

    对于nor启动时,我们可以直接从nor上取指令执行,所以可以只进行数据段的重定位,我们编写链接脚本relocate.lds如下所示:

     SECTIONS {
    .text 0 : { *(.text) }//所有文件的.text
    .rodata : { *(.rodata) } //只读数据段
    .data 0x30000000 : AT(0x800) { *(.data) } //放在0x800,但运行时在0x3000000
    .bss : { *(.bss) *(.COMMON) }//所有文件的bss段,所有文件的.COMMON段
    }

    Makefile如下所示:

     all:
    arm-linux-gcc -c -o led.o led.c
    arm-linux-gcc -c -o uart.o uart.c
    arm-linux-gcc -c -o init.o init.c
    arm-linux-gcc -c -o main.o main.c
    arm-linux-gcc -c -o start.o start.S
    #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-objcopy -O binary -S sdram.elf sdram.bin
    arm-linux-objdump -D sdram.elf > sdram.dis

    编译出sdram.bin,然后修改start.s进行.data段的重定位。我们需要将以0x800为.data段基地址的整个数据段copy到0x30000000处去,start.s修改如下:

     .text
    .global _start _start: /* 关闭看门狗 */
    /* 初始化时钟 */
    /* 设置栈 */
    /*初始化sdram*/
    ...
    /* 重定位data段,把加载地址0x800(bin文件中在nor中)的数据段的内容重定位到sdram的baseaddr */
    mov r1, #0x800
    ldr r0, [r1]
    mov r1, #0x30000000
    str r0, [r1] bl main halt:
    b halt

    这里是用的相对跳转指令bl main,因为还没有重定位整个完整的代码,所以不能用ldr绝对跳转。前面的初始化时钟、sdram我就不写了,参考

    时钟编程(二、配置时钟寄存器)

    (三、UART编程实现)

    内存控制器(五、SDRAM编程实现)

    缺点:

    这里只是人为的对.data段写死了,那么当我有多个全局变量时,还要计算重定位的次数,而且我们也不知道有多少个全局变量,所以这重定位方式有缺陷。那么我们对这种重定位.data断的方法做一个改进,将链接脚本修改如下:

     SECTIONS
    {
    .text 0 : { *(.text) }
    .rodata : { *(.rodata) }
    .data 0x30000000 : AT(0x800)
    {
    data_load_addr = LOADADDR(.data); /* data段在bin文件中的地址, 加载地址 */
    data_start = . ; /* data段在重定位地址, 运行时的地址 */
    *(.data)
    data_end = . ; /* data段结束地址 */
    }
    .bss : { *(.bss) *(.COMMON) }
    }

    上面的链接脚本用一个变量data_load_addr指定了加载地址(data段在bin文件中的地址,即0x800),用变量data_start指定了运行地址(即为0x30000000),那么用data_end - data_start就是我们数据段的总长度。

    对start.s做出如下修改:

     	/* 重定位data段 */
    ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */
    ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
    ldr r3, =data_end /* data段结束地址 */ cpy:
    ldrb r4, [r1]
    strb r4, [r2]
    add r1, r1, #1
    add r2, r2, #1
    cmp r2, r3
    ble cpy bl main halt:
    b halt

    注意,这里start.s中用到了链接脚本中的变量,r4作为临时变量,依次从data_load_addr读取出来后写入到data_start。

    优点:可以不用计算有多少个全局变量,链接脚本自动帮我们弄好了。

    缺点:由于我们的程序可能会大于SRAM或者nor的容量,那么就必须连代码段也一起进行重定位,

    下面这种重定位方式更好,在实际应用中也是用的下面这种方式去做的重定位。

  2. 重定位整个程序

    重定位整个程序的过程用下图更直观:

    我们修改链接脚本如下:

     SECTIONS
    {
    . = 0x30000000; . = ALIGN(4);
    .text :
    {
    *(.text)
    } . = ALIGN(4);
    .rodata : { *(.rodata) } . = ALIGN(4);
    .data : { *(.data) } . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    _end = .;
    }

    在这里我们将代码段的地址设置为0x3000_0004,然后紧接着放.rodata段,然后再紧接着放.data段。这样我们的bin文件就不再有“空洞”了。

    修改start.s如下:

     .text
    .global _start _start:
    ...
    ...
    /* 重定位text, rodata, data段整个程序 */
    mov r1, #0
    ldr r2, =_start /* 第1条指令运行时的地址,也就是.text段的runtime addr,在这里是0x3000_0004*/
    ldr r3, =__bss_start /* bss段的起始地址,也就是整个程序的结束地址 */ cpy:
    ldrb r4, [r1]
    strb r4, [r2]
    add r1, r1, #1
    add r2, r2, #1
    cmp r2, r3
    ble cpy bl main
    halt:
    b halt

    我们来分析下这段重定位:整个bin文件程序的长度(.text + .rodata + .data)为__bss_start - _start,那么我们是把bin文件从存储介质的0地址copy到程序的运行地址0x3000_0004,这样我们访问.data段时就是访问sdram中重定位后的数据段了。

s3c2440裸机-代码重定位(2.编程实现代码重定位)的更多相关文章

  1. Windows PE 重定位表编程(枚举重定位地址)

    原理之前单独总结过,在这里: http://blog.csdn.net/u013761036/article/details/54051347 下面是枚举重定位信息的代码: // ReLocation ...

  2. s3c2440裸机-代码重定位、清bss的改进和位置无关码

    1.代码重定位的改进 用ldr.str代替ldrb, strb加快代码重定位的速度. 前面重定位时,我们使用的是ldrb命令从的Nor Flash读取1字节数据,再用strb命令将1字节数据写到SDR ...

  3. s3c2440裸机-时钟编程(二、配置时钟寄存器)

    s3c2440裸机编程-时钟编程(二.配置时钟寄存器) 1.2440时钟时序 下图是2440时钟配置时序: 1.上电后,nRESET复位信号拉低,此时cpu还无法取指令工作. 2.nRESET复位信号 ...

  4. 十三、S3C2440 裸机 — 初始化代码及MMU

    13.1 NOR FLASH 搬运 把程序从 nor flash 上搬运到 SDRAM 中 程序存储在 nor flash 上,运行时将程序搬运到 SDRAM 中运行 nor flash 启动:nor ...

  5. linux上使用J-Link调试S3C2440裸机代码

    linux上使用J-Link调试S3C2440裸机代码 工具: segger的jlink仿真器 segger的jlink for linux 交叉编译工具链里面的arm-xx-linux-xx-gdb ...

  6. 【转载】s3c2440裸机开发调试环境(MDK4.6,Jlink v8,mini2440)

    用于arm裸机程序开发的IDE基本有 以下3个:MDK,IAR,还有ADS.具体它们的具体情况在这里我就不多说了,百度一下就明白了.由于之前开发c51,stm32时候都使用了MDK开发环境,而且MDK ...

  7. iOS6定位服务编程详解

    现在的移动设备很多都提供定位服务,使用iOS系统的iPhone.iPod Touch和iPad都可以提供位置服务,iOS设备能提供3种不同途径进行定位:Wifi, 蜂窝式移动电话基站, GPS卫星 i ...

  8. iOS定位服务编程详解

    现在的移动设备很多都提供定位服务,使用iOS系统的iPhone.iPod Touch和iPad都可以提供位置服务,iOS设备能提供3种不同途径进行定位:Wifi, 蜂窝式移动电话基站, GPS卫星 i ...

  9. FastMM 定位内存泄露的代码位置

    FastMM 定位内存泄露的代码位置 开源的FastMM,使用很简单,在工程的第一行引用FastMM4即可(注意,一定要在第一个Uses的位置),可以在调试程序时提示内存泄露情况,还可以生成报告. 在 ...

随机推荐

  1. [TimLinux] JavaScript 原生AJAX介绍

    1. AJAX 异步JavaScript + XML,用于浏览器内部通过前端JavaScript语言操纵,与HTTP服务器进行连接通信的技术. 2. XMLHttpRequest对象 从IE7+,以及 ...

  2. 2019 AI Bootcamp Guangzhou 参会日记

    2019年的全球AI训练营在北京.上海.广州.杭州.宁波五个地方同时举办! 12月14日,微软全球AI Bootcamp活动再次驾临广州,本次会议结合 ML.NET 和基于 SciSharp 社区介绍 ...

  3. 【Git】远程分支

    [Git]远程分支 转载:https://www.cnblogs.com/yangchongxing/p/10239270.html 目录 ============================ 1 ...

  4. 牛客NOIP暑期七天营-提高组3

    第一题:破碎的矩阵 题目链接:https://ac.nowcoder.com/acm/contest/932/A    刚看到这题的时候感觉特别熟悉...诶,这不就是codeforces某场比赛的某某 ...

  5. 微信小程序 存储数据到本地以及本地获取数据

    1.wx存储数据到本地以及本地获取数据 存到本地就是存到你的手机 wx.setStorageSync与wx.setStorage 1.1 wx.setStorageSync(string key, a ...

  6. C#中怎样获取默认配置文件App.config中配置的键值对内容

    场景 在新建一个程序后,项目中会有一个默认配置文件App.config 一般会将一些配置文件信息,比如连接数据库的字符串等信息存在此配置文件中. 怎样在代码中获取自己配置的键值对信息. 注: 博客主页 ...

  7. npm 镜像配置

    npm 默认 registry:https://registry.npmjs.org/  npm中文文档:https://www.npmjs.cn   淘宝 NPM 镜像:https://npm.ta ...

  8. 如何使用TG Pro for Mac的自定义控制功能完全覆盖系统

    在某些情况下,可能需要完全覆盖系统风扇控制并使用自定义算法.通过将Auto Boost规则的强大功能与覆盖系统功能相结合,可以使用TG Pro.请记住,当风扇模式设置为Auto Boost时,这将完全 ...

  9. 13-Node.js学习笔记-MongoDB

    数据库相关概念 在一个数据库软件最终可以包含多个数据仓库,在每个数据仓库中可以包含多个数据集合,每个数据集合中可以包含多条文档(具体的数据) database: 数据库,mongoDB数据库软件中可以 ...

  10. <科普>CPU进行四则运算(加减乘除)的主流方法

    以下除特殊说明外均为32位数的运算 1.加法运算 A   +   B    =   C 无符号整数加法和有符号整数加法均采用以下方案进行操作 用到的寄存器与初始化内容: 32位加数寄存器------- ...