嵌入式交叉编译环境简单使用教程。
GCC交叉编译工具链
安装
将光盘中toolchain工具链拷贝到linux中,选择一个目录安装,我安装在/usr/local/arm目录下,没有该目录可以自行创建
设置环境变量
设置环境变量,可以编辑家目录下的”.bashrc”文件,添加”export PATH=$PATH:/usr/local/arm/bin”,然后执行命令”source .bashrc”即可.
可以为每一个命令都配置一个软连接来简化命令记忆,并且可以达到上面的效果,但是我比较懒,就不想做了:( …
GCC基本入门
基本后缀选项
- -o:指定输出文件
基本命令
- gcc:编译指令
- -c:只编译,不进行链接
- ld:链接指令,得到elf可执行文件,linux中可以直接执行elf文件,但是在嵌入式裸机中需要把elf转换成可烧写的bin或者img文件
- -Ttext:指定链接基地址,就是该程序要链接到的位置
- objcopy:将可执行文件转换为可烧写的bin或者img文件
- -O:指定文件格式”binary”二进制文件
- objdump:将可执行文件反汇编得到一个dis反汇编文件
- -D: 反汇编
objdump反汇编工具
GCC中的反汇编工具,将链接好的elf可执行程序进行反汇编,得到dis汇编源代码,常用格式:arm-linux-objdump -D led_usb.bin > led_usb.dis
- -D指明要进行反汇编
- >用于链接反汇编原文件和输出文件
反汇编可以帮助我们分析理解程序,尤其是链接脚本和链接地址,还可以帮助我们进行程序的调试,以下是led_usb.elf的反汇编代码:
led.elf: file format elf32-littlearm // 文件名:文件格式(大小端)
Disassembly of section .text: // 代码段
00000000 <_start>: // 地址 <标号>
0: e59f009c ldr r0, [pc, #156] ; a4 <delay_loop+0x10> // 链接地址:机器码 汇编指令;
4: e59f109c ldr r1, [pc, #156] ; a8 <delay_loop+0x14>
8: e5810000 str r0, [r1]
c: e3a04010 mov r4, #16
10: e59f5094 ldr r5, [pc, #148] ; ac <delay_loop+0x18>
14: e5854000 str r4, [r5]
00000018 <flash>:
18: e3a00002 mov r0, #2
1c: e59f108c ldr r1, [pc, #140] ; b0 <delay_loop+0x1c>
20: e5810000 str r0, [r1]
24: e3e00008 mvn r0, #8
28: e59f1084 ldr r1, [pc, #132] ; b4 <delay_loop+0x20>
2c: e5810000 str r0, [r1]
30: eb000015 bl 8c <delay>
34: e3a00002 mov r0, #2
38: e59f1070 ldr r1, [pc, #112] ; b0 <delay_loop+0x1c>
3c: e5810000 str r0, [r1]
40: e3e00010 mvn r0, #16
44: e59f1068 ldr r1, [pc, #104] ; b4 <delay_loop+0x20>
48: e5810000 str r0, [r1]
4c: eb00000e bl 8c <delay>
50: e3a00002 mov r0, #2
54: e59f1054 ldr r1, [pc, #84] ; b0 <delay_loop+0x1c>
58: e5810000 str r0, [r1]
5c: e3e00020 mvn r0, #32
60: e59f104c ldr r1, [pc, #76] ; b4 <delay_loop+0x20>
64: e5810000 str r0, [r1]
68: eb000007 bl 8c <delay>
6c: e3a00000 mov r0, #0
70: e59f1038 ldr r1, [pc, #56] ; b0 <delay_loop+0x1c>
74: e5810000 str r0, [r1]
78: e3a00038 mov r0, #56 ; 0x38
7c: e59f1030 ldr r1, [pc, #48] ; b4 <delay_loop+0x20>
80: e5810000 str r0, [r1]
84: eb000000 bl 8c <delay>
88: eaffffe2 b 18 <flash>
0000008c <delay>:
8c: e59f2024 ldr r2, [pc, #36] ; b8 <delay_loop+0x24>
90: e3a03000 mov r3, #0
00000094 <delay_loop>:
94: e2422001 sub r2, r2, #1
98: e1520003 cmp r2, r3
9c: 1afffffc bne 94 <delay_loop>
a0: e1a0f00e mov pc, lr
a4: 11111111 tstne r1, r1, lsl r1
a8: e0200240 eor r0, r0, r0, asr #4
ac: e02000a0 eor r0, r0, r0, lsr #1
b0: e02000a4 eor r0, r0, r4, lsr #1
b4: e0200244 eor r0, r0, r4, asr #4
b8: 0098967f addseq r9, r8, pc, ror r6
Disassembly of section .ARM.attributes:
00000000 <.ARM.attributes>:
0: 00001341 andeq r1, r0, r1, asr #6
4: 61656100 cmnvs r5, r0, lsl #2
8: 01006962 tsteq r0, r2, ror #18
c: 00000009 andeq r0, r0, r9
10: 01080106 tsteq r8, r6, lsl #2
烧录的bin文件其实最终都是将一条条指令写入,每条指令都有一个指令地址,这个指令地址由链接脚本决定,上边的程序在编译时使用使用:arm-none-eabi-ld -Ttext 0x0 -o led.elf $^
语句指定了链接地址为0x0,所以可以看到程序的代码段是从00000000地址开始的
makeFile入门
使用makeFile以便于进行C语言工程的编译。
makeFile基本格式
makeFile基本格式如下:
目标: 依赖1 依赖2 ……
命令
例如:
helloworld: helloworld.c helloworld.h
gcc helloworld.c
则只要执行make,目标helloworld就会被编译出来
makeFile特点
- 目标不限于可执行的二进制程序,可以是任何使用命令可以生成的结果
- 依赖是用于生成目标的原材料,包括且不限于可执行程序,动态/静态库,源文件和头文件等
- 命令不限于linux中所有可以执行的命令
- 命令之前不能使用空格,只能是一个Tab
- linux中makeFile文件名一般命名为”makeFile”,也可以使用-f指定要使用的makeFile文件
makeFile规则
显示规则
- 格式:%.o:%.S/c:表示所有的以.o结尾的目标都是以.S/c为依赖编译而来的
- $@:自动变量,指代当前的目标
- $<:自动变量,指代当前所有的依赖
mkv210_image.c
该文件不是在嵌入式环境下执行的,是在宿主机上用于将用于USB启动使用的镜像转换为SD卡启动使用的镜像,由于是在宿主机上运行,所以编译该文件时不能使用arm-linux-gcc,而要使用宿主机上的gcc.
编译和使用步骤为:
- gcc mkv210_image.c -o x210
- ./x210 led_usb.bin led_sd.bin
SD卡启动和USB启动
启动区别
- 当SD卡启动时,BL0需要检查校验和,所以要在前16个字节上填充上校验和,而且需要烧写到0xd0020000位置上,才可以执行
- 当USB启动时BL0读取到BL1时不检查校验和,直接从BL1的实际内容开始执行,跳过了前面的16个字节,执行地址为0xd0020010,那我们的镜像在USB启动时前面不需要放置校验和,并直接将镜像下载到0xd0020010地址即可,
mkv210_image.c
作用
我们在编译链接得到led_usb.bin,是要执行的实际内容,没有添加校验和,所以只能在USB启动的时候烧写到0xd0020010执行,要使用SD卡启动,就需要添加校验和,mkv210_image.c就做了这个工作:
工作流分析
宏定义
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUFSIZE (16*1024)
#define IMG_SIZE (16*1024)
#define SPL_HEADER_SIZE 16
//#define SPL_HEADER "S5PC110 HEADER "
#define SPL_HEADER "****************"
部分1
FILE *fp;
char *Buf, *a;
int BufLen;
int nbytes, fileLen;
unsigned int checksum, count;
int i;
// 1. 3个参数
if (argc != 3)
{
printf("Usage: %s <source file> <destination file>\n", argv[0]);
return -1;
}
检查参数,由于我们需要传入两个文件名,所以参数个数必须是3.
部分2
// 2. 分配16K的buffer
BufLen = BUFSIZE;
Buf = (char *)malloc(BufLen);
if (!Buf)
{
printf("Alloc buffer failed!\n");
return -1;
}
memset(Buf, 0x00, BufLen);
使用malloc分配16K的内存区域,memset由于初始化该内存区域,全部写成0
部分3
// 3. 读源bin到buffer
// 3.1 打开源bin
fp = fopen(argv[1], "rb");
if( fp == NULL)
{
printf("source file open error\n");
free(Buf);
return -1;
}
// 3.2 获取源bin长度
fseek(fp, 0L, SEEK_END); // 定位到文件尾
fileLen = ftell(fp); // 得到文件长度
fseek(fp, 0L, SEEK_SET); // 再次定位到文件头
// 3.3 源bin长度不得超过16K-16byte
count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
// 3.4 buffer[0~15]存放"S5PC110 HEADER "
memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE);
// 3.5 读源bin到buffer[16]
nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);
if ( nbytes != count )
{
printf("source file read error\n");
free(Buf);
fclose(fp);
return -1;
}
fclose(fp);
读取源文件到分配的内存空间中,由于我们分配的内存空间为16K,后续还要加上16byte的头信息,所以源文件不能大于16K-16byte,然后跳过内存空间的前16byte,将源文件写入到后面的区域,
第四步
// 4. 计算校验和
// 4.1 从第16byte开始统计buffer中共有几个1
// 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果
a = Buf + SPL_HEADER_SIZE;
for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
checksum += (0x000000FF) & *a++;
// 4.2 将校验和保存在buffer[8~15]
a = Buf + 8; // Buf是210.bin的起始地址,+8表示向后位移2个字,也就是说写入到第3个字
*( (unsigned int *)a ) = checksum;
计算源文件的校验和,写到内存空间8-15个字节中,0~7存放”********”,前面可以看到
部分5
// 5. 拷贝buffer中的内容到目的bin
// 5.1 打开目的bin
fp = fopen(argv[2], "wb");
if (fp == NULL)
{
printf("destination file open error\n");
free(Buf);
return -1;
}
// 5.2 将16k的buffer拷贝到目的bin中
a = Buf;
nbytes = fwrite( a, 1, BufLen, fp);
if ( nbytes != BufLen )
{
printf("destination file write error\n");
free(Buf);
fclose(fp);
return -1;
}
free(Buf);
fclose(fp);
return 0;
打开目的文件,并将内存空间中的数据写入到目的文件中,释放内存空间。