函式連結與呼叫,匯入函式的呼叫
環境:VS2017 Community,Win32 Debug,專案屬性,常規,全程式優化,無全程式優化
首先來看普通函式的呼叫過程
#include <stdio.h>
void hello()
{
printf("Hello World\r\n");
return;
}
int main()
{
hello();
getchar();
getchar();
return 0;
}
來看反彙編:
通常我們都會在這裡F10進入函式,今天我們選擇F11單步執行如下:
注意,程式跳轉到了一個位置,從反彙編來看,此區域是一個比較大的跳轉表,而且這個跳轉表是在exe 模組內部的,其上下的跳轉表對應的都是XXXcrtXXXX函式,即執行時函式,由此可見,這個表跳轉是函式內部的一個跳轉表。
為什麼函式呼叫要通過這種看似間接的方式進行?
《老碼識途》作者給出的解釋是:PE (Windows 平臺下)的生成通常包括兩步:編譯,連結。編譯針對的是.cpp/.c 檔案,產物是.obj 檔案。當原始碼存在多個模組(.cpp/.c檔案)時,模組之間(.cpp/.c檔案)可能會有函式呼叫關係,為了方便的引用模組之間的函式,每個obj 檔案都有兩個列表,一個列表,包含了本模組的所有函式,另一個列表,包含了本模組所引用的尚未確定的函式。連結前,obj檔案中的函式呼叫是通過call offset + jmp 地址來進行函式呼叫的,如果所呼叫的函式尚未確定,此時call(E8 offset) 後面的地址為0。連結時,連結器一次解決所有模組中尚未確定的模組的地址,並將E8指令後的偏移修正為正確的值。
當所呼叫的函式為匯入函式的時候是什麼樣的情況呢?
構建如下的DLL:
// dllmain.cpp : 定義 DLL 應用程式的入口點。
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
extern "C" __declspec(dllexport) void hello()
{
printf("Hello World\r\n");
return;
}
BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
hello();
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
上圖分別是Dll 內部自己呼叫hello 函式,與exe 呼叫匯入的hello 函式的過程。我們可以看到,當dll內部,即pe 呼叫內部的函式時將會使用e8+偏移指令,然後跳轉到一個jmp 指令,jmp 跳轉到真正的位置執行我們的函式。
當exe 呼叫匯入的函式時,首先通過一個ff 15 [地址]指令,跳轉到匯入表中指定的地址,然後再執行jmp 指令跳轉到真正的函式實現的地址。通過對比我們發現,該jmp 指令的地址是相同的,且該jmp 指令在dll 模組中。
由此我們可以總結得到函式呼叫的過程為:
call + jmp
當我們使用GetProcAddress 和 函式指標時,得到的函式地址是什麼格式的?
FF 55 EC(取反加一即為-14) call dword ptr [ebp-14h]
同樣是通過讀取記憶體來進行第一次的跳轉,而這個記憶體為棧記憶體
同上。
&函式名為什麼過程???
同上