GNU的汇编器是GNU Tools的一部分,可以用来ARM的汇编语言源代码编译为二进制文件.关于GNU汇编器的介绍可以搜索《GNU Assembler Manual》.这里我们只是做一个简短的介绍,对GNU汇编器有一个大概的认识,同时通过两个例子了解一下GNU ARM汇编.
给出一个模板文件:
.text ; Executable code follows _start: .global _start ; "_start" is required by the linker .global main ; "main" is our main program b main ; Start running the main program main: ; Entry to the function "main" ; Insert your code here mov pc,lr ; Return to the caller .end汇编器的使用:
一种汇编器是arm-elf-as,一种是arm-linux-as之类的,这两种汇编器是有细微区别.但是一般做开发,半导体厂商都会提供特定的编译器,用那个编译器应该是没错的,而且优化效果应该是最优的,毕竟是芯片公司提供的嘛.他们对体系架构最了解,很清楚的知道怎么去优化.而我们一般的开发者也可以了解处理器的体系架构和嵌入式系统的系统的特征来对汇编代码和c代码做优化.
编译过程:
arm-elf-as -marm7tdmi --gdwarf2 -o filename.o filename.s
-marm7tdmi是指定CPU,arm7tdmi是属于ARMv4T的,一般来说同是ARMv4T应该是兼容的.
--gdwarf2是表示包含debug信息.
链接过程:
arm-elf-ld -o filename.elf filename.o
和UNIX系统编程一样,我们可以根据上面的步骤写makefile,然后make一下.
具体ARM的指令集,伪指令就不写了,资料很多.
下面举两个ARM汇编的实例,一个是裸机下的蜂鸣器(简单的控制GPIO而已,比流水灯还简单),一个是ARM linux下的"hello world"(利用系统调用来实现的).
蜂鸣器的例子如下:
beep.lds beep.S Makefile start.S
start.S:
.text .global _start _start: ldr r3, =0x53000000 @ WATCHDOG寄存器地址 mov r4, #0x0 str r4, [r3] @ 写入0,禁止WATCHDOG,否则CPU会不断重启 ldr sp, =1024*2 @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K bl _main @ 跳转到main函数 halt_loop: b halt_loop
.equ GPBCON, 0x56000010 .equ GPBDAT, 0x56000014 .global _main _main: ldr r0,=GPBCON ldr r1,=0x1 str r1, [r0] loop: ldr r2,=GPBDAT ldr r1,=0x1 str r1,[r2] bl delay ldr r2,=GPBDAT ldr r1,=0x0 str r1,[r2] bl delay b loop delay: ldr r3,=0x4ffffff delay1: sub r3,r3,#1 cmp r3,#0x0 bne delay1 mov pc,lr .end
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS{ . = 0x33000000; .text : { *(.text) *(.rodata) } .data ALIGN(4): { *(.data) } .bss ALIGN(4): { *(.bss) } }
CROSS = arm-linux- CFLAGS = -nostdlib beep.bin: start.S beep.S ${CROSS}gcc $(CFLAGS) -c -o start.o start.S ${CROSS}gcc $(CFLAGS) -c -o beep.o beep.S ${CROSS}ld -Tbeep.lds start.o beep.o -o beep.elf ${CROSS}objcopy -O binary -S beep.elf beep.bin rm -f *.o clean: rm -f *.elf *.o rm -f beep.bin
1.lds编译链接文件的写法和技巧 //后续要继续追
2.elf文件的格式 //elf格式是比较新的可执行文件格式,目前在很多OS上都是用这种格式.这个格式可以在有操作系统的情况下直接运行,但是对于裸机的情况,必须对elf文件 做objcopy处理 后续也要继续追
hello world的例子如下:
helloworld.S:
.data msg: .asciz "hello, world\n" .text .align 2 .global _start _start: ldr r1, =msg @ address mov r0, #1 @ stdout mov r2, #13 @ length swi #0x900004 @ sys_write mov r0, #0 swi #0x900001 @ sys_exit .align 2
all: arm-linux-as helloworld.S -o helloworld.o arm-linux-ld helloworld.o -o helloworld
对比x86下同样用系统调用来输出hello world的程序:
.data msg: .string "hello\n" len = . - msg .text .global _start _start: nop movl $len, %edx movl $msg, %ecx movl $1, %ebx movl $4, %eax int $0x80 movl $0, %ebx movl $1, %eax int $0x80
1.arm是用swi指令来进行软中断,陷入内核态来实现系统调用的.而x86是用int $0x80
2.x86的系统调用号是用eax寄存器表示的,是第一个参数.而arm的swi直接带有系统调用号,0x900004是0x900000+4,其中0x900000是base.
根据google,做了上面的总结,对GNU ARM汇编有了认识,并且对系统调用软中断,中断处理,uboot异常向量表等等有了探究的欲望,也对elf格式和编译链接有了兴趣,根据自己的方向和精力,后续对这些内容做一个或深或浅的学习.