重定位与链接脚本

1.为什么需要重定位  

  位置无关编码(PIC,position independent code):汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关。
  位置有关编码:汇编源码编码成二进制可执行程序后和内存地址是有关的。

  我们在设计一个程序时,会给这个程序指定一个运行地址(链接地址)。就是说我们在编译程序时其实心里是知道我们程序将来被运行时的地址(运行地址)的,而且必须给编译器链接器指定这个地址(链接地址)才行。最后得到的二进制程序理论上是和你指定的运行地址有关的,将来这个程序被执行时必须放在当时编译链接时给定的那个地址(链接地址)下才行,否则不能运行(就叫位置有关代码)。但是有个别特别的指令他可以跟指定的地址(链接地址)没有关系,也就是说这些代码实际运行时不管放在哪里都能正常运行。

  对于位置有关代码来说:最终执行时的运行地址和编译链接时给定的链接地址必须相同,否则一定出错。
  之前的裸机程序中,Makefile中用 -Ttext 0x0 来指定链接地址是0x0。这意味着我们认为这个程序将来会放在0x0这个内存地址去运行。
  但是实际上我们运行时的地址是0xd0020010(我们用dnw下载时指定的下载地址)。这两个地址看似不同,但是实际相同。这是因为S5PV210内部做了映射,把SRAM映射到了0x0地址去。

  分清楚这两个概念:
  链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本)
  运行地址:程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)

2.再解S5PV210的启动过程:三星推荐和uboot的实现是不同的

  三星推荐的启动方式中:bootloader必须小于96KB并大于16KB,假定bootloader为80KB,启动过程是这样子:先开机上电后BL0运行,BL0会加载外部启动设备中的bootloader的前16KB(BL1)到SRAM中去运行,BL1运行时会加载BL2(bootloader中80-16=64KB)到SRAM中(从SRAM的16KB处开始用)去运行;BL2运行时会初始化DDR并且将OS搬运到DDR去执行OS,启动完成。
  uboot实际使用的方式:uboot大小随意,假定为200KB。启动过程是这样子:先开机上电后BL0运行,BL0会加载外部启动设备中的uboot的前16KB(BL1)到SRAM中去运行,BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中,然后用一句长跳转(从SRAM跳转到DDR)指令从SRAM中直接跳转到DDR中继续执行uboot直到uboot完全启动。uboot启动后在uboot命令行中去启动OS。

  链接地址和运行地址有时候必须不相同,而且还不能全部用位置无关码,这时候只能重定位。


  扩展:
  分散加载:把uboot分成2部分(BL1和整个uboot),两部分分别指定不同的链接地址。启动时将两部分加载到不同的地址(BL1加载到SRAM,整个uboot加载到DDR),这时候不用重定位也能启动。
  评价:分散加载其实相当于手工重定位。重定位是用代码来进行重定位,分散加载是手工操作重定位的。

 

3.对比操作系统下的程序与裸机程序

  linux中的应用程序。gcc hello.c -o hello,这时使用默认的链接地址就是0x0,所以应用程序都是链接在0地址的。因为应用程序运行在操作系统的一个进程中,在这个进程中这个应用程序独享4G的虚拟地址空间。所以应用程序都可以链接到0地址,因为每个进程都是从0地址开始的。(编译时可以不给定链接地址而都使用0)
  210中的裸机程序。运行地址由我们下载时确定,下载时下载到0xd0020010,所以就从这里开始运行。(这个下载地址也不是我们随意定的,是iROM中的BL0加载BL1时事先指定好的地址,这是由CPU的设计决定的)。所以理论上我们编译链接时应该将地址指定到0xd0020010,但是实际上我们在之前裸机程序中都是使用位置无关码PIC,所以链接地址可以是0。

4.关于链接

  从源码到可执行程序的步骤:预编译、编译、链接、strip
  预编译:预编译器执行。譬如C中的宏定义就是由预编译器处理,注释等也是由预编译器处理的。
  编译: 编译器来执行。把源码.c .S编程机器码.o文件。
  链接: 链接器来执行。把.o文件中的各函数(段)按照一定规则(链接脚本来指定)累积在一起,
  形成可执行文件。
  strip: strip是把可执行程序中的符号信息给拿掉,以节省空间。(Debug版本和Release版本)
  objcopy:由可执行程序生成可烧录的镜像bin文件。

  程序段的概念:代码段、数据段、bss段(ZI段)、自定义段
  段就是程序的一部分,我们把整个程序的所有东西分成了一个一个的段,给每个段起个名字,然后在链接时就可以用这个名字来指示这些段。也就是说给段命名就是为了在链接脚本中用段名来让段站在核实的位置。

  段名分为2种:一种是编译器链接器内部定好的,先天性的名字;一种是程序员自己指定的、自定义的段名。
先天性段名:
  代码段:(.text),又叫文本段,代码段其实就是函数编译后生成的东西
  数据段:(.data),数据段就是C语言中有显式初始化为非0的全局变量
  bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,对应C语言中初始化为0的全局变量。
  后天性段名:

5、链接脚本究竟要做什么?
  链接脚本其实是个规则文件,他是程序员用来指挥链接器工作的。链接器会参考链接脚本,并且使用其中规定的规则来处理.o文件中那些段,将其链接成一个可执行程序。
  链接脚本的关键内容有2部分:段名 + 地址(作为链接地址的内存地址)
  链接脚本的理解:
  SECTIONS {} 这个是整个链接脚本
  . 点号在链接脚本中代表当前位置。
  = 等号代表赋值

 

6.代码重定位实战(理论分析)

  任务:在SRAM中将代码从0xd0020010重定位到0xd0024000
  任务解释:本来代码是运行在0xd0020010的,但是因为一些原因我们又希望代码实际是在0xd0024000位置运行的。这时候就需要重定位了。
  注解:本练习对代码本身运行无实际意义,我们做这个重定位纯粹是为了练习重定位技能。但是某些情况重定位就是必须的,譬如在uboot中。

  思路:
  第一点:通过链接脚本将代码链接到0xd0024000
  第二点:dnw下载时将bin文件下载到0xd0020010
  第一点加上第二点,就保证了:代码实际下载运行在0xd0020010,但是却被链接在0xd0024000。从而为重定位奠定了基础。
  当我们把代码链接地址设置为0xd0024000时,实际隐含意思就是我这个代码将来必须放在0xd0024000位置才能正确执行。如果实际运行地址不是这个地址就要出事(除非代码是PIC位置无关码),当以上都明白了后,就知道重定位代码的作用就是:在PIC执行完之前(在代码中第一句位置有关码执行之前)必须将整个代码搬移到0xd0024000位置去执行,这就是重定位。
  第三点:代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000
  第四点:使用一个长跳转跳转到0xd0024000处的代码继续执行,重定位完成

总结:
重定位实际就是在运行地址处执行一段位置无关码PIC,让这段PIC(也就是重定位代码)从运行地址处把整个程序镜像拷贝一份到链接地址处,

完了之后使用一句长跳转指令从运行地址处直接跳转到链接地址处去执行同一个函数(led_blink),这样就实现了重定位之后的无缝连接。

 

7.代码重定位实战(代码)

  adr与ldr伪指令的区别
  ldr和adr都是伪指令,区别是ldr是长加载、adr是短加载。
  重点:adr指令加载符号地址,加载的是运行时地址;ldr指令在加载符号地址时,加载的是链接地址。

  第一步:重定位(代码拷贝)
  重定位就是汇编代码中的copy_loop函数,代码的作用是使用循环结构来逐句复制代码到链接地址。
  复制的源地址是SRAM的0xd0020010,复制目标地址是SRAM的0xd0024000,复制长度是bss_start减去_start
  所以复制的长度就是整个重定位需要重定位的长度,也就是整个程序中代码段+数据段的长度。
  bss段(bss段中就是0初始化的全局变量)不需要重定位。
  第二步:清bss段
  清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的这个特性的)。一般情况下我们的程序是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们的main函数之前运行,这段代码就负责清除bss)。但是在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那一份代码中的bss,而未清除重定位地址处开头的那一份代码的bss,所以重定位之后需要自己去清除bss。
  第三步:长跳转
  清理完bss段后重定位就结束了。然后当前的状况是:
  1、当前运行地址还在0xd0020010开头的(重定位前的)那一份代码中运行着。
  2、此时SRAM中已经有了2份代码,1份在d0020010开头,另一份在d0024000开头的位置。
  然后就要长跳转了。

 

/** 文件名:    led.s    * 作者:    朱老师* 描述:    演示重定位(在SRAM内部重定位)*/#define WTCON        0xE2700000#define SVC_STACK    0xd0037d80.global _start                    // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:// 第1步:关看门狗(向WTCON的bit5写入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:设置SVC栈ldr sp, =SVC_STACK// 第3步:开/关icachemrc p15,0,r0,c1,c0,0;            // 读出cp15的c1到r0中//bic r0, r0, #(1<<12)            // bit12 置0  关icacheorr r0, r0, #(1<<12)            // bit12 置1  开icachemcr p15,0,r0,c1,c0,0;
    // 第4步:重定位// adr指令用于加载_start当前运行地址adr r0, _start          // adr加载时就叫短加载        // ldr指令用于加载_start的链接地址:0xd0024000ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载    // bss段的起始地址ldr r2, =bss_start    // 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可cmp r0, r1            // 比较_start的运行时地址和链接地址是否相等beq clean_bss        // 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位// 重定位完成后继续执行clean_bss。// 用汇编来实现的一个while循环
copy_loop:ldr r3, [r0], #4    // 源 将地址为r0的数据给r3.然后r0=r0+4str r3, [r1], #4    // 目的   这两句代码就完成了4个字节内容的拷贝cmp r1, r2            // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2bne copy_loop// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:ldr r0, =bss_start                    ldr r1, =bss_endcmp r0, r1                // 如果r0等于r1,说明bss段为空,直接下去beq run_on_dram            // 清除bss完之后的地址mov r2, #0
clear_loop:str r2, [r0], #4        // 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),cmp r0, r1                // 然后r0 = r0 + 4bne clear_looprun_on_dram:    // 长跳转到led_blink开始第二阶段ldr pc, =led_blink                // ldr指令实现长跳转// 从这里之后就可以开始调用C程序了//bl led_blink                    // bl指令实现短跳转// 汇编最后的这个死循环不能丢b .

 

led_blink在c语言程序里,与上章代码相同不再展开。
链接脚本
link.lds
SECTIONS
{. = 0xd0024000;    //指定链接地址
    .text : {start.o* (.text)}.data : {* (.data)}bss_start = .; 
    .bss : {* (.bss)}bss_end  = .;    
}

Makefile

led.bin: start.o led.oarm-linux-ld -Tlink.lds -o led.elf $^      //-T后面跟的为链接脚本arm-linux-objcopy -O binary led.elf led.binarm-linux-objdump -D led.elf > led_elf.disgcc mkv210_image.c -o mkx210./mkx210 led.bin 210.bin%.o : %.Sarm-linux-gcc -o $@ $< -c -nostdlib%.o : %.carm-linux-gcc -o $@ $< -c -nostdlibclean:rm *.o *.elf *.bin *.dis mkx210 -f  

 

 

转载于:https://www.cnblogs.com/PengfeiSong/p/6345615.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457251.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux bashrc和profile的用途和区别

导读使用终端ssh登录Linux操作系统的控制台后&#xff0c;会出现一个提示符号&#xff08;例如&#xff1a;#或~&#xff09;&#xff0c;在这个提示符号之后可以输入命令&#xff0c;Linux根据输入的命令会做回应&#xff0c;这一连串的动作是由一个所谓的Shell来做处理。Shel…

python读取word文档结构图_Word 有什么技巧,让你相见恨晚?

Word作为日常办公最常用的软件之一&#xff0c;其实真没你想得那么简单&#xff01;你不知道的每一个技巧&#xff0c;都会让你相见恨晚&#xff01;每当身边的小伙伴询问这些疑难杂症时&#xff0c;我都会抛出这张图…真的没骗你&#xff0c;我们遇到的 99% 的Word难题&#x…

Golang 特性简介

by sheepbao 主要大概介绍go语言的历史和特性&#xff0c;简单的入门。 来历 很久以前&#xff0c;有一个IT公司&#xff0c;这公司有个传统&#xff0c;允许员工拥有20%自由时间来开发实验性项目。在2007的某一天&#xff0c;公司的几个大牛&#xff0c;正在用c开发一些比较繁…

HTML实体字符转化为HTML标签

html_entity_decode方法 参数描述string必需。规定要解码的字符串。flags 可选。规定如何处理引号以及使用哪种文档类型。 可用的引号类型&#xff1a; ENT_COMPAT - 默认。仅解码双引号。ENT_QUOTES - 解码双引号和单引号。ENT_NOQUOTES - 不解码任何引号。规定所使用文档类型…

华为2017java笔试题_2017年java华为面试题

2017年java华为面试题通过HCNP认证&#xff0c;将证明您对中小型网络有全面深入的了解&#xff0c;掌握中小型网络的通用技术&#xff0c;并具备独立设计中小型网络以及使用华为路由交换设备实施设计的能力。下面是小编收集的关于java华为面试题&#xff0c;希望大家认真阅读!1…

Tomcat 配置详解/优化方案

Server.xml 【原地址&#xff1a;http://blog.csdn.net/cicada688/article/details/14451541】 Server.xml配置文件用于对整个容器进行相关的配置。 <Server>元素&#xff1a;是整个配置文件的根元素。表示整个Catalina容器。 属性&#xff1a;className&#xff1a;实现…

MySQL创建数据库与创建用户以及授权

1、create schema [数据库名称] default character set utf8 collate utf8_general_ci;--创建数据库 采用create schema和create database创建数据库的效果一样。 2、create user [用户名称]% identified by [用户密码];--创建用户 密码8位以上&#xff0c;包括&#xff1a;大写…

java 防止url重复请求_Web项目如何防止客户端重复发送请求

在Web项目中&#xff0c;有一些请求或操作会对数据产生影响(比如新增、删除、更新)&#xff0c;针对这类请求一般都需要做一些保护&#xff0c;以防止用户有意或无意的重复发起这样的请求导致的数据错乱。本文总结了一些防止客户端重复发送请求的方法。方法一&#xff1a;JS监听…

【bzoj1010-toy】斜率优化入门模板

dsy1010: [HNOI2008]玩具装箱 【题目描述】 有n个数&#xff0c;分成连续的若干段&#xff0c;每段&#xff08;假设从第j个到第i个组成一段&#xff09;的分数为 (X-L)^2&#xff0c;X为j-iSigma(Ck) i<k<j&#xff0c;其中L是一个常量。目标&#xff1a;各段分数的总和…

itellyou操作系统,office等软件的很全的下载站

itellyou操作系统&#xff0c;office等软件的很全的下载站http://www.itellyou.cn/转载于:https://blog.51cto.com/wangheyu1/1894724

矩阵的马鞍点

#include<stdio.h>#define n 4//马鞍点是第I行值最小第J列值最大 void maxmin(int a[n][n]){ int i,j ,flag; int max[n],min[n]; for(i0;i<n;i) { min[i]a[i][0];//将数组每行的第一个元素赋值给min[]数组 for(j1;j<n;j) { if(a[i][j]<min[i]) min[i]a[i][j];…

Linux运维工程师面试-部分题库

一、Linux操作系统知识 1.常见的Linux发行版本都有什么&#xff1f;你最擅长哪一个&#xff1f;它的官网网站是什么&#xff1f;说明你擅长哪一块&#xff1f; 2.Linux开机启动流程详细步骤是什么&#xff1f;系统安装完&#xff0c;忘记密码如何破解&#xff1f; 3.企业中Linu…

java统计系统线程数_Java并发(八)计算线程池最佳线程数

目录一、理论分析二、实际应用为了加快程序处理速度&#xff0c;我们会将问题分解成若干个并发执行的任务。并且创建线程池&#xff0c;将任务委派给线程池中的线程&#xff0c;以便使它们可以并发地执行。在高并发的情况下采用线程池&#xff0c;可以有效降低线程创建释放的时…

php大小写转换函数

1.将字符串转换成小写 strtolower(): 该函数将传入的字符串参数所有的字符都转换成小写,并以小定形式放回这个字 符串.例: <?php$str "I want To FLY";$str strtolower($str);echo $str; ?>输出结果: i want to fly 2.将字符转成大写 strtoupper(): 该…

关于移动端 1px 像素问题

移动端1px变粗的原因 移动端html的header总会有一句<meta name"viewport" content"widthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalableno">这句话定义了本页面的viewport的宽度为设备宽度,初始缩放值和最大缩放值都为1,并禁止了…

java框架概念_java概念(2)

java概念(2)重载和重写重载&#xff1a;同一个类中&#xff0c;方法名相同&#xff0c;参数不同重写&#xff1a;父子类中&#xff0c;子类重新定义父类的方法多态​ 多态&#xff1a;同一种行为&#xff0c;不同的对象有不同的表现形式。​ 重载 编译时根据参数决定调用的方法…

CentOS(八)--crontab命令的使用方法

crontab命令常见于Unix和Linux的操作系统之中&#xff0c;用于设置周期性被执行的指令。该命令从标准输入设备读取指令&#xff0c;并将其存放于"crontab"文件中&#xff0c;以供之后读取和执行。 在Linux系统中&#xff0c;Linux任务调度的工作主要分为以下两类&…

有健忘症吗?

今天兴高采烈&#xff0c;早上空气不错&#xff0c; 但是骑自行车的我&#xff0c;还是得戴一个面罩。 半个小时后买了早餐&#xff0c; 一份炒粉、一豆浆&#xff0c;今天早上豆浆没有掉地上&#xff0c; 但是~~~~~~~~~~~~~~shit~~!~!~,居然忘记带要换的衣服了&#xff0c; …

下载java后缀的文件闪退_关于jarfile 打开闪退问题

后面才发现&#xff0c;原来是因为我把文件拖入了新建的文件夹&#xff0c;改变了路径&#xff0c;而且我的java环境没有配置好是全局变量&#xff0c;所以新建文件夹之后&#xff0c;就会出现找不到了路径&#xff0c;闪退的问题&#xff0c;&#xff0c;&#xff0c;还有就是…