[英]Can someone provide a visualization or detailed flow of the stack frame in this Assembly MIPS code block?
addi $sp, $sp, -32 # stack frame is 32 bytes long
sw $ra, 20($sp) # save return address
sw $fp, 16($sp) # save frame pointer
addi $fp, $sp, 28 # set up frame pointer
sw $a0, 0($fp) # save argument (n)
我對堆棧框架的工作原理有點迷茫,我對理解這個過程仍然很陌生。 有人可以逐行解釋發生了什么嗎? 如果可能,您能否還提供實際堆棧幀的可視化以及幀指針和堆棧指針的位置及其移動方式?
要可視化,首先我們需要建立可視化的方向,通常是向上還是向下。 兩者都是正確的,但一個可能比另一個更難閱讀。
在任何情況下,堆棧都會向編號較低的內存地址“增長”,並向編號較高的內存地址收縮。 內存基本上總是在那里(並且不會移動!)——堆棧指針寄存器告訴內存的哪一部分被占用/正在使用,與未占用/可用/空閑; 它只是從占用/使用的堆棧內存中划分出空閑/未使用的堆棧內存。
對於堆棧在視覺上“向下”增長的圖片:
+------------+
0x7ffff000 | | <-- $sp+4 second word of occupied stack space
+------------+
0x7fffeffc | | <-- $sp first word of occupied stack space
+------------+
0x7fffeff8 | | <-- $sp-4 first word of unoccupied stack space
+------------+
對於堆棧“向上”增長的可視化。
+------------+
0x7fffeff8 | | <-- $sp-4 first word of unoccupied stack space
+------------+
0x7fffeffc | | <-- $sp first word of occupied stack space
+------------+
0x7ffff000 | | <-- $sp+4 second word of occupied stack space
+------------+
大多數人發現堆棧向下增長更容易理解,但如果您將堆棧幀視為一個對象/記錄,則會令人困惑,因為在視覺上更容易看到向前布置的對象(即更高的地址)。
作為替代,我們可以使用左/右(這里的地址向左偏低,向右偏高,堆棧向左“增長”):
0x7ffffeff8 0x7ffffeffc 0x7fffff000
+-------------+-------------+-------------+
| | | |
+-------------+-------------+-------------+
first free first used second used
^ ^ ^
| | |
$sp-4 $sp $sp+4
我使用術語 first used/occupied 來指代堆棧的“頂部”。
接下來,有一條分配堆棧空間的指令: addiu $sp,$sp,-32
。 該指令簡單地添加到堆棧指針,以計算堆棧頂部的新位置。 我使用addiu
是因為指針算術應該使用無符號算術來完成。 (如果堆棧增長超過某個點,使用addi
將導致不需要的溢出陷阱。) -32
是負數,因此它正在增長堆棧 - 擴大堆棧的使用部分,只需移動輪廓符$sp
。 -32
是字節數,因此就字而言,這是 8 個字(在 32 位機器上每個字 4 個字節)。
0x7ffffefdc 0x7ffffefe0 0x7ffffefe4 0x7ffffefe8 0x7ffffefec 0x7ffffeff0 0x7ffffeff4 0x7ffffeff8 0x7ffffeffc 0x7fffff000
+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
| | | | | | | | | | |
+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
<------------------ newly allocated, uninitialized -----------------------------------------------------------> <--- old previously in use --->
^ ^
| |
$sp old $sp
在此之后,堆棧指針不會移動,因此我們可以簡單地注釋新分配的存儲:
sp+0 sp+4 sp+8 sp+12 sp+16 sp+20 sp+24 sp+28 sp+32 sp+36
0x7ffffefdc 0x7ffffefe0 0x7ffffefe4 0x7ffffefe8 0x7ffffefec 0x7ffffeff0 0x7ffffeff4 0x7ffffeff8 0x7ffffeffc 0x7fffff000
+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
| | | | | old $fp | $ra | | $a0 | | |
+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
<------------------ newly allocated --------------------------------------------------------------------------> <--- old previously in use --->
^ ^ ^
| | |
$sp $fp old $sp
這里的所有都是它的。 棧是一段內存; 棧指針寄存器中保存的值表示棧頂在哪里,這也是未使用/空閑/可用棧內存和使用中/已分配棧內存的划分。 堆棧指針寄存器的值發生變化以增大/縮小堆棧(內存不會移動)。 任何需要一些堆棧空間的函數都通過移動堆棧指針來分配,然后使用$sp
相對尋址。 它事先不知道堆棧的實際地址,但它知道可以使用$sp
寄存器來引用堆棧頂部,並且可以使用$sp
計算局部變量的地址。
因此,它並不重要棧指針實際上是和,這使得main
調用A
,也main
調用B
和B
打電話給A
-在這種情況下A
只與第一稱為main
堆棧和后來與main
和B
在堆棧上。 由於A
僅使用$sp
相對尋址,因此它用於其幀的實際地址並不直接重要。 例如,這也允許A
是遞歸的,因為它可以有多個激活(堆棧上的堆棧幀)。
您的代碼段還建立了一個幀指針$fp
,這通常是不必要的,但有時還是會這樣做。 在 MIPS 上,堆棧使用是扁平化的:函數所需的所有堆棧空間通常在開頭的一條指令中分配(稱為序言)。 由於這種扁平化,通常不需要幀指針。 (然而,在 x86 上,堆棧指針可能經常移動,例如在調用另一個函數時通過推送傳遞參數,然后彈出。因為堆棧指針在函數體執行期間移動,因此可以使用幀指針很有幫助,因為它在函數執行期間不會移動。)
在設置幀指針之前,它將幀指針的舊值保存到內存位置(堆棧幀中新分配的字之一)。
幀指針通過指針算法初始化——這里指向新分配的堆棧空間的最高地址。 (指針算術應該做addiu
而不是addi
)。
在此代碼片段之后,該函數的代碼可以使用$sp
相對加載和存儲(使用偏移值 0..28)或使用$fp
相對加載和存儲(使用偏移值 0..-28)來訪問新分配的幀存儲器。 使用適當的偏移量,我們可以使用$sp
或$fp
到達相同的內存位置,例如, 20($sp)
與-8($fp)
地址相同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.