一,IMX6ULL映像文件
1,格式概述
对于IMX6ULL,烧写在EMMC、SD/TF卡上的程序,并不能“自己复制自己”,是“别人把它复制到内存里”。一上电首先运行的是boot ROM上的程序,它从EMMC、SD/TF卡上把程序复制进内存里。
烧写在EMMC、SD卡或是TF卡上的,除了程序本身,还有位置信息、DCD 信息,这些内容合并成一个映像文件,如下图:
这4 部分内容合并成为一个映像文件,烧写在EMMC、SD 卡或TF 卡等启动设备的某个固定地址,boot ROM 程序去这个固定地址读出映像文件。启动设备不同, 固定地址不同,如下图:
2,格式详解
下面的讲解图中,列有C 语言格式的结构体,这些结构体来源于U-boot 的tools 目录下的imximage.h。
2.1 Image Vector Table(IVT)
IVT 会被放在固定的地址,IVT 中是一系列的地址,boot ROM 程序会根据这些地址来确定映像文件中其他部分在哪里。
IVT 格式如下:
要注意的是上图中这4项:
a) header:里面有3项:tag、length、version。length表示IVT的大小,它是32字节。要注意是的,它是大字节序的。
b) entry:用户程序运行时第1条指令的地址,就是程序的链接地址、程序被复制到内存哪里。
c) dcd:映像被复制到内存后,其中的DCD数据的地址。
d) boot data:映像被复制到内存后,其中的boot data的地址。
e) self:映像被复制到内存后,IVT自己所在的地址。
2.2 Boot data
映像被复制到内存后,整个映像文件(IVT之前还有几个扇区数据,比如分区表)所在的地址。
a) start:这是映像文件在内存中的地址,以SD/TF卡为例:
映像文件=(1K数据,内含分区表等信息)+IVT+BootData+DCD+用户数据(bin文件)
注意,IVT并不在映像文件的最前面,start也不是IVT在内存中的地址,而是整个映像文件在内存中的地址:
start = IVT在内存中的地址 - IVT offset
什么意思?假设IVT被保存在启动设备TF卡1024偏移地址处,IVT被复制到内存地址0x87000000,那么start=0x87000000-1024。
所以start表示的是启动设备开头的数据,被复制到内存哪里去。
从它的含义也可以推理出:boot ROM程序会把启动设备开头的数据,复制到内存;而不仅仅是从IVT开始复制。
b) length:保存在启动设备上的整个映像文件的长度,从0地址开始(不是从IVT开始)。
c) plugin:这是一个标记位,当它为1时表示这个映像文件是“plugin”,即插件。
boot ROM程序可以支持有限的启动设备,如果你想双持更多的启动设备比如网络启动、CDROM启动,就需要提供对应的驱动。这些驱动就是“plugin”,我们的教程不涉及,该标记位为0。
Boot data就是用来表示映像文件应该被复制到哪里去,以及它的大小。boot ROM程序就是根据它来把整个映像文件复制到内存去的。
2.3 DCD
“Device Configuration Data”,设备配置数据(DCD),这些DCD将会跟bin文件一起打包烧写在启动设备上。boot ROM程序会从启动设备上读出DCD数据,根据DCD来写对应的寄存器以便初始化芯片。DCD中列出的是对某些寄存器的读写操作,我们可以在DCD中设置DDR控制器的寄存器值,可以在DCD中使用更优的参数设置必需的硬件。这样boot ROM程序就会帮我们初始化DDR和其他硬件,然后才可以把bin程序读到DDR中并运行。
实际上DCD还可以更复杂,它支持多种命令:write data、check data、nop、unlock。我们可以通过write data命令写寄存器,通过check data命令等待寄存器就绪。
2.4 User code and data
就是用户程序或数据,原原本本地添加到映像文件里就可以。
3,实例
制作过程中各项值的计算方法如下图所示:
我们不需要手工去计算,一个mkimage命令就搞定了。上图中各步骤细说如下:
1)确定入口地址entry
我们的程序运行时要放在内存中哪一个位置,这是我们决定的。它被称为入口地址、链接地址。
2)确定映像文件在内存中的地址start
boot ROM程序启动时,会把“Initial Load Region”读出来,“Initial load Region”里含有IVT、Boot data、DCD。boot ROM根据DCD初始化设备后,再把整个映像文件读到内存。
在启动设备上,“Initial Load Region”之后紧跟着我们的程序,反过来说就是我们程序的前面,放着“Initial Load Region”。假设“Initial Load Region”的大小为load_size,那么在内存中“Initial Load Region”的位置start = entry – load_size。
注意:“Initial Load Region”位于启动设备0位置,它的头部并不是IVT,而是一些无用的数据(或是分区信息)。
在IMX6ULL中有一个表格,列出了不同启动设备对应的“Initial Load Region Size”:
3)确定IVT在内存中的地址self:
我们知道IVT在启动设备上某个固定的位置,上或中的“Image Vector Table Offset”:ivt_offset。那么在内存中它的位置可以如下计算:
self = start + ivt_offset = entry – load_size + ivt_offset
4)确定Boot data在内存中的地址boot_data
IVT的大小是32字节,IVT之后就是Boot data,而IVT中的boot_data值表示Boot data在内存中的位置,计算如下:
boot_data = self + 32 = entry – load_size + ivt_offset + 32
5)确定DCD在内存中的地址dcd
Boot data的大小是12字节,Boot data之后就是DCD,而IVT中的dcd值表示DCD在内存中的位置,计算如下:
dcd = boot_data + 12 = entry – load_size + ivt_offset + 44
6)写入DCD的数据:
DCD是用初始化硬件的,特别是初始化DDR。而DDR的初始化非常的复杂、专业,我们一般是使用硬件厂家提供的代码。
我们是使用类似下面的指令来制作映象文件:
./tools/mkimage -n board/freescale/mx6ullevk/imximage.cfg.cfgtmp -T imximage -e 0x87800000 -d u-boot-dtb.bin u-boot-dtb.imx
上述命令中的imximage.cfg.cfgtmp就是厂家提供的,内部截取部分贴出来:
从上图也可以看到imximage.cfg.cfgtmp文件中基本是对寄存器的写操作。
mkimage程序来自u-boot,它会把imximage.cfg.cfgtmp中的内容转换为DCD数据。
7)写入用户程序
经过上述7个步骤,整个映像文件就构造出来了,可以把它烧入启动设备。
二,u-boot.lds链接脚本
1,u-boot.lds
链接脚本控制程序的链接过程,它规定如何把输入文件内的段放入输出文件, 并控制输出文件内的各部分在程序地址空间内的布局。
如果没有编译过 u-boot的话链接脚本为 arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下 u-boot,编译完成以后就会在 uboot 根目录下生成 u-boot.lds
arm-buildroot-linux-gnueabihf-gcc -E -Wp,-MD,./.u-boot.lds.d -D__KERNEL__ -D__UBOOT__ -D__ARM__ -marm -mno-thumb-interwork -mabi=aapcs-linux -mword-relocations -fno-pic -mno-unaligned-access -ffunction-sections -fdata-sections -fno-common
-ffixed-r9 -msoft-float -pipe -march=armv7-a -D__LINUX_ARM_ARCH__=7 -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h -I. -nostdinc -isystem
/home/zhuwg1/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/7.5.0/include -ansi -include ./include/u-boot/u-boot.lds.h -DCPUDIR=arch/arm/cpu/armv7
-D__ASSEMBLY__ -x assembler-with-cpp -P -o u-boot.lds arch/arm/cpu/u-boot.lds
u-boot.lds:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
/*
* 首先定义了二进制程序的输出格式为"elf32-littlearm",
* 架构是"arm",程序入口为"_start"符号;
*/
ENTRY(_start)
SECTIONS
{
//定义了程序链接的基地址,默认是0,通过配置CONFIG_SYS_TEXT_BASE可修改这个默认值
. = 0x00000000;
//地址4字节对齐
. = ALIGN(4);
//代码段
.text :
{
/*
* __image_copy_start和__image_copy_end用于定义需要重定向的段,
* u-boot将启动初始化分为了两个部分,重定向前初始化board_f和
* 重定向后初始化board_r,在重定向之前完成一些必要初始化,
* 包括可能的ddr初始化,然后通过__image_copy_start和__image_copy_end
* 将u-boot搬运到ddr中,并在ddr中进行重定向后初始化。
*(.__image_copy_start)
//vectors段, vectors 段保存中断向量表,从u-boot.lds文件我们知道了vectors.S的代码是存在vectors段中的;从地址映射文件中,vectors段的起始地址也是0X87800000,说明整个uboot的起始地址就是 0X87800000。
*(.vectors)
//将 arch/arm/cpu/armv7/start.s 编译出来的代码放到中断向量表后面
arch/arm/cpu/armv7/start.o (.text*)
// text段,其他的代码段就放到这里
*(.text*)
}
. = ALIGN(4);
// .rodata只读数据段(一般存放常量)
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
//数据段 (一般存放已初始化的全局和静态变量)
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
/*
* u_boot_list段定义了系统中当前支持的所有命令和设备驱动,此段把散落在各个文件中
* 通过U_BOOT_CMD的一系列拓展宏定义的命令和U_BOOT_DRIVER的拓展宏定义的设备驱动收集到一起,
* 并按照名字排序存放,以便后续在命令行快速检索到命令并执行和检测注册的设备和设备树匹配
* probe设备驱动初始化;(设备驱动的probe只在定义了dm模块化驱动时有效)
*/
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
// 在定义了efi运行时相关支持时才会出现使用的段,一般不用关心
.__efi_runtime_start : {
*(.__efi_runtime_start)
}
.efi_runtime : {
*(efi_runtime_text)
*(efi_runtime_data)
}
.__efi_runtime_stop : {
*(.__efi_runtime_stop)
}
.efi_runtime_rel_start :
{
*(.__efi_runtime_rel_start)
}
.efi_runtime_rel : {
*(.relefi_runtime_text)
*(.relefi_runtime_data)
}
.efi_runtime_rel_stop :
{
*(.__efi_runtime_rel_stop)
}
. = ALIGN(8);
//.image_copy_end:uboot 拷贝的结束地址
.image_copy_end :
{
*(.__image_copy_end)
}
/*
* 一般u-boot运行时是根据定义的基地址开始执行,如果加载地址和链接地址
* 不一致则会出现不能执行u-boot的问题。通过一个
* 配置CONFIG_POSITION_INDEPENDENT即可打开地址无关功能,
* 此选项会在链接u-boot时添加-PIE参数。此参数会在u-boot ELF文件中
* 生成rela*段,u-boot通过读取此段中表的相对地址值与实际运行时地址值
* 依次遍历进行修复当前所有需要重定向地址,使其可以实现地址无关运行;
* 即无论链接基地址如何定义,u-boot也可以在任意ram地址
* 运行(一般需要满足最低4K或者64K地址对齐);
*
* 注意此功能只能在sram上实现,因为此功能会在运行时修改文本段数据段中的地址,
* 如果此时运行在片上flash,则不能写flash,导致功能失效无法实现地址无关;
*/
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
//bbs段
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
_start 在文件 arch/arm/lib/vectors.S 中有定义,表示代码执行入口,也就是第一条指令要放的位置。注意armv7的入口在vectors.S(armv8在start.s),中断向量表放在指令入口最开始的位置:可以看到_start后面就是中断向量表,从图中的“.section ".vectors", "ax”可以得到,此代码存放在.vectors 段里面。
打开u-boot.map如下图:因此代码段的排列顺序为:先放中断向量表,也就是vectors.s,然后再放start.s相关内容,最后放其他的.text段(一大堆built-in.o)。
注意这里为什么uboot.map中_start入口地址为什么是0x8780,0000。 链接脚本指定了程序的运行(链接)地址:
程序链接时会指定程序的运行(链接)地址:
arm-buildroot-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic -Ttext 0x87800000 -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o --start-group arch/arm/cpu/built-in.o arch/arm/cpu/armv7/built-in.o arch/arm/imx-common/built-in.o
arch/arm/lib/built-in.o board/freescale/common/built-in.o board/freescale/mx6ullevk/built-in.o cmd/built-in.o common/built-in.o disk/built-in.o drivers/built-in.o drivers/dma/built-in.o drivers/gpio/built-in.o drivers/i2c/built-in.o
drivers/mmc/built-in.o drivers/mtd/built-in.o drivers/mtd/onenand/built-in.o drivers/mtd/spi/built-in.o drivers/net/built-in.o drivers/net/phy/built-in.o drivers/pci/built-in.o drivers/power/built-in.o drivers/power/battery/built-in.o
drivers/power/domain/built-in.o drivers/power/fuel_gauge/built-in.o drivers/power/mfd/built-in.o drivers/power/pmic/built-in.o drivers/power/regulator/built-in.o drivers/serial/built-in.o drivers/spi/built-in.o drivers/usb/cdns3/built-in.o
drivers/usb/common/built-in.o drivers/usb/dwc3/built-in.o drivers/usb/emul/built-in.o drivers/usb/eth/built-in.o drivers/usb/gadget/built-in.o drivers/usb/gadget/udc/built-in.o drivers/usb/host/built-in.o drivers/usb/musb-new/built-in.o
drivers/usb/musb/built-in.o drivers/usb/phy/built-in.o drivers/usb/ulpi/built-in.o fs/built-in.o lib/built-in.o net/built-in.o test/built-in.o test/dm/built-in.o --end-group arch/arm/lib/eabi_compat.o arch/arm/lib/lib.a -Map u-boot.map
运行地址0x87800000定义在:
include/configs/mx6_common.h:86:#define CONFIG_SYS_TEXT_BASE 0x87800000
2,System.map/u-boot.map
U-Boot编译之后会在其顶级目录中生成System.map和u-boot.map两个文件。
System.map用于存放符号表信息。 符号表是所有符号和其对应地址的一个列表,随着每次的编译,就会产生一个新的对应的System.map文件,当运行出错时, 通过System.map中的符号表解析,就可以查到一个地址值对应的变量名。按链接地址由小到大的顺序列出所有符号:
87800000 T __image_copy_start
87800000 T _start
87800020 T _undefined_instruction
87800024 T _software_interrupt
87800028 T _prefetch_abort
8780002c T _data_abort
87800030 T _not_used
87800034 T _irq
87800038 T _fiq
87800040 T IRQ_STACK_START_IN
87800060 t undefined_instruction
878000c0 t software_interrupt
87800120 t prefetch_abort
87800180 t data_abort
878001e0 t not_used
87800240 t irq
878002a0 t fiq
878002e8 T reset
878002ec T save_boot_params_ret
87800328 T c_runtime_cpu_setup
87800338 W save_boot_params
8780033c T cpu_init_cp15
87800390 T cpu_init_crit
87800398 T __v7_flush_dcache_all
878003ac t start_flush_levels
878003b0 t flush_levels
由上面信息可知,_start 符号被链接在最前面的地址0x87800000,它是U-Boot的入口。SDRAM初始化完成后,需要将U-Boot加载到上述地址。
u-boot.map中包含了链接过程中涉及的目标文件和所依赖的库文件,然后所链接的目标文件的先后顺序并列出各目标文件中各符号所链接的地址。
Memory ConfigurationName Origin Length Attributes*default* 0x0000000000000000 0xffffffffffffffffLinker script and memory mapAddress of section .text set to 0x878000000x0000000000000000 . = 0x00x0000000000000000 . = ALIGN (0x4)//代码段.text 0x0000000087800000 0x499f0*(.__image_copy_start).__image_copy_start0x0000000087800000 0x0 arch/arm/lib/built-in.o0x0000000087800000 __image_copy_start*(.vectors)//中断向量表.vectors 0x0000000087800000 0x2e8 arch/arm/lib/built-in.o0x0000000087800000 _start0x0000000087800020 _undefined_instruction0x0000000087800024 _software_interrupt0x0000000087800028 _prefetch_abort0x000000008780002c _data_abort0x0000000087800030 _not_used0x0000000087800034 _irq0x0000000087800038 _fiq0x0000000087800040 IRQ_STACK_START_INarch/arm/cpu/armv7/start.o(.text*)//arch/arm/cpu/armv7/start.o.text 0x00000000878002e8 0xb0 arch/arm/cpu/armv7/start.o0x00000000878002e8 reset0x00000000878002ec save_boot_params_ret0x0000000087800328 c_runtime_cpu_setup0x0000000087800338 save_boot_params0x000000008780033c cpu_init_cp150x0000000087800390 cpu_init_crit//其它代码段*(.text*).text 0x0000000087800398 0x16c arch/arm/cpu/armv7/built-in.o0x0000000087800398 __v7_flush_dcache_all0x000000008780042c v7_flush_dcache_all0x000000008780043c __v7_invalidate_dcache_all0x00000000878004d0 v7_invalidate_dcache_all0x00000000878004e0 lowlevel_init.text.invalidate_icache_all0x0000000087800504 0x18 arch/arm/cpu/armv7/built-in.o0x0000000087800504 invalidate_icache_all... ....rodata 0x00000000878499f0 0x11d08*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))).rodata.efi_boot_services0x00000000878499f0 0xc8 lib/built-in.o.rodata.lcd_pads0x0000000087849ab8 0xf0 board/freescale/mx6ullevk/built-in.o.rodata.uart1_pads0x0000000087849ba8 0x10 board/freescale/mx6ullevk/built-in.o.rodata 0x0000000087849bb8 0x80 arch/arm/lib/built-in.o.rodata 0x0000000087849c38 0x8 drivers/mmc/built-in.o.rodata.CSWTCH.110x0000000087849c40 0xc8 arch/arm/imx-common/built-in.o.rodata.CSWTCH.320x0000000087849d08 0x20 common/built-in.o.rodata.CSWTCH.380x0000000087849d28 0x20 disk/built-in.o.rodata.CSWTCH.570x0000000087849d48 0x14 cmd/built-in.o.rodata.asix_eth_id_table0x0000000087849d5c 0x138 drivers/usb/eth/built-in.o.rodata.asix_eth_ops... ....data 0x000000008785b710 0xec04*(.data*).data.rel.ro 0x000000008785b710 0x0 arch/arm/cpu/armv7/start.o.data.imx_ccm 0x000000008785b710 0x4 arch/arm/cpu/armv7/built-in.o0x000000008785b710 imx_ccm.data.base 0x000000008785b714 0x4 arch/arm/imx-common/built-in.o.data.reset_cause0x000000008785b718 0x4 arch/arm/imx-common/built-in.o.data.display_count0x000000008785b71c 0x4 board/freescale/mx6ullevk/built-in.o0x000000008785b71c display_count.data.bootm_help_text... ....u_boot_list 0x000000008786a320 0x1580*(SORT_BY_NAME(.u_boot_list*)).u_boot_list_2_blk_driver_10x000000008786a320 0x0 drivers/built-in.o.u_boot_list_2_blk_driver_2_mmc0x000000008786a320 0x18 drivers/mmc/built-in.o0x000000008786a320 _u_boot_list_2_blk_driver_2_mmc.u_boot_list_2_blk_driver_2_usb0x000000008786a338 0x18 common/built-in.o0x000000008786a338 _u_boot_list_2_blk_driver_2_usb.u_boot_list_2_blk_driver_30x000000008786a350 0x0 drivers/built-in.o.u_boot_list_2_cmd_10x000000008786a350 0x0 cmd/built-in.o.u_boot_list_2_cmd_10x000000008786a350 0x0 common/built-in.o.u_boot_list_2_cmd_2_base0x000000008786a350 0x1c cmd/built-in.o0x000000008786a350 _u_boot_list_2_cmd_2_base.u_boot_list_2_cmd_2_bdinfo0x000000008786a36c 0x1c cmd/built-in.o0x000000008786a36c _u_boot_list_2_cmd_2_bdinfo... ....__efi_runtime_start0x000000008786b8a0 0x0*(.__efi_runtime_start).__efi_runtime_start0x000000008786b8a0 0x0 arch/arm/lib/built-in.o.efi_runtime 0x000000008786b8a0 0x100*(efi_runtime_text)efi_runtime_text0x000000008786b8a0 0x24 lib/built-in.o0x000000008786b8a0 efi_reset_system0x000000008786b8a4 efi_get_time*(efi_runtime_data)*fill* 0x000000008786b8c4 0x4... ....image_copy_end0x000000008786ba30 0x0*(.__image_copy_end).__image_copy_end0x000000008786ba30 0x0 arch/arm/lib/built-in.o... ....rel_dyn_start 0x000000008786ba30 0x0*(.__rel_dyn_start).__rel_dyn_start0x000000008786ba30 0x0 arch/arm/lib/built-in.o.rel.dyn 0x000000008786ba30 0xa538*(.rel*).rel.got 0x000000008786ba30 0x0 arch/arm/cpu/armv7/start.o... ....rel_dyn_end 0x0000000087875f68 0x0*(.__rel_dyn_end).__rel_dyn_end0x0000000087875f68 0x0 arch/arm/lib/built-in.o.end 0x0000000087875f68 0x0*(.__end).__end 0x0000000087875f68 0x0 arch/arm/lib/built-in.o0x0000000087875f68 _image_binary_end = .0x0000000087876000 . = ALIGN (0x1000)... ....bss_start 0x000000008786ba30 0x0*(.__bss_start).__bss_start 0x000000008786ba30 0x0 arch/arm/lib/built-in.o0x000000008786ba30 __bss_start0x000000008786ba30 __bss_base = ..bss 0x000000008786ba30 0x37f00*(.bss*).bss.modes 0x000000008786ba30 0x8 arch/arm/imx-common/built-in.o.bss.params 0x000000008786ba38 0x4 arch/arm/lib/built-in.o... ....bss_end 0x00000000878a3930 0x0*(.__bss_end).__bss_end 0x00000000878a3930 0x0 arch/arm/lib/built-in.o0x00000000878a3930 __bss_end
可以从u-boot.map获取下面符号的地址:
变量 | 数值 | 描述 |
*(.vectors) | 0x87800000 | 中断向量表 |
arch/arm/cpu/armv7/start.o | 0x87800300 | start.c |
__image_copy_start | 0x87800000 | u-boot拷贝的首地址 |
__image_copy_end | 0x8785dd54 | u-boot拷贝的结束地址 |
.rodata | 0x878499f0 | 只读数据段 |
.data | 0x8785b710 | 数据段 |
__rel_dyn_start | 0x8785dd54 | .rel.dyn 段起始地址 |
__rel_dyn_end | 0x878668f4 | .rel.dyn 段结束地址 |
_image_binary_end | 0x878668f4 | 镜像结束地址 |
__bss_start | 0x8785dd54 | .bss 段起始地址 |
__bss_end | 0x878a8e74 | .bss 段结束地址 |
参考链接:
https://www.cnblogs.com/fuzidage/p/17901516.html