代码重定位(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. BZOJ2809 dispatching(左偏树)

    在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿.在这个帮派里,有一名忍者被称之为 Master.除了 Master以外,每名忍者都有且仅有一个上级.为保密,同时增强忍者们的 ...

  2. HDU3870- intervals(差分约束)

    You are given n closed, integer intervals [ai, bi] and n integers c1, ..., cn. Write a program that: ...

  3. 【广州.NET社区推荐】【译】Visual Studio 2019 中 WPF & UWP 的 XAML 开发工具新特性

    原文 | Dmitry 翻译 | 郑子铭 自Visual Studio 2019推出以来,我们为使用WPF或UWP桌面应用程序的XAML开发人员发布了许多新功能.在本周的 Visual Studio ...

  4. python 金融应用(三)数据可视化

    matplotlib 库( http://www.matp1otlìb.org )的基本可视化功能. 主要是2-D绘图.金融绘图和3-D绘图 一.2-D绘图 1.1一维数据集 #导入所需要的包impo ...

  5. 使用stm32开发 USB_CAN 适配器测试

    USB_CAN 适配器测试例程 采用CDC透传模式 一.简介 CAN总线无处不在,在设计开发中,到处需要用到CAN总线调试工具,本工具可以作为CAN的基础测试工具,用于监听CAN总线,或测试CAN数据 ...

  6. v-if和v-show 的区别

    区别 1.手段:v-if是通过控制dom节点的存在与否来控制元素的显隐:v-show是通过设置DOM元素的display样式,block为显示,none为隐藏: 2.编译过程:v-if切换有一个局部编 ...

  7. python爬虫--多任务异步协程, 快点,在快点......

    多任务异步协程asyncio 特殊函数: - 就是async关键字修饰的一个函数的定义 - 特殊之处: - 特殊函数被调用后会返回一个协程对象 - 特殊函数调用后内部的程序语句没有被立即执行 - 协程 ...

  8. java中小数点位数

    import java.math.BigDecimal;import java.text.DecimalFormat;import java.text.NumberFormat;import java ...

  9. 约数之和(POJ1845 Sumdiv)

    最近应老延的要求再刷<算法进阶指南>(不得不说这本书不错)...这道题花费了较长时间~(当然也因为我太弱了)所以就写个比较易懂的题解啦~ 原题链接:POJ1845 翻译版题目(其实是AcW ...

  10. ActiveMQ消息队列从入门到实践(4)—使用Spring JMS收发消息

    Java消息服务(Java Message Service ,JMS)是一个Java标准,定义了使用消息代理的通用API .在JMS出现之前,每个消息代理都有私有的API,这就使得不同代理之间的消息代 ...