看板 C_and_CPP 關於我們 聯絡資訊
在 IA32 with Windows or Linux 之下, 每個程式都有一塊用途特殊的記憶體區, 稱為堆疊, 由 ESP 指向其頂端 (EBP 也可) 對於呼叫與回返的指令 (CALL / RET), 它會把下一步要執行的指令位址 (由 EIP 指向) 推到堆疊裡 / 從堆疊拿出來, 並且 ESP 也會做增減的動作 PUSH 時是 ESP 先減 4 再放值, POP 時是先取值 ESP 再加 4 當你使用過量時就會 RE. 所以要小心遞迴過深. 此外對於大部分的 C Compiler, 會把區域變數以及函式呼叫的參數都放在堆疊當中 以區域變數來說的話, 許多 Compiler 會直接把 ESP 增減一個值, 然後用 EBP 去指向原先 ESP 的位址, 以 EBP ~ ESP 的區域做為你的堆疊框架 區域變數就是放在這之中, 這也是為什麼開太大的區域變數會 RE 此外對於函式呼叫的部份, 除了在 CALL 指令時會把下一個指令的位址推入堆疊 在 CALL 之前編譯器還會把呼叫的參數推入堆疊 以 C 調用者約定來說, 是 : 把參數由右到左推入堆疊、調用者清理堆疊 也就是說 C 的被調用者可能不知道你到底給了他多少參數, 因此 C 支援可變長度參數, 如 printf, scanf 就是可變長度的 (用 '...' ) 宣告 假設有個函式 void Q(int a, int b, int c) { } 那你呼叫 Q 時, 在 Q 建立自己的堆疊框架前, 堆疊可能長得像這樣 | ? | +--------+ | c | <-- ESP+C +--------+ | b | <-- ESP+8 +--------+ | a | <-- ESP+4 +--------+ |ret addr| <-- ESP +--------+ | ? | 而 printf 函式就是從 "假定" 有參數的地方開始去找 比如有可能在建立堆疊框架後, EBP+8 是 const char* format 那麼依序下去 EBP+C, EBP+10, EBP+14, ... 就該是你一去傳給他的參數了 比如我們可以直接用 printf("%p\n%p\n%p\n%p\n"); 來印出堆疊中部份的內容 至於函數的回傳值則是視情況放在 al / ax / eax / edx:eax 當中 (char ~ long long) 那題給的程式是 #include <stdio.h> void main() { printf("%s%s%s%s%s%s%s%s%s%s%s%s%s"); // 我也不知道有幾個 '%s' ... } 這樣幾乎一定會 RE 有可能, 第一個 %s 抓到 Old EBP, 第二個抓到 main ret addr (probably to CRT) 第三個抓到 char** argv, 第四個抓到 int argc, 再來就不知道可能是什麼了 ※ 引述《raincole (冷雨)》之銘言: : 我想請問的是printf中%d和%f的事實上是如何作用的。 : 例如說printf("%s");會記憶體錯誤,但printf("%d");只是輸出一個垃圾值, : 是什麼原因造成這種差別? -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 220.137.66.67
hilorrk:跪下了... 12/22 20:49
suhorng:忽然發現某些細節有寫錯 ... Orz 12/22 21:05
shik:淚推.. 12/22 21:21
bibo9901:推強者 12/22 21:49
小修一下內文錯誤:P ※ 編輯: suhorng 來自: 220.137.66.67 (12/22 22:13)
VictorTom:拜....<(_ _)> 12/22 22:39
ducksteven:<(_ _)> 12/23 01:33
Yshuan:推 跟自己寫組語刻subroutine感覺一樣 orz.... 12/23 01:43
abilitylife:<(_ _)> 12/23 01:44