最近從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
初看這段**可能覺得有些奇怪,且看下文一一分解。
首先是.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@-
彙編巨集可能更為常見,其用法如下(乙個帶參,乙個不帶參):
.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
此時,再回到第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指令集,也就能夠編譯通過了。
為什麼要搞這麼一套,而不直接使用bx
指令呢?u-boot裡面的用法不太容易看出來原因,不妨將目光轉向kernel:
.irp c,,eq,ne,cs,cc,mi,pl,vs,vc,hi,ls,ge,lt,gt,le,hs,lo
.macro ret\c, reg
#if __linux_arm_arch__ < 6
mov\c pc, \reg
#else
.ifeqs "\reg", "lr"
bx\c \reg
.else
mov\c pc, \reg
.endif
#endif
.endm
.endr
kernel既要支援比較老的arm晶元,也要支援比較新的。而老的晶元可能不支援thumb指令,函式返回使用mov pc, lr
,而新的晶元則使用bx lr
。在原始碼裡面,對每個彙編巨集進行條件編譯實在是不合理,而使用.irp
和.macro
可以不重複的使用非常少的**完成這一任務,彙編源中只需要統一使用ret巨集
,豈不美哉!
[1] gnu官方文件
ARM的ADS彙編器與GCC彙編器
arm的ads彙編器與gcc彙編器 2009 10 14 10 06 彙編器與指令集,不同的cpu對應不同的指令集 不同的彙編器對應不同的語法和偽指令集。每種彙編器都可以有自己的偽指令集和自己的語法,但實際上,由於事實標準的原因,所有的cpu廠商會提供指令使用手冊,手冊中的指令書寫樣式,實際上就是事...
ARM彙編器與GCC彙編器支援的組合語言差別
將arm sdt下的彙編 移植到gcc for arm編譯器時,經常要做如下修改 1 注釋 或者 2 偽操作符替換 jumpaddr jumpaddr 符號定義加 號 include include equ equ tclk2 equ pb25 equ tclk2,pb25 tclk2 equ pb...
實現乙個彙編器
上文 彙編器原始碼剖析 中,我們對一彙編器進行了原始碼剖析,這裡我們仿照其實現乙個自己版本的彙編器,90 的東西都是借鑑於上文中的原始碼。實現乙個彙編器,首先需要定義乙個彙編指令集,這裡我們還是沿用上文中的彙編指令集。彙編指令與指令之間是一一對應的關係,也就是說是直譯的過程。我們的指令集是列舉型別,...