虽然在Linux下运用C或C++编写程序很便利,但汇编源程序用于体系最基本的初始化,如初始化仓库指针、设置页表、操作ARM的协处理器等。初始化完成后就能够跳转到C代码碑文。需求留意的是,GNU的汇编器遵从AT&T的汇编语法,能够从GNU的站点(www.gnu.org)上下载有关标准。
一. Linux汇编行结构
任何汇编行都是如下结构:
[:] [} @ comment
[:] [} @ 注释
Linux ARM 汇编中,任何故冒号完毕的标识符都被认为是一个标号,而纷歧定非要在一行的开端。
界说一个add的函数,回来两个参数的和。
.section .text, “x”
.global add @ give the symbol add external linkage
add:
ADD r0, r0, r1 @ add input arguments
MOV pc, lr @ return from subroutine
@ end of program
二. Linux 汇编程序中的标号
标号只能由a~z,A~Z,0~9,“.”,_等字符组成。当标号为0~9的数字时为部分标号,部分标号能够重复呈现,运用方法如下:
? 标号f: 在引证的当地向前的标号
? 标号b: 在引证的当地向后的标号
运用部分符号的比如,一段循环程序
1:
subs r0,r0,#1 @每次循环使r0=r0-1
bne 1f @跳转到1标号去碑文
部分标号代表它地点的地址,因而也能够当作变量或许函数来运用。
三. Linux汇编程序中的分段
(1).section伪操作
用户能够经过.section伪操作来自界说一个段,格局如下:
.section section_name [, flags[, %type[,flag_specific_arguments]]]
每一个段以段名为开端, 以下一个段名或许文件完毕为完毕。这些段都有缺省的标志(flags),衔接器能够辨认这些标志。(与armasm中的AREA相同)。
下面是ELF格局答应的段标志
<标志> 意义
a 答应段
w 可写段
x 碑文段
界说段
.section .mysection @自界说数据段,段名为 “.mysection”
.align 2
strtemp:
.ascii Temp string /n/ 0
(2)汇编体系预界说的段名
.text @代码段
.data @初始化数据段
.bss @未初始化数据段
.sdata @
.sbss @
需求留意的是,源程序中.bss段应该在.text之前。
四. 界说进口点
汇编程序的缺省进口是 start标号,用户也能够在衔接脚本文件顶用ENTRY标志指明其它进口点。
界说进口点
.section.data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:
五. Linux汇编程序中的宏界说
格局如下:
.macro 宏名 参数名列表 @伪指令.macro界说一个宏
宏体
.endm @.endm一共宏完毕
假如宏运用参数,那么在宏体中运用该参数时添加前缀“/”。宏界说时的参数还能够运用默认值。
能够运用.exitm伪指令来退出宏。
宏界说
.macro SHIFTLEFT a, b
.if b < 0 MOV a, a, ASR #-b .exitm .endif MOV a, a, LSL #b .endm 六. Linux汇编程序中的常数 (1)十进制数以非0数字最初,如:123和9876; (2)二进制数以0b最初,其间字母也能够为大写;
(3)八进制数以0开端,如:0456,0123;
(4)十六进制数以0x最初,如:0xabcd,0X123f;
(5)字符串常量需求用引号括起来,中心也能够运用转义字符,如: “You are welcome!//n”;
(6)当时地址以“.”一共,在汇编程序中能够运用这个符号代表当时指令的地址;
(7)表达式:在汇编程序中的表达式能够运用常数或许数值, “-”一共取负数, “~”一共取补,“<>”一共不持平,其他的符号如:+、-、*、/、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、||跟C言语中的用法相似。 七. Linux下ARM汇编的常用伪操作 在前面现已说到过了一些为操作,还有下面一些为操作: ? 数据界说伪操作: .byte,.short,.long,.quad,.float,.string/.asciz/.ascii,重复界说伪操作.rept,赋值查办.equ/.set ;
? 函数的界说 ;
? 对齐方法伪操作 .align;
? 源文件完毕伪操作.end;
? .include伪操作;
? if伪操作;
? .global/ .globl 伪操作 ;
? .type伪操作 ;
? 列表操控查办 ;
? 差异于gas汇编的通用伪操作,下面是ARM特有的伪操作 :.reg ,.unreq ,.code ,.thumb ,.thumb_func ,.thumb_set, .ltorg ,.pool
1. 数据界说伪操作
(1) .byte:单字节界说,如:.byte 1,2,0b01,0x34,072,s ;
(2) .short:界说双字节数据,如:.short 0x1234,60000 ;
(3) .long:界说4字节数据,如:.long 0x12345678,23876565
(4) .quad:界说8字节,如:.quad 0x1234567890abcd
(5) .float:界说浮点数,如:
.float 0f-314159265358979323846264338327/
95028841971.693993751E-40 @ – pi
(6) .string/.asciz/.ascii:界说多个字符串,如:
.string abcd, efgh, hello!
.asciz qwer, sun, world!
.ascii welcome// 0
需求留意的是:.ascii伪操作界说的字符串需求自行添加完毕字符// 0。
(7) .rept:重复界说伪操作, 格局如下:
.rept 重复次数
数据界说
.endr @完毕重复界说
例如:
.rept 3
.byte 0x23
.endr
(8) .equ/.set: 赋值查办, 格局如下:
.equ(.set) 变量名,表达式
例如:
.equ abc 3 @让abc=3
2.函数的界说伪操作
(1)函数的界说,格局如下:
函数名:
函数体
回来查办
一般的,函数假如需求在其他文件中调用, 需求用到.global伪操作将函数声明为大局函数。为了不至于在其他程序在调用某个C函数时产生紊乱,对寄存器的运用咱们需求遵从APCS原则。函数编译器将处理为函数代码为一段.global的汇编码。
(2)函数的编写应当遵从如下规矩:
? a1-a4寄存器(参数、成果或暂存寄存器,r0到r3 的同义字)以及浮点寄存器f0-f3(假如存在浮点协处理器)在函数中是不用保存的;
? 假如函数回来一个不大于一个字巨细的值,则在函数完毕时应该把这个值送到 r0 中;
? 假如函数回来一个浮点数,则在函数完毕时把它放入浮点寄存器f0中;
? 假如函数的进程改动了sp(仓库指针,r13)、fp(结构指针,r11)、sl(仓库约束,r10)、lr(衔接寄存器,r14)、v1-v8(变量寄存器,r4 到 r11)和 f4-f7,那么函数完毕时这些寄存器应当被康复为包含在进入函数时它所持有的值。
3. .align .end .include .incbin伪操作
(1).align:用来指定数据的对齐方法,格局如下:
.align [absexpr1, absexpr2]
以某种对齐方法,在未运用的存储区域填充值. 第一个值一共对齐方法,4, 8,16或 32. 第二个表达式值一共填充的值。
(2).end:标明源文件的完毕。
(3).include:能够将指定的文件在运用.include 的当地打开,一般是头文件,例如:
.include “myarmasm.h”
(4).incbin伪操作能够将原封不动的一个二进制文件编译到当时文件中,运用方法如下:
.incbin file[,skip[,count]]
skip标明是从文件开端越过skip个字节开端读取文件,count是读取的字数.
4. .if伪操作
依据一个表达式的值来决议是否要编译下面的代码, 用.endif伪操作来一共条件判别的完毕, 中心能够运用.else来决议.if的条件不满足的情况下应该编译哪一部分代码。
.if有多个变种:
.ifdef symbol @判别symbol是否界说
.ifc string1,string2 @字符串string1和string2是否持平,字符串能够用单引号括起来
.ifeq expression @判别expression的值是否为0
.ifeqs string1,string2 @判别string1和string2是否持平,字符 串有必要用双引号括起来
.ifge expression @判别expression的值是否大于等于0
.ifgt absolute expression @判别expression的值是否大于0
.ifle expression @判别expression的值是否小于等于0
.iflt absolute expression @判别expression的值是否小于0
.ifnc string1,string2 @判别string1和string2是否不持平, 其用法跟.ifc恰好相反。
.ifndef symbol, .ifnotdef symbol @判别是否没有界说symbol, 跟.ifdef恰好相反
.ifne expression @假如expression的值不是0, 那么编译器将编译下面的代码
.ifnes string1,string2 @假如字符串string1和string2不相 等, 那么编译器将编译下面的代码.
5. .global .type .title .list
(1).global/ .globl :用来界说一个大局的符号,格局如下:
.global symbol 或许 .globl symbol
(2).type:用来指定一个符号的类型是函数类型或许是方针类型, 方针类型一般是数据, 格局如下:
.type 符号, 类型描绘
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 10
.section .text
.type asmfunc, @function
.globl asmfunc
asmfunc:
mov pc, lr
(3)列表操控查办:
.title:用来指定汇编列表的标题,例如:
.title “my program”
.list:用来输出列表文件.
6. ARM特有的伪操作
(1) .reg: 用来给寄存器赋予别号,格局如下:
别号 .req 寄存器名
(2) .unreq: 用来撤销一个寄存器的别号,格局如下:
.unreq 寄存器别号
留意被撤销的别号有必要事前界说过,不然编译器就会报错,这个伪操作也能够用来撤销体系预制的别号, 例如r0, 但假如没有必要的话不引荐那样做。
(3) .code伪操效果来挑选ARM或许Thumb指令集,格局如下:
.code 表达式
假如表达式的值为16则标明下面的指令为Thumb指令,假如表达式的值为32则标明下面的指令为ARM指令.
(4) .thumb伪操作同等于.code 16, 标明运用Thumb指令, 相似的.arm同等于.code 32
(5) .force_thumb伪操效果来强制方针处理器挑选thumb的指令集而不论处理器是否支撑
(6) .thumb_func伪操效果来指明一个函数是thumb指令集的函数
(7) .thumb_set伪操作的效果相似于.set, 能够用来给一个标志起一个别号, 比.set功用添加的一点是能够把一个标志标记为thumb函数的进口, 这点功用同等于.thumb_func
(8) .ltorg用于声明一个数据缓冲池(literal pool)的开端,它能够分配很大的空间。
(9) .pool的效果同等.ltorg
(9).space {,}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。(与armasm中的SPACE功用相同)
(10).word {,} …
刺进一个32-bit的数据行列。(与armasm中的DCD功用相同)
能够运用.word把标识符作为常量运用
例如:
Start:
valueOfStart:
.word Start
这样程序的最初Start便被存入了内存变量valueOfStart中。
(11).hword {,} …
刺进一个16-bit的数据行列。(与armasm中的DCW相同)
八. GNU ARM汇编特别字符和语法
代码行中的注释符号:‘@’
整行注释符号: ‘#’
查办别离符号: ‘;’
直接操作数前缀: ‘#’ 或 ‘$’
第二部分 GNU的编译器和调试东西
一. 编译东西
1.修改东西介绍
GNU供给的编译东西包含汇编器as、C编译器gcc、C++编译器g++、衔接器ld和二进制转化东西objcopy。根据ARM渠道的东西别离为arm-linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux- objcopy。GNU的编译器功用十分强壮,共有上百个操作选项,这也是这类东西让初学者头痛的原因。不过,实践开发中只需求用到有限的几个,大部分能够选用缺省选项。GNU东西的开发流程如下:编写C、C++言语或汇编源程序,用gcc或g++生成方针文件,编写衔接脚本文件,用衔接器生成终究方针文件(elf格局),用二进制转化东西生成可下载的二进制代码。
(1)编写C、C++言语或汇编源程序
一般汇编源程序用于体系最基本的初始化,如初始化仓库指针、设置页表、操作ARM的协处理器等。初始化完成后就能够跳转到C代码碑文。需求留意的是,GNU的汇编器遵从AT&T的汇编语法,读者能够从GNU的站点(www.gnu.org)上下载有关标准。汇编程序的缺省进口是 start标号,用户也能够在衔接脚本文件顶用ENTRY标志指明其它进口点(见下文关于衔接脚本的阐明)。
(2)用gcc或g++生成方针文件
假如应用程序包含多个文件,就需求进行别离编译,终究用衔接器衔接起来。如笔者的引导程序包含3个文件:init.s(汇编代码、初始化硬件)xmrecever.c(通讯模块,选用Xmode协议)和flash.c(Flash擦写模块)。
别离用如下指令生成方针文件: arm-linux-gcc-c-O2-oinit.oinit.s arm-linux-gcc-c-O2-oxmrecever.oxmrecever.c arm-linux-gcc-c-O2-oflash.oflash.c 其间-c指令一共只生成方针代码,不进行衔接;-o指令指明方针文件的称号;-O2一共选用二级优化,选用优化后可使生成的代码更短,运转速度更快。假如项目包含许多文件,则需求编写makefile文件。关于makefile的内容,请感兴趣的读者参阅相关材料。
(3)编写衔接脚本文件
gcc等编译器内置有缺省的衔接脚本。假如选用缺省脚本,则生成的方针代码需求操作体系才干加载运转。为了能在嵌入式体系上直接运转,需求编写自己的衔接脚本文件。编写衔接脚本,首要要对方针文件的格局有必定了解。GNU编译器生成的方针文件缺省为elf格局。elf文件由若干段(section)组成,如不特别指明,由C源程序生成的方针代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的方针代码中还包含.fini(析构函数代码)和. init(结构函数代码)等。衔接器的使命便是将多个方针文件的.text、.data和.bss等段衔接在一起,而衔接脚本文件是告知衔接器从什么地址开端放置这些段。例如衔接文件link.lds为:
ENTRY(begin)
SECTION
{
.=0x30000000;
.text:{*(.text)}
.data:{*(.data)}
.bss:{*(.bss)}
}
其间,ENTRY(begin)指明程序的进口点为begin标号;.=0x00300000指明方针代码的开端地址为0x30000000,这一段地址为MX1的片内RAM;.text:{*(.text)}一共从0x30000000开端放置一切方针文件的代码段,随后的.data:{* (.data)}一共数据段从代码段的结尾开端,再后是.bss段。
(4)用衔接器生成终究方针文件
有了衔接脚本文件,如下指令可生成终究的方针文件:
arm-linux-ld –no stadlib –o bootstrap.elf -Tlink.lds init.o xmrecever.o flash.o
其间,ostadlib一共不衔接体系的运转库,而是直接从begin进口;-o指明方针文件的称号;-T指明选用的衔接脚本文件(也能够运用-Ttext address,address一共碑文区地址);终究是需求衔接的方针文件列表。
(5)生成二进制代码
衔接生成的elf文件还不能直接下载碑文,经过objcopy东西可生成终究的二进制文件:
arm-linux-objcopy –O binary bootstrap.elf bootstrap.bin
其间-O binary指定生成为二进制格局文件。Objcopy还能够生成S格局的文件,只需将参数换成-O srec。还能够运用-S选项,移除一切的符号信息及重定位信息。假如想将生成的方针代码反汇编,还能够用objdump东西:
arm-linux-objdump -D bootstrap.elf
至此,所生成的方针文件就能够直接写入Flash中运转了。
2.Makefile实例
example: head.s main.c
arm-linux-gcc -c -o head.o head.s
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Tlink.lds head.o ain.o -o example.elf
arm-linux-objcopy -O binary -S example_tmp.o example
arm-linux-objdump -D -b binary -m arm example >ttt.s
二. 调试东西
Linux下的GNU调试东西主要是gdb、gdbserver和kgdb。其间gdb和gdbserver可完成对方针板上Linux下应用程序的长途调试。gdbserver是一个很小的应用程序,运转于方针板上,可监控被调试进程的运转,并经过串口与上位机上的gdb通讯。开发者能够经过上位机的gdb输入指令,操控方针板上进程的运转,检查内存和寄存器的内容。gdb5.1.1今后的版别参加了对ARM处理器的支撑,在初始化时参加- target==arm参数可直接生成根据ARM渠道的gdbserver。gdb东西能够从ftp: //ftp.gnu.org/pub/gnu/gdb/上下载。
关于Linux内核的调试,能够选用kgdb东西,相同需求经过串口与上位机上的gdb通讯,对方针板的Linux内核进行调试。能够从http://oss.sgi.com/projects/kgdb/上了解详细的运用方法。