start.s文件是U-Boot启动最先执行的代码,对start.s 文件的正确理解是整个U-Boot源码理解的开端和基础。要理解start.S 文件,需要具备一些预备知识,下面先介绍这些预备知识。
一、start.s预备知识
1.start.s中的汇编指令
1.1 .globl
汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。.globl伪指令的用法是:
.globl xxx
.globl xxx的作用是将xxx符号声明为外部程序可访问的标签,也就是说xxx符号可以被其他.c .s文件使用。
1.2 .word
.word伪指令的用法是:
.word xxx
.word xxx的作用就是在当前位置放一个word型的值,这个值就是xxx。举例来说:
_rWTCON:
.word 0x15300000
上面两句的意思就是在当前地址,即_rWTCON处放一个值0x15300000。
1.3 .balignl
.balignl是.balign的变体,.balign的意思是,在以当前地址开始,地址计数器必须是以第一个参数为整数倍的地址为结尾,并且在前面填入一个字节长度的信息,信息内容为第二个参数。举例来说:
.balign 8, 0xde
上面这条语句的意思是,在以当前地址开始,在地址为8的倍数的位置的前面填入一个字节内容为0xde的内容。如果当前地址正好是8的倍数,则没有东西被写入到内存。
那么以此类推,.balignw则表示第二个参数填入的内容长度为一个字的长度,即16位,所以一般有这样的形式出现:
.balignw 4,0x368d
以此类推,.balignl这个指令用来填写一个双字,即内容长度为4个字节。举例来说:
.balignl 16,0xdeadbeef
上面这条语句的意思是,在以当前地址开始,在地址为16的倍数的位置的前面填入4个字节长度的内容,填入的内容为0xdeadbeef。
1.4 .align
.align伪指令的用法是:
.align n
.align n 它的含义就是使得下面的代码按一定规则对齐。.align n 指令的对齐值有两种方案:n 或 2^n 。各种平台最初的汇编器一般都不是gas ,采取方案1 或2 的都很多,gas 的目标是取代原来的汇编器,必然要保持和原来汇编器的兼容,因此在gas 中如何解释.align 指令会显得有些混乱,原因在于保持兼容。arm-linux 是按照 2^n 的方案对齐的,需要说明的是这个对齐和ld-script 里的对齐不同,不是一会事。下面的英文就不同平台的对齐进行了说明:版本2.11.92.0.12 的gas 的info(Mandrake 8.2 上的) 这样说:
The way the required alignment is specified varies from system to system. For the a29k, hppa, m68k, m88k, w65, sparc, and Hitachi SH, and i386 using ELF format, the first expression is the alignment request in bytes. For example .align 8 advances the location counter until it is a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
For other systems, including the i386 using a.out format, and the arm and strongarm, it is the number of low-order zero bits the location counter must have after advancement. For example `.align 3' advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
从这段文字来看,ARM 的 .align 5就是 2 的 5次方对齐,也就是 4 字节对齐,通过反汇编也可以看出对齐方式。
1.5 .macro ... .endm
.macro ... .endm伪指令的作用相当于c语言的#define,即宏定义。
1.6 b和bl
b指令是ARM分支指令,实现程序的跳转。其语法是:
b{} label
地址label是以一个有符号的相对于pc的偏移量保存在指令中,且必须被限制在分支指令的约32MB范围内。
大多数汇编语言通过使用地址标号来隐藏分支指令编码的细节。地址标号放在一行的开始处,汇编器会记录该行指令的地址,用于计算跳转的偏移量。
bl指令和b指令类似,区别在于bl指令是带返回的跳转,它会把bl后面的的第一条指令地址赋值给lr寄存器。
1.7 ldr和str
ldr用于把存储器里的一个32位数据传入至一个处理器寄存器。其语法是:
ldr reg, address
其功能是把存储器地址address处的一个32位数据传入至处理器寄存器reg。
str用于把一个32位数据存储至指定地址。其语法是:
str reg, address
其功能是把reg的值存储至address地址处。
1.8 mrs和msr
mrs指令用于把状态寄存器(cpsr或spsr)的值传送到通用寄存器。指令语法:
mrs rd, cpsr|spsr
msr指令用于把通用寄存器的值传送到状态寄存器(cpsr或spsr)。指令语法:
mrs cpsr|spsr, rm
1.9 bic
bic指令用于实现逻辑位清除。指令语法:
bic rd, rn, n
其功能相当于rd = rn & (~n)。
1.10 orr
orr指令用于实现32位逻辑或。指令语法:
bic rd, rn, n
其功能相当于rd = rn | n。
1.11 mrc和mcr
mrc和mcr是协处理器传送指令。mrc指令把协处理器寄存器的值传送至通用寄存器。mcr指令把通用寄存器的值传送至协处理器寄存器。指令语法:
mrc|mcr cp, opcode1, rd, cn, cm, {, opcode2}
在协处理器指令语法中,cp代表协处理器编号,为p0~p15。opcode描述要在协处理器中执行的操作。cn、cm描述在协处理器中的寄存器(cn一般是主寄存器,cm是辅寄存器)。协处理器的操作和寄存器依赖于具体使用的协处理器。协处理器15(cp15)是为系统控制预留的,如内存管理、写缓冲控制、cache控制及寄存器识别等。
1.12 move
move指令把一个32位数送到一个寄存器。指令语法:
move rd, n
n是32为数,rd是寄存器,指令结果相当于rd = n。
1.13 adr
adr是一条伪指令,它把一个相对地址写入寄存器中,它将会使用一个包括pc相对地址的表达式进行编码。指令语法:
adr rd, label
rd是寄存器,label是地址标号。adr指令以程序计数器pc的基值与label偏移量相加得到的和赋给rd。比如start.s的第149行:
adr r0, _start
该语句的意思是,如果这段代码在 0x02000000 (FLASH起始地址)运行,即此时pc基值=0x02000000,_start偏移量等于0,那么adr r0, _start 得到 r0 = 0x02000000。如果这段代码在地址 0x81008000(Boot在RAM中加载地址)运行,即此时pc基值=0x81008000,_start偏移量等于0,那么r0就是 0x81008000 了。
1.14 ldm和stm指令
ldm指令从目标地址装载多个数据至多个寄存器。stm指令把多个寄存器的值存储至目标地址。指令语法:
ldm|stm rd{!}, regs
ldm指令从寄存器rd里的地址装载多个数据至多个寄存器regs(比如r3-r10,共8个寄存器)。
ldm和stm指令有多种不同的寻址模式,分别是ia(执行后增加)、ib(执行前增加)、da(执行后减少)、db(执行前减少)。比如:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
第一条指令表示从r0地址装载完4个字节数据至r3后,地址自动增加4,然后从新地址装载4个字节数据至r4后,以此类推。
第2条指令表示把r3的值存储至r1地址后,地址自动增加4,然后把r4的值存储至新地址,以此类推。
1.15 cmp
cmp指令是比较指令,指令语法:
cmp rn, n
cmp指令根据rn减n的值设置cpsr的z标志位。如果rn等于n,则cpsr的z标志位为1。如果rn不等于n,则cpsr的z标志位为0。
在设置cpsr的z标志位后,其他指令可通过条件执行来改变程序的执行流程。比如ble指令,如果cpsr的z标志位为1,则程序跳转。
1.16 add和sub
add指令是32位加法指令,指令语法:
add rd, rn, n
其相当于rd = rn + n。
sub指令是32位减法指令,指令语法:
sub rd, rn, n
其相当于rd = rn - n。
2. u-boot.lds
对于u-boot.lds文件,它定义了整个程序编译之后的链接过程,决定了一个可执行程序的各个段的存储位置。
先看一下GNU官方网站上对.lds文件形式的完整描述:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
secname和 contents是必须的,其他的都是可选的。下面介绍几个常用的:
1)secname
secname表示段名
2)contents
决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(bss段、data段、rodata段、text段)。下面简单介绍下
bss段、data段、rodata段、text段:
bss段 (bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。bss是英文Block Started by Symbol的简称。BSS段属于静态内存分配。该段用于存储未初始化的全局变量或者是默认初始化为0的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。
data段用于存储初始化的全局变量,初始化为0的全局变量出于编译优化的策略还是被保存在BSS段。
rodata段也叫常量区,用于存放常量数据。
text段是用于存放程序代码的,编译时确定只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text段合在一起,但不是简单的将它们“堆”在一起就完事,还需要处理各个段之间的函数引用问题。
3)start
本段链接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
4)AT(ldadr)
定义本段存储(加载)的地址。
下面看下freescale的mx28系列的u-boot.lds文件:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm926ejs/start.o (.text)
*(.text)
}
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss (NOLOAD) : { *(.bss) }
_end = .;
}
第1行:指定输出可执行文件是elf格式,32位ARM指令,小端。
第2行:指定输出可执行文件的平台为ARM。
第3行:指定输出可执行文件的起始代码段为_start。
第6行:指定整个u-boot程序从地址0x00000000开始链接。
第7行:指定代码以4字节对齐。
第8-12行:表示该段的名字为.text,第10行指定start.s文件中的text段位于地址0x00000000,u-boot程序的其他text段位于其后面。
第13行:表示该段的名字为.rodata,指定u-boot程序的rodata段位于text段后面。
第15行:表示该段的名字为.data,指定u-boot程序的data段位于rodata段后面。
第17行:表示该段的名字为.got,指定u-boot程序的got段位于data段后面。got段是uboot自定义的一个段, 非标准段。
第20行:表示把当前地址值赋给全局变量__u_boot_cmd_start(__u_boot_cmd_start 定义在其他文件里) 。
第21行:表示该段的名字为.u_boot_cmd ,指定u-boot程序的uboot命令位于got段后面。
第22行:表示把当前地址值赋给全局变量__u_boot_cmd_end(__u_boot_cmd_end定义在其他文件里) 。
第25行:表示把当前地址(即bss段的开始位置)赋给全局变量__bss_start (__bss_start 在start.s中被使用) 。
第26行:表示该段的名字为.bss,指定u-boot程序的bss段位于.u_boot_cmd段后面。
第27行:表示把当前地址(即bss段的结束位置)赋给全局变量_end (_end在start.s中被使用) 。
从mx28系列的u-boot.lds文件可以知道生成的u-boot映像的地址分布如下图所示:
start.s文件是U-Boot启动最先执行的代码,对start.s 文件的正确理解是整个U-Boot源码理解的开端和基础。要理解start.S 文件,需要具备一些预备知识,下面先介绍这些预备知识。
一、start.s预备知识
1.start.s中的汇编指令
1.1 .globl
汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。.globl伪指令的用法是:
.globl xxx
.globl xxx的作用是将xxx符号声明为外部程序可访问的标签,也就是说xxx符号可以被其他.c .s文件使用。
1.2 .word
.word伪指令的用法是:
.word xxx
.word xxx的作用就是在当前位置放一个word型的值,这个值就是xxx。举例来说:
_rWTCON:
.word 0x15300000
上面两句的意思就是在当前地址,即_rWTCON处放一个值0x15300000。
1.3 .balignl
.balignl是.balign的变体,.balign的意思是,在以当前地址开始,地址计数器必须是以第一个参数为整数倍的地址为结尾,并且在前面填入一个字节长度的信息,信息内容为第二个参数。举例来说:
.balign 8, 0xde
上面这条语句的意思是,在以当前地址开始,在地址为8的倍数的位置的前面填入一个字节内容为0xde的内容。如果当前地址正好是8的倍数,则没有东西被写入到内存。
那么以此类推,.balignw则表示第二个参数填入的内容长度为一个字的长度,即16位,所以一般有这样的形式出现:
.balignw 4,0x368d
以此类推,.balignl这个指令用来填写一个双字,即内容长度为4个字节。举例来说:
.balignl 16,0xdeadbeef
上面这条语句的意思是,在以当前地址开始,在地址为16的倍数的位置的前面填入4个字节长度的内容,填入的内容为0xdeadbeef。
1.4 .align
.align伪指令的用法是:
.align n
.align n 它的含义就是使得下面的代码按一定规则对齐。.align n 指令的对齐值有两种方案:n 或 2^n 。各种平台最初的汇编器一般都不是gas ,采取方案1 或2 的都很多,gas 的目标是取代原来的汇编器,必然要保持和原来汇编器的兼容,因此在gas 中如何解释.align 指令会显得有些混乱,原因在于保持兼容。arm-linux 是按照 2^n 的方案对齐的,需要说明的是这个对齐和ld-script 里的对齐不同,不是一会事。下面的英文就不同平台的对齐进行了说明:版本2.11.92.0.12 的gas 的info(Mandrake 8.2 上的) 这样说:
The way the required alignment is specified varies from system to system. For the a29k, hppa, m68k, m88k, w65, sparc, and Hitachi SH, and i386 using ELF format, the first expression is the alignment request in bytes. For example .align 8 advances the location counter until it is a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
For other systems, including the i386 using a.out format, and the arm and strongarm, it is the number of low-order zero bits the location counter must have after advancement. For example `.align 3' advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
从这段文字来看,ARM 的 .align 5就是 2 的 5次方对齐,也就是 4 字节对齐,通过反汇编也可以看出对齐方式。
1.5 .macro ... .endm
.macro ... .endm伪指令的作用相当于c语言的#define,即宏定义。
1.6 b和bl
b指令是ARM分支指令,实现程序的跳转。其语法是:
b{} label
地址label是以一个有符号的相对于pc的偏移量保存在指令中,且必须被限制在分支指令的约32MB范围内。
大多数汇编语言通过使用地址标号来隐藏分支指令编码的细节。地址标号放在一行的开始处,汇编器会记录该行指令的地址,用于计算跳转的偏移量。
bl指令和b指令类似,区别在于bl指令是带返回的跳转,它会把bl后面的的第一条指令地址赋值给lr寄存器。
1.7 ldr和str
ldr用于把存储器里的一个32位数据传入至一个处理器寄存器。其语法是:
ldr reg, address
其功能是把存储器地址address处的一个32位数据传入至处理器寄存器reg。
str用于把一个32位数据存储至指定地址。其语法是:
str reg, address
其功能是把reg的值存储至address地址处。
1.8 mrs和msr
mrs指令用于把状态寄存器(cpsr或spsr)的值传送到通用寄存器。指令语法:
mrs rd, cpsr|spsr
msr指令用于把通用寄存器的值传送到状态寄存器(cpsr或spsr)。指令语法:
mrs cpsr|spsr, rm
1.9 bic
bic指令用于实现逻辑位清除。指令语法:
bic rd, rn, n
其功能相当于rd = rn & (~n)。
1.10 orr
orr指令用于实现32位逻辑或。指令语法:
bic rd, rn, n
其功能相当于rd = rn | n。
1.11 mrc和mcr
mrc和mcr是协处理器传送指令。mrc指令把协处理器寄存器的值传送至通用寄存器。mcr指令把通用寄存器的值传送至协处理器寄存器。指令语法:
mrc|mcr cp, opcode1, rd, cn, cm, {, opcode2}
在协处理器指令语法中,cp代表协处理器编号,为p0~p15。opcode描述要在协处理器中执行的操作。cn、cm描述在协处理器中的寄存器(cn一般是主寄存器,cm是辅寄存器)。协处理器的操作和寄存器依赖于具体使用的协处理器。协处理器15(cp15)是为系统控制预留的,如内存管理、写缓冲控制、cache控制及寄存器识别等。
1.12 move
move指令把一个32位数送到一个寄存器。指令语法:
move rd, n
n是32为数,rd是寄存器,指令结果相当于rd = n。
1.13 adr
adr是一条伪指令,它把一个相对地址写入寄存器中,它将会使用一个包括pc相对地址的表达式进行编码。指令语法:
adr rd, label
rd是寄存器,label是地址标号。adr指令以程序计数器pc的基值与label偏移量相加得到的和赋给rd。比如start.s的第149行:
adr r0, _start
该语句的意思是,如果这段代码在 0x02000000 (FLASH起始地址)运行,即此时pc基值=0x02000000,_start偏移量等于0,那么adr r0, _start 得到 r0 = 0x02000000。如果这段代码在地址 0x81008000(Boot在RAM中加载地址)运行,即此时pc基值=0x81008000,_start偏移量等于0,那么r0就是 0x81008000 了。
1.14 ldm和stm指令
ldm指令从目标地址装载多个数据至多个寄存器。stm指令把多个寄存器的值存储至目标地址。指令语法:
ldm|stm rd{!}, regs
ldm指令从寄存器rd里的地址装载多个数据至多个寄存器regs(比如r3-r10,共8个寄存器)。
ldm和stm指令有多种不同的寻址模式,分别是ia(执行后增加)、ib(执行前增加)、da(执行后减少)、db(执行前减少)。比如:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
第一条指令表示从r0地址装载完4个字节数据至r3后,地址自动增加4,然后从新地址装载4个字节数据至r4后,以此类推。
第2条指令表示把r3的值存储至r1地址后,地址自动增加4,然后把r4的值存储至新地址,以此类推。
1.15 cmp
cmp指令是比较指令,指令语法:
cmp rn, n
cmp指令根据rn减n的值设置cpsr的z标志位。如果rn等于n,则cpsr的z标志位为1。如果rn不等于n,则cpsr的z标志位为0。
在设置cpsr的z标志位后,其他指令可通过条件执行来改变程序的执行流程。比如ble指令,如果cpsr的z标志位为1,则程序跳转。
1.16 add和sub
add指令是32位加法指令,指令语法:
add rd, rn, n
其相当于rd = rn + n。
sub指令是32位减法指令,指令语法:
sub rd, rn, n
其相当于rd = rn - n。
2. u-boot.lds
对于u-boot.lds文件,它定义了整个程序编译之后的链接过程,决定了一个可执行程序的各个段的存储位置。
先看一下GNU官方网站上对.lds文件形式的完整描述:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
secname和 contents是必须的,其他的都是可选的。下面介绍几个常用的:
1)secname
secname表示段名
2)contents
决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(bss段、data段、rodata段、text段)。下面简单介绍下
bss段、data段、rodata段、text段:
bss段 (bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。bss是英文Block Started by Symbol的简称。BSS段属于静态内存分配。该段用于存储未初始化的全局变量或者是默认初始化为0的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。
data段用于存储初始化的全局变量,初始化为0的全局变量出于编译优化的策略还是被保存在BSS段。
rodata段也叫常量区,用于存放常量数据。
text段是用于存放程序代码的,编译时确定只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text段合在一起,但不是简单的将它们“堆”在一起就完事,还需要处理各个段之间的函数引用问题。
3)start
本段链接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
4)AT(ldadr)
定义本段存储(加载)的地址。
下面看下freescale的mx28系列的u-boot.lds文件:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm926ejs/start.o (.text)
*(.text)
}
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss (NOLOAD) : { *(.bss) }
_end = .;
}
第1行:指定输出可执行文件是elf格式,32位ARM指令,小端。
第2行:指定输出可执行文件的平台为ARM。
第3行:指定输出可执行文件的起始代码段为_start。
第6行:指定整个u-boot程序从地址0x00000000开始链接。
第7行:指定代码以4字节对齐。
第8-12行:表示该段的名字为.text,第10行指定start.s文件中的text段位于地址0x00000000,u-boot程序的其他text段位于其后面。
第13行:表示该段的名字为.rodata,指定u-boot程序的rodata段位于text段后面。
第15行:表示该段的名字为.data,指定u-boot程序的data段位于rodata段后面。
第17行:表示该段的名字为.got,指定u-boot程序的got段位于data段后面。got段是uboot自定义的一个段, 非标准段。
第20行:表示把当前地址值赋给全局变量__u_boot_cmd_start(__u_boot_cmd_start 定义在其他文件里) 。
第21行:表示该段的名字为.u_boot_cmd ,指定u-boot程序的uboot命令位于got段后面。
第22行:表示把当前地址值赋给全局变量__u_boot_cmd_end(__u_boot_cmd_end定义在其他文件里) 。
第25行:表示把当前地址(即bss段的开始位置)赋给全局变量__bss_start (__bss_start 在start.s中被使用) 。
第26行:表示该段的名字为.bss,指定u-boot程序的bss段位于.u_boot_cmd段后面。
第27行:表示把当前地址(即bss段的结束位置)赋给全局变量_end (_end在start.s中被使用) 。
从mx28系列的u-boot.lds文件可以知道生成的u-boot映像的地址分布如下图所示:
举报