GNU ARM汇编--(二)汇编编译链接与运行
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
.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
.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
beep.S
- .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
.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
beep.lds
- 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)
- }
- }
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)
}
}
makefile:
- 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
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
编译后将beep.bin文件烧写到dram中,就可以听到声音了.虽然可以运行了,但还是有两个疑问:
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
.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
makefile:
- all:
- arm-linux-as helloworld.S -o helloworld.o
- arm-linux-ld helloworld.o -o helloworld
all:
arm-linux-as helloworld.S -o helloworld.o
arm-linux-ld helloworld.o -o helloworld
将elf文件放到跑有linux的arm板子中,运行就输出hello world.也可以在ubuntu中qemu-arm 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
.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格式和编译链接有了兴趣,根据自己的方向和精力,后续对这些内容做一个或深或浅的学习.