1. 什么是裸机开发
裸机就是没有穿衣服的机器。
- 没有操作系统(OS-Linux)
- 不能使用带系统调用的标准函数
- 编译方式不同于应用程序,生成的可执行程序是不带格式
- 执行方式是直接下载到指定的内存地址并让程序计数器PC指向该内存地址执行
- 比一定从main函数开始执行
- 编译的时候必须要制定链接地址,而且这个地址一般为物理地址
- 编译完成的可执行程序必须去除多余的格式信息,得到纯二进制文件
2. Liinux程序在裸机中的存储按分段存储有
段 | 描述 |
b/B | .bss(b静态/B非静态)未初始化的变量 |
d/D | .data(d静态/D非静态)已初始化变量 |
r/R | .rodata(r静态/R非静态)只读数据段 |
t/T | .text(t/静态/T非静态)代码段 |
A | 不可改变的绝对值 |
B | .O中未初始化非静态变量 |
N | 调试用的符号 |
U | 表示符号只有声明没有定义 |
如何查看这些段,就要用到GNU工具了
3. 常用的工具
nm:符号显示器
arm-linux-gnueabi-nm -n main_elf
显示内容:
- 第一列为符号地址
- 第二列为符号所在段
- 第三列为符号名称
$ arm-linux-gnueabi-nm -n /usr/arm-linux-gnueabi/lib/crt1.o
U abort
U __libc_csu_fini
U __libc_csu_init
U __libc_start_main
U main
00000000 D __data_start
00000000 W data_start
00000000 R _IO_stdin_used
00000000 T _start
nm符号显示器结果总结
- 静态变量和非静态的全局变量,所分配的段只于其是否初始化有关,如果初始化了测被分配在.data段中,负责在. bss段中
- 函数无论是静态还是非静态的,总是被分配在.text段中
- 函数内的局部变量由于分配在栈上,所以在nm中看不到他们的
信息查看器
arm-linux-gnueabi-objdump main_elf
- 查看所有段信息 $objdump -h main_elf
- 查看头文件头信息 $objdump -f main_elf
- 查看反汇编 $objdump -d main_elf
- 查看内嵌反汇编 $objdump -S -d main_elf
段剪辑器
arm-linux-gnueabi-objcopy
去除elf格式信息
arm-linux-gnueabi-objcopy -O binary -S main_elf main.bin
4. 程序的编译过程
分为4个阶段,
- 预编译 -E 生成 .i 文件
- 编译 -S 生成 .S 文件
- 汇编 -c 生成.o目标文件
- 链接 -o 生成ELF格式和执行文件
那什么是链接了?
即在链接是是链接多有源文件中相同的段放在内存中的同一片内存地址。
在GNU工具下,链接用arm-linux-gnueabi-ld 将用户程序和相关库链接生成可执行文件。
编写程序
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
若正常编译连接
arm-linux-gnueabi-gcc -c -o main mian.c
所生成的文件是ELF文件格式的,可以vim main查看
裸机的程序不能带格式,所以需要自己连接,首先使用编译器生成.o文件
arm-linux-gnueabi-gcc -c -o main.o main.c
若只使用连接工具
arm-linux-gnueabi-ld -o main main.o
结果显示
arm-linux-gnueabi-ld: 警告: 无法找到项目符号 _start; 缺省为 0000000000010074
main.o:在函数‘main’中:
main.c:(.text+0xc):对‘puts’未定义的引用
·结论:
- 连接过程中需要一个标号(_start)作为程序入口
- 标号(_start)的作用是:将用户程序从汇编带到了C语言的程序入口,即main()函数,从此开始运行用户程序
- _start 标号所在的汇编文件在编译工具链下面:/usr/arm-linux-gnueabi/lib/crt1.o
如果加上crt1.o的链接了
arm-linux-gnueabi-ld -o main main.o /usr/arm-linux-gnueabi/lib/crt1.o
结果出现
/usr/arm-linux-gnueabi/lib/crt1.o:在函数‘_start’中:
(.text+0x28):对‘__libc_start_main’未定义的引用
/usr/arm-linux-gnueabi/lib/crt1.o:在函数‘_start’中:
(.text+0x2c):对‘abort’未定义的引用
/usr/arm-linux-gnueabi/lib/crt1.o:在函数‘_start’中:
(.text+0x30):对‘__libc_csu_fini’未定义的引用
/usr/arm-linux-gnueabi/lib/crt1.o:在函数‘_start’中:
(.text+0x38):对‘__libc_csu_init’未定义的引用
main.o:在函数‘main’中:
main.c:(.text+0xc):对‘puts’未定义的引用
结果出现缺少更多的链接文件。
为避免出现上面的错误,可以手动实现一个_start入口函数,或者重新指向一个新的函数入口,手动链接并生成可执行文件。
直接通过参数指定程序入口和段地址:
int _start()
{
...
return 0;
}
arm-linux-gnueabi-ld -Ttext=0x30000000 -Tdata=0x40000000 -e main -o app head.o main.o
通过链接脚本来指定程序入口和段地址:
arm-linux-gnueabi-ld -Tapp.lds -o app head.o main.o
链接app.lds为:
ENTRY(main)
SECTIONS
{
.=0x30000000;
.text:{*{.text}}
.=0x40000000;
.data:{*{.data}}
.bss:{*{.bss}}
}
// '*'号指所有目标,可以指定.O目标文件,多个用逗号隔开
// '.'指的是当前位置
裸机程序完整的编译环境过程:
arm-linux-gnueabi-gcc -c -o main.o mian.c
arm-linux-gnueabi-ld -Tapp.lds -o app_elf main.o
arm-linux-gnueabi-objcopy -O binary -S app_elf app.bin
然后将app.bin下载虚拟开发板u-boot交互界面,使用go语言执行
使用 tftp 网络格式下载到开发板u-boot
然后使用go命令执行。