tiny4412裸机程序之位置无关码

2020-03-25来源: eefocus关键字:tiny4412  裸机程序  位置无关码

在上篇文章中,将代码的.text、.data、.rodata段重定位到了0x02026400位置处去执行点亮LED的操作。但是,在链接脚本里指定的链接地址是0x02026400,那么为什么在重定位之前的代码能够在0x02023400地址处可以开始执行?就是因为前面使用的是位置无关码。写介绍介个概念:


链接地址:链接脚本里指定的,理论上程序运行时所处的地址。在编译时,编译器会根据链接地址来翻译位置有关码。


加载地址(运行地址):程序运行时,实际所处的地址。


位置无关码,位置有关码,是相对于一条指令的正常目的来说的。比如ldr r0, =xx,它的正常目的是取得标号处的地址,对于这个目的,它是位置有关码,运行的地址不对就获取不到正确的标号地址,其实它无论在哪都是获取的程序加载地址等于链接地址时,标号的地址,如果你就是想要这个值,那么用这条指令是非常正确的,就不用理会什么位置无关码,位置有关码的概念了,这一点非常重要。


因此,当加载地址不等于链接地址时,并不是不可以用位置无关码,而是要看你用位置无关码是否达到了你想要的目的。


位置无关码,依赖于程序当前运行的PC值,进行相对的跳转,导致的结果就是,无论代码在哪,总能达到指令的正常目的,因此是位置无关的。


位置有关码,不依赖当前PC值,是绝对跳转,只有程序运行在链接地址处时,才能达到指令的正常目的,因此是位置有关系的。


下面,我们来看常用的汇编指令以及C语言中哪些操作是位置有关码,哪些是位置无关码。


示例1:


借助前面介绍的uart的代码来做这个实验;


start.S文件内容如下:


.text

.global _start

_start:

ldr sp, =0x02027400 //调用C函数之前必须设置栈,栈用于保存运行环境,给局部变量分配空间;

//参考ROM手册P14,我们把栈指向BL2的最上方;

//即:0x02020000(iROM基地址)+5K(iROM代码用)+8K(BL1用)+16K(BL2用)

bl main //跳转到C函数中执行

mov r0, pc

bl printPc

 

halt: //死循环

b halt


首先,设置栈,然后调整到main函数执行,最后将pc的值保存到R0寄存器中,然后调用printPc函数将R0寄存器中的值打印出来。


mian.c的内容如下:


//main函数

int main()

{

//初始化串口操作

uart0_init();

//打印16进制数

puthex(100);

return 0;

}

 

int printPc(unsigned int value)

{

puthex(value);

}


在main()函数中调用了uart0_init()函数初始化串口。然后打印一个100的值;另一个函数printPc函数打印传入的值。


链接脚本如下:


SECTIONS {

. = 0x0;

.text : { *(.text) }

.rodata ALIGN(4) : {*(.rodata)}

.data ALIGN(4) : { *(.data) }

.bss ALIGN(4) : { *(.bss) *(COMMON) }

}

其中,指定的链接地址是0x0,就是期望程序在0x0地址处开始运行。


编译烧写程序,查看输出如下:

第一个打印的数字是100;第二个打印的pc的值;大家可以发现,我们指定的链接地址是0x0,而程序运行在0x02023400地址处开始执行的,也能成功执行,为什么呢?就是因为我们使用的是位置无关码。查看反汇编:

已经在图中做了详细的注释。首先,就是将当前pc值加偏移量12处的值保存sp栈中。然后执行bl跳转到main()函数处执行。注意:bl 18这个指令并不是跳转到18地址处开始执行。bl是一条相对跳转指令,当前PC值加偏移量。其机器码是0xeb000003;我们将链接地址设置为0x02023400,看看这条指令的机器码是多少:

可以看到,将链接地址更改为0x02023400地址处,这个条指令的机器码还是0xeb000003。


可以得出结论:B/BL指令是位置无关码,是根据当前PC值加某个符合距离当前位置的offset值,不管链接地址是多少,此值是固定的。也就是不管链接地址是多少都能正常跳转。


还是看第一幅图片,我们将当前PC的值保存到了R0寄存器中了,然后跳转到printPc()函数执行,打印出的值是0x02023410。


也就是说,打印的值是加载地址,并不是链接地址。链接地址是期望程序运行的地址。加载地址是程序实际运行的地址。


结论:


1.程序的链接地址和加载地址不是一回事。


2.在重定位代码之前应该使用位置无关码,位置无关码的操作时根据当前PC值加上偏移量在进行执行。


怎么编写位置无关码:


1.使用相对跳转指令(B/BL);

2.重定位之前不可以使用绝对地址;不可以访问全局变量和静态变量;

3.重定位之后可以访问全局变量和静态变量;使用ldr pc, =xxx;指令跳转到某个地址开始执行;

4.不可访问有初始值的数组,数组中每个成员是位于栈中的,但是其初始值是保存在.rodata中的;

5.如果出了问题,最根本的办法是看反汇编。


在写位置无关码是不能使用全局变量和有初始值的数组:


全局变量(静态或非静态),如果有初始值则保存.data段中,没有初始值则保存到.bss段中。而取用.data段中的数据是根据链接地址来操作的。


有初始值的数组,数组中的每个变量是位于栈中的,但是其初始值是放在.rodata段中的,其地址是链接时就确定下来了。


本文完毕!

关键字:tiny4412  裸机程序  位置无关码 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic492497.html 本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:ARM指令adr和ldr的区别
下一篇:tiny4412裸机程序——代码重定位

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

tiny4412开发板时钟操作示例
在上一节总我们介绍了《Exynos4412芯片的时钟管理单元》,有了上一节的基础知识我们就可以写程序操作CPU的时钟了。通过操作led来感受时钟速率的变化。本文总共有三个示例,第一个是写一个LED循环点亮的程序;第二个是将iROM中设置的时钟禁止掉,观察LED变化速率;第三个就是设置CPU的时钟速率为1.4GHz,观察LED的变化速率。第一个程序很简单,有两个文件start.S和main.c。其中,start.Sz文件中设置栈,然后跳转到C函数中执行。在main.c中设置LED的亮灭。首先,来看start.S内容:.text.global _start_start: ldr sp, =0x02027400 //设置栈 bl
发表于 2020-03-26
tiny4412开发板icache操作程序
首先,来介绍关于cache的概念。cache的作用:基于程序访问的局限性,在主存和CPU通用寄存器之间设置了一个高速的、容量相对较小的存储器,把正在执行的指令地址附近的一部分指令或数据从主存调入这个存储器,供CPU在一段时间内使用,这对提高程序的运行速度有很大的作用。这个介于主存和CPU之间的高速小容量存储器称作高速缓存存储器(Cache)。启用Cache后,CPU读取数据时,如果Cache中有这个数据的复本则直接返回,否则从主存中读入数据,并存入Cache中,下次再使用(读/写)这个数据时,可以直接使用Cache中的复本。启用Cache后,CPU写数据时有写穿式和回写式两种方式。(1)写穿式(Write Through)任一从CP
发表于 2020-03-25
tiny4412开发板的串口介绍与操作
字符"A"(二进制值为0b1000001)时,TTL/CMOS逻辑电平对应的波形。图2. TTL/CMOS逻辑电平下,传输"A"时的波形UART还有其他功能,比如流量控制等,想深入了解的读者请自行查阅相关资料。Exynos4412的UART特性:Exynos4412中UART,有4个独立的通道,每个通道都可以工作于中断模式或DMA模式,即UART可以发出中断或DMA请求以便在UART、CPU间传输数据。UART由波特率发生器、发送器、接收器和控制逻辑组成。使用系统时钟时,Exynos4412的UART波特率可以达到4Mbps。波特率可以通过编程进行控制。Exynos4412 UART的通道
发表于 2020-03-25
tiny4412开发板的串口介绍与操作
tiny4412裸机程序——代码重定位
。链接地址是程序的链接地址,即程序运行时应该位于的运行地址。编译程序时,可以在链接脚本中指定程序的链接地址。对于tiny4412而言,前面我们已经说过:启动时BL1只会从sd等启动设备中拷贝14K的代码到iRAM中,那么当我们的程序超过14K怎么办?那就需要我们在前14K的代码中将整个程序完完整整地拷贝到LPDDR等其他更大存储空间,然后再跳转到LPDDR中继续运行我们的代码,这个拷贝然后跳转的过程就叫重定位。本章中讲解学习如何重定位,但是并不会涉如何使用到LPDDR,而是简单地将代码从iRAM的0x02023400处拷贝到iRAM的0x02026400处,然后跳转到0x02026400处继续运行我们的代码。程序文件介绍
发表于 2020-03-25
tiny4412开发板GPIO试验
GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平、或者通过它们读入引脚的状态——是高电平还是低电平。三星Exynos4412,它有304个 GPIO,分为GPA0、GPA1、GPB、GPC0、GPC1等共37组。可以通过设置寄存器来确定某个引脚用于输入、输出还是其它特殊功能。比如可以设置GPC0、GPC1作为一般的输入引脚、输出引脚,或者用于AC97、SPDIF、I2C、SPI口。GPIO的操作是所有硬件操作的基础,由此扩展开来可以了解所有硬件的操作,这是底层开发人员必须掌握的。Exynos4412芯片的GPIO寄存器:既然一个引脚可以用于输入
发表于 2020-03-25
tiny4412开发板GPIO试验
c语言多文件 6410 led裸机程序
Isr_Eint11(void); #ifdef __cplusplus}#endif #endif //__GPIO_H__这个gpio.h 可以参照三星的示例程序自己改写,可以试一下,很简单。发现的问题:c语言的宏定义,在gpio.c 中定义的  结构体,宏定义 只能只啊.c文件中使用,通过.h 后不能使用。所以,这个变量和宏定义当要在多个文件中使用时,可以定义在.h 文件中,或是 用include“xx.c”(不推荐!!!)。
发表于 2020-03-09
小广播
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2020 EEWORLD.com.cn, Inc. All rights reserved