s3c2440裸机-代码重定位(2.编程实现代码重定位)

代码重定位(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中重定位后的数据段了。

原文地址:https://www.cnblogs.com/fuzidage/p/12043586.html

时间: 2024-11-05 02:22:01

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

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

s3c2440裸机编程-时钟编程(二.配置时钟寄存器) 1.2440时钟时序 下图是2440时钟配置时序: 1.上电后,nRESET复位信号拉低,此时cpu还无法取指令工作. 2.nRESET复位信号结束后变为高电平,此时cpu开始工作.此时cpu主频FCLK=osc. 3.此时可以配置PLL,经过lock time后,FCLK倍频成新的时钟. 2.如何配置时钟 在参考手册的特性里介绍了S3C2440的工作频率,Fclk最高400MHz,Hclk最高136MHz,Pclk最高68MHz.那么 我

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

用于arm裸机程序开发的IDE基本有 以下3个:MDK,IAR,还有ADS.具体它们的具体情况在这里我就不多说了,百度一下就明白了.由于之前开发c51,stm32时候都使用了MDK开发环境,而且MDK的界面确实看起来舒服多了,所以我选择了MDK作为我的s3c2440裸机开发的IDE.以下主要介绍一下如何使用MDK配合J-link来调试基于s3c2440的开发板. 首先,我们需要下载的有以下2样: MDK J-link 驱动 我的开发环境:windows 7 64位,J-linkv8,mini24

iOS定位服务编程详解

现在的移动设备很多都提供定位服务,使用iOS系统的iPhone.iPod Touch和iPad都可以提供位置服务,iOS设备能提供3种不同途径进行定位:Wifi, 蜂窝式移动电话基站, GPS卫星 iOS 不像Android系统在定位服务编程时,可以指定采用哪种途径进行定位.iOS的API把底层这些细节屏蔽掉了,开发人员和用户并不知道现在设备是采用 哪种方式进行定位的,iOS系统会根据设备的情况和周围的环境,采用一套最佳的解决方案.这个方案是这样的,如果能够接收GPS信息,那么设备优先采用 GP

s3c2440裸机-异常中断(二. und未定义指令异常)

1._und(未定义指令异常)介绍 我们之前分析过5种异常,那么如何进入未定义指令异常,当然是cpu读取指令发生异常,出现了指令解析异常. 我们先来看下当cpu解析到什么样的指令才会触发未定义指令异常呢? 从上面的arm指令格式中可知,只要指令码属于划线的格式,就属于未定义指令异常. 2.汇编向c函数传参 我们知道汇编给C语言函数传参是通过r0,r1,...通过堆栈的方式去传递的参数,比如r0=1, r1=2;那么在被调用的c函数中argv0就是r0, argv1就是r1...,那么我们如果通过

《zw版·Halcon-delphi系列原创教程》航母舰载机·视觉定位标志的识别代码

<zw版·Halcon-delphi系列原创教程>航母舰载机·视觉定位标志的识别代码 航母舰载机机身上的黄黑圆圈的标志是什么意思,辐射?核动力?战术核弹? <百度百科>介绍如下     这是工业数字摄影测量的人工标志    数字摄影测量(Basic concept of digital photogrammetry)是基于数字影像和摄影测量的基本原理,应用计算机技术.数字影像处理.影像匹配.模式识别    等多学科的理论与方法,提取所摄对像以数字方式表达的几何与物理信息的摄影测量学

jQuery实现固定顶部 定位滚动导航效果代码

jQuery实现固定顶部 定位滚动导航效果代码,很常见的效果,非常实用:滚动滚动条时,导航固定顶部,并且滚动到对应的板块时,当前导航高亮显示:点击导航文字时平滑跳转到对应的板块. $(function(){          var subNav_active = $(".adv_active");         var subNav_scroll = function(target){             subNav_active.removeClass   ("a

如何通过阅读别人的代码提高自己的编程能力

代码阅读的必要性 阅读别人的代码作为研发人员是一件经常要做的事情.一个是学习新的编程语言的时候通过阅读别人的代码是个最佳的学习方法,另外是积累编程经验.如果你有机 会阅读一些操作系统的代码会帮助你理解一些基本的原理.更有就是在你作为一个质量确保人员或一个小领导的时候如果你要做白盒测试的时候没有阅读代码的能力 是不能完成相应的任务.最后一个就是如果你中途接手一个项目的时候或给一个项目做售后服务的时候是要有阅读代码的能力的. 收集所有可能收集的材料阅 读代码要做的第一件事情是收集所有和项目相关的资料

FastMM 定位内存泄露的代码位置(转)

FastMM 定位内存泄露的代码位置 开源的FastMM,使用很简单,在工程的第一行引用FastMM4即可(注意,一定要在第一个Uses的位置),可以在调试程序时提示内存泄露情况,还可以生成报告. 在Delphi2007以后版本中,使用更加简单,只需要在工程开始的位置加上语句: ReportMemoryLeaksOnShutdown := True;就可以了,并且在运行时不会出现提示.如果想要生成文件报告,还需要FastMM4,Delphi中没有别的设置可以生成文件报告. 可以修改FastMM4

nodejs 编程建议(代码规范)

1. 不要使用 "try ... catch" ,因为nodejs都是异步操作,try catch无法捕捉回调里面的异常,除非你在回调里面也写try catch try...catch statement cannot catch the error in callback. Here is an example: fs = require('fs'); try { fs.stat('doesnt_exist.txt', function(err, stats) { if (err)