uboot2012(一)分析重定位


title: uboot2012(一)分析重定位
date: 2019/02/23 21:53:21
toc: true
---

引入

关于移植,搜索关键英文词语portting

移植简单的介绍在readme中,手册是它的使用帮助

代码仓库地址 02-uboot重定位加入自己的代码

环境配置

这里使用编译工具arm-linux-gcc-4.3.2.tar,具体安装参考更换gcc工具链.md

编译体验

 make smdk2410_config
 make

入口查找

我们可以从顶层Makefile开始分析,也可以直接看到编译结果,查看最后的链接过程如下,搜索arm-linux-

arm-linux-ld  -pie -T u-boot.lds -Bstatic -Ttext 0x0 $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group api/libapi.o 

可以看到这里指定了链接脚本以及代码段的地址在-Ttext 0,第一个文件是arch/arm/cpu/arm920t/start.o

代码分析

简单的流程如下:

  1. set the cpu to SVC32 mode
  2. close watchdog
  3. mask all IRQs by setting all bits in the INTMR
  4. FCLK:HCLK:PCLK = 1:2:4
  5. cpu_init_crit
    1. flush v4 I/D caches
    2. disable MMU stuff and caches
    3. lowlevel_init
      1. memory control configuration [set sdram]
    4. set sp [Set stackpointer in internal RAM]
    5. board_init_f
      1. init_sequence
        1. board_early_init_f
        2. set clock
        3. gpio
      2. ----....---
    6. relocate_code
    7. copy_loop 复制代码
    8. fixloop 修改全局变量等数据段
    9. clear_bss

board_init_f

这里有一个关键的变量gd,可以看到文件头上面有个宏DECLARE_GLOBAL_DATA_PTR,定义如下

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

也就是定义gd为寄存器变量r8,可以看到编译输出有这么一句,表示编译器不使用r8寄存器

-fno-common -ffixed-r8

pie

新版本的uboot是在初始化完执行完board_init_f后进行重定位代码,但是代码本身的链接脚本就是在0地址的,那么它为什么需要再重定位代码?

我们可以看到链接命令输出如下

arm-linux-ld  -pie -T u-boot.lds -Bstatic -Ttext 0x0 $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group api/libapi.o 

搜索下pie相关的内容,就是说创建位置无关可执行程序

$ arm-linux-ld --help | grep "pie"
  -pie, --pic-executable      Create a position independent executable

内存分布分析

我们从进入c函数的地方开始分析,前面的就不看了,c函数跳转首先需要这是sp

SP设置

到设置堆栈的代码为入口分析

mark

/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
    ldr r0,=0x00000000
    bl  board_init_f    

这里设置sp=CONFIG_SYS_INIT_SP_ADDR,我们可以查看反汇编得到实际的值是0x30000f80

00000098 <call_board_init_f>:
      98:   e59fd3d8    ldr sp, [pc, #984]  ; 478 <fiq+0x58>
      9c:   e3cdd007    bic sp, sp, #7  ; 0x7
      a0:   e3a00000    mov r0, #0  ; 0x0
      a4:   eb0007f1    bl  2070 <board_init_f>
      
478:    30000f80    .word   0x30000f80   

接下来仔细看下代码,这里有个宏DEFINE,目的是将后面的参数1作为一个宏传递给汇编文件,值是第二个参数,这里的值就是global_data也就是gd_t向上16对齐

#define PHYS_SDRAM_1        0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE   0x04000000 /* 64 MB */
#define CONFIG_SYS_SDRAM_BASE   PHYS_SDRAM_1
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x1000 - \
                GENERATED_GBL_DATA_SIZE)

lib\asm-offsets.c
    DEFINE(GENERATED_GBL_DATA_SIZE,
        (sizeof(struct global_data) + 15) & ~15);
    
typedef struct  global_data {
    bd_t        *bd;
    unsigned long   flags;
    unsigned long   baudrate;
    unsigned long   have_console;   /* serial_init() was called */

    unsigned long   env_addr;   /* Address  of Environment struct */
    unsigned long   env_valid;  /* Checksum of Environment valid? */
    unsigned long   fb_base;    /* base address of frame buffer */

#ifdef CONFIG_ARM
    /* "static data" needed by most of timer.c on ARM platforms */
    unsigned long   timer_rate_hz;
    unsigned long   tbl;
    unsigned long   tbu;
    unsigned long long  timer_reset_value;
    unsigned long   lastinc;
#endif
    unsigned long   relocaddr;  /* Start address of U-Boot in RAM */
    phys_size_t ram_size;   /* RAM size */
    unsigned long   mon_len;    /* monitor len */
    unsigned long   irq_sp;     /* irq stack pointer */
    unsigned long   start_addr_sp;  /* start_addr_stackpointer */
    unsigned long   reloc_off;
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
    unsigned long   tlb_addr;
#endif
    const void  *fdt_blob;  /* Our device tree, NULL if none */
    void        **jt;       /* jump table */
    char        env_buf[32];    /* buffer for getenv() before reloc. */
} gd_t;   

刚开始的时候,没有考虑到CONFIG_ARM,计算出来的值与实际对不上,后来仔细搜索发现是定义了的

# grep  -nR "CONFIG_ARM" ./
./arch/arm/config.mk:34:PLATFORM_CPPFLAGS += -DCONFIG_ARM -D__ARM__
./include/autoconf.mk:126:CONFIG_ARM=y

这里理论计算的就是22*4+32=120===+15&-15=128

最终sp=0x30000000+0x1000-128=0x30000F80与汇编结果是一致的

接着我们仔细看下这个来自内核的宏DEFINE,它是嵌入汇编,实际上他是编译不了的,因为没有->指令,实际上,这个asm-offsets.c文件根本不是用来运行的,只是在编译的时候,用它生成一个asm-offsets.s文件,然后Kbuild会处理这个asm-offsets.s文件,生成asm-offsets.h文件。这个头文件最终被汇编文件引用,其中定义的变量最终得到应用。
如果要在自己的内核模块中使用这些变量,引用这个头文件即可#include <asm/asm-offsets.h>. 摘自内核黑科技之DEFINE宏

include\linux\kbuild.h
#define DEFINE(sym, val) \
    asm volatile("\n->" #sym " %0 " #val : : "i" (val))

后来在编译结果中搜索CONFIG_SYS_INIT_SP_ADDR

./include/generated/generic-asm-offsets.h:10:#define GENERATED_GBL_DATA_SIZE (128) /* (sizeof(struct global_data) + 15) & ~15 */

其实这个是我看了说明之后才去搜索的,这个DEFINE就是根据lib/asm-offsets.c生成asm-offsets.h

$ cat asm-offsets.h
#ifndef DO_DEPS_ONLY

#include <generated/generic-asm-offsets.h>
/* #include <generated/asm-offsets.h> */

#endif

最终的宏也就在generated/generic-asm-offsets.h中了

#define GENERATED_GBL_DATA_SIZE (128) /* (sizeof(struct global_data) + 15) & ~15 */
#define GENERATED_BD_INFO_SIZE (48) /* (sizeof(struct bd_info) + 15) & ~15 */

接下来就是初始化以及内存分布设置

board_init_f

简单的代码解释如下

call_board_init_f:
    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance 清除低3位 */
    ldr r0,=0x00000000
    bl  board_init_f

board_init_f    
    /*这里指的就是栈顶 调用之前是 ldr    sp, =(CONFIG_SYS_INIT_SP_ADDR) bic  sp, sp, #7  */
    gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
    
    
    gd->mon_len = _bss_end_ofs; /**bss_end-start 就是整个程序的大小/
    
    /*一些初始化操作*/
    *init_sequence()
    
    addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;    //这个ram_size在dram_init 初始化为64M,CONFIG_SYS_SDRAM_BASE 也就是sdram基地址
                                                    //addr 就是ram最高地址 0x04000000+0x3000,0000

    /* reserve TLB table */                         //保留4kb给tlb后向下64kb对齐,也就是消除0xffff 0x3400,0000-0x4000=0x33ffc,0000
    addr -= (4096 * 4);                             // 对齐后就是 0x33ff,0000                                                    
    /* round down to next 64 kB limit */
    addr &= ~(0x10000 - 1);
    gd->tlb_addr = addr;


    /*                                              //代码段,gd->mon_len=_bss_end_ofs
     * reserve memory for U-Boot code, data & bss
     * round down to next 4 kB limit
     */
    addr -= gd->mon_len;
    addr &= ~(4096 - 1);
    
    /*
     * reserve memory for malloc() arena
     */
    addr_sp = addr - TOTAL_MALLOC_LEN;
    
    /*
     * (permanently) allocate a Board Info struct
     * and a permanent copy of the "global" data
     */
    addr_sp -= sizeof (bd_t);
    bd = (bd_t *) addr_sp;
    gd->bd = bd;    

    addr_sp -= sizeof (gd_t);
    id = (gd_t *) addr_sp;

    /* leave 3 words for abort-stack    */
    addr_sp -= 12;

    /* 8-byte alignment for ABI compliance */
    addr_sp &= ~0x07;

    gd->relocaddr = addr;
    gd->start_addr_sp = addr_sp;
    gd->reloc_off = addr - _TEXT_BASE;
    memcpy(id, (void *)gd, sizeof(gd_t));
    
    /*这里最后进行重定位代码 */
    relocate_code(addr_sp, id, addr);
    
relocate_code:
    mov r4, r0  /* save addr_sp */
    mov r5, r1  /* save addr of gd */
    mov r6, r2  /* save addr of destination */

int dram_init(void)
{
    /* dram_init must store complete ramsize in gd->ram_size */
    gd->ram_size = PHYS_SDRAM_1_SIZE;  //64M
    return 0;
}

mark

最后的分配内存如下

mark

重定位

uboot的链接地址是0,那么它刚开始在nor上运行是能够读取指令运行的,但是他的变量怎么办呢?

比如我有一个变量在0x100,需要修改,nor上的数据并不能像内存一样修改?那么怎么解决呢?

这里我们假设想要在sdram上运行,那么我们搬运到0x3200,0000上去,那么我们不仅需要搬运代码,还要修改代码也就是说将变量0x100变为0x3200,0100

那么我们怎么知道旧变量的地址0x100,这里就是在链接的时候加入pie选项,会有新的段生成,可以看下lds文件

.rel.dyn : {
  __rel_dyn_start = .;
  *(.rel*)
  __rel_dyn_end = .;
 }
 .dynsym : {
  __dynsym_start = .;
  *(.dynsym)
 }

mark

代码段重定位实现

代码重定位是在board_init_f中调用的

// addr_sp 最后的sp
// id gd结构的位置
// addr 重定位的位置
relocate_code(addr_sp, id, addr)

我们来计算下实际的代码段加bss段的大小

.globl _bss_start_ofs
_bss_start_ofs:
    .word __bss_start - _start

.globl _bss_end_ofs
_bss_end_ofs:
    .word __bss_end__ - _start

.globl _end_ofs
_end_ofs:
    .word _end - _start

// 查看具体的反汇编

00000040 <_TEXT_BASE>:
      40:   00000000    .word   0x00000000

00000044 <_bss_start_ofs>:
      44:   0006b568    .word   0x0006b568

00000048 <_bss_end_ofs>:
      48:   000ae4e0    .word   0x000ae4e0

0000004c <_end_ofs>:
      4c:   000736d8    .word   0x000736d8

可以看到整个的大小是_bss_end_ofs=0x000ae4e0,我们计算下

gd->relocaddr = addr=0x33ff,0000-0x000ae4e0=33F41B20
4k对齐 &~(4096-1)=fff
=33F4,1000

具体的汇编如下

    .globl  relocate_code
relocate_code:
    mov r4, r0  /* save addr_sp */
    mov r5, r1  /* save addr of gd */
    mov r6, r2  /* save addr of destination */

    /*这里设置新的sp*/
    /* Set up the stack                         */
stack_setup:
    mov sp, r4

    adr r0, _start
    cmp r0, r6
    beq clear_bss       /* skip relocation */
    mov r1, r6          /* r1 <- scratch for copy_loop */
    ldr r3, _bss_start_ofs
    add r2, r0, r3      /* r2 <- source end address     */

    /*支持nor的复制,不支持nand的*/
copy_loop:
    ldmia   r0!, {r9-r10}       /* copy from source address [r0]    */
    stmia   r1!, {r9-r10}       /* copy to   target address [r1]    */
    cmp r0, r2          /* until source end address [r2]    */
    blo copy_loop

变量地址修改

程序的链接地址是0,访问全局变量、静态变量、调用函数时是使"基于0地址编译得到的地址

现在把程序复制到了SDRAM,需要修改代码,把"基于0地址编译得到的地址"改为新地址

程序里有些地址在链接时不能确定,要到运行前才能确定:fixabs

我们先来看下全局变量是怎么在汇编中使用的?查看文档全局变量反汇编与重定位.md,在文档中已经知道要怎么做了, 接下去看这个汇编的实现即可,uboot总结来说有两种情况,我只了解第一种情况

    从.rel.dyn 段中依次获得要修改的变量地址
    如果下一个值Y是0x17
        *(adr+offset)=*(adr+offset)+offset
    如果下一个值Y的低8位是0x02,这里加的是绝对地址,和自身存储的地址值无关
        *(adr+offset)=*[Y>>4+段dynsym_r10+4]+offset
  1. 正常的我们全局变量,指针的处理

  2. 某个lable存的值是个地址,他是一个绝对的偏移,与当前的位置无关,这个老师说是动态链接的时候是需要这样的,没有了解动态链接,暂时不去深究.

    从代码的意思来说,就是该地址是固定的,它是一个确定的位置*[Y>>4+段dynsym_r10+4]这个值就是表格里面死的值

mark

比如我们代码偏移了offset=20,有两个地址单元【4】=14,【5】=15

  • 假设都为0x17标记,则【24】=14+20=34,【25】=15+20=35
  • 【5】0x02标记,则【24】=14+20=34,【25】=固定的值+20

在代码上体现是如下

/*
    1. 计算偏移地址
    2. 获得特殊段的 _dynsym_start_ofs  _rel_dyn_start_ofs 位置
*/
    /*
     * fix .rel.dyn relocations
     */
    ldr r0, _TEXT_BASE      /* r0 <- Text base */
    sub r9, r6, r0      /* r9 <- relocation offset */
    ldr r10, _dynsym_start_ofs  /* r10 <- sym table ofs */
    add r10, r10, r0        /* r10 <- sym table in FLASH */
    ldr r2, _rel_dyn_start_ofs  /* r2 <- rel dyn start ofs */
    add r2, r2, r0      /* r2 <- rel dyn start in FLASH */
    ldr r3, _rel_dyn_end_ofs    /* r3 <- rel dyn end ofs */
    add r3, r3, r0      /* r3 <- rel dyn end in FLASH */
    
/*
    从.rel.dyn 段中依次获得要修改的变量地址
    如果下一个值Y是0x17
        *(adr+offset)=*(adr+offset)+offset
    如果下一个值Y的低8位是0x02,这里加的是绝对地址,和自身存储的地址值无关
        *(adr+offset)=*[Y>>4+段dynsym_r10+4]+offset
*/  

fixloop:
    ldr r0, [r2]        /* r0 <- location to fix up, IN FLASH! */
    add r0, r0, r9      /* r0 <- location to fix up in RAM */
    ldr r1, [r2, #4]
    and r7, r1, #0xff
    cmp r7, #23         /* relative fixup? */
    beq fixrel
    cmp r7, #2          /* absolute fixup? */
    beq fixabs
    /* ignore unknown type of fixup */
    b   fixnext
fixabs:
    /* absolute fix: set location to (offset) symbol value */
    mov r1, r1, LSR #4      /* r1 <- symbol index in .dynsym */
    add r1, r10, r1     /* r1 <- address of symbol in table */
    ldr r1, [r1, #4]        /* r1 <- symbol value */
    add r1, r1, r9      /* r1 <- relocated sym addr */
    b   fixnext
fixrel:
    /* relative fix: increase location by offset */
    ldr r1, [r0]
    add r1, r1, r9
fixnext:
    str r1, [r0]
    add r2, r2, #8      /* each rel.dyn entry is 8 bytes */
    cmp r2, r3
    blo fixloop

进一步每行代码分析如下

    调用之前的值
    
    mov r4, r0  /* save addr_sp */
    mov r5, r1  /* save addr of gd */
    mov r6, r2  /* save addr of destination */  
    
    
    r0 链接地址这里是0
    r6 目标地址
    r9 这里就是flash与sdram的偏移地址了
    /*
     * fix .rel.dyn relocations
     */
    ldr r0, _TEXT_BASE      /* r0 <- Text base */
    sub r9, r6, r0      /* r9 <- relocation offset */
    
    
    -----------------------------------------
    r0 链接地址这里是0
    r6 目标地址
    r9 这里就是flash与sdram的偏移地址了    
    --------------------------------------------
    
    

    
    ldr r10, _dynsym_start_ofs  /* r10 <- sym table ofs */
    add r10, r10, r0        /* r10 <- sym table in FLASH */
    
    -----------------------------------------------
    _dynsym_start_ofs:
    .word __dynsym_start - _start
    r10 就是  __dynsym_start 在flash 的实际地址
    -----------------------------------------------
    
    ldr r2, _rel_dyn_start_ofs  /* r2 <- rel dyn start ofs */
    add r2, r2, r0      /* r2 <- rel dyn start in FLASH */
    
    ----------------------------------------------- 
    _rel_dyn_start_ofs:
        .word __rel_dyn_start - _start
    r2 就是  __rel_dyn_start 在flash 的实际地址
    -----------------------------------------------
    
    ldr r3, _rel_dyn_end_ofs    /* r3 <- rel dyn end ofs */
    add r3, r3, r0      /* r3 <- rel dyn end in FLASH */
    
    ----------------------------------------------- 
    _rel_dyn_end_ofs:
    .word __rel_dyn_end - _start
        r3 就是  __rel_dyn_end 在flash 的实际地址
    -----------------------------------------------
    
    -----------------------------------------------
    总结来说就是先确定了具体在flash上的地址
    .rel.dyn : 
    {
        __rel_dyn_start = .;        ---------r2
        *(.rel*)
        __rel_dyn_end = .;          ---------r3
    }
    .dynsym : 
    {
        __dynsym_start = .;         ----------r10
        *(.dynsym)
    }
    
    
0006b568 <__rel_dyn_start>:   //接下去用A标志这里    r2
   6b568:   00000020    .word   0x00000020
   6b56c:   00000017    .word   0x00000017
   6b570:   00000024    .word   0x00000024
   6b574:   00000017    .word   0x00000017
   6b578:   00000028    .word   0x00000028
   6b57c:   00000017    .word   0x00000017
   ..................................................end=r3
    
00073608 <__dynsym_start>:  //接下去用B标志这里     r10
    ...
   73624:   00010003    .word   0x00010003
   73628:   00000000    .word   0x00000000
   7362c:   00068de4    .word   0x00068de4
   73630:   00000000    .word   0x00000000
   73634:   00050003    .word   0x00050003
   73638:   00000049    .word   0x00000049
   7363c:   0006b568    .word   0x0006b568  
    -----------------------------------------------
    

这里的 r2 可以先理解为是nor上的地址 也就是加载地址   
    
fixloop:

// r2 表示当前从 A 取址的地址

    ldr r0, [r2]        /* r0 <- location to fix up, IN FLASH! */
// 从A表示的地址 取出具体的值   
    add r0, r0, r9      /* r0 <- location to fix up in RAM */
// 将其值加上offset  
    ldr r1, [r2, #4]    
    and r7, r1, #0xff
    cmp r7, #23         /* relative fixup? */
//取出下一个A的值,如果是0x17 跳转到  fixrel
//                 如果是0x02 跳转到  fixabs

//---  r0 是修正过的地址值
//---  r1  是__rel_dyn_start下一个的值
    beq fixrel
    cmp r7, #2          /* absolute fixup? */
    beq fixabs
    /* ignore unknown type of fixup */
    b   fixnext
fixabs:

//到这里的时候 从A取出来的值已经加上偏移了

    /* absolute fix: set location to (offset) symbol value */
    
    mov r1, r1, LSR #4      /* r1 <- symbol index in .dynsym */
// r1 右移4位 这里应该是后四位无效用来表示 #2    
// 猜测 r1 这里表示的是 B的序号 也就是第几个了
    
    add r1, r10, r1     /* r1 <- address of symbol in table */
    ldr r1, [r1, #4]        /* r1 <- symbol value */
    add r1, r1, r9      /* r1 <- relocated sym addr */
    b   fixnext
fixrel:
//到这里的时候 从A取出来的值r0已经加上偏移了
    /* relative fix: increase location by offset */
    ldr r1, [r0]
// 从重定位后的ram中取出值.老师的笔记这里写错了哦 #########################  
    add r1, r1, r9
// 将这个值加上offset r1  
fixnext:
    str r1, [r0]
    add r2, r2, #8      /* each rel.dyn entry is 8 bytes */
    cmp r2, r3
    blo fixloop

参考

内核黑科技之DEFINE宏

全局变量反汇编与重定位

转载于:https://www.cnblogs.com/zongzi10010/p/10424480.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值