1 從一個彙編錯誤開始說起
最近從u-boot中摘了一段代碼出來(源於kernel),這段代碼作用是用軟件(彙編)實現除法和取模運算,因爲有些老的CPU是沒有相關的硬件指令的。在編譯的時候,彙編器報了一些錯誤,這些錯誤都是同一類型,這裏僅列出其中的一個:Error: bad instruction 'reteq lr'
。
錯誤本身還是比較好理解的,arm32是沒有ret
指令的。但讓人不理解的是爲什麼這段代碼在u-boot中可以編譯通過。直到我看到了這麼一段彙編代碼:
/*
* We only support cores that support at least Thumb-1 and thus we use
* 'bx lr'
*/
.irp c,,eq,ne,cs,cc,mi,pl,vs,vc,hi,ls,ge,lt,gt,le,hs,lo
.macro ret\c, reg
.ifeqs "\reg", "lr"
bx\c \reg
.else
mov\c pc, \reg
.endif
.endm
.endr
初看這段代碼可能覺得有些奇怪,且看下文一一分解。
2 GNU彙編器的.irp
首先是.irp
的用法:.irp symbol,values . . .
symbol是符號,values 是一串值,這一串值會被逐一賦給symbol,在引用symbol需要加上斜槓,即\symbol
。舉例來說,彙編下面這段代碼:
.irp param,1,2,3
move d\param,sp@-
.endr
等價於彙編:
move d1,sp@-
move d2,sp@-
move d3,sp@-
3 GNU彙編器的.macro
彙編宏可能更爲常見,其用法如下(一個帶參,一個不帶參):
.macro macname
.macro macname macargs ...
macname爲宏名,如果宏帶參的話,參數之間以空格或逗號隔開。對於宏參數,可以通過在其後添加:req
來表明引用宏時,該參數必須賦一個非空值;也可以添加:vararg
來表明引用宏時,該參數接收所有剩下的引用時傳參;還可以添加=deflt
來給參數指定一個值爲deflt
的默認默認值。引用宏時,按照定義時的參數順序傳參(如果有的話)即可。當然,也可以按照macname=value
的形式傳參,這時就不必按照定義時的參數順序了。
看一個具體的例子,宏定義如下:
.macro sum from=0, to=5
.long \from
.if \to-\from
sum "(\from+1)",\to
.endif
.endm
使用sum 0, 3
或等價的形式sum to=3, from=0
來引用宏,那麼可以得到如下的結果:
.long 0
.long 1
.long 2
.long 3
4 當.irp遇到.macro
此時,再回到第1節中的那段代碼:
.irp c,,eq,ne,cs,cc,mi,pl,vs,vc,hi,ls,ge,lt,gt,le,hs,lo
.macro ret\c, reg
.ifeqs "\reg", "lr"
bx\c \reg
.else
mov\c pc, \reg
.endif
.endm
.endr
通過.irp
定義的符號c
(條件碼)擁有一系列的取值,如eq
、ne
等,還要注意最開始的那個空值。在.irq
和.endr
之間的那一段是一個宏定義,當我們將那一系列的值賦給c
時,就可以得到一系列的彙編宏:
.macro ret, reg
.ifeqs "\reg", "lr"
bx \reg
.else
mov pc, \reg
.endif
.endm
.macro reteq, reg
.ifeqs "\reg", "lr"
bxeq \reg
.else
moveq pc, \reg
.endif
.endm
......(不再列出)
至此一切真相大白,reteq lr
中的reteq
是一個宏,由於參數是lr
,因此該宏引用會被替換爲bxeq lr
,而bx
指令當然屬於arm32指令集,也就能夠編譯通過了。
參考文獻
[1] GNU官方文檔