一、反汇编
简单代码分析
把以下代码生成的elf文件使用命令arm-linux-objdump -D led_on.elf > led_on.dis
反汇编
原始文件
.text
.global _start
_start:
ldr r0, =0x56000010
mov r1, #0x00015400
str r1, [r0]
ldr r0, =0x56000014
ldr r1, =0x00000040
str r1, [r0]
halt:
b halt
反汇编文件
led_on_elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e59f0014 ldr r0, [pc, #20] ; 1c <.text+0x1c>
4: e3a01b55 mov r1, #87040 ; 0x15400
8: e5801000 str r1, [r0]
c: e59f000c ldr r0, [pc, #12] ; 20 <.text+0x20>
10: e3a01040 mov r1, #64 ; 0x40
14: e5801000 str r1, [r0]
00000018 <halt>:
18: eafffffe b 18 <halt>
1c: 56000010 undefined
20: 56000014 undefined
sublime可以安装HexViewer查看bin二进制文件HexViewer的使用
二进制码
1400 9fe5 551b a0e3 0010 80e5 0c00 9fe5
4010 a0e3 0010 80e5 feff ffea 1000 0056
1400 0056
-
ldr r0, [pc, #20]
:表示PC的地址(当前地址也就是第一列值换算成十进制 + 十进制8)加上#20
(十进制20)地址0x1c
上的数据0x56000010
存入r0 - 可以看出开头的1列0: 4: ..这些表示机器码所在的起始地址是第几个字节,一行指令是
4
字节32
位,第二列表示的是储存在内存中实际的字节数据(机器码),第三列表示的是字节数据对应翻译成的汇编指令 - 第一列最大值为0x20,0x20处存在4字节数据所以一直到0x23,所以bin程序一共有36字节(0x0~0x23)
二、代码段重定位
- Norflash可以直接读但是无法直接写入
- Norflash启动的栈设置与Nand启动不同
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
- 在
arm-linux-ld -Ttext 0x0 $^ -o main.elf
中加入-Tdata 位置
可以简单指定.data段位置 - 从Nor启动片内SRAM起始地址为
0x4000_0000
,从Nand启动片内SRAM起始地址为0x0
名称 | 中文名称 | 存放类型 | 保存位置 |
---|---|---|---|
.text | 代码段 | 存放代码部分 | bin文件中 |
.data | 数据段 | 存放全局变量 | bin文件中 |
.rodata | 只读数据段 | 存放const只读变量 | bin文件中 |
.bss | bss段 | 存放无初始值或初始值为0的全局变量 | 不在bin文件中 |
.comment | 注释段 | 存放命令ascii码 | 不在bin文件中 |
关于指定.data段
因为如果data段存在于norflash中全局变量无法修改,所以我们需要把全局变量放入SDRAM中。
可以通过在Makefile中加入-Tdata 位置
简单的指定data段位置但是会存在数据段和代码段之间有一个巨大的间隙空间最终导致bin文件可能非常的巨大,例如0~0x3000_0000,可以通过两种办法解决
- 重定位.data段
- 重定位整个代码数据段
链接脚本lds文件
简单示例
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
.data 0x30000000 : AT(0x800) {*(.data)}
.bss : {*(.bss) *(.COMMON)}
}
- AT(0x800)指定data实际位置为0x800,声明位置为0x30000000,需要把值真正拷贝到0x3000000处CPU才可以访问到,因为CPU只会去0x30000000处找变量,所以要重定位把值拷贝到0x30000000,注意这里需要初始化sdram
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]
通用智能动态重定位示例
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
.data 0x30000000 : AT(0x800) {
data_load_addr = LOADADDR(.data)
= .;
*(.data)
data_end = .;
}
.bss : {*(.bss) *(.COMMON)}
}
-
LOADADDR(.data)
:data段在bin文件的地址 - data_start :CPU访问的开始地址
- data_end:CPU访问的结束地址
重定位代码(低效率)
ldr r1, =data_load_addr
ldr r2, =data_start
ldr r3, =data_end
cpy:
ldrb r4, [r1] #从data_load_addr拷贝1个字节 效率低
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy #不等 继续copy
重定位代码(高效率)
ldr r1, =data_load_addr
ldr r2, =data_start
ldr r3, =data_end
cpy:
ldr r4, [r1] #从data_load_addr拷贝1个字节 效率低
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy #不等 继续copy
三、代码实例分析
1. <001>验证norflash内数据无法通过str
命令直接修改
- Makefile关键代码
arm-linux-ld -Ttext 0x0 -Tdata 0x800 $^ -o main.elf
指定data段位置为0x800 - 反汇编dis文件可以看出
SendChar
是从0x800处取值的,而如果是nor启动0x800是位于norflash内部,因为norflash无法直接修改,这就是程序循环输出同一个值得原因
main反汇编部分代码
00000074 <main>:
74: e1a0c00d mov ip, sp
78: e92dd800 stmdb sp!, {fp, ip, lr, pc}
7c: e24cb004 sub fp, ip, #4 ; 0x4
80: eb000029 bl 12c <Init_UART1>
84: e59f0030 ldr r0, [pc, #48] ; bc <.text+0xbc>
88: eb000079 bl 274 <SendString>
8c: e59f302c ldr r3, [pc, #44] ; c0 <.text+0xc0>
90: e5d33000 ldrb r3, [r3]
94: e1a00003 mov r0, r3
98: eb00004f bl 1dc <SendChar>
9c: e59f201c ldr r2, [pc, #28] ; c0 <.text+0xc0>
a0: e59f3018 ldr r3, [pc, #24] ; c0 <.text+0xc0>
a4: e5d33000 ldrb r3, [r3]
a8: e2833001 add r3, r3, #1 ; 0x1
ac: e5c23000 strb r3, [r2]
b0: e3a00ffa mov r0, #1000 ; 0x3e8
b4: eb000002 bl c4 <delay>
b8: eafffff3 b 8c <main+0x18>
bc: 000002bc streqh r0, [r0], -ip
c0: 00000800 andeq r0, r0, r0, lsl #16
2. <002>使用-Tdata重定位data段到sdram内(仅做测试)
arm-linux-ld -Ttext 0x0 -Tdata 0x30000000 $^ -o main.elf
- 不能通过
-Tdata
直接指定data段位置,会导致bin文件非常巨大而要使用链接脚本(使用见上文)
3. <003>使用简单链接脚本进行重定位
- 需要自己实现copy函数,把data段copy到指定的位置
- 需要先进行sdram的初始化才可以使用
main反汇编部分代码
00000074 <main>:
74: e1a0c00d mov ip, sp
78: e92dd800 stmdb sp!, {fp, ip, lr, pc}
7c: e24cb004 sub fp, ip, #4 ; 0x4
80: eb000029 bl 12c <Init_UART1>
84: e59f0030 ldr r0, [pc, #48] ; bc <.text+0xbc>
88: eb000079 bl 274 <SendString>
8c: e59f302c ldr r3, [pc, #44] ; c0 <.text+0xc0>
90: e5d33000 ldrb r3, [r3]
94: e1a00003 mov r0, r3
98: eb00004f bl 1dc <SendChar>
9c: e59f201c ldr r2, [pc, #28] ; c0 <.text+0xc0>
a0: e59f3018 ldr r3, [pc, #24] ; c0 <.text+0xc0>
a4: e5d33000 ldrb r3, [r3]
a8: e2833001 add r3, r3, #1 ; 0x1
ac: e5c23000 strb r3, [r2]
b0: e3a00ffa mov r0, #1000 ; 0x3e8
b4: eb000002 bl c4 <delay>
b8: eafffff3 b 8c <main+0x18>
bc: 000002bc streqh r0, [r0], -ip
c0: 30000000 tsteq r0, #0 ; 0x0
从反汇编部分可以看到cpu会从0x30000000处取值,所以要把data段从0x800(实际保存位置)复制到0x30000000处,也就是重定位
start.S重定位部分代码
//****重定位****
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]
4. <004>使用通用链接脚本进行重定位(可以添加任意个全局变量)
- 全局变量个地址范围是动态变更的
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
.data 0x30000000 : AT(0x800) {
data_load_addr = LOADADDR(.data);
data_start = .;
*(.data)
data_end = .;
}
.bss : {*(.bss) *(.COMMON)}
}
-
LOADADDR(.data)
:data段在bin文件的地址,这里是0x800 - data_start :CPU访问的开始地址,这里是0x30000000
- data_end:CPU访问的结束地址
start.S重定位部分代码
ldr r1, =data_load_addr
ldr r2, =data_start
ldr r3, =data_end
cpy:
ldrb r4, [r1] //从data_load_addr拷贝1个字节 效率低
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy //不等 继续copy
5. <005>在bss段中未初始化或初值为0的全局变量,程序不清零的话是无效的(仅做测试)
打印HEX函数
void PrintHex(unsigned int val){
int i;
unsigned char arr[8];
for(i=0; i<8; i++){
arr[i] = val & 0xf;
val >>= 4;
}
SendString("0x");
for(i=7; i>=0; i--){
if(arr[i] >= 0 && arr[i] <= 9)
SendChar(arr[i] + '0');//换算成ASCII码
else if(arr[i] >= 0xA && arr[i] <= 0XF)
SendChar(arr[i] - 0xA + 'A');//换算成ASCII码
}
}
输出g_A = 0x51E23333
6. <006>在005基础上清理了bss段,需要修改链接脚本
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
.data 0x30000000 : AT(0x800) {
data_load_addr = LOADADDR(.data);
data_start = .;
*(.data)
data_end = .;
}
bss_start = .;
.bss : {*(.bss) *(.COMMON)}
bss_end = .;
}
//清除bss段
ldr r1, =bss_start
ldr r2, =bss_end
mov r3, #0
clean:
strb r3, [r1]
add r1, r1, #1
cmp r1, r2
bne clean
7. <007>改进代码拷贝速度
- 修改ldrb,strb为ldr, str命令是以4字节操作
- 链接脚本修改bss段4字节对齐
//****重定位****
ldr r1, =data_load_addr
ldr r2, =data_start
ldr r3, =data_end
cpy:
ldr r4, [r1] //从data_load_addr拷贝1个字节 效率低
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy //小于
//清除bss段
ldr r1, =bss_start
ldr r2, =bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
.data 0x30000000 : AT(0x800) {
data_load_addr = LOADADDR(.data);
. = ALIGN(4);
data_start = .;
*(.data)
data_end = .;
}
. = ALIGN(4);
bss_start = .;
.bss : {*(.bss) *(.COMMON)}
bss_end = .;
}
. = ALIGN(4);
向4对齐 往数值大的方向
8. <008>代码整段重定位和位置无关码
新lds脚本
SECTIONS
{
. = 0x30000000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
copy整段代码start.S部分
//****重定位****
mov r1, #0
ldr r2, =_start //第一条指令地址
ldr r3, =__bss_start //bss段起始地址
cpy:
ldr r4, [r1] //从data_load_addr拷贝1个字节 效率低
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy //小于
//清除bss段
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
虽然链接脚本里面指定了runtime address但是可以看做是和真实地址之间的一个映射,实际还是从0地址开始执行,可以看做是以runtime address做为基地址执行,bl
跳转指令都是根据当前pc值+固定偏移地址做相对跳转,重定位之后需要使用ldr pc, =main
做绝对跳转,可以观察到在sdram运行速度快于norflash
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。