uboot2013.07lds分析
U-boot代码解析
u-boot源码解析u-boot介绍Uboot是德国DENX小组的开发用于多种嵌入式CPU的bootloader程序, UBoot不仅仅支持嵌入式Linux系统的引导,当前,它还支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS 嵌入式操作系统。
UBoot除了支持PowerPC系列的处理器外,还能支持MIPS、 x86、ARM、NIOS、XScale等诸多常用系列的处理器。
board:和一些已有开发板有关的文件。
每一个开发板都以一个子目录出现在当前目录中,子目录中存放与开发板相关的配置文件。
它的每个子文件夹里都有如下文件:makefileconfig.mksmdk2410.c 和板子相关的代码(以smdk2410为例)flash.c Flash操作代码memsetup.s 初始化SDRAM代码u-boot.lds 对应的连接文件common:实现uboot命令行下支持的命令,每一条命令都对应一个文件。
例如bootm命令对应就是cmd_bootm.c。
cpu:与特定CPU架构相关目录,每一款Uboot下支持的CPU在该目录下对应一个子目录,比如有子目录arm920t等。
cpu/ 它的每个子文件夹里都有如下文件:makefileconfig.mkcpu.c 和处理器相关的代码interrupts.c 中断处理代码serial.c 串口初始化代码start.s 全局开始启动代码disk:对磁盘的支持。
doc:文档目录。
Uboot有非常完善的文档,推荐大家参考阅读。
drivers:Uboot支持的设备驱动程序都放在该目录,比如各种网卡、支持CFI的Flash、串口和USB等。
fs: 支持的文件系统,Uboot现在支持cramfs、fat、fdos、jffs2和registerfs。
include:Uboot使用的头文件,还有对各种硬件平台支持的汇编文件,系统的配置文件和对文件系统支持的文件。
gcc编程环境基础4--ld命令和u-boot中的lds文件实例和简单实例分析
4.简单例子
5.简单脚本命令
6.对符号的赋值
7. SECTIONS命令
8. MEMORY命令
9. PHDRS命令
10. VERSION命令
11.脚本内的表达式
12.暗含的连接脚本
1.概论
--------------------------------------------------------------------------------
符号(symbol):每个目标文件都有符号表(SYMBOL TABLE),包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息.
符号值:每个符号对应一个地址,即符号值(这与c程序内变量的值不一样,某种情况下可以把它看成变量的地址).可用nm命令查看它们. (nm的使用方法可参考本blog的GNU binutils笔记)
如果.data section的LMA为0x08050000,显然结果是j=2
如果.data section的LMA为0x08050004,显然结果是j=1
还可这样理解LMA:
.text section内容的开始处包含如下两条指令(intel i386指令是10字节,每行对应5字节):
jmp 0x08048285
-T选项用以指定自己的链接脚本,它将代替默认的连接脚本.你也可以使用<暗含的连接脚本>以增加自定义的链接命令.
以下没有特殊说明,连接器指的是静态连接器.
2.基本概念
--------------------------------------------------------------------------------
UBOOT源码分析
UBOOT源码分析UBOOT是一种开放源码的引导加载程序。
作为嵌入式系统启动的第一阶段,它负责初始化硬件设备、设置系统环境变量、加载内核镜像以及跳转到内核开始执行。
Uboot的源码是开放的,让我们可以深入了解其内部工作机制和自定义一些功能。
Uboot源码的文件组织结构非常清晰,主要分为三个大类:目录、文件和配置。
其中目录包含了一系列相关的文件,文件存放具体的源码实现代码,配置文件包含了针对特定硬件平台的配置选项。
Uboot源码的核心部分是启动代码,位于arch目录下的CPU架构相关目录中。
不同的CPU架构拥有不同的启动代码实现,如arm、x86等。
这些启动代码主要包括以下几个关键功能:1. 初始化硬件设备:Uboot首先需要初始化硬件设备,例如设置时钟、中断控制器、串口等设备。
这些初始化操作是在启动代码中完成的。
通过查看该部分代码,我们可以了解硬件的初始化过程,以及如何配置相关寄存器。
2. 设置启动参数:Uboot启动参数存储在一个称为"bd_info"的数据结构中,它包含了一些关键的设备和内存信息,例如DRAM大小、Flash 大小等。
这些参数是在启动代码中设置的,以便内核启动时能够正确识别硬件情况。
3. 加载内核镜像:Uboot负责加载内核镜像到内存中,以便内核可以正确执行。
在启动代码中,会通过读取Flash设备或者网络等方式,将内核镜像加载到指定的内存地址处。
加载过程中,可能会进行一些校验和修正操作,以确保内核数据的完整性。
4. 启动内核:在内核镜像加载完成后,Uboot会设置一些寄存器的值,并执行一个汇编指令,跳转到内核开始执行。
此时,Uboot的使命即结束,控制权交由内核处理。
除了启动代码,Uboot源码中还包含了许多其他功能模块,如命令行解析器、存储设备驱动、网络协议栈等。
这些功能模块可以根据需求进行配置和编译,以满足不同平台的需求。
例如,可以通过配置文件选择启用一些功能模块,或者自定义一些新的功能。
uboot笔记uboot命令分析+实现
uboot笔记uboot命令分析+实现uboot笔记:uboot命令分析+实现Ubootuboot命令分析+实现先贴⼀个重要结构,位于uboot/include/command.h,这个结构代表每个uboot命令struct cmd_tbl_s {char *name; /* Command Name */int maxargs; /* maximum number of arguments*/int repeatable;/* autorepeat allowed? *//* Implementation function */int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);char *usage; /* Usage message (short)简短⽤法信息*/#ifdef CFG_LONGHELPchar *help; /* Help message (long) 长的帮助信息*/#endif#ifdef CONFIG_AUTO_COMPLETE/* do auto completion on the arguments */ int (*complete)(intargc, char *argv[], charlast_char, intmaxv, char *cmdv[]); #endif};typedefstruct cmd_tbl_s cmd_tbl_t;============================================================uboot的第⼀阶段:硬件相关初始化0.reset执⾏arm920t/start.s 过程如下1.设置cpu svc管理模式2.关看门狗中断,mmu等3.设置时钟,sdram,外部总线4.代码重定位,搬运代码,从flash到sdram5.设置栈,bss段清零, bss⽤于未初始化的全局变量和静态变量6.ldr pc, _start_armboot即进⼊uboot启动的第⼆阶段,调⽤c函数start_armboot()从start_armboot开始经过⼀系列外设初始化⽐如falsh_initnand_init...最后循环调⽤mian_loop()main_loop主要流程{1. ⽣成环境变量mtdparts, 调⽤mtdparts_init2. 在启动过程中若⽆空格键按下则boot_zImage,即run_command(getenv("bootcmd"),0)有空格键按下则run_command("menu",0)3. shell过程,读取⽤户的输⼊并执⾏相应的命令{从控制台获得命令,保存在全局变量comsole_buffer中解析命令⾏字符串,分割命令与参数,最后执⾏run_command(...); }}也就是说在mian_loop中,是处理环境变量和控制台⼈机交互,mian_loop调⽤readline ()读取命令⾏到console_buffer,再把console_buffer复制到lastcommand中去,还要设置flag,最后调⽤run_command (lastcommand, flag)函数,run_command (lastcommand, flag)函数中,⾸先定义cmd_tbl_t *cmdtp,再解析命令⾏。
u_boot移植(五)之分析uboot源码中nand flash操作
u_boot移植(五)之分析uboot源码中nand flash操作一、OneNand 和Nand Flash我们已经能从Nand Flash启动了,启动之后,大家会看到如下效果:可以看出,我们的uboot默认使用的是OneNand。
需要注意的是我们的FSC100上面是没有OneNand的,有的是K9F2G08U0B型号的NAND FLASH。
前面我们了解过Nor Flash 和Nand Flash,那OneNand Flash又是什么呢?二、uboot 源码中Nand Flash部分代码分析我们从Nand Flash初始化看起,打开lib_arm/board.c文件,为了紧抓主线,以下代码只列举出了主线代码。
可以看出,我们可以通过CONFIG_CMD_NAND和CONFIG_CMD_ONENAND两个宏来选择NAND FLASH初始化还是 ONENAND FLASH初始化。
uboot 中默认定义了宏CONFIG_CMD_ONENAND,所以选择的是ONENAND FLASH初始化。
我们的FSC100上面使用的是NAND FLASH,所以我们要定义CONFIG_CMD_NAND宏,取消CONFIG_CMD_ONENAND宏的定义。
嗯!先做个记录:修改include/configs/fsc100.h,定义宏CONFIG_CMD_NAND,取消宏CONFIG_CMD_ONENAND。
好了,接下我们看看nand_init()函数时如何实现的。
看以看出,这段代码调用根据CONFIG_SYS_MAX_NAND_DEVICE宏[默认没有定义]的值来决定系统中Nand Flash设备的个数。
接着调用nand_init_chip()函数完成Nand Flash初始化,然后计算出每块Nand Flash的大小。
最终会输出Nand Flash总的容量。
嗯!做个记录:修改include/configs/fsc100.h,定义宏CONFIG_SYS_MAX_NAND_DEVICE,值为1没有看明白的地方是给nand_init_chip()函数传递的参数,接下来我们来看看他们是如何定义的。
uboot启动流程分析
uboot启动流程分析Uboot启动流程分析。
Uboot是一种常用的嵌入式系统启动加载程序,它的启动流程对于嵌入式系统的正常运行至关重要。
本文将对Uboot启动流程进行分析,以便更好地理解其工作原理。
首先,Uboot的启动流程可以分为以下几个步骤,Reset、初始化、设备初始化、加载内核。
接下来我们将逐一进行详细的分析。
Reset阶段是整个启动流程的起点,当系统上电或者复位时,CPU会跳转到Uboot的入口地址开始执行。
在这个阶段,Uboot会进行一些基本的硬件初始化工作,包括设置栈指针、初始化CPU寄存器等。
接着是初始化阶段,Uboot会进行一系列的初始化工作,包括初始化串口、初始化内存控制器、初始化时钟等。
这些初始化工作是为了确保系统能够正常地运行,并为后续的工作做好准备。
设备初始化阶段是Uboot启动流程中的一个重要环节,它包括对外设的初始化和检测。
在这个阶段,Uboot会初始化各种外设,如网卡、存储设备等,并对其进行检测,以确保它们能够正常工作。
最后一个阶段是加载内核,Uboot会从存储设备中加载操作系统的内核镜像到内存中,并跳转到内核的入口地址开始执行。
在这个过程中,Uboot会进行一些必要的设置,如传递启动参数给内核,并最终将控制权交给内核。
总的来说,Uboot的启动流程是一个非常重要的过程,它涉及到系统的硬件初始化、外设的初始化和内核的加载等工作。
只有当这些工作都顺利完成时,系统才能够正常地启动运行。
因此,对Uboot启动流程的深入理解对于嵌入式系统的开发和调试具有重要意义。
通过本文对Uboot启动流程的分析,相信读者对Uboot的工作原理有了更清晰的认识。
希望本文能够对大家有所帮助,谢谢阅读!。
UBOOT的lds文件
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")/*指定输出可执行文件是elf格式,32位ARM指令,小端*/OUTPUT_ARCH(arm)/*指定输出可执行文件的平台为ARM*/ENTRY(_start)/*指定输出可执行文件的起始代码段为_start*/SECTIONS{/*指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。
必须使编译器知道这个地址,通常都是修改此处来完成*/. = 0x00000000;/*;从0x0位置开始*/. = ALIGN(4);/*代码以4字节对齐*/.text :{cpu/arm920t/start.o (.text)/*代码的第一个代码部分*/*(.text)/*下面依次为各个text段函数*/}. = ALIGN(4);/*代码以4字节对齐*/.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }/*指定只读数据段*/. = ALIGN(4);/*代码以4字节对齐*/.data : { *(.data) }. = ALIGN(4);/*代码以4字节对齐*/.got : { *(.got) }/*指定got段, got段是uboot自定义的一个段, 非标准段*/. = .;__u_boot_cmd_start = .;/*把__u_boot_cmd_start赋值为当前位置, 即起始位置*/.u_boot_cmd : { *(.u_boot_cmd) }/*指定u_boot_cmd段, uboot把所有的uboot命令放在该段.*/__u_boot_cmd_end = .;/*把__u_boot_cmd_end赋值为当前位置,即结束位置*/. = ALIGN(4);/*代码以4字节对齐*/__bss_start = .;/*把__bss_start赋值为当前位置,即bss段的开始位置*/.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }/*指定bss段,告诉加载器不要加载这个段*/__bss_end = .;/*把_end赋值为当前位置,即bss段的结束位置*/}看完上面的解析思路本来应该是很清晰的,于是乎编译u-boot,查看一下System.map,30100000 T _start30100020 t _undefined_instruction30100024 t _software_interrupt30100028 t _prefetch_abort3010002c t _data_abort30100030 t _not_used30100034 t _irq30100038 t _fiq发现_start 的链接地址不是u-boot.lds中.text 的当前地址0x00000000,而是0x30100000,这就产生很多疑问了:(1) 为什么u-boot.lds指定的 .text 的首地址不起作用?(2) 0x30100000是什么地址,由谁指定.text的首地址是0x30100000的呢?(3) 假如有其他动作改变了 .text 的首地址,那么该动作跟u-boot.lds的优先级又是怎么决定的呢?其实这三个问题都在Makefile的LDFLAGS 变量和u-boot.lds 中找到答案。
lds分析
;指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
;指定输出可执行文件的平台为ARM
ENTRY(_start)
_start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,
即u-boot在把代码拷贝到RAM中去执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */
现在,我们首先开看一看 xloader.lds 的代码:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(XLOADER_ENTRY) SECTIONS {
. = 0x00000000; . = ALIGN(4); .text : {
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
. = 0x00000000 . = ALIGN(4) .text :
; 从0x0位置开始 ; 代码以4字节对齐 ;指定代码段
{
cpu/arm920t/start.o (.text) ; 代码的第一个码部分 *(.text) ;其它代码部分
uboot命令解释与运行分析
uboot命令解释与运行分析题记: 省略200字这一回来分析一下uboot中命令行的解释, 所以我们直接从main_loop开始分析.1. 从汇编阶段进入c阶段的第一个函数是start_xxx, 如/lib_unicore/board.c中的start_unicoreboot. 前半部分调用了若干初始化函数来进行部分硬件的初始化, 并设置一下环境. 这里不是我们本回要讨论的所以一一跳过. 在start_xxx的最后调用了main_loop(), 而且还是被一个死循环死死圈住了;2. 现在我们已经进入了这个圈套那么只能往里钻了. common/main.c文件中的main_loop().上面代码主要是对自启动部分的描述, 其中命令执行部分是在run_command中进行的, 这个等在后文分析. 如果我们没有bootcmd 或者在延时中被打断, 那么代码会继续向下执行3.read_line()读取到命令行后会调用common/main.c文件中的run_command().现在是分析run_command()的时候了,不管是从环境变量还是终端获得命令,都是由run_command()来处理的.中场休息,下面要进入处理cmdbuf的循环中了, 长征马上开始以;分割. 忽略'\;'for(inquotes = 0, sep = str;*sep; sep++){if((*sep=='\'')&&(*(sep-1)!='\\'))inquotes=!inquotes;if(!inquotes &&(*sep ==';')&&( sep != str)&&(*(sep-1)!='\\'))break;}//如果上面for循环找到一条以';'结束的命令, 那么sep指向命令末尾token = str;if(*sep){str = sep + 1;*sep ='\0';}elsestr = sep;process_macros (token, finaltoken);if((argc = parse_line (finaltoken, argv))== 0){rc =-1;4.就此打断一下, 我们要分析一下find_cmd了, 不能再跳过了. find_cmd()在.u_boot_cmd段中寻找该命令的cmd_tbl_t结构, 找到后返回该结构. 该命令的结构是通过定义在include/command.h中的宏定义U_BOOT_CMD登记进.u_boot_cmd段中的.5. 刚才我们在长征的半路翻越了一座雪山, 现在继续回到while循环中if(cmdtp->cmd == do_bootd){if(flag & CMD_FLAG_BOOTD){puts("'bootd' recursion detected\n");rc =-1;continue;}else{flag |= CMD_FLAG_BOOTD;}}#endif//长征马上结束, 胜利就在眼前! 调用结构体中注册的cmd函数, 何时注册的呢? 上面不远处介绍的U_BOOT_CMD!if((cmdtp->cmd)(cmdtp, flag, argc, argv)!= 0){ rc =-1;}repeatable &= cmdtp->repeatable;if(had_ctrlc ())return-1;}。
uboot 2013 07 运行流程
uboot 2013 07 运行流程board:board/friendlyarm/mini2440目前只支持nor启动1、由编译链接文件u-boot.lds// arch/arm/cpu/u-boot.lds可知,第一个运行的文件为:CPUDIR/start.o//arch/arm/cpu/arm920t/start.S1)设置异常向量2)设置CPU的工作模式3)关中断4)关看门狗5)设置CPU时钟6)配置caches7)禁mmu8)bl lowlevel_init],跳到lowlevel_init.S2、lowlevel_init.S//在samsung/smdk2410/lowlevel_init.S,自己添加进来1)存储控制器初始化:位数,时序等2)返回到start.S,然后bl _main,跳到crt0.S3、crt0.S// arch/arm/lib(此文件官方说明:2013/1/ 8arm: move C runtime setup code in crt0.SMove all the C runtime setup code from every start.Sin arch/arm into arch/arm/lib/crt0.S. This coversthe code sequence from setting up the initial stackto calling into board_init_r().从2013/1/ 8起,把C语言运行环境的设置,放到crt0.S文件)1)堆栈设置2)blboard_init_f4、board_init_f()// arch/arm/lib/board.c1)第一阶段平台的初始化工作timer_init,env_init, ram_init... 计算堆栈addr_sp,设置gd、bd结构体空间,异常中断堆空间,计算出重定向u-boot的位置addr,控制台初始化,打印一些消息2)回到crt0.S设置新的sp and gd3)brelocate_code,跳到relocate.S5、relocate.S//arch/arm/lib1)把U-Boot复制到SDRAM中指定的位置处。
海思uboot启动流程详细分析(一)
海思uboot启动流程详细分析(⼀)第⼀阶段 start.S⾸先我们可以在u-boot.lds中看到ENTRY(_start),即指定了⼊⼝_start,_start也就是整个start.S的最开始;1. reset在arch\arm\cpu\armv8\hi3559av100中的start.S注意x30在ARMV8中代表lr寄存器reset:/** Could be EL3/EL2/EL1, Initial State:* Little Endian, MMU Disabled, i/dCache Disabled*/adr x0, vectorsswitch_el x1, 3f, 2f, 1f3: msr vbar_el3, x0mrs x0, scr_el3orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */msr scr_el3, x0msr cptr_el3, xzr /* Enable FP/SIMD */#ifdef COUNTER_FREQUENCYldr x0, =COUNTER_FREQUENCYmsr cntfrq_el0, x0 /* Initialize CNTFRQ */#endifb 0f2: msr vbar_el2, x0mov x0, #0x33ffmsr cptr_el2, x0 /* Enable FP/SIMD */b 0f1: msr vbar_el1, x0mov x0, #3 << 20msr cpacr_el1, x0 /* Enable FP/SIMD */0:/** Cache/BPB/TLB Invalidate* i-cache is invalidated before enabled in icache_enable()* tlb is invalidated before mmu is enabled in dcache_enable()* d-cache is invalidated before enabled in dcache_enable()*//** read system register REG_SC_GEN2* check if ziju flag*/ldr x0, =SYS_CTRL_REG_BASEldr w1, [x0, #REG_SC_GEN2]ldr w2, =0x7a696a75 /* magic for "ziju" */cmp w1, w2bne normal_start_flowmov x1, sp /* save sp */str w1, [x0, #REG_SC_GEN2] /* clear ziju flag */adr x0, vectors,其中的vectors代表了异常向量表主要做了如下事情:1)reset SCTRL寄存器具体可参考reset_sctrl函数,由CONFIG_SYS_RESET_SCTRL控制,⼀般不需要打开。
uboot分析和笔记
uboot一、uboot是ppcboot和armboot合并而成,现在主流的bootloader为uboot和redboot二、bootm addr_kernel addr_initrd三、移植uboot时最好(一定)要找到一个自己板子的原形(即自己的板子是在这个板子上做一些修改而来的)的版本,这样就可以事半功倍。
这样要修改的地方就比较少,也比较容易了。
uboot支持很多平台,与一个具体平台相关的主要有三个地方:1、./include/configs/xxxxx.h, 主要定义了flash、sdram的起始地址等信息,一般要修改flash的起始地址、大小,有时候会有位宽等。
2、./board/xxxxx/*,这个目录下主要有两三个.c文件,主要为该平台的初始化和flash操作的函数。
有时候flash的操作需要修改,不过一般都是找一个现有的支持该flash的驱动,一般情况在uboot 别的./board/平台下就会有现成的,拷贝过了就可以了。
3、./cpu/xxxxxx/arch_xxx/xxxxxx/*, 一般是此cpu的初始等函数。
四、具体移植的时候最多涉及到的会是./include/configs/xxxx.h,如果有现成的平台(uboot现在支持绝大部分我们常用的平台),可能只需要对着原来的xxxx.h文件,修改几个我们在硬件上修改了的地方,一般会是flash的起始地址、大小;内存大小(内存的起始地址应该都是0);uboot设置信息保存的地址和长度;console 口和它的波特率;默认的设置;uboot的入口地址等(具体情况可能会有一些变化),如果不是从相同的平台移植,可能会比较麻烦,因为这时候要修改一些和此cpu相关的一些寄存器、频率和内存等硬件方面的东西了(也在这个xxxx.h中),虽然这时改动的地方也不多,但是会很痛苦,因为经常不知道要改哪里或者改为多少。
所以可能需要参考cpu的datasheet和到网上找一些资料了并且慢慢试了。
gcc编程环境基础4--ld命令和u-boot中的lds文件实例和简单实例分析
gcc编程环境基础4--ld命令和u-boot中的lds文件实例和简单实例分析ld选项和lds文件==================================================================================0. Contents1. 概论2. 基本概念3. 脚本格式4. 简单例子5. 简单脚本命令6. 对符号的赋值7. SECTIONS命令8. MEMORY命令9. PHDRS命令10. VERSION命令11. 脚本内的表达式12. 暗含的连接脚本1. 概论--------------------------------------------------------------------------------每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.连接器有个默认的内置连接脚本, 可用ld --verbose查看. 连接选项-r和-N可以影响默认的连接脚本(如何影响?).-T选项用以指定自己的链接脚本, 它将代替默认的连接脚本.你也可以使用<暗含的连接脚本>以增加自定义的链接命令.以下没有特殊说明,连接器指的是静态连接器.2. 基本概念--------------------------------------------------------------------------------链接器把一个或多个输入文件合成一个输出文件.输入文件: 目标文件或链接脚本文件.输出文件: 目标文件或可执行文件.目标文件(包括可执行文件)具有固定的格式, 在UNIX或GNU/Linux平台下, 一般为ELF格式. 若想了解更多, 可参考UNIX/Linux平台可执行文件格式分析有时把输入文件内的section称为输入section(input section), 把输出文件内的section称为输出section(output sectin).目标文件的每个section至少包含两个信息: 名字和大小. 大部分section还包含与它相关联的一块数据, 称为section contents(section内容). 一个section可被标记为“loadable(可加载的)”或“allocatable(可分配的)”.loadable section: 在输出文件运行时, 相应的section内容将被载入进程地址空间中.allocatable section: 内容为空的section可被标记为“可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同section指定大小的部分. 某些情况下, 这块内存必须被置零.如果一个section不是“可加载的”或“可分配的”, 那么该section通常包含了调试信息. 可用objdump -h命令查看相关信息.每个“可加载的”或“可分配的”输出section通常包含两个地址: VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址). 通常VMA和LMA是相同的.在目标文件中, loadable或allocatable的输出section有两种地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是执行输出文件时section所在的地址, 而LMA是加载输出文件时section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定).可这样来理解VMA和LMA, 假设:(1) .data section对应的VMA地址是0x08050000, 该section内包含了3个32位全局变量, i、j和k, 分别为1,2,3.(2) .text section内包含由"printf( "j=%d ", j );"程序片段产生的代码.连接时指定.data section的VMA为0x08050000, 产生的printf指令是将地址为0x08050004处的4字节内容作为一个整数打印出来.如果.data section的LMA为0x08050000,显然结果是j=2如果.data section的LMA为0x08050004,显然结果是j=1还可这样理解LMA:.text section内容的开始处包含如下两条指令(intel i386指令是10字节,每行对应5字节):jmp 0x08048285movl $0x1,%eax如果.text section的LMA为0x08048280, 那么在进程地址空间内0x08048280处为“jmp 0x08048285”指令, 0x08048285处为movl $0x1,%eax 指令. 假设某指令跳转到地址0x08048280, 显然它的执行将导致%eax寄存器被赋值为1.如果.text section的LMA为0x08048285, 那么在进程地址空间内0x08048285处为“jmp 0x08048285”指令, 0x0804828a处为movl $0x1,%eax 指令. 假设某指令跳转到地址0x08048285, 显然它的执行又跳转到进程地址空间内0x08048285处, 造成死循环.符号(symbol): 每个目标文件都有符号表(SYMBOL TABLE), 包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息.符号值: 每个符号对应一个地址, 即符号值(这与c程序内变量的值不一样, 某种情况下可以把它看成变量的地址). 可用nm命令查看它们. (nm的使用方法可参考本blog的GNU binutils笔记)3. 脚本格式--------------------------------------------------------------------------------链接脚本由一系列命令组成, 每个命令由一个关键字(一般在其后紧跟相关参数)或一条对符号的赋值语句组成. 命令由分号…;‟分隔开.文件名或格式名内如果包含分号';'或其他分隔符, 则要用引号…"‟将名字全称引用起来. 无法处理含引号的文件名./* */之间的是注释.4. 简单例子--------------------------------------------------------------------------------在介绍链接描述文件的命令之前, 先看看下述的简单例子:以下脚本将输出文件的text section定位在0x10000, data section定位在0x8000000:SECTIONS{. = 0x10000;.text : { *(.text) }. = 0x8000000;.data : { *(.data) }.bss : { *(.bss) }}解释一下上述的例子:. = 0x10000 : 把定位器符号置为0x10000 (若不指定, 则该符号的初始值为0)..text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0x10000.. = 0x8000000 :把定位器符号置为0x8000000.data : { *(.data) } : 将所有输入文件的.text section合并成一个.data section, 该section的地址被置为0x8000000..bss : { *(.bss) } : 将所有输入文件的.bss section合并成一个.bss section,该section的地址被置为0x8000000+.data section的大小.连接器每读完一个section描述后, 将定位器符号的值*增加*该section的大小. 注意: 此处没有考虑对齐约束.5. 简单脚本命令--------------------------------------------------------------------------------- 1 -ENTRY(SYMBOL) : 将符号SYMBOL的值设置成入口地址.入口地址(entry point): 进程执行的第一条用户空间的指令在进程地址空间的地址)ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)1, ld命令行的-e选项2, 连接脚本的ENTRY(SYMBOL)命令3, 如果定义了start符号, 使用start符号值4, 如果存在.text section, 使用.text section的第一字节的位置值5, 使用值0- 2 -INCLUDE filename : 包含其他名为filename的链接脚本相当于c程序内的的#include指令, 用以包含另一个链接脚本.脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2, 文件2内INCLUDE文件3... , 文件10内INCLUDE文件11. 那么文件11内不能再出现INCLUDE指令了.- 3 -INPUT(files): 将括号内的文件做为链接过程的输入文件ld首先在当前目录下寻找该文件, 如果没找到, 则在由-L指定的搜索路径下搜索. file可以为-lfile形式,就象命令行的-l选项一样. 如果该命令出现在暗含的脚本内, 则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.- 4 -GROUP(files) : 指定需要重复搜索符号定义的多个输入文件file必须是库文件, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现.- 5 -OUTPUT(FILENAME) : 定义输出文件的名字同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out- 6 -SEARCH_DIR(PATH) :定义搜索路径,同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索.- 7 -STARTUP(filename) : 指定filename为第一个输入文件在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件.- 8 -OUTPUT_FORMAT(BFDNAME) : 设置输出文件使用的BFD格式同ld选项-o format BFDNAME, 不过ld选项优先级更高.- 9 -OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定义三种输出文件的格式(大小端)若有命令行选项-EB, 则使用第2个BFD格式; 若有命令行选项-EL,则使用第3个BFD格式.否则默认选第一个BFD格式.TARGET(BFDNAME):设置输入文件的BFD格式同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD 格式.另外还有一些:ASSERT(EXP, MESSAGE):如果EXP不为真,终止连接过程EXTERN(SYMBOL SYMBOL ...):在输出文件中增加未定义的符号,如同连接器选项-uFORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配NOCROSSREFS(SECTION SECTION ...):检查列出的输出section,如果发现他们之间有相互引用,则报错.对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用.OUTPUT_ARCH(BFDARCH):设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一.可以用命令objdump -f查看. 可通过man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍.6. 对符号的赋值--------------------------------------------------------------------------------在目标文件内定义的符号可以在链接脚本内被赋值. (注意和C语言中赋值的不同!) 此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.e.g. 通过下面的程序查看变量a的地址:/* a.c */#include <stdio.h>int a = 100;int main(void){printf( "&a=0x%p ", &a );return 0;}/* a.lds */a = 3;$ gcc -Wall -o a-without-lds a.c&a = 0x8049598$ gcc -Wall -o a-with-lds a.c a.lds&a = 0x3注意: 对符号的赋值只对全局变量起作用!一些简单的赋值语句能使用任何c语言内的赋值操作:SYMBOL = EXPRESSION ;SYMBOL += EXPRESSION ;SYMBOL -= EXPRESSION ;SYMBOL *= EXPRESSION ;SYMBOL /= EXPRESSION ;SYMBOL <<= EXPRESSION ;SYMBOL >>= EXPRESSION ;SYMBOL &= EXPRESSION ;SYMBOL |= EXPRESSION ;除了第一类表达式外, 使用其他表达式需要SYMBOL被定义于某目标文件.. 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用.注意:赋值语句包含4个语法元素:符号名、操作符、表达式、分号;一个也不能少.被赋值后,符号所属的section被设值为表达式EXPRESSION所属的SECTION(参看11. 脚本内的表达式)赋值语句可以出现在连接脚本的三处地方:SECTIONS命令内,SECTIONS命令内的section描述内和全局位置;如下,floating_point = 0; /* 全局位置*/SECTIONS{.text :{*(.text)_etext = .; /* section描述内*/}_bdata = (. + 3) & ~ 4; /* SECTIONS命令内*/.data : { *(.data) }}PROVIDE关键字该关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号.例子:SECTIONS{.text :{*(.text)_etext = .;PROVIDE(etext = .);}}当目标文件内引用了etext符号,确没有定义它时,etext符号对应的地址被定义为.text section之后的第一个字节的地址.7. SECTIONS命令--------------------------------------------------------------------------------SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA).该命令格式如下:SECTIONS{SECTIONS-COMMANDSECTIONS-COMMAND...}SECTION-COMMAND有四种:(1) ENTRY命令(2) 符号赋值语句(3) 一个输出section的描述(output section description)(4) 一个section叠加描述(overlay description)如果整个连接脚本内没有SECTIONS命令, 那么ld将所有同名输入section合成为一个输出section内, 各输入section的顺序为它们被连接器发现的顺序. 如果某输入section没有在SECTIONS命令中提到, 那么该section将被直接拷贝成输出section.输出section描述输出section描述具有如下格式:SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP][ ]内的内容为可选选项, 一般不需要.SECTION:section名字SECTION左右的空白、圆括号、冒号是必须的,换行符和其他空格是可选的.每个OUTPUT-SECTION-COMMAND为以下四种之一,符号赋值语句一个输入section描述直接包含的数据值一个特殊的输出section关键字输出section名字(SECTION):输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存在.text、.data和.bss section名.而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要用引号将其组合在一起.输出section地址(ADDRESS):ADDRESS是一个表达式,它的值用于设置VMA.如果没有该选项且有REGION选项,那么连接器将根据REGION设置VMA;如果也没有REGION选项,那么连接器将根据定位符号….‟的值设置该section的VMA,将定位符号的值调整到满足输出section对齐要求后的值,输出section的对齐要求为:该输出section描述内用到的所有输入section的对齐要求中最严格的.例子:.text . : { *(.text) }和.text : { *(.text) }这两个描述是截然不同的,第一个将.text section的VMA设置为定位符号的值,而第二个则是设置成定位符号的修调值,满足对齐要求后的.ADDRESS可以是一个任意表达式,比如ALIGN(0x10)这将把该section的VMA设置成定位符号的修调值,满足16字节对齐后的.注意:设置ADDRESS值,将更改定位符号的值.输入section描述:最常见的输出section描述命令是输入section描述.输入section描述是最基本的连接脚本描述.输入section描述基础:基本语法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)FILENAME文件名,可以是一个特定的文件的名字,也可以是一个字符串模式.SECTION名字,可以是一个特定的section名字,也可以是一个字符串模式例子是最能说明问题的,*(.text) :表示所有输入文件的.text section(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有输入文件的.ctors section.data.o(.data) :表示data.o文件的.data sectiondata.o :表示data.o文件的所有section*(.text .data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第一个文件的.data section,第二个文件的.text section,第二个文件的.data section,...*(.text) *(.data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第二个文件的.text section,...,最后一个文件的.text section,第一个文件的.data section,第二个文件的.data section,...,最后一个文件的.data section下面看连接器是如何找到对应的文件的.当FILENAME是一个特定的文件名时,连接器会查看它是否在连接命令行内出现或在INPUT命令中出现.当FILENAME是一个字符串模式时,连接器仅仅只查看它是否在连接命令行内出现.注意:如果连接器发现某文件在INPUT命令内出现,那么它会在-L指定的路径内搜寻该文件.字符串模式内可存在以下通配符:* :表示任意多个字符? :表示任意一个字符[CHARS] :表示任意一个CHARS内的字符,可用-号表示范围,如:a-z:表示引用下一个紧跟的字符在文件名内,通配符不匹配文件夹分隔符/,但当字符串模式仅包含通配符*时除外.任何一个文件的任意section只能在SECTIONS命令内出现一次.看如下例子,SECTIONS {.data : { *(.data) }.data1 : { data.o(.data) }}data.o文件的.data section在第一个OUTPUT-SECTION-COMMAND命令内被使用了,那么在第二个OUTPUT-SECTION-COMMAND命令内将不会再被使用,也就是说即使连接器不报错,输出文件的.data1 section的内容也是空的.再次强调:连接器依次扫描每个OUTPUT-SECTION-COMMAND命令内的文件名,任何一个文件的任何一个section都只能使用一次.读者可以用-M连接命令选项来产生一个map文件,它包含了所有输入section到输出section的组合信息.再看个例子,SECTIONS {.text : { *(.text) }.DATA : { [A-Z]*(.data) }.data : { *(.data) }.bss : { *(.bss) }}这个例子中说明,所有文件的输入.text section组成输出.text section;所有以大写字母开头的文件的.data section组成输出.DATA section,其他文件的.data section组成输出.data section;所有文件的输入.bss section组成输出.bss section.可以用SORT()关键字对满足字符串模式的所有名字进行递增排序,如SORT(.text*).通用符号(common symbol)的输入section:在许多目标文件格式中,通用符号并没有占用一个section.连接器认为:输入文件的所有通用符号在名为COMMON的section内.例子,.bss { *(.bss) *(COMMON) }这个例子中将所有输入文件的所有通用符号放入输出.bss section内.可以看到COMMOM section的使用方法跟其他section的使用方法是一样的.有些目标文件格式把通用符号分成几类.例如,在MIPS elf目标文件格式中,把通用符号分成standard common symbols(标准通用符号)和small common symbols(微通用符号,不知道这么译对不对?),此时连接器认为所有standard common symbols在COMMON section内,而small common symbols 在.scommon section内.在一些以前的连接脚本内可以看见[COMMON],相当于*(COMMON),不建议继续使用这种陈旧的方式.输入section和垃圾回收:在连接命令行内使用了选项--gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的section,可用KEEP()关键字达此目的.如KEEP(*(.text))或KEEP(SORT(*)(.text))最后看个简单的输入section相关例子:SECTIONS {outputa 0x10000 :{all.ofoo.o (.input1)}outputb :{foo.o (.input2)foo1.o (.input1)}outputc :{*(.input1)*(.input2)}}本例中,将all.o文件的所有section和foo.o文件的所有(一个文件内可以有多个同名section).input1 section依次放入输出outputa section内,该section 的VMA是0x10000;将foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入输出outputb section内,该section的VMA 是当前定位器符号的修调值(对齐后);将其他文件(非all.o、foo.o、foo1.o)文件的. input1 section和.input2 section放入输出outputc section内.在输出section存放数据命令:能够显示地在输出section内填入你想要填入的信息(这样是不是可以自己通过连接脚本写程序?当然是简单的程序).BYTE(EXPRESSION) 1 字节SHORT(EXPRESSION) 2 字节LOGN(EXPRESSION) 4 字节QUAD(EXPRESSION) 8 字节SQUAD(EXPRESSION) 64位处理器的代码时,8 字节输出文件的字节顺序big endianness 或little endianness,可以由输出目标文件的格式决定;如果输出目标文件的格式不能决定字节顺序,那么字节顺序与第一个输入文件的字节顺序相同.如:BYTE(1)、LANG(addr).注意,这些命令只能放在输出section描述内,其他地方不行.错误:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }正确:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }在当前输出section内可能存在未描述的存储区域(比如由于对齐造成的空隙),可以用FILL(EXPRESSION)命令决定这些存储区域的内容,EXPRESSION 的前两字节有效,这两字节在必要时可以重复被使用以填充这类存储区域.如FILE(0x9090).在输出section描述中可以有=FILEEXP属性,它的作用如同FILE()命令,但是FILE命令只作用于该FILE指令之后的section区域,而=FILEEXP属性作用于整个输出section区域,且FILE命令的优先级更高!!!输出section内命令的关键字:CREATE_OBJECT_SYMBOLS :为每个输入文件建立一个符号,符号名为输入文件的名字.每个符号所在的section是出现该关键字的section. CONSTRUCTORS :与c++内的(全局对象的)构造函数和(全局对像的)析构函数相关,下面将它们简称为全局构造和全局析构.对于a.out目标文件格式,连接器用一些不寻常的方法实现c++的全局构造和全局析构.当连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件格式,连接器把与全局构造和全局析构的相关信息放入出现CONSTRUCTORS关键字的输出section内.符号__CTORS_LIST__表示全局构造信息的的开始处,__CTORS_END__表示全局构造信息的结束处.符号__DTORS_LIST__表示全局构造信息的的开始处,__DTORS_END__表示全局构造信息的结束处.这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束.一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用.是不是对于某些目标文件格式才这样???对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入.ctors section和.dtors section内,然后在连接脚本内加入如下,__CTOR_LIST__ = .;LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)*(.ctors)LONG(0)__CTOR_END__ = .;__DTOR_LIST__ = .;LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)*(.dtors)LONG(0)__DTOR_END__ = .;如果使用GNU C++提供的初始化优先级支持(它能控制每个全局构造函数调用的先后顺序),那么请在连接脚本内把CONSTRUCTORS替换成SORT (CONSTRUCTS),把*(.ctors)换成*(SORT(.ctors)),把*(.dtors)换成*(SORT(.dtors)).一般来说,默认的连接脚本已作好的这些工作.输出section的丢弃:例子,.foo { *(.foo) },如果没有任何一个输入文件包含.foo section,那么连接器将不会创建.foo输出section.但是如果在这些输出section描述内包含了非输入section描述命令(如符号赋值语句),那么连接器将总是创建该输出section.有一个特殊的输出section,名为/DISCARD/,被该section引用的任何输入section将不会出现在输出文件内,这就是DISCARD的意思吧.如果/DISCARD/ section被它自己引用呢?想想看.输出section属性:终于讲到这里了,呵呵.我们再回顾以下输出section描述的文法:SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]前面我们浏览了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相关信息,下面我们将浏览其他属性.TYPE :每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型.它可以为以下五种值,NOLOAD :该section在程序运行时,不被载入内存.DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来.这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存.输出section的LMA :默认情况下,LMA等于VMA,但可以通过关键字AT()指定LMA.用关键字AT()指定,括号内包含表达式,表达式的值用于设置LMA.如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围.这个属性主要用于构件ROM境象.例子,SECTIONS{.text 0x1000 : { *(.text) _etext = . ; }.mdata 0x2000 :AT ( ADDR (.text) + SIZEOF (.text) ){ _data = . ; *(.data); _edata = . ; }.bss 0x3000 :{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}}程序如下,extern char _etext, _data, _edata, _bstart, _bend;char *src = &_etext;char *dst = &_data;/* ROM has data at end of text; copy it. */while (dst < &_edata) {*dst++ = *src++;}/* Zero bss */for (dst = &_bstart; dst< &_bend; dst++)*dst = 0;此程序将处于ROM内的已初始化数据拷贝到该数据应在的位置(VMA地址),并将为初始化数据置零.读者应该认真的自己分析以上连接脚本和程序的作用.输出section区域:可以将输出section放入预先定义的内存区域内,例子,MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }SECTIONS { ROM : { *(.text) } >rom }输出section所在的程序段:可以将输出section放入预先定义的程序段(program segment)内.如果某个输出section设置了它所在的一个或多个程序段,那么接下来定义的输出section的默认程序段与该输出section的相同.除非再次显示地指定.例子,PHDRS { text PT_LOAD ; }SECTIONS { .text : { *(.text) } :text }可以通过:NONE指定连接器不把该section放入任何程序段内.详情请查看PHDRS命令输出section的填充模版:这个在前面提到过,任何输出section描述内的未指定的内存区域,连接器用该模版填充该区域.用法:=FILEEXP,前两字节有效,当区域大于两字节时,重复使用这两字节以将其填满.例子,SECTIONS { .text : { *(.text) } =0x9090 }覆盖图(overlay)描述:覆盖图描述使两个或多个不同的section占用同一块程序地址空间.覆盖图管理代码负责将section的拷入和拷出.考虑这种情况,当某存储块的访问速度比其他存储块要快时,那么如果将section拷到该存储块来执行或访问,那么速度将会有所提高,覆盖图描述就很适合这种情形.文法如下,SECTIONS {...OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )]{SECNAME1{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [:PHDR...] [=FILL]SECNAME2{OUTPUT-SECTION-COMMANDOUTPUT-SECTION-COMMAND...} [:PHDR...] [=FILL]...} [>REGION] [:PHDR...] [=FILL]...}由以上文法可以看出,同一覆盖图内的section具有相同的VMA.SECNAME2的LMA为SECTNAME1的LMA加上SECNAME1的大小,同理计算SECNAME2,3,4...的LMA.SECNAME1的LMA由LDADDR决定,如果它没有被指定,那么由START决定,如果它也没有被指定,那么由当前定位符号的值决定.NOCROSSREFS关键字指定各section之间不能交叉引用,否则报错.对于OVERLAY描述的每个section,连接器将定义两个符号__load_start_SECNAME和__load_stop_SECNAME,这两个符号的值分别代表SECNAME section的LMA地址的开始和结束.连接器处理完OVERLAY描述语句后,将定位符号的值加上所有覆盖图内section大小的最大值.看个例子吧,SECTIONS{...OVERLAY 0x1000 : AT (0x4000){.text0 { o1/*.o(.text) }.text1 { o2/*.o(.text) }}...}.text0 section和.text1 section的VMA地址是0x1000,.text0 section加载于地址0x4000,.text1 section紧跟在其后.程序代码,拷贝.text1 section代码,extern char __load_start_text1, __load_stop_text1;memcpy ((char *) 0x1000, &__load_start_text1,&__load_stop_text1 - &__load_start_text1);8. 内存区域命令---------------注意:以下存储区域指的是在程序地址空间内的.在默认情形下,连接器可以为section分配任意位置的存储区域.你也可以用MEMORY命令定义存储区域,并通过输出section描述的> REGION属性显示地将该输出section限定于某块存储区域,当存储区域大小不能满足要求时,连接器会报告该错误.MEMORY命令的文法如下,MEMORY {NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2...}NAME :存储区域的名字,这个名字可以与符号名、文件名、section名重复,因为它处于一个独立的名字空间.ATTR :定义该存储区域的属性,在讲述SECTIONS命令时提到,当某输入section没有在SECTIONS命令内引用时,连接器会把该输入section直接拷贝成输出section,然后将该输出section放入内存区域内.如果设置了内存区域设置了ATTR属性,那么该区域只接受满足该属性的section(怎么判断该section是否满足?输出section描述内好象没有记录该section的读写执行属性).ATTR属性内可以出现以下7个字符,R 只读sectionW 读/写sectionX 可执行sectionA …可分配的‟sectionI 初始化了的sectionL 同I! 不满足该字符之后的任何一个属性的sectionORIGIN :关键字,区域的开始地址,可简写成org或oLENGTH :关键字,区域的大小,可简写成len或l例子,MEMORY{rom (rx) : ORIGIN = 0, LENGTH = 256Kram (!rx) : org = 0x40000000, l = 4M}此例中,把在SECTIONS命令内*未*引用的且具有读属性或写属性的输入section放入rom区域内,把其他未引用的输入section放入ram.如果某输出section要被放入某内存区域内,而该输出section又没有指明ADDRESS属性,那么连接器将该输出section放在该区域内下一个能使用位置.9. PHDRS命令------------该命令仅在产生ELF目标文件时有效.ELF目标文件格式用program headers程序头(程序头内包含一个或多个segment程序段描述)来描述程序如何被载入内存.可以用objdump -p命令查看. 当在本地ELF系统运行ELF目标文件格式的程序时,系统加载器通过读取程序头信息以知道如何将程序加载到内存.要了解系统加载器如何解析程序头,请参考ELF ABI文档.在连接脚本内不指定PHDRS命令时,连接器能够很好的创建程序头,但是有时需要更精确的描述程序头,那么PAHDRS命令就派上用场了.注意:一旦在连接脚本内使用了PHDRS命令,那么连接器**仅会**创建PHDRS命令指定的信息,所以使用时须谨慎.PHDRS命令文法如下,PHDRS{NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ][ FLAGS ( FLAGS ) ] ;}其中FILEHDR、PHDRS、AT、FLAGS为关键字.NAME :为程序段名,此名字可以与符号名、section名、文件名重复,因为它在一个独立的名字空间内.此名字只能在SECTIONS命令内使用.一个程序段可以由多个…可加载‟的section组成.通过输出section描述的属性:PHDRS可以将输出section加入一个程序段,: PHDRS中的PHDRS为程序。
uboot分析
uboot分析BootLoader指系统启动后,在操作系统内核运行之前运行的一段小程序。
通过BootLoader,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。
因此,在嵌入式世界里建立一个通用的BootLoader 几乎是不可能的。
尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。
BootLoader的操作模式一般分为自启动模式和交互模式。
自启动模式:BootLoaderd从目标机上的某个固态设备上将操作系统加载到RAM中运行,整个过程没有用户的介入;交互模式:目标机上的BootLoader将通过串口或网络等通信手段从开发板上下载内核映像和根文件系统映像等到RAM中,可以写到目标机上的固态存储介质中,或者直接进行系统的引导。
也可以通过串口接收用户的命令。
BootLoader基本功能:初始化相关硬件;把BootLoader自搬移到内存中;执行用户的命令(访问环境变量;通过网络/串口通信;读写RAM/Flash);加载并执行内核。
一个嵌入式Linux系统从软件的角度看通常可以分为四个部分:BootLoader、Linux内核、跟文件系统及用户的应用程序。
BootLoader处于系统的最底层,运行于系统启动的最初阶段。
系统加电或复位后,所有CPU都会从某个地址开始执行,这是由处理器设计决定的。
比如,X86的复位向量在高地址端,ARM处理器在复位时从地址0x00000000取第一条指令。
嵌入式系统的开发板都要把板上ROM或Flash映射到这个地址。
因此,必须把Bootloader程序存储在相应的Flash位置。
系统加电后,CPU将首先执行它。
BootLoader的启动过程可以是单阶段的,也可以是多阶段的。
U-Boot编译过程完全分析
U-Boot编译过程完全分析2.1U-BootMakefile分析2.1.1U-Boot编译命令对于mini2440开发板,编译U-Boot需要执行如下的命令:$makemini2440_config$makeall使用上面的命令编译U-Boot,编译生成的所有文件都保存在源代码目录中。
为了保持源代码目录的干净,可以使用如下命令将编译生成的文件输出到一个外部目录,而不是在源代码目录中,下面的2种方法都将编译生成的文件输出到/tmp/build目录:$e某portBUILD_DIR=/tmp/build$makemini2440_config$makeall或$makeO=/tmp/buildmini2440_config(注意是字母O,而不是数字0)$makeall为了简化分析过程,方便读者理解,这里主要针对第一种编译方式(目标输出到源代码所在目录)进行分析。
2.1.2U-Boot配置、编译、连接过程1.U-Boot配置过程(1)定义主机系统架构HOSTARCH:=$(helluname-m|\\ed-e/i.86/i386/\\-e/un4u/parc64/\\-e/arm.某/arm/\\-e/a110/arm/\\-e/powerpc/ppc/\\-e/ppc64/ppc/\\-e/macppc/ppc/)“ed–e”表示后面跟的是一串命令脚本,而表达式“/abc/def/”表示要从标准输入中,查找到内容为“abc”的,然后替换成“def”。
其中“abc”表达式用可以使用“.”作为通配符。
(2)定义主机操作系统类型HOSTOS:=$(helluname-|tr'[:upper:]''[:lower:]'|\\ed-e'/\\(cygwin\\).某/cygwin/')'[:upper:]''[:lower:]'”作用是将标准输入中的所有大写字母转换为响应的小写字母。
uboot启动代码详解分析
·1 引言在专用的嵌入式板子运行GNU/Linux 系统已经变得越来越流行。
一个嵌入式Linux 系统从软件的角度看通常可以分为四个层次:1. 引导加载程序。
固化在固件(firmware)中的boot 代码,也就是Boot Loader,它的启动通常分为两个阶段。
2. Linux 内核。
特定于嵌入式板子的定制内核以及内核的启动参数。
3. 文件系统。
包括根文件系统和建立于Flash 内存设备之上文件系统,root fs。
4. 用户应用程序。
特定于用户的应用程序。
有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。
常用的嵌入式GUI 有:MicroWindows 和MiniGUI 等。
引导加载程序是系统加电后运行的第一段软件代码。
回忆一下PC 的体系结构我们可以知道,PC 机中的引导加载程序由BIOS(其本质就是一段固件程序)和位于硬盘MBR 中的OS Boot Loader(比如,LILO 和GRUB 等)一起组成。
BIOS 在完成硬件检测和资源分配后,将硬盘MBR 中的Boot Loader 读到系统的RAM 中,然后将控制权交给OS Boot Loader。
Boot Loader 的主要运行任务就是将内核映象从硬盘上读到RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。
而在嵌入式系统中,通常并没有像BIOS 那样的固件程序(注,有的嵌入式CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由Boot Loader 来完成。
比如在一个基于ARM7TDMI core 的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000 处开始执行,而在这个地址处安排的通常就是系统的Boot Loader 程序。
·2 bootloader简介简单地说,Boot Loader (引导加载程序)程序,它的作用就是加载操作系统,它是系统加电后运行的第一段软件代码。
uboot 读 文件 解析 字段
uboot 读文件解析字段English: U-Boot is a popular bootloader used in many embedded systems. It provides a flexible and configurable environment for booting an operating system. When it comes to reading files and parsing fields in U-Boot, there are a few steps involved. First, U-Boot needs to have the file system support enabled, either built into the U-Boot binary or as a separate module. Once the file system support is available, U-Boot can use various commands and functions to read files from the file system. These commands typically include 'fatload', 'ext4load', 'tftp', and others, depending on the supported file systems. The 'fatload' command, for example, allows U-Boot to load a file from a FAT filesystem into memory.After reading the file into memory, U-Boot can then parse the file to extract specific fields or data. The exact process of parsing depends on the file's format and structure. U-Boot provides functions and APIs that can be used to parse various file formats such as binary files, configuration files (like ''), and scripts (like ''). These functions help extract specific fields or data from the file and store them in memory variables for later use.To parse a file, one needs to identify the structure of the file and the desired fields to be extracted. This may involve understanding thefile's header, sections, and the format of the fields within those sections. U-Boot provides various functions to help navigate through the file's structure, such as 'fopen', 'fread', and 'fclose' for binary files, or 'env_get' for configuration files. By using these functions, one can read and parse the file step by step, extracting the required fields or data.In summary, reading files and parsing fields in U-Boot involves enabling file system support, using appropriate commands/functions to read files, and then utilizing U-Boot's provided functions and APIs to parse the file's structure and extract desired fields or data. Understanding the file's format and structure is crucial for successful parsing.中文翻译: U-Boot 是许多嵌入式系统中常用的引导加载程序。
uboot代码详细分析
uboot代码详细分析目录u-boot-1.1.6之cpu/arm920t/start.s分析 (2)u-boot中.lds连接脚本文件的分析 (12)分享一篇我总结的uboot学习笔记(转) (15)U-BOOT内存布局及启动过程浅析 (22)u-boot中的命令实现 (25)U-BOOT环境变量实现 (28)1.相关文件 (28)2.数据结构 (28)3.ENV的初始化 (30)3.1env_init (30)3.2env_relocate (30)3.3env_relocate_spec (31)4.ENV的保存 (31)U-Boot环境变量 (32)u-boot代码链接的问题 (35)ldr和adr在使用标号表达式作为操作数的区别 (40)start_armboot浅析 (42)1.全局数据结构的初始化 (42)2.调用通用初始化函数 (43)3.初始化具体设备 (44)4.初始化环境变量 (44)5.进入主循环 (44)u-boot编译过程 (44)mkconfig文件的分析 (47)从NAND闪存中启动U-BOOT的设计 (50)引言 (50)NAND闪存工作原理 (51)从NAND闪存启动U-BOOT的设计思路 (51)具体设计 (51)支持NAND闪存的启动程序设计 (51)结语 (53)参考文献 (53)U-boot给kernel传参数和kernel读取参数—structtag(以及补充) (53)1、u-boot给kernel传RAM参数 (54)2、Kernel读取U-boot传递的相关参数 (56)3、关于U-boot中的bd和gd (59)U-BOOT源码分析及移植 (60)一、u-boot工程的总体结构: (61)1、源代码组织 (61)2.makefile简要分析 (61)3、u-boot的通用目录是怎么做到与平台无关的? (63)4、smkd2410其余重要的文件: (63)二、u-boot的流程、主要的数据结构、内存分配 (64)1、u-boot的启动流程: (64)2、u-boot主要的数据结构 (66)3、u-boot重定位后的内存分布: (68)三、u-boot的重要细节。
u-boot.lds文件简介
u-boot.lds⽂件简介可执⾏⽂件由许多链接在⼀起的对象⽂件组成。
对象⽂件有许多节,如⽂本、数据、init 数据、bss等。
这些对象⽂件都是由⼀个称为链接器脚本(*lds)的⽂件链接并装⼊的。
这个链接器脚本的功能是将输⼊对象⽂件的各节映射到输出⽂件中;换句话说,它将所有输⼊对象⽂件都链接到单⼀的可执⾏⽂件中,将该可执⾏⽂件的各节装⼊到指定地址处。
因此在分析u-boot代码是,⾸先应关注的是u-boot.lds⽂件,它位于$(U-BOOT_SRC_ROOT)/board/$(BOARD_NAME)⽬录下。
1/*2* OUTPUT_FORMAT(default, big, little),在链接的时候,如果使⽤了-EB的命令⾏参数,则使⽤这⾥的big3* 参数指定的字节序,如果使⽤了-EL;的命令⾏参数,则使⽤这⾥的little参数指定的字节序,如果没有使⽤4* 任何命令⾏参数,则使⽤这⾥的default参数指定的字节序。
5* 由$(SRC_ROOT)/board/samsung/smdk6410/u-boot.lds中的定义可见,不管在链接的时候使⽤了何种命令⾏参数,6* 输出的⽬标⽂件都是使⽤elf32-littlearm⽅式的字节序。
7*/8/*以下语句是指定输出可执⾏⽂件是elf格式,32位ARM指令,⼩端 */9 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")10/*指定输出可执⾏⽂件的平台为ARM*/11 OUTPUT_ARCH(arm)12/*指定输出可执⾏⽂件的起始代码段为_start.*/13 ENTRY(_start)14 SECTIONS15 {16 . = 0x00000000; /*定位当前地址为0x0地址*/1718 . = ALIGN(4); /*指定代码以4字节对齐*/19 .text : /*指定代码段 */20 {21 arch/arm/cpu/arm1176/start.o (.text)22 board/samsung/smdk6410/libsmdk6410.o (.text)23 *(.text)24 }2526 . = ALIGN(4);27 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } /*指定只读数据段 */2829 . = ALIGN(4);30 .data : { *(.data) } /*指定读写数据段*/3132 . = ALIGN(4);33 .got : { *(.got) } /*指定got段, got段式是uboot⾃定义的⼀个段, ⾮标准段*/343536 . = ALIGN(4);37 .u_boot_list : {38 #include <u-boot.lst>39 }4041 . = ALIGN(4);42 .mmudata : { *(.mmudata) }4344 . = ALIGN(4);4546 .rel.dyn : {47 __rel_dyn_start = .;48 *(.rel*)49 __rel_dyn_end = .;50 }5152 .dynsym : {53 __dynsym_start = .;54 *(.dynsym)55 }5657 _end = .;5859 .bss __rel_dyn_start (OVERLAY) : {60 __bss_start = .;61 *(.bss)62 . = ALIGN(4);63 __bss_end__ = .;64 }6566 /DISCARD/ : { *(.dynstr*) }67 /DISCARD/ : { *(.dynamic*) }68 /DISCARD/ : { *(.plt*) }69 /DISCARD/ : { *(.interp*) }70 /DISCARD/ : { *(.gnu*) }71 }。
u-boot lds文件详解
对于.lds文件,决定一个可执行程序的各个段的存储位置,以及入口地址,这也是链接定位的作用。
这里以u- boot的lds为例说明uboot的链接过程。
首先看一下GNU官方网站上对.lds文件形式的完整描述:SECTIONS {...secname start BLOCK(align) (NOLOAD) : AT ( ldadr ){ contents } >region :phdr =fill...}secname和contents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段,以下是对这个描述中的一些关键字的解释。
1、secname:段名2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)3、start:是段的重定位地址,本段连接(运行)的地址,如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上。
start可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段存储(加载)的地址,如果不使用这个选项,则加载地址等于运行地址,通过这个选项可以控制各段分别保存于输出文件中不同的位置。
例:/* nand.lds */SECTIONS {firtst 0x00000000 : { head.o init.o }second 0x30000000 : AT(4096) { main.o }}以上,head.o放在0x00000000地址开始处,init.o放在head.o 后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但它的运行地址在0x30000000,运行之前需要从0x1000(加载地址处)复制到0x30000000(运行地址处),此过程也就需要读取 flash,把程序拷贝到相应位置才能运行。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
uboot 2013.07 lds 分析
转载自: uboot 2013.07 lds 分
析 .OUTPUT_FORMAT("elf32-littlearm", "elf32-
littlearm",
//指定输出可执行文件是 elf 格式 ,32 位
ARM 指令 ,小端 OUTPUT_ARCH(arm) //指定输出可执行
文件的平台为 ARMENTRY(_start) // 指定函数入口点为 _start 。
cpu/arm920t/start.S
0x00000000; // 指定可执行 image 文件的全局入口点,通常
这个地址都放在 ROM(flash)0x0 位置。
必须使编译器知道这
以4 字节对齐 .text : //代码段
00000000 T __image_copy_start 见 __image_copy_start 等同于 _start
CPUDIR/start.o (.text*) // 代码段的第一个代码部分 ALIGN(4); .rodata :
"elf32-littlearm") 中定义 SECTIONS{
个地址,通常都是修改此处来完成 . = ALIGN(4); // 代码
*(.__image_copy_start) // 在 System.map
匚=f
00000000 T _start, *(.text*)
//其它代码部分
{ *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } 对应原来的 U_BOOT_CMD 对于那个的段 .
KEEP(*(SORT(.u_boot_list*))); * 在 System.map 中
0006f52c D _u_boot_list_2_env_clbk_2_flags
0006f534 D _u_boot_list_2_env_clbk_2_loadaddr
0006f53c B __bss_base
0006f53c B __bss_start
0006f53c B monitor_flash_len
0006f53c D __image_copy_end
0006f53c D __rel_dyn_start
//指定只读数据段 . = ALIGN(4); .data : { // 指定读 / 写数据段
*(.data*) ALIGN(4); . = ALIGN(4); .u_boot_list : {
// } . = ALIGN(4);/*
*/ .image_copy_end :
*(.__image_copy_end) } .rel_dyn_start : //0006f53c D __rel_dyn_start
*(.rel*) //.rel 段落保存了相对跳转的地址
和相对 } .rel_dyn_end : //00078874 R
this MMU section is used by pxa at present but should not be used by new boards/CPUs. bss_start and __bss_end, see arch/arm/lib/bss.c *
bss_base and __bss_limit are for linker only
(overlay
容 .bss_start __rel_dyn_start (OVERLAY) :
0006f53c D __image_copy_end
.bss_end __bss_limit (OVERLAY) :
KEEP(*(.__bss_end)); //000bd6a0 B
bss_end } /DISCARD/ : { *(.dynsym) }
*(.__rel_dyn_start) } .rel.dyn :
跳转的类型
rel_dyn_end *(.__rel_dyn_end)
end = .; //00078874 A _end /*
* Deprecated: */
ALIGN(4096); .mmutable :
*(.mmutable) }/* * Compiler-generated
ordering) */ //保存了未初始化的全局变量的内
{ //0006f53c B __bss_start
0006f53c D __rel_dyn_start
KEEP(*(.__bss_start));
bss_base } .bss
bss_base (OVERLAY) : { //0006f53c B __bss_base *(.bss*) . = ALIGN(4);
bss_limit = .; //000bd6a0 B
bss_limi }
/DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }}。