最近公司接手了一个大型汇编工程,我也不知道这个年代为什么还有人会耗费精力去编写十几万行的汇编代码,而且这套代码即将由我来接手,想想就头大。
我本人接触linux的时间并不长,很多东西只是摸过,不能说出个所以然来。
借着这个机会学习一下,高手勿笑。
我的第一个目标就是利用汇编程序在6410开发板上点亮LED。
查看原理图,找到连接开发板上的4个LED分别接在了MCU的GPM0,1,2,3,并且是低电平点亮(具体怎么找到的就不说了),然后无非就是设置寄存器呗。了解到6410的IO不需要配置时钟,那就很简单了,根据经验只需要设置IO口为上拉输出,然后输出0就OK了。
查看芯片手册找到了和GPM相关的寄存器:
配置IO功能的描述如下:
显然我要把4个引脚全部设置为Output,所以要将0x1111写入GPMCON寄存器。
下表是GMP的数据和上下拉的配置,我选择上拉模式,输出0,也就是GMPDAT为0,GPLPUD为0xAA这样就可以点亮LED了。
其实我压根没有编写过汇编代码,看了一些资料后新建了一个led.s,写入下面这段代码:
1 LDR R0, =0x7F008820 @GPM base address 2 LDR R1, =0x1111 3 STR R1, [R0] @GPM Output 4 5 MOV R1, #0xAA 6 STR R1, [R0, #8] @GPM pull-up enabled 7 8 MOV R1, #0 9 STR R1, [R0, #4] @GPM OUT 0
作用就是点亮4个LED。那么问题来了,我该怎么编译它。
以下是我的思路,作为小白来说可能是一种比较正常的逻辑思维,不乏写出来分享一下。
1. 之前写过linux下的应用程序,以最简单的“Hello World”为例,创建一个hello.c,编写一个Hello World,然后用交叉编译工具编译:arm-none-linux-gnueabi-gcc hello.c 生成a.out,然后在开发板终端输入./a.out(此时内核已经起来了),就能在终端中显示"Hello World",所以我想点灯是不是也是这个原理。于是有了下面的操作:
提示没有main函数,想想觉得很有道理啊,用C语言编程也是要有main函数的啊,于是我把代码改成了下面的样子:
1 main: 2 LDR R0, =0x7F008820 @GPM base address 3 LDR R1, =0x1111 4 STR R1, [R0] @GPM Output 5 6 MOV R1, #0xAA 7 STR R1, [R0, #8] @GPM pull-up enabled 8 9 MOV R1, #0 10 STR R1, [R0, #4] @GPM OUT 0 11 12 B .
编译还是报同样的错(后边的B .是我后来加上去的,我觉得点完灯后不能让它继续运行了,否则会出现指令无法识别的错误)。
2. 好吧,难道main函数只能写在C语言里?那我就用C语言的main函数里面来调用这个汇编函数,于是我将led.s改成了下面的样子:
1 led_on: 2 LDR R0, =0x7F008820 @GPM base address 3 LDR R1, =0x1111 4 STR R1, [R0] @GPM Output 5 6 MOV R1, #0xAA 7 STR R1, [R0, #8] @GPM pull-up enabled 8 9 MOV R1, #0 10 STR R1, [R0, #4] @GPM OUT 0 11 12 B .
然后编写一个main.c,代码如下:
1 extern void led_on (void); 2 3 int main(int argc, const char *argv[]) 4 { 5 led_on(); 6 return 0; 7 }
输入arm-none-linux-gnueabi-gcc led.s main.c编译,结果如下:
提示没有找到led_on。好吧,main.c里面找不到led.s里的led_on标号,其实我知道是怎么回事,就是在led.s里面没有声明led_on这个标号是全局的,于是将led.s改成了下面的样子:
1 .global led_on 2 led_on: 3 LDR R0, =0x7F008820 @GPM base address 4 LDR R1, =0x1111 5 STR R1, [R0] @GPM Output 6 7 MOV R1, #0xAA 8 STR R1, [R0, #8] @GPM pull-up enabled 9 10 MOV R1, #0 11 STR R1, [R0, #4] @GPM OUT 0 12 13 B .
其实就是在第一行加入了.global led_on,表明led_on这个标号是全局的,main.c可以访问到它。然后继续编译:
坑就坑在这里,它竟然编译过了,并且生成了一个a.out。。。。。。我满心欢喜将我的a.out拷贝到nfs共享目录下,然后在开发板终端执行a.out:
提示段错误。OK,机智的我很快我就反应过来,内核起来后MMU也起来了,我写入的地址是一个虚拟地址,并不是实际地址。想到我的u-boot是没有启动MMU的(这点是在从u-boot启动内核的时候发现的,如果跳转的地址为50008000就表示没有启动MMU,如果是C0008000就说明启动u-boot时就已经启动MMU了)。所以我重启板子,在u-boot中通过tftp将a.out下载到50008000这个内存中去,然后bootm 跳转到这个内存中去运行,结果发生data abort:
3. 这时我注意到下载的文件大小是5019个字节,怎么可能这么大?除非是编译器给我加入了很多其他的东西,为了验证这个想法,我编了一个main.c,里面只有一个空的main函数,结果发现编译出来的a.out有4924个字节。应该是编译器加了东西。冷静思考一下,第一,我把C语言加入进来纯粹是为了有一个main函数,之前学习汇编的时候我就知道main函数并不是第一个执行的地方,也就是之前肯定还有别的东西。第二、汇编文件没有理由不能单独存在,之前编译不过一定是因为我的编译方式有问题或者文件内容有问题(事实上两者都有问题)。
4. 在查找一些资料后了解到汇编文件的入口是_start(GNU下的)无疑了, 并且我的s文件格式写的也有问题,改正之后的led.s变成了这个样子:
1 .section .text 2 .global _start 3 _start: 4 LDR R0, =0x7F008820 @GPM base address 5 LDR R1, =0x1111 6 STR R1, [R0] @GPM Output 7 8 MOV R1, #0xAA 9 STR R1, [R0, #8] @GPM pull-up enabled 10 11 MOV R1, #0 12 STR R1, [R0, #4] @GPM OUT 0 13 loop: 14 B loop 15 16 .end
并且我之前的编译方式也是不正确的,应该输入下面几条命令:
可以看到生成的led.bin只有40个字节,这还差不多。此处感谢一篇博文:https://blog.csdn.net/liushaowei2008/article/details/7847918
将生成的led.bin通过tftp下载到内存中(此时u-boot已经起来了),然后跳转到指定地址中去,发现LED点亮了。
注意此时led点亮仍是在u-boot启动之后,我将该程序放在u-boot启动之前还是启动不了,因为有些必要的初始化工作我没做。另外还发现一个问题,就是生成led.elf的时候指定文件保存的位置为0地址,但是实际上我将文件拷贝到50008000地址中还是成功点亮LED了,应该是程序中并没有涉及到绝对地址吧。本次最大的收获应该是将led.s编译成led.bin的过程。