ARM 代码重定位实战

news2024/11/15 11:56:40

前言

任务

在 SRAM 中将代码从 0xd0020010 重定位到 0xd0024000。

任务解释:本来代码是运行在0xd0020010的,但是因为一些原因我们又希望代码实际是在0xd0024000位置运行
的。这时候就需要重定位了。

注解:本练习对代码本身运行无实际意义,我们做这个重定位纯粹是为了练习重定位技能。但是某些情况重定位就是必须的,譬如在 uboot 中。


一、思路

第一点:通过链接脚本将代码链接到 0xd0024000;
第二点:dnw 下载时将 bin 文件下载到 0xd0020010;

第一点加上第二点,就保证了:代码实际下载运行在 0xd0020010,但是却被链接 0xd0024000。从而为重定位奠定了基础。

当我们把代码链接地址设置为 0xd0024000 时,实际隐含意思就是: 我这个代码将来必须放在 0xd0024000 位置才能正确执行。如果实际运行地址不是这个地址就要出事(除非代码是 PIC 位置无关码),当以上都明白了后,就知道重定位代码的作用就是:在 PIC 执行完之前(在代码中第一句位置有关码执行之前)必须将整个代码搬移到 0xd0024000 位置去执行,这就是重定位。

第三点:代码执行时, 通过代码前段的少量位置无关码将整个代码搬移到 0xd0024000;
第四点:使用一个长跳转跳转到 0xd0024000 处的代码继续执行,重定位完成;

长跳转:首先这句代码是一句跳转指令(ARM 中的跳转指令就是类似于分支指令 B、BL 等作用的指令),跳转指令通过给 PC(r15)赋一个新值来完成代码段的跳转执行。长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广。

当我们执行完代码重定位后,实际上在 SRAM 中有 2 份代码的镜像(一份是我们下载到0xd0020010 处开头的,另一份是重定位代码复制到 0xd0024000 处开头的),这两份内容完全相同,仅仅地址不同。重定位之后使用 ldr pc, =led_blink 这句长跳转, 直接从 0xd0020010 处代码跳转到 0xd0024000 开头的那一份代码的 led_blink 函数处去执行。(实际上此时在 SRAM中 有 2 个 led_blink 函数镜像,两个都能执行,如果短跳转: bl led_blink 则执行的就是 0xd0020010 开头的这一份,如果长跳转: ldr pc, =led_blink 则执行的是 0xd0024000 开头处的这一份)。这就是短跳转和长跳转的区别。

当链接地址和运行地址相同时,短跳转和长跳转实际效果是一样的;但是当链接地址不等于运行地址时,短跳转和长跳转就有差异了。这时候短跳转实际执行的是运行地址处的那一份,而长跳转执行的是链接地址处那一份。

在这里插入图片描述

总结:重定位实际就是在运行地址处执行一段位置无关码 PIC,让这段 PIC(也就是重定位代码)从运行地址处, 把整个程序镜像拷贝一份到链接地址处,完了之后使用一句长跳转指令, 从运行地址处直接跳转到链接地址处去执行同一个函数(led_blink),这样就实现了重定位之后的无缝连接。


二、链接脚本分析讲解

1、adr 与 ldr 伪指令的区别

ldradr 都是伪指令,区别是 ldr 是长加载、adr 是短加载。

重点:adr 指令加载符号地址,加载的是运行时地址;ldr 指令在加载符号地址时,加载的是链接地址。

深入分析:只要知道 adrldr 分别用于加载运行地址和链接地址,从而可以判断是否需要重定位即可;根本不需知道为什么 adrldr 是这样子,但是我们还是给大家扩展讲下为什么 adrldr 可以加载不同的地址。


可以看到,
adr 指令对应转换为 sub r0, pc, #40 ; 0x28 , 从当前 pc 指针的值,减去 40;
ldr 指令对应转换为 ldr r1, [pc, #72],当前 pc 指针的值,加上 72;

在这里插入图片描述

下面我们再具体看一下:
注意:有个前提必须知道,下方代码块的最左侧一栏,0xd0024000 是链接地址,即我们期望程序运行时的地址,当时这个链接地址不一定等于运行时地址;在这个例子中,我们用 dwn 将程序下载到 0xd0020010

也就是说,程序的链接地址是 0xd0024000 , 但是刚开始的运行地址是 0xd0020010. 需要我们完成代码重定向,实现运行地址等于链接地址的工作。

pc 寄存器的值,表示 CPU 下一条需要指令的地址;因此 pc 寄存器的内容是运行地址。
于是我们往下走:

sub r0, pc, #40 ; 0x28,首先当前 pc 寄存器的值并不是左侧栏的 d0024020,但是 pc 寄存器的值肯定指向某个地址,该地址处存放 sub r0, pc, #40 ; 0x28 指令的内容,这个对应关系是不会变的;然后,考虑到 ARM 流水线的结构,实际的 pc 指向的位置,应该是 sub r0, pc, #40 ; 0x28 指令开始,继续偏移两条指令的地址处(对应指令 ldr r2, [pc, #72])。

现在,将 pc 寄存器的值减去 40(等于16进制减去 0x28),也就是说,pc 寄存器向上偏移 10条 ARM 指令(10 * 4 = 40),于是 pc 指向了 _start 标号处的第一条指令的地址处。所以说, adr 指令得到的地址,依然是运行时地址,是从当前 pc 指针向前偏移,属于相对寻址。

d0024000 <_start>:
d0024000:       e59f0064        ldr     r0, [pc, #100]  ; d002406c <run_on_dram+0x8>
d0024004:       e3a01000        mov     r1, #0
d0024008:       e5801000        str     r1, [r0]
d002400c:       e59fd05c        ldr     sp, [pc, #92]   ; d0024070 <run_on_dram+0xc>
d0024010:       ee110f10        mrc     15, 0, r0, cr1, cr0, {0}
d0024014:       e3c00a01        bic     r0, r0, #4096   ; 0x1000
d0024018:       e3800a01        orr     r0, r0, #4096   ; 0x1000
d002401c:       ee010f10        mcr     15, 0, r0, cr1, cr0, {0}
d0024020:       e24f0028        sub     r0, pc, #40     ; 0x28
d0024024:       e59f1048        ldr     r1, [pc, #72]   ; d0024074 <run_on_dram+0x10>
d0024028:       e59f2048        ldr     r2, [pc, #72]   ; d0024078 <run_on_dram+0x14>
d002402c:       e1500001        cmp     r0, r1
d0024030:       0a000003        beq     d0024044 <clean_bss>

同理:
ldr 指令对应转换为 ldr r1, [pc, #72],从 pc 寄存器的值加上 72,于是 pc 寄存器指向的位置,应该就是来到了 0xd0024074 对应的地方。

从下面代码块看到,将来 pc 寄存器指向的位置,取得的指令是一个数字:d0024000 ,这个值就是我们期望的链接地址。

所以说, ldr r1, [pc, #72] 指令是绝对寻址,因为它的作用是:将 pc 寄存器偏移到一个位置,这个位置专门存放数据。

d0024064 <run_on_dram>:
d0024064:       e59ff014        ldr     pc, [pc, #20]   ; d0024080 <run_on_dram+0x1c>
d0024068:       eafffffe        b       d0024068 <run_on_dram+0x4>
d002406c:       e2700000        rsbs    r0, r0, #0
d0024070:       d0037d80        andle   r7, r3, r0, lsl #27
d0024074:       d0024000        andle   r4, r2, r0
d0024078:       d002412c        andle   r4, r2, ip, lsr #2
d002407c:       d002412c        andle   r4, r2, ip, lsr #2
d0024080:       d00240a0        andle   r4, r2, r0, lsr #1
d0024084:       00001a41        andeq   r1, r0, r1, asr #20
d0024088:       61656100        cmnvs   r5, r0, lsl #2
d002408c:       01006962        tsteq   r0, r2, ror #18
d0024090:       00000010        andeq   r0, r0, r0, lsl r0
d0024094:       45543505        ldrbmi  r3, [r4, #-1285]        ; 0x505
d0024098:       08040600        stmdaeq r4, {r9, sl}
d002409c:       00010901        andeq   r0, r1, r1, lsl #18

 adr  r0, _start     
 ldr  r1, =_start  
 
因此,r0 寄存器中的值是程序运行时地址, r1 寄存器中的值是链接地址。

2、重定位(代码拷贝)

在这里插入图片描述

从上节的分析我们知道,r0 寄存器中的值是程序运行时地址, r1 寄存器中的值是链接地址。

重定位就是汇编代码中的 copy_loop 函数,代码的作用是:使用循环结构来逐句复制代码到链接地址。
复制的源地址是 SRAM 的 0xd0020010(运行时地址),复制目标地址是 SRAM 的 0xd0024000 (链接地
址),复制长度是  r1 到 r2,即 bss_start 减去 _start。

在这里插入图片描述

可以看到,链接脚本中定义 bss_start 到 bss_end 的地址范围,是 bss 段的内容; bss_start 地址前面的内容,是整个程序中的 .text 代码段 和 .data 数据段。

在这里插入图片描述

我们还可以看到, r1 和 r2 寄存器的值,分别对应 d0024000 和 d002412c,都是链接地址;
所以复制的长度,就是整个重定位需要重定位的长度,也就是整个程序中代码段+数据段的长度;
bss 段(bss 段中就是0初始化的全局变量)不需要重定位。


3、清 bss 段

在这里插入图片描述

清除 bss 段是为了满足 C 语言的运行时要求(C 语言要求显式初始化为 0 的全局变量,或者未显式初始化的全
局变量的值为 0,实际上 C 语言编译器就是通过清 bss 段来实现 C 语言的这个特性的)。一般情况下我们的程
序是不需要负责清零 bss 段的(C 语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们
的 main 函数之前运行,这段代码就负责清除 bss)。但是在我们代码重定位了之后,因为编译器帮我们附加的
代码只是帮我们清除了运行地址那一份代码中的 bss,而未清除重定位地址处开头的那一份代码的 bss,所以重
定位之后需要自己去清除 bss。

4、长跳转

清理完 bss 段后重定位就结束了。然后当前的状况是:
1、当前运行地址还在 0xd0020010 开头的(重定位前的)那一份代码中运行着。
2、此时 SRAM 中已经有了 2 份代码,1份在 d0020010 开头,另一份在 d0024000 开头的位置。
然后就要长跳转了。

在这里插入图片描述

可以看到,最终 pc 寄存器指向的地址的内容是:0xd002,40a0,即链接地址范围内的 led_blink 的入口地址。


源自朱有鹏老师.

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

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

相关文章

你都工作两年半了,还不会RabbitMQ?

What is rabbitMQ &#xff1f; RabbitMQ 是一个由 Erlang 语言开发的 AMQP(高级消息队列协议) 的开源实现。 RabbitMQ 是轻量级且易于部署的&#xff0c;能支持多种消息协议。 RabbitMQ 可以部署在分布式和联合配置中&#xff0c;以满足高规模、高可用性的需求。 具体特点包括…

ADI Blackfin DSP处理器-BF533的开发详解29:TOUCH_LINE(屏幕画线)(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件设计原理图 功能介绍 代码实现了读取触摸屏坐标&#xff0c;并将触摸屏坐标换算为液晶屏的显示坐标&#xff0c;将像素点显示到触摸坐标的位…

学习Python中turtle模块的基本用法(4:绘制科赫曲线和谢尔宾斯基三角形)

科赫曲线和谢尔宾斯基三角形是常见的分形图形&#xff08;详细介绍见参考文献1&#xff09;&#xff0c;本文使用turtle库绘制这两类图形。 科赫曲线 科赫曲线的详细介绍见参考文献2&#xff0c;其中的绘图思路是“画正三角形&#xff0c;并把每一边三等分,取三等分后的一边中…

【LeetCode】Day194-超级丑数

题目 313. 超级丑数【中等】 题解 之前做过丑数&#xff0c;规定丑数是质因数只包含2,3,5的正整数&#xff0c;而这道题丑数升级为超级丑数&#xff0c;规定为包含的质因数是在primes数组中的正整数 丑数的题解用动态规划&#xff0c;那么超级丑数也可以利用相同的方法解答…

CSS -- CSS元素显示模式总结(块元素,行内元素,行内块元素)

文章目录CSS 的元素显示模式1 什么是元素显示模式2 块元素3 行内元素4 行内块元素5 元素的显示模式总结CSS 的元素显示模式 1 什么是元素显示模式 作用&#xff1a;网页的标签非常多&#xff0c;在不同地方会用到不同类型的标签&#xff0c;了解他们的特点可以更好的布局我们…

[附源码]JAVA毕业设计-学生宿舍故障报修管理信息系统-(系统+LW)

[附源码]JAVA毕业设计-学生宿舍故障报修管理信息系统-&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff…

开源即巅峰,《Java程序性能优化实战》GitHub三小时标星已超34k

蓦然回首自己做开发已经十年了&#xff0c;这十年中我获得了很多&#xff0c;技术能力、培训、出国、大公司的经历&#xff0c;还有很多很好的朋友。但再仔细一想&#xff0c;这十年中我至少浪费了五年时间&#xff0c;这五年可以足够让自己成长为一个优秀的程序员&#xff0c;…

项目设置分页条件查询接口

一、分页 1、HospPlusConfig中配置分页插件1 /** 2 * 分页插件 3 */ 4 Bean 5 public PaginationInterceptor paginationInterceptor() { 6 return new PaginationInterceptor(); 7 }2、分页Controller方法 HospitalSetController中添加分页方法1 ApiOperation(value "分…

Python学习基础笔记四十二——序列化模块

1、序列化的概念&#xff1a; 序列&#xff1a;就是字符串。 序列化&#xff1a;将原本的字典、列表等内容转换成一个字符串数据类型的过程就叫做序列化。 反序列化&#xff1a;从字符串到数据类型的过程。 2、序列化的目的&#xff1a; 1、以某种存储形式使自定义的数据持…

servlet+Mysql实现的校园论坛管理系统(功能包含登录,首页帖子查看、发帖、个人帖子删除编辑、帖子评论回复、用户管理等)

博客目录servletMysql实现的校园论坛管理系统实现功能截图系统功能使用技术代码完整源码servletMysql实现的校园论坛管理系统 本系统是一个简单的校园论坛系统&#xff0c;学生可以在线发帖并进行帖子评论回复&#xff0c;同同时管理员可以对用户进行管理。 (文末查看完整源码…

win11: cmake+glfw+imgui

下载源码&#xff1a;imgui github地址 将需要的文件拖拽入项目外部库的imgui文件夹 backends文件夹里选择与环境适配的文件&#xff0c;我这里用了glfw和opengl3 目录结构&#xff1a; CMakeLists.txt cmake_minimum_required(VERSION 3.24) project(proforlearn) set(CM…

基于java+springmvc+mybatis+jsp+mysql的实验室计算机故障报修系统

项目介绍 本系统采用java语言开发&#xff0c;后端采用ssm框架&#xff0c;前端采用jsp技术&#xff0c;数据库采用mysql进行数据存储。 前端页面&#xff1a; 功能&#xff1a;首页、设备信息、公告资讯、个人中心、后台管理、联系客服 管理员后台页面&#xff1a; 功能&…

电子学会2020年12月青少年软件编程(图形化)等级考试试卷(二级)答案解析

目录 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题【该题由测评师线下评分】&#xff08;共2题&#xff0c;共30分&#xff09; 青少年软件…

业务流程监控:让多维度监控有了灵魂

需求 《可视化业务流程监控&#xff0c;是解决方案更是运维之道&#xff01;》一文让我们知道可以借助Grafana 两个插件&#xff1a;Diagram、FlowCharting&#xff0c;满足我们对于图形数据业务流程的可视化监控&#xff0c;但是在使用前需要我们做好以下两点工作&#xff1a…

修复自定义标题word题注错误:错误,文档中没有指定样式的文字以及编号无法随章节变化问题

一、单个修复指定章节号 假设采用自定义样式“毕业”&#xff0c;如果出现类似提示&#xff0c;可以具体操作如下&#xff1a; 光标定位与错误题注的位置&#xff0c;按shift F9 {STYLEREF 1 \s} - 2 将内容修改为 图 {STYLEREF "毕业" \s} - 2 右击&#xff0c;更…

被人叫做砖家模拟器的ChatGPT,它真的靠谱吗?

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 前言 最近很热门的聊天机器人ChatGPT&#xff0c;据说五天时间用户就破了百万&#xff0c;这几天在体验过程中发现了一些问题&#xff0c;今天我就以下列几个方面来跟大家简单的交流下ChatGPT。 ChatGPT怎么注册国…

【博客555】prometheus的step,durations,rate interval,scrape interval对数据查询结果的影响

prometheus的step&#xff0c;durations&#xff0c;rate interval&#xff0c;scrape interval对数据查询结果的影响 1、场景&#xff1a;在查询prometheus数据时出现很多诡异的现象 1、为什么同样的查询语句在不同的时间点查询&#xff0c;对过去某一时刻的数据展示却不一样…

网络拓扑配置案例练习(VRRP,浮动路由,DCHP,三层交换机配置)

网络拓扑配置案例网络拓扑配置案例练习网络拓扑需求描述具体操作命令交换机创建vlan&#xff0c;配置access、trunk口&#xff0c;划分vlanvrrp配置路由配置验证vrrp和浮动路由DHCP配置总结网络拓扑配置案例练习 在这篇文章中将记录网络的常见配置&#xff1a;VRRP&#xff0c…

vue-cli和vue有什么区别

目录 1、什么是 Vue&#xff1f; 2、什么是vue-cli&#xff1f; 3、区别和关联 &#x1f449; 区别 &#x1f449; 关联 &#x1f340; 扩展知识 “vue-cli”和vue的区别&#xff1a;vue是“vue.js”的简称&#xff0c;是一个成熟的用于构建用户界面的JavaScript渐进式框…

「Docker学习系列教程」9-Docker容器数据卷介绍

通过前面8篇文章的学习&#xff0c;我们已经学会了docker的安装、docker常用的命令已经docker镜像修改后提交的远程镜像仓库及提交到公司的私服仓库中。接下来&#xff0c;我们再来学学Docker另外一个重要的东西-容器数据卷。 我们先来看看一个场景&#xff1a;我们有多个dock…