Re: [問題] export dll時 lib中symbol的問題

看板C_and_CPP作者 (purpose)時間11年前 (2013/03/13 19:46), 編輯推噓5(503)
留言8則, 6人參與, 最新討論串2/2 (看更多)
之所以會有 __imp__functionName 這種符號名稱, 是因為當初在原型宣告時,有被加上 __declspec(dllimport) 修飾,比如: __declspec(dllimport) void functionName(void); 其實大部分的 Windows API 函數都有加 __declspec(dllimport), 比如 MessageBoxA(),在檔案 winuser.h 中,可知以下資訊: #define WINUSERAPI DECLSPEC_IMPORT #define DECLSPEC_IMPORT __declspec(dllimport) #define WINAPI __stdcall WINUSERAPI int WINAPI MessageBoxA(__in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType); --------------------------------------------------------------------- 為什麼要加 __declspec(dllimport)?很多人會說因為 DLL 的匯出函數一定要加, 不加不能用.................................................這當然是錯的。 要知道原因,就得明白,當你只寫了一份原始碼 Hello.c 時,那在該檔案內 定義的函數就是叫內部函數。除此之外的函數,通通叫外部函數、外來函數、CCR函數。 外部函數分成兩種: (1) 直接呼叫 (2) 間接呼叫 直接呼叫的外部函數,是指 CPU 指令寫成: call 函數位址 E8 XX XX XX XX (32-bit 記憶體位址) 間接呼叫的外部函數,是指 CPU 指令寫成: call dword ptr [函數位址放我這] FF15 XX XX XX XX 直接呼叫的外部函數,又是指在 Compile time 就能決定其函數位址在哪。 比如 ptt() 被定義在 ptt.c 內,又 Hello.c 有呼叫 ptt(),則編譯器會把 Hello.obj 的機器碼寫成 E8 00 00 00 00,等到連結器要合成 Hello.exe 時,再把 位址 00 00 00 00 改成正確值。因此就 Hello.c 的角度來看,這個 ptt() 函數 是被直接呼叫的外部函數。 間接呼叫的外部函數,則需要到 Run-Time 才能決定函數位址。通常就是 指 DLL 函數隱式呼叫 (implicit link)。至於顯式呼叫,對於編譯器來說就只是一個 function pointer 形態的變數,真正 Runtime 時能不能、會不會取得位址還是兩說, 所以不在考慮範圍內。 隱式呼叫 DLL 函數就是透過 CPU 指令達成: call dword ptr [函數位址放我這] 假設是 FF15 A8533400 其中 A8533400 為 little-endian,原記憶體位址為 003453A8,該位址為 IAT 陣列 所在。利用間接呼叫的 FF15 call 指令,將運算元設為 compile time 就能決定 的 IAT 區域,然後 Runtime 時,再由作業系統寫入真正的 DLL 函數位址到 IAT 內。 我們知道,殭屍會武術,神都擋不住。 同理,如果連結器也會翻譯 C 語言的話,那編譯器讓它當不就好了。 所以連結器其實只能修改 call XX XX XX XX 其中的位址這種小事。 當 ptt(); 被編譯器翻譯成 E8 00 00 00 00 時,連結器就只能將 00 00 00 00 位址改掉。可以改 operand 不能改 opcode,沒有將 E8 改成 FF15 這種權限。 因此每個 DLL 匯出函數,在其匯入用函數庫 (DllName.lib) 裡,都會有兩個符號名稱。 一個是 _functionName,一個是 __imp__functionName。 當程式設計師很負責任的這樣寫原型宣告時: __declspec(dllimport) void functionName(void); 編譯器就會完全理解該函數是「間接呼叫的外部函數」,所以其符號名稱會 使用 __imp__functionName 這個版本,且其 CPU 指令會是最精簡的: FF 15 00 00 00 00 call dword ptr [__imp__functionName] 連結時,連結器就會將 00 00 00 00 改成正確的 IAT 位址。 當程式設計師死不講,這樣寫原型宣告時: void functionName(void); 編譯器就會將函數當成普通的「直接呼叫的外部函數」,而且按照約定俗成的 符號命名方式,使用 _functionName 來等連結器搜尋該函數。 等連結器來將 E8 00 00 00 00 call _functionName 的位址修正。 因為隱式呼叫一定要用 FF15 call 才能正確調用,所以真正執行 FF15 call 的函數是偽函數 (stub),也就是來自匯入用函數庫 (DllName.lib) 裡 的 _functionName。 更精確來說,只要是透過 IAT 來間接調用都能過關,不限於 FF15 call, 所以 stub 偽函數的真正內容是: jmp dword ptr [functionName 的 IAT 所在] FF25 XX XX XX XX 就是用 FF25 jmp 無條件跳躍來取代,這樣在 return 時才能剛好返回原調用處。 --------------------------------------------------------------------- 對於 main.c 的編譯器來說: ┌ 內部函數 (functions that are defined in main.c) 函數 ─│ │ ┌ 直接呼叫 (E8 call) └ 外部函數 ─│ └ 間接呼叫 (FF15 call) 對於 DLL 匯出函數 void foo(); 來說: ┌ _foo (偽函數 stub,偽裝成 「直接呼叫型外部函數」) 符號名稱─│ └ __imp__foo (真名,利用 __declspec(dllimport) 強制編譯器使用) --------------------------------------------------------------------- 操作範例 == 檔案 myset.def LIBRARY myset EXPORTS foo == 檔案 myset.c /* compile with: cl.exe /LD myset.c myset.def */ #include <stdio.h> void foo() { puts("foo() is called."); } == 檔案 callit.c #pragma comment(lib, "myset") #pragma comment(lib, "user32") #include <stdio.h> #include <windows.h> int main() { puts("Hello World"); foo(); MessageBoxA(NULL, "test", "no", MB_OK); return 0; } == 執行畫面 Hello World foo() is called. == 反組譯指令 dumpbin /DISASM callit.obj Microsoft (R) COFF/PE Dumper Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file callit.obj File Type: COFF OBJECT _main: 00000000: 55 push ebp 00000001: 8B EC mov ebp,esp 00000003: 68 00 00 00 00 push offset $SG81344 00000008: E8 00 00 00 00 call _puts 0000000D: 83 C4 04 add esp,4 00000010: E8 00 00 00 00 call _foo 00000015: 6A 00 push 0 00000017: 68 00 00 00 00 push offset $SG81347 0000001C: 68 00 00 00 00 push offset $SG81348 00000021: 6A 00 push 0 00000023: FF 15 00 00 00 00 call dword ptr [__imp__MessageBoxA@16] 00000029: 33 C0 xor eax,eax 0000002B: 5D pop ebp 0000002C: C3 ret 其中 puts() 函數是靜態連結,foo() 函數是隱式連結 DLL 但先連到偽函數 _foo。 而 MessageBoxA() 函數是標準的加上 __declspec(dllimport) 後的結果。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 124.8.128.101

03/13 20:37, , 1F
推 (CCR函數
03/13 20:37, 1F

03/13 20:38, , 2F
沒推到= ="
03/13 20:38, 2F

03/14 00:38, , 3F
推:)
03/14 00:38, 3F

03/14 08:43, , 4F
有沒有 ELF 的版本 XD
03/14 08:43, 4F

03/14 09:28, , 5F
Push !
03/14 09:28, 5F

03/14 16:39, , 6F
ELF 不太熟,我查查資料晚點再看看
03/14 16:39, 6F

03/15 12:25, , 7F
p 大好久不見, 推一個 :D
03/15 12:25, 7F

03/15 12:42, , 8F
午安
03/15 12:42, 8F
文章代碼(AID): #1HG6Sf2U (C_and_CPP)
文章代碼(AID): #1HG6Sf2U (C_and_CPP)