• 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)
    
    libasm-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宏

    includelinuxkbuild.h
    #define DEFINE(sym, val) 
    	asm volatile("
    ->" #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宏

    全局变量反汇编与重定位

  • 相关阅读:
    (八十五)c#Winform自定义控件-引用区块-HZHControls
    (八十四)c#Winform自定义控件-导航菜单(Ribbon菜单)-HZHControls
    (八十三)c#Winform自定义控件-导航菜单(扩展)-HZHControls
    (八十二)c#Winform自定义控件-穿梭框-HZHControls
    (八十一)c#Winform自定义控件-时间轴-HZHControls
    (八十)c#Winform自定义控件-分割线标签-HZHControls
    (七十九)c#Winform自定义控件-导航菜单-HZHControls
    (七十八)c#Winform自定义控件-倒影组件-HZHControls
    (七十七)c#Winform自定义控件-采样控件-HZHControls
    (七十六)c#Winform自定义控件-表单验证组件-HZHControls
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10424480.html
Copyright © 2020-2023  润新知