链接地址:链接脚本中指定的,编译器使用,体现在反汇编代码中,
运行地址:PC(程序计数器)中的地址。
若程序的运行地址和链接地址不一样,需要用到位置无关码,位置相关码,在去网上学习相关概念的过程中,记录了下来。
首先需要会看反汇编的代码
从左到右面的依次是链接地址,机器码,机器码对应的汇编代码(反汇编),注释。
然后学习位置无关码和位置相关码,
(此处引用)
00000000 30000000 <_start>:
30000000: e3a0da01 mov sp, #4096 ; 0x1000
30000004: eb00000a bl 30000034 <disable_watch_dog>
30000008: eb00000d bl 30000044 <clock_init>
3000000c: eb000026 bl 300000ac <memsetup>
30000010: eb000040 bl 30000118 <copy_steppingstone_to_sdram>
30000014: e59ff00c ldr pc, [pc, #12] ; 30000028 <.text+0x28>
30000018 <on_sdram>:
30000018: e3a0d30d mov sp, #872415232 ; 0x34000000
3000001c: e59fe008 ldr lr, [pc, #8] ; 3000002c <.text+0x2c>
30000020: e59ff008 ldr pc, [pc, #8] ; 30000030 <.text+0x30>
30000024 <halt_loop>:
30000024: eafffffe b 30000024 <halt_loop>
30000028: 30000018 andcc r0, r0, r8, lsl r0
3000002c: 30000024 andcc r0, r0, r4, lsr #32
30000030: 30000200 andcc r0, r0, r0, lsl #4
00000034 30000034 <disable_watch_dog>: ... ...
从反汇编中可以看出当执行ldr pc, =on_sdram 时的反汇编是 ldr pc, [pc, #12] ; 相当于pc=*(pc+12)=30000018,此时的*(pc+12)是指的pc+12地址所指的地址,所以无论pc怎么变都是指的30000018这个常量来执行on_sdram,属于绝对转移。
在这个例子中我认识到链接地址也是放到存储器中,但若运行地址为0,在存储器中程序也是从00000000到00000034依次读取的吗,但这样存储是在不算入链接地址占用的存储空间。
就是说烧写程序中带有链接地址,但好像用PC读取指令是无视链接地址占用的存储空间,我不理解在运行程序时链接地址是如何使用的,比如指令执行的顺序:取址,译码,执行,一开始取址PC为00000000 该地址的机器码对应链接地址30000000处的机器码,很明显取址忽略了链接地址。但运行到执行ldr pc, =on_sdram 时,“*(pc+12)是指的pc+12地址所指的地址”又用到了链接地址。
那么问题来了,在运行地址和链接地址下如何解释指令的执行顺序,假设运行地址是00000000
考虑实际应用中,即链接脚本中指定的链接地址,是重定位使用,即在自己程序烧写完成后,先应该有一段引导程序,引导程序将烧写的程序重定位到链接地址处。
还是不对,通过这个作者(乱世半仙)的介绍,并没有引导程序,
PC(程序计数器)刚开始为地址00000000,该地址的机器码是不是这个呢?
第二个伪指令表示将标号_start的值给到寄存器r1,即立即数传值,此时_start标号的值为0x30000000,adr如何获得运行地址传给r0?
经过又一番挣扎,了解了literal pool (文字池),ARM汇编语言代码节中的文字池是什么-电子发烧友网
那为什么要使用文字池呢?当想要在一条指令中使用一个 4字节长度的常量数据(这个数据可能是内存地址,可能是数字常量)的时候,由于ARM指令集是定长的(ARM指令4字节或Thumb指令2字节),就无法把这个4字节的常量数据编码在一条编译后的指令中。此时,ARM编译器(编译C源程序)/汇编器(编译汇编程序) 就会在代码节中分配一块内存,并把这个4字节的数据常量保存起来,之后,再使用一条指令把这个4 字节的数字常量加载到寄存器中参与运算。 在写C程序中,文字池的分配是由编译器在编译时自行分配安排的,但是,在写汇编程序时,开发者可以自己进行文字池的分配,当然如果没有自己分配汇编器也会代劳。
比如:LDR R0, =0X12345678,会将0X12345678存入文字池
再去理解上面的加载运行地址和链接地址到寄存器,标号_start(0x30000000)代表的链接地址会存放到文字池中。
这样看来反汇编程序中显示的链接地址并不是都会烧写进去,烧写的只是标号的链接地址,且烧写的位置在一块独立内存(文字池)中,不和指令一起存放。
t通过对adr r0,_start和ldr r1,=_start两条汇编指令的理解我意识到链接地址是给汇编器看和用的,链接地址体现在标号和指令上,比如
adr r0,_start:PC+偏移 偏移量是根据链接地址确定的即指令的链接地址和标号的链接地址,这一步应该在汇编器实现,汇编器用add/sub PC #off_set指令替换adr伪指令,
ldr r1,=_start :将标号_start的值即链接地址传给寄存器,标号的链接地址存放到了文字池。但我不太理解作者的这句话
“这里取得的是标号 _start 的绝对地址,这个绝对地址(链接地址)是在链接的时候确定的。它要占用 2 个 32bit的空间,一条是指令,另一条是文字池中存放_start 的绝对地址”
这里的 2 个 32bit的空间 如何理解呢
由于ARM指令集是定长的(ARM指令4字节或Thumb指令2字节),就无法把这个4字节的常量数据编码在一条编译后的指令中。此时,ARM编译器(编译C源程序)/汇编器(编译汇编程序) 就会在代码节中分配一块内存,并把这个4字节的数据常量保存起来,之后,再使用一条指令把这个4 字节的数字常量加载到寄存器中参与运算。于是就需要2个32bit的空间了。
//2022-3-30//
经过这段时间的学习,对之前的困顿有了新的理解,对于伪指令ADR,通过汇编反汇编可以看到会用sub和add指令替换,即目标距离当前运行指令的offset,属于位置无关跳转指令;对于伪指令LDR Rn,=label;若label不能通过立即数表示,会在某一地址存放label,然后通过读内存指令读取label的值。
反汇编最左侧的链接地址并不会烧写进flash中,我觉得链接地址只是给程序提供了绝对地址的引用。
但又碰到了新的问题:
连接脚本中会有_bss_end=.这样的用法,然后C源文件通过&_bss_end实现对该符号值的引用,这里涉及了编译知识,好比定义一个全局整型变量int a=123,假设a的地址为0x12345678;汇编代码中引用a的值应该是根据链接地址,比如main函数实现a=a-1,汇编代码应该这样实现,上电后先对data进行初始化,
LDR r0 ,[12345678]
sub r0,r0,#1;
STR r0,[12345678]
对符号_bss_end的引用在汇编代码中又是怎样的呢?
作者:喜欢吃猫的鱼
出处: http://www.cnblogs.com/lifexy/p/7117345.html