作者suhorng ( )
看板C_and_CPP
標題Re: [語法] %d和%s的實際行為?
時間Tue Dec 22 20:18:26 2009
在 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