使用了未赋值的局部变量_macOS上的汇编入门(七)——字面量与局部变量
在上一篇文章中,我們分析了第一個匯編程序。
# exit.s .section __TEXT,__text.globl _main _main:movq $0, %raxretq這個匯編程序是我們所有匯編程序的框架,因為它實現了程序進入和程序退出的功能。我們接下來所有的程序都是在這個程序的基礎上進行修改。
在這篇文章中,我主要介紹的是匯編語言中變量的使用。在x86-64架構下,寄存器的數量很少。而且,寄存器的作用往往是用于運算而不是用于存儲。那么,我們在程序中該如何使用變量呢?
.equ定義字面量
最簡單的定義變量的方式,是利用匯編器指令.equ. 這類似于C語言中的#define. 比如說,我在程序開頭寫上
.equ maxCount, 0x114514那么,我在之后的程序里就可以寫
movq $maxCount, %rax來表示將0x114514賦值給rax寄存器。
同時這里應當指出,這個指令是匯編器指令,在匯編的時候,會自動將所有的maxCount直接用0x114514替代。比如說,我有以下程序:
.text.globl _main.equ maxCount, 0x114514 _main:movq $maxCount, %raxretq我們通過匯編、鏈接以后,得到一個test可執行文件。我們可以用之前提到的MachOView軟件,或者在終端中鍵入
otool -v -t ./test來查看生成的可執行文件中__TEXT段__text節的內容:
由此可知,最終生成的文件中,是直接替換得到的。
此外,.equ還有一個比較方便的地方在于,它可以支持簡單的算術運算,如加減乘除等。比如說,我可以寫.equ maxCount, 1919-810, 那么接下來所有出現maxCount的地方,都會用1109來替代。
但是,正如C語言中的#define定義的宏一樣,.equ定義的變量只是一個簡單的替換,并不支持對這個變量重新賦值之類的操作。這個變量也沒有其地址,只是一個字面量。
局部變量
棧
我們知道,在C語言中,局部變量在棧上分配。在匯編語言中也是這樣。因此,我們來回憶一下「棧」的概念。
在操作系統基礎中,我們談到,在一個程序運行的時候,系統會自動給這個程序分配一個棧區。這個棧區和數據結構中所說的棧類似,也支持壓棧和彈棧的操作。棧區在邏輯地址空間里是一塊連續的空間,棧底是固定的,每次壓棧,都會使棧頂向邏輯地址減小的方向移動。
在幾個寄存器中,有一個寄存器和棧的關系非常大,那就是rsp寄存器。從它的名字就可以看出來,stack pointer, 它存儲的值永遠是棧頂的地址,所以它又被叫做棧頂指針。我們可以用(%rsp)來獲取棧頂存儲的值,通過a(%rsp), 其中a是任何一個整數,來獲取地址是rsp存儲的值加a處的內存單元的值。比如說,2(%rsp)就是棧頂上方(邏輯地址增大方向)2個字節處的值,-2(%rsp)就是棧頂下方(邏輯地址減小方向)2個字節處的值。關于這個記號,我也會在之后的尋址方式中提到。
在匯編語言中,壓棧和彈棧的助記符分別是push和pop. 這兩個操作均有一個操作數。push的操作是將棧頂指針向下移動(也就是將rsp內的值減小),并將移動后rsp對應位置內存區域的值賦為其操作數,而pop則相反。這里“向下移動”的距離是根據push后面跟著的字母決定的,如pushq就是把rsp內的值減8.
此外,如果是想獲得棧頂的值,而不彈棧,可以直接用mov來實現。如popq %rax是將棧頂的8個字節內存儲的值賦給rax, 并且棧頂指針向上移動8個字節。而movq (%rsp), %rax則是只將棧頂的8個字節內存儲的值賦給rax, 不涉及棧頂指針的移動。而如果只想彈棧卻不想賦值,那么直接對rsp進行add即可。如想把棧頂的8個字節的數據彈棧,就直接addq $8, %rsp.
同時,對于push而言,如果我們一下子準備把許多值壓入棧內,那么可以先用sub指令減小rsp, 再用mov移動。比如說:
# method 1 pushq $0x114514 pushq $0x1919 pushq $0x810# method 2 subq $24, %rsp movq $0x114514, 16(%rsp) movq $0x1919, 8(%rsp) movq $0x810, (%rsp)方法一和方法二的最終效果是一樣的。但是,我們建議使用方法二,也就是“先sub, 再mov”,因為這樣更高效。
使用局部變量
講完了棧的概念,接下來就是如何使用局部變量了。使用局部變量非常簡單,就是將局部變量放到棧上,然后使用的時候直接去訪問棧上對應的地址空間就行。然后在返回之前,把棧恢復即可。
但是,這里有一個常用的技巧。像上面的例子中寫的,我們是通過對rsp中存儲的地址加偏移量去訪問局部變量,但是,如果我們之后又有了壓棧、彈棧的操作,那么,偏移量就會改變。這種不穩定性十分不利于我們編程。因此,我們又用了另一個寄存器rbp來解決這個問題。rbp, 顧名思義,base pointer, 基地址指針,一般是用來使用偏移量尋址的。我們使用的技巧是,先將rbppush進棧(之所以保留我會在后面的調用約定里說到),然后利用之前的手法對rspsub. 然后,利用rbp的偏移量來引用局部變量。最后在返回前,將rbp賦值給rsp, 此時棧頂指針指向的是最初對rbppush之后的位置,然后將棧頂pop出來給rbp,最后返回。
比如說,我有以下C程序:
int main() {int a = 0x114514;int b = 0x1919;int c = 0x810;return 0; }那么,它對應的匯編程序如下:
_main:pushq %rbpmovq %rsp, %rbpsubq $24, %rspmovq $0x114514, -8(%rbp)movq $0x1919, -16(%rbp)movq $0x810, -24(%rbp)movq $0, %raxmovq %rbp, %rsppopq %rbpretq它對應的棧的變化如圖所示:
由此可見,在執行完popq %rbp之后,棧又恢復為最初進入時的模樣。
我們在使用rbp+偏移量來訪問局部變量的時候,有時候會覺得要把變量對應的偏移量記住,這會比較麻煩。我們可以結合上面講到的.equ定義字面量來解決這一問題:
_main:.equ a, -8.equ b, -16.equ c, -24pushq %rbpmovq %rsp, %rbpsubq $24, %rspmovq $0x114514, a(%rbp)movq $0x1919, b(%rbp)movq $0x810, c(%rbp)movq $0, %raxmovq %rbp, %rsppopq %rbpretq這樣,我們只需要之后用a(%rbp)就可以指代a了。
可以在哪看到這系列文章
我在我的GitHub上,知乎專欄上和CSDN上同步更新。
上一篇文章:macOS上的匯編入門(六)——匯編語言初識
下一篇文章:macOS上的匯編入門(八)——尋址方式與全局變量
總結
以上是生活随笔為你收集整理的使用了未赋值的局部变量_macOS上的汇编入门(七)——字面量与局部变量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vba动态二维数组_VBA实战技巧05:
- 下一篇: 人工智能 ppt_【138期】厉害了!人