使用GNU工具链进行嵌入式编程(一)

使用GNU工具链进行嵌入式编程

1. 简介
2. 建立ARM实验环境
3. Hello ARM
4. 更多的ARM汇编原语
5. 使用RAM
6. 链接器
7. 链接器脚本文件
8. RAM中数据(举例)
9. 异常处理
10. C启动程序
11. 使用C语言库
12. 内联汇编
13.....

本文翻译自:www.bravegnu.org/gnu-eprog/index.html

1. 简介

GNU工具链在当前的嵌入式软件开发中被广泛的使用, 这种类型的软件开发称之为单独C编程或裸C编程, 单独C编程带来一个新问题, 就是编程时需要对GNU工具链有非常深入的理解. GNU工具链手册提供了优秀的信息. 然而对于GNU工具链的新用户在使用中仍然会存在很多的疑惑.

本文试图从问题的角度来解释GNU工具链手册中存在的疑惑, 希望可以让更多的人可以在自己的工程中使用GNU工具链

2. 建立ARM实验环境

该部分主要介绍如何建立简单的ARM开发和测试环境, 使用QEMU和GNU工具链, QEMU时一种仿真软件,可以用来仿真各种类型机器,包括基于ARM的机器.
下面的ARM实验环境, 使用ARM汇编编写程序, 使用GNU工具链编译, 在QMEU中进行运行和测试.

1) 安装QEMU

参考blog:
或者通过命令:
sudo apt-get install qemu

2) 安装ARM GNU工具链

下载工具链:https://launchpad.net/gcc-linaro/
并设置环境变量:
$PATH=$HOME/toolchains/arm-linux-gnueabi/bin:$PATH
把该语句加入到/etc/profile或者.bashrc中.

3. Hello ARM(第一个ARM汇编程序)

在该部分,将编写一个简单的ARM汇编程序, 并在QEMU仿真的ARM环境中进行测试.

汇编程序源代码由一系列的语句顺序组成, 每个一行, 各个语句的格式如下:
label:    instruction    @comment

各个部分都是可选的
label: 一种方便的方式用来定位指令在内存中的位置, 当访问一个地址时,可以使用label, 例如,对于分支指令的一个操作数.
        label名称由字母, 数字, _和$组成.

comment:
        comment从@符号开始, 表示@后面出现的字符被忽略.

instruction:
        instruction可以是ARM指令或者汇编器原语, 汇编器原语是汇编器的命令, 汇编器原语总是以.(点号)开始.

以下是简单的ARM汇编程序用来实现两个数相加


.text: 表示是汇编器原语, 表示后面的指令必须被汇编到代码段部分,
.data: 表示数据段部分.

1) 构建二进制

保存为add.s文件, 使用GNU工具链中的as来对该文件进行汇编, 命令如下:


fatansy@fantasy:arm_lab$ arm-linux-gnueabi-as -o add.o add.s

-o选项指定输出文件名

注:交叉工具链总是加上目标架构的前缀, 以免与主机工具链名称冲突,为了可读性,本文涉及到的工具链名称都不加上前缀.

为了产生可执行文件, 调用GNU工具链的链接器ld,命令如下:

fatansy@fantasy:arm_lab$ arm-linux-gnueabi-ld -Ttext=0x0 -o add.elf add.o
此处
-o:表示指定输出文件名
-Ttext=0x0:指定分配给label的地址, 这样指令从地址0x0开始, 为了查看各种label分配的地址,可以使用nm命令来查看,如下:

fatansy@fantasy:arm_lab$ arm-linux-gnueabi-nm add.elf
可以看到分配给start和stop标签的地址
start标签的地址:0x0,因为它是第一条指令,
stop标签的地址:是从start标签开始后,还要执行3条指令,才到stop标签处, 由于各个指令都是4字节的,所以stop标签的地址为0xC(12)

对于指令,指定不同的基地址进行链接将导致不同的地址分配给标签,如下:


ld创建的输出文件是ELF格式文件, 各个格式的文件可以用来存储可执行代码, ELF格式对于有OS系统时才能正常工作. 由于此处我们编写的代码是运行在裸系统上, 因此,我们需要把ELF文件转换为简单的binary格式文件.

binary格式文件包含从指定内存地址开始的连续字节,没有其他附加的信息存储在文件中,这个对于Flash编程工具来说是非常方便的.

GNU工具链的objcopy命令可以用来在不同的对象文件格式之间进行转换, 一种通用的命令使用如下:
objcopy -O <output-format> <in-file> <out-file>
转换add.elf到binary格式使用如下命令:

fatansy@fantasy:arm_lab$ arm-linux-gnueabi-objcopy -O binary add.elf add.bin

检查该文件大小,刚好16字节,因为该程序有4条指令,每条指令占4个字节.


2) QEMU中执行

当ARM处理器复位时,从地址0x0开始执行,在connex单板上有一个16MB Flash, 从地址0x0开始,Flash开始处的执行将会执行.

当QEMU仿真connex单板时, 必须要指定一个文件作为Flash内存, 这种Flash文件格式非常简单, 为了从Flash中地址X处获取字节, QEMU从文件偏移地址X处读取字节, 实际上,这种和binary文件格式相同.

为了测试程序, 在仿真的Gumstix Connex单板上,首先我们创建一个16MB的文件表示Flash, 我们使用dd命令从/dev/zero中复制16MB的0到文件flash.bin文件中,该数据以4K的块大小进行复制.

fatansy@fantasy:arm_lab$ dd if=/dev/zero of=flash.bin bs=4096 count=4096

add.bin文件然后复制到Flash的开始处, 使用如下命令:

fatansy@fantasy:arm_lab$ dd if=add.bin of=flash.bin bs=4096 conv=notrunc

这个等价于在Flash内存中编程bin文件.

ARM单板复位后, 处理器将开始从地址0x0处开始执行, 程序的指令将获得执行, 如下命令调用qemu执行:

fatansy@fantasy:arm_lab$ qemu-system-arm -M connex -snapshot -pflash flash.bin -nographic -serial /dev/null

-M connex选项:指定机器connex进行仿真
-pflash选项:指定flash.bin文件表示Flash内存
-nographic:表示不需要图像显示仿真
-serial /dev/null: 表示connex单板的串口链接到/dev/null, 这样串口数据就丢弃了.

系统执行指令,完成滞后,保持无限循环stop: b stop指令, 为了查看寄存器中的内容, 可以使用qemu的monitor接口, monitor接口时一种命令行接口,通过它仿真的系统可以被控制, 同时可以观察系统的状态, 当qemu启动后,monitor接口以Qemu的标准I/0口提供.

可以在monitor接口中,输入info registers命令来查看寄存器内容


注意到在寄存器R02中的值, 该寄存器保存的是相加后的结果, 与期望的值相匹配.

3) 更多的Monitor命令

一些有用的Qemu Monitor命令如下列出所示


xp命令值得更多的解释, fmt参数指定内存内容如何显示, fmt语法为: <count><format><size>.
    count: 指定显示的数据项数
    size: 指定各个数据项的大小, b表示8bits, h表示16bits, w表示32bits, g表示64bits
    format:指定显示的格式,x表示hex, d表示有符号整数, u表示无符号整数, o表示octal, c表示字符, i表示asm指令.

xp命令带i格式: 用来反汇编内存中的指令. 为了反汇编内存中0x0地址处的指令,xp命令使用fmt格式为4iw. 4表示4个数据项将被显示,i指定了数据项将以指令的形式打印出来, w指定了数据项的大小是32bits.命令的输出显示如下:









你可能感兴趣的:(使用GNU工具链进行嵌入式编程(一))