要区分“存储地址”和“运行地址”这两个概念,“存储地址”就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。“运行地址”就是代码运行的时候所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。比如 I.MX6U 支持 SD 卡、EMMC、NAND 启动,因此代码可以存储到 SD 卡、EMMC 或者 NAND 中,但是要运行的话就必须将代码从 SD 卡、EMMC 或者NAND 中拷贝到其运行地址(链接地址)处,“存储地址”和“运行地址”可以一样,比如STM32 的存储起始地址和运行起始地址都是 0X08000000。本教程所有的裸机例程都是烧写到 SD 卡中,上电以后 I.MX6U 的内部 boot rom 程序会将可执行文件拷贝到链接地址处,这个链接地址可以在 I.MX6U 的内部 128KB RAM 中(0X900000~0X91FFFF),也可以在外部的 DDR 中。本教程所有裸机例程的链接地址都在 DDR中,链接起始地址为0X87800000。I.MX6U-ALPHA 开发板的 DDR 容量有两种:512MB 和256MB,起始地址都为 0X80000000,只不过 512MB 的终止地址为 0X9FFFFFFF,而 256MB 容量的终止地址为 0X8FFFFFFF。之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址,不容易记混。
下面是正点原子裸机led实例的Makefile:
led.bin:led.s arm-linux-gnueabihf-gcc -g -c led.s -o led.o arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin arm-linux-gnueabihf-objdump -D led.elf > led.dis clean: rm -rf *.o led.bin led.elf led.dis
1、arm-linux-gnueabihf-gcc 编译文件
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
上述命令就是将 led.s 编译为 led.o,其中“-g”选项是产生调试信息,GDB 能够使用这些
调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文
件名字,这里我们指定 led.s 编译完成以后的文件名字为 led.o。执行上述命令以后就会编译生
成一个 led.o 文件,如图 8.4.1.1 所示:
2、arm-linux-gnueabihf-ld 链接文件
arm-linux-gnueabihf-ld 用来将众多的.o 文件链接到一个指定的链接位置,STM32程序也有链接只不过用户不知道,查看工程的*.map文件如下:
stm32程序所有源文件编译成.o文件,这些.o 文件就是从0x08000000这个链接地址开始依次存放,最终生成一个可以下载的 hex 或者 bin 文件
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。
3、arm-linux-gnueabihf-objcopy 格式转换
arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为led.bin 文件,命令如下:
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
4、arm-linux-gnueabihf-objdump 反汇编
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
arm-linux-gnueabihf-objdump -D led.elf > led.dis
从反汇编结果:
led.elf: 文件格式 elf32-littlearm Disassembly of section .text: 87800000 <_start>: 87800000: e59f0080 ldr r0, [pc, #128] ; 87800088 <loop+0x4> 87800004: e3e01000 mvn r1, #0 87800008: e5801000 str r1, [r0] 8780000c: e59f0078 ldr r0, [pc, #120] ; 8780008c <loop+0x8> 87800010: e5801000 str r1, [r0] 87800014: e59f0074 ldr r0, [pc, #116] ; 87800090 <loop+0xc> 87800018: e5801000 str r1, [r0] 8780001c: e59f0070 ldr r0, [pc, #112] ; 87800094 <loop+0x10> 87800020: e5801000 str r1, [r0] 87800024: e59f006c ldr r0, [pc, #108] ; 87800098 <loop+0x14> 87800028: e5801000 str r1, [r0] 8780002c: e59f0068 ldr r0, [pc, #104] ; 8780009c <loop+0x18>
可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以 0X87800000 为起始地址的区域
代码烧写
我们在调试裸机和 Uboot 的时候是将代码下载到 SD 中,因为方便嘛,当调试完成以后量产的时候要将裸机或者 Uboot 烧写到 SPI NOR Flash、EMMC、NAND 等这些存储介质中的。
使用 imxdownload 向 SD 卡烧写 led.bin 文件,命令格式如下: ./imxdownload <.bin file> <SD device>
./imxdownload led.bin /dev/sdd //不能烧写到/dev/sda 或 sda1 设备里面!那是系统磁盘
烧写完成以后会在当前工程目录下生成一个 load.imx 的文件,load.imx 这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容,在 led.bin 文件前面添加了一些数据头以后生成的。最终烧写到 SD 卡里面的就是这个 load.imx 文件,而非led.bin。至于具体添加了些什么内容,我们会在下一章讲解
程序的段定义:
汇编系统预定义了一些段名:
.text 表示代码段。
.data 初始化的数据段。
.bss 未初始化的数据段。
.rodata 只读数据段。
#用file命令查看gcc编译文件属性: file main.o ledc.elf main.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped ledc.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped #用size命令查看gcc编译文件信息: $size main.o start.o ledc.elf text data bss dec hex filename 460 0 0 460 1cc main.o 28 0 0 28 1c start.o 680 0 0 680 2a8 ledc.elf
下面是正点原子裸机led实例C语言版本的Makefile:
objs := start.o main.o ledc.bin:$(objs) arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^ arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@ arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis %.o:%.s arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $< %.o:%.S arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $< %.o:%.c arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $< clean: rm -rf *.o ledc.bin ledc.elf ledc.dis
下面是正点原子裸机led实例C语言版本的start.s:
.global _start /* 全局标号 */ /* * 描述:_start函数,程序从此函数开始执行,此函数主要功能是设置C 运行环境。 */ _start: /* 进入SVC模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式*/ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0X80200000 /* 设置栈指针 */ b main /* 跳转到main函数 */
注意“-nostdlib”编译选项,他的意思时不用C、C++标准库,标准库里面有”_start”汇编标号,不用C库那么就要自己写一个汇编文件 初始化栈的指针并跳转到main函数,本例就是start.s。在连接的时候如果用户不指定链接地址,编译器会自动分配链接地址