比如這是乙個 arm64 架構下的 macho 檔案。mach 載入器會讀取 macho 檔案中的載入命令(load commands),這些命令決定了如何布局這個程序的記憶體空間。
(這裡需要注意:記憶體位址和 macho 檔案的 offset 是兩個概念,對於 text 和 data 段中的東西,arm64 架構下,可以認為0x100000000 + offset + aslr = 記憶體位址)
那麼我們首先看看呼叫 printf() 的彙編是什麼樣的。
然後在呼叫 printf() 時打乙個斷點。執行到這個斷點時,我們看到了這樣一行彙編**:
0x10044e694 : bl 0x10044ea78 ; symbol stub for: printf
意思是跳轉到 0x10044ea78 位址繼續執行,並且這個位址,代表了 printf 函式。0x10044ea78改位址,實際是什麼呢?(記憶體中的位址!)
由於 aslr 技術,我們需要減去 image 的起始位址,才能得到 rebase 之前的位址,用於和 macho 檔案對比。通過 image list 命令得到各個 image 的起始位址後,我們發現,0x10044ea78 落在起始位址為 0x0000000100448000 的主二進位制中,二者相減,偏移量為 0x6a78。
則記憶體中的0x10044ea78對應 macho的主二進位制檔案中的偏移為????0x6a78位址中(該位址位於text 段 __stubs 節),說明呼叫printf() 首先跳轉到了**段的某個位址上。
0000000100006a78 nop
0000000100006a7c ldr x16, #0x15dc
0000000100006a80 br x16
//nop 為空命令
ldr 這一行的意思是,將當前 pc 暫存器中的值,加上 0x15dc,再存到 x16 暫存器中
br 這一行的意思是,跳轉到 x16 暫存器的值指向的位址。
總的來說就是就是跳轉到了x16暫存器的位址中(0000000100006a7c + 0x15dc =0x8058)
去檢視 0x8058 這個位址,它位於 data 段 __la_symbol_ptr 節。
__la_symbol_ptr節是一系列指標,這些指標指向的,是某乙個指令的位址。
這裡 0x8058 這個位址中存的是 0x100006b08,
這個 0x100006b08 是 macho 還未 or 剛剛載入記憶體中時,記憶體中 0x100008058 位址存的值。但是記憶體中的值是可以被改變的。事實上,只有第一次呼叫 printf 的時候,這一步會跳轉到 0x100006b08,之後,這個記憶體會被寫入新的位址,即動態庫中 printf() 函式的位址。???這就是lazy bind。
接下來0x100006b08位於 __text 段 __stub_helper 節,它的彙編指令是:
接下來會跳轉到0x6a84這個位址,這個位址是 __stub_helper 節的開頭。
執行幾個指令後,就回去呼叫 dyld_stub_binder 函式。
dyld_stub_binder 是 dyld 執行 bind 的函式。這個函式執行完後,__la_symbol_ptr 節中的內容,將不再是指向 __stub_helper 節的位址,而是 printf 函式真正的位址。
那麼下次呼叫 printf 函式的時候,就可以直接通過 __la_symbol_ptr,找到真正的 printf 函式位址了。
乙個printf引發的問題
牛客網上的乙個題目 intmain 看起來挺簡單的,牽扯的東西比較多。這是我的思路 printf函式執行的時候,會先把這三個數字壓入棧裡,然後再執行列印。壓入棧的時候按照資料本身的長度來,首先把c和b壓入,並且每乙個都是8個位元組 printf自動轉化為double 然後再壓入a是4個位元組。然後再...
自己實現乙個printf函式
在arm嵌入式開發環境中,串列埠一般使用arm pl011的uart實現,uart的實現原理就是實現了乙個8bits寬度,32深度的fifo,不停的往螢幕輸出乙個byte,乙個byte。這個就是硬體的實現,那麼軟體是怎麼實現列印 高階程式語言中定義的char,short,int,long,float...
vc 中printf的乙個實現
int cdecl messageboxprintf tchar szcaption,tchar szformat,int winapi winmain in hinstance hinstance,in opt hinstance hprevinstance,in opt lpstr lpcmdl...