gnu arm 彙編指令(2008-10-29
00:16:10)
标簽:
分類:
第一部分
linux下arm彙編文法盡管在linux下使用c或c++編寫程式很友善,但彙編源程式用于系統最基本的初始化,如初始化堆棧指針、設定頁表、操作
arm的協處理器等。初始化完成後就可以跳轉到c代碼執行。需要注意的是,gnu的彙編器遵循at&t的彙編文法,可以從gnu的站點(www.gnu.org)上下載下傳有關規範。
一.
linux彙編行結構
任何彙編行都是如下結構:
[:] [} @ comment
[:] [} @ 注釋
linux arm 彙編中,任何以冒号結尾的辨別符都被認為是一個标号,而不一定非要在一行的開始。
【例1】定義一個"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: 在引用的地方向後的标号
【例2】使用局部符号的例子,一段循環程式
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 執行段
【例3】定義段
.section .mysection @自定義資料段,段名為 “.mysection”
.align 2
strtemp:
.ascii "temp string \n\0"
(2)彙編系統預定義的段名
.text @代碼段
.data @初始化資料段
.bss @未初始化資料段
.sdata @
.sbss @
需要注意的是,源程式中.bss段應該在.text之前。
四. 定義入口點
彙程式設計式的預設入口是 start标号,使用者也可以在連接配接腳本檔案中用entry标志指明其它入口點。
【例4】定義入口點
.section.data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:
< instruction code goes here>
五. linux彙程式設計式中的宏定義
格式如下:
.macro 宏名 參數名清單 @僞指令.macro定義一個宏
宏體
.endm @.endm表示宏結束
如果宏使用參數,那麼在宏體中使用該參數時添加字首“\”。宏定義時的參數還可以使用預設值。
可以使用.exitm僞指令來退出宏。
【例5】宏定義
.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_r @判斷expression_r的值是否為0
.ifeqs
string1,string2 @判斷string1和string2是否相等,字元 串必須用雙引号括起來
.ifge
expression_r @判斷expression_r的值是否大于等于0
.ifgt absolute expression_r @判斷expression_r的值是否大于0
.ifle
expression_r @判斷expression_r的值是否小于等于0
.iflt absolute expression_r @判斷expression_r的值是否小于0
.ifnc
string1,string2 @判斷string1和string2是否不相等,
其用法跟.ifc恰好相反。
.ifndef symbol, .ifnotdef symbol @判斷是否沒有定義symbol,
跟.ifdef恰好相反
.ifne expression_r @如果expression_r的值不是0,
那麼編譯器将編譯下面的代碼
.ifnes string1,string2 @如果字元串string1和string2不相 等,
那麼編譯器将編譯下面的代碼.
5. .global .type .title .list
(1).global/ .globl :用來定義一個全局的符号,格式如下:
.global symbol 或者 .globl symbol
(2).type:用來指定一個符号的類型是函數類型或者是對象類型, 對象類型一般是資料, 格式如下:
.type 符号, 類型描述
【例6】
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 10
【例7】
.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>}
配置設定number_of_bytes位元組的資料空間,并填充其值為fill_byte,若未指定該值,預設填充0。(與armasm中的space功能相同)
(10).word
<word1> {,<word2>} …
插入一個32-bit的資料隊列。(與armasm中的dcd功能相同)
可以使用.word把辨別符作為常量使用
例如:
start:
valueofstart:
.word start
這樣程式的開頭start便被存入了記憶體變量valueofstart中。
(11).hword <short1> {,<short2>} …
插入一個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/上了解具體的使用方法。
參考資料:
1. richard blum,professional assembly language
2. gnu
arm
彙編快速入門,http://blog.chinaunix.net/u/31996/showart.php?id=326146
3. arm
彙編僞指令簡介,http://www.cppblog.com/jb8164/archive/2008/01/22/41661.aspx
gnu彙編使用經驗,http://blog.chinaunix.net/u1/37614/showart_390095.html
5.
gnu的編譯器和開發工具,http://blog.ccidnet.com/blog-htm-do-showone-uid-34335-itemid-81387-type-blog.html
6.
用gnu工具開發基于arm的嵌入式系統,http://blog.163.com/liren0@126/blog/static/32897598200821211144696/
7.
objcopy指令介紹,http://blog.csdn.net/junhua198310/archive/2007/06/27/1669545.aspx