在本博客中,你将学到的知识:
(1):反汇编代码的分析;
(2):连接器脚本的分析;
(3):运行地址和装载地址的概念和联系,以及什么时候时候才允许实际运行地址和指定运行地址不一样的情况出现;
(4):位置无关代码编写涉及到的指令。
(5):什么时候不允许编写位置无关代码,或者说怎么编写位置无关码
首先,对反汇编代码的分析:
汇编地址是:
.text
.global _start
_start:
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
bl clock_init @ 设置MPLL,改变FCLK、HCLK、PCLK
bl memsetup @ 设置存储控制器以使用SDRAM
bl copy_steppingstone_to_sdram @ 复制代码到SDRAM中
ldr pc, =on_sdram @ 跳到SDRAM中继续执行
on_sdram:
ldr sp, =0x34000000 @ 设置栈指针
ldr lr, =halt_loop @ 设置返回地址
ldr pc, =main @ 调用main函数
halt_loop:
b halt_loop
其对应的反汇编代码:
Disassembly of section .text:
30000000 <_start>: //代码执行的入口
程序运行时应该处在的位置 指令的机器码 对应的汇编指令
30000000: e3a0da01 mov sp, #4096 ; 0x1000
以 bl disable_watch_dog 这条汇编指令为例, 根据arm的体系结构规定,pc的值,等于当前执行指令的地址+8 即bl指令相当于PC(new)= ( PC(now)+8 )+偏移量
以 bl disable_watch_dog 为例偏移量的计算方法:偏移量=0x30000034-0x30000004=0x28 ,所以pc=(0x30000004+8)+0x28=0x30000034,即 disable_watch_dog的地址处
30000004: eb00000a bl 30000034 <disable_watch_dog>
下面我们来分析对应的机器码eb00000a ,根据bl指令的编码格式:
30000008: eb00000d bl 30000044 <clock_init>
3000000c: eb000026 bl 300000ac <memsetup>
30000010: eb00003f bl 30000114 <copy_steppingstone_to_sdram>
30000014: e59ff00c ldr pc, [pc, #12] ; 30000028 <halt_loop+0x4>
。。。。。。。。。
走到这里我们已经学会分析反汇编代码了,在这个程序里,我们知道,这个程序运行时的地址应该是从0x30000000开始,然而从这个位置开始是谁指定的呢?:
连接器脚本
SECTIONS { ;指定以段方式存储
. = 0x30000000; ;指定代码运行的开始位置
.text : { *(.text) } ;指定代码段
.rodata ALIGN(4) : {*(.rodata)} ;指定只读数据段
.data ALIGN(4) : { *(.data) } ;指定读/写数据段
.bss ALIGN(4) : { *(.bss) *(COMMON) };指定bss段
}
补充:有时候
看个连接脚本中的一段 first 0x30000000 : AT(0){main.o}
如果程序是烧到nand flash中。这句话里面的意思就是main.o烧到nand flash 中从0地址(当然也可以是其他数)开始的地方,但他的运行地址是在 从0x30000000地址开始的地方,这里0x30000000就是指定的程序运行时”应该“所在的位置, AT(0),指定程序装载时所在的位置。对于本程序在没有执行完 copy_steppingstone_to_sdram之前,程序实际运行的地址是从0开始的,而不是0x30000000,然而程序还是可以正常运行的,这是因为在没copy_steppingstone_to_sdram之前使用的都是相对跳转指令(eg,B,BL 等),这些指令的运行, 即计算机不管当前pc指针是多少,他执行的是相对于当前位置的跳转,让PC根据偏移量去执行程序,而不是使用绝对地址。从而c在本例中opy_steppingstone_to_sdram之前的代码都是与(指定运行)地址位置无关的代码,简称:位置无关代码。
然而,那些指令编写的代码是位置无关代码,那些指令又是位置相关代码呢??
位置无关代码,即该段代码无论放在内存的哪个地址,都能正确运行。究其原因,是因为代码里没有使用绝对地址,都是相对地址。
总结:
运行地址:也叫链接地址,是程序定位的绝对地址,即在编译连接时确定的地址。如果程序中有位置相关指令,程序在运行时,程序必须在运行地址上。
加载地址:程序放置的位置。
位置无关的写法:
(1) B指令
B指令接受一个相对地址,因此在汇编里用B跳转到一个标号时,实际编译的结果是一个相对跳转。
相对地址有个范围限制,即目标不能太远,一般目标放在同一个文件里是肯定可以的。
_start:
b _reset
_reset:
...
(2) BL
BL用于调用函数,也是一个相对跳转
(3) ADR
获取标号的地址,在编译时会使用PC+偏移的方式得到该位置的地址。例如,当TEXT_BASE是0时
SMRDATA可能被放在0x100的位置,当TEXT_BASE为0x30000000时放在0x30000100的位置。使用ADR
总能获取正确的位置,与程序的加载地址无关。
ADR R0, SMRDATA
SMRDATA:
.word 0x22111120
.word 0x00002F50
.word 0x00000700
(相应的, LDR Rn, =LABEL是位置相关的)
(4) LDR
当加标号时,LDR可以用于伪指令,也可以真指令。
真指令: (标号前不加=号,表示取标号处的值)
LDR R0, SDRDATA
实际被编译为LDR R0, [PC, #NN],其中NN是目标的相对距离
伪指令: (标号前加=号,取标号的地址)
LDR R0, = SDRDATA
实际编译的时候的时候,会在某位置存处SDRDATA的值,然后用一个LDR取出来。
显然,用LDR时,加不加=号有很大区别。
无=号:取该标号处的值,位置无关
有=号:取该标号的地址,位置相关
要想编写代码无关的程序。除了要使用B、BL跳转指令外,还要在C语言函数中,避免使用全局变量和静态变量
补充:这里增加一个连接器脚本文件的程序,以便学习编写一个好的连接器脚本:
ENTRY(_start)
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
.= BOOTADDR ; bootloader的开始地址/
.= ALIGN(4); 代码以4字节对齐
.text :;指定代码段
{
cpu/arch/start.o (.text) ; bootloader中的text段
*(.text) ;其它text段
}
.= ALIGN(4)
.rodata :{*(.rodata)};指定只读数据段
.= ALIGN(4);
.data :{*(.data)};指定读/写数据段
.= ALIGN(4);
__bss_start =.; 把__bss_start赋值为当前位置,即bss段的开始位置
.bss :{*(.bss)}; 指定bss段
_end =.; 把_end赋值为当前位置,即bss段的结束位置
}
未经本人同意,本文拒绝转载。