一、反汇编

简单代码分析

把以下代码生成的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,可以通过两种办法解决

  1. 重定位.data段
  2. 重定位整个代码数据段
链接脚本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文件非常巨大而要使用链接脚本(使用见上文)

clipboard.png

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

完整代码链接


Kyseng
1 声望3 粉丝

电子爱好者一枚,利用工作空余时间记录一下学习过程