1. 程式人生 > >函式連結與呼叫,匯入函式的呼叫

函式連結與呼叫,匯入函式的呼叫

環境: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(取反加一即為-14call dword ptr [ebp-14h]

同樣是通過讀取記憶體來進行第一次的跳轉,而這個記憶體為棧記憶體
這裡寫圖片描述

同上。

&函式名為什麼過程???

這裡寫圖片描述

同上