Trao đổi với tôi

http://www.buidao.com

11/5/09

[Programming] MSVC 6.0 C++ Name Mangling

[Tips] MSVC 6.0 C++ Name Mangling

Trong quá trình tìm hiểu DLL Injection, thấy bên virusvn.com có một số câu hỏi khá hay quyết định note lại vài chữ lưu trữ. Trong bài viết này, chúng ta sẽ giải quyết 2 câu hỏi sau:

1. Giữa 2 khai báo

__declspec(dllexport) return_type function_name [parameter_list]

extern “C” __declspec(dllexport) return_type function_name [parameter_list]

trong file .cpp của một DLL dùng để export hàm có tên là function_name có gì khác nhau. Sau đó load 2 file DLL theo 2 cách khai báo khác nhau thì thấy function_name khác nhau, một là function_name, một là ?function_name@YAxxx?

2. Để export function trong DLL, bạn có thể dùng 2 cách là extern “C” __declspec(dllexport) return_type function_name [parameter_list] và dùng export file .DEF. Sử dụng cái nào tiện và ổn định hơn?

Để trả lời câu hỏi thứ 1, cách tốt nhất để bạn thấy được sự khác nhau là thử code 2 DLL sử dụng 2 cách trên.

Cách 1: (Sample1.CPP)

#include 
#include 
#include 
 
__declspec (dllexport) void HelloWorld ()
{
    MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
 
BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason ,LPVOID reserved)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        HelloWorld();
        break;
    case DLL_PROCESS_DETACH:
        MessageBox (0, "DLL Detached!\n", "Inj Detach", MB_ICONINFORMATION);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }  
    return TRUE;
}

Khi biên dịch ra, bạn được Sample1.DLL. Dùng Dependence Walker hoặc CFF Explorer để quan sát hàm HelloWorld như thế nào?

clip_image002

clip_image004

Vậy là với cách 1 này, bạn thấy VC++ đã mã hóa tên hàm từ HelloWorld thành

?HelloWorld@@YAXXZ

Cách 2: (Sample2.CPP)

#include 
#include 
#include 
 
extern "C"__declspec (dllexport) void HelloWorld ()
 
{
    MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
 
BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason ,LPVOID reserved)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        HelloWorld();
        break;
    case DLL_PROCESS_DETACH:
        MessageBox (0, "DLL Detached!\n", "Inj Detach", MB_ICONINFORMATION);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }  
    return TRUE;
}

Khi biên dịch ra, bạn được Sample2.DLL. Dùng Dependence Walker hoặc CFF Explorer để quan sát hàm HelloWorld như thế nào?

clip_image006

clip_image008

Với cách 2 này, bạn thấy VC++ đã không mã hóa tên hàm HelloWorld.

Tại sao với cách 1, VC++ mã hóa function của chúng ta làm gì? Để trả lời câu hỏi này, trước hết chúng ta làm quen với khái niệm Name Mangling trong VC++

Name mangling (còn được gọi là name decoration), dịch ra tiếng việt là cách đặt lại tên hay là mã hóa tên, là một phương pháp được các trình biên dịch (compilers) C++ dùng để thêm thông tin bổ sung cho tên của các hàm và đối tượng trong file đối tượng. Thông tin này được trình liên kết (linkers) sử dụng khi một hàm hoặc đối tượng được định nghĩa trong cùng một module và được một module khác gọi. Name mangling được dùng vào 3 mục đích sau:

· Để cho linkers phân biệt được hai khai báo khác nhau của cùng một hàm (function overloading).

· Để cho linkers có thể kiểm tra xem các đối tượng và hàm được khai báo chính xác trong tất cả các module hay không

· Để cho linkers có thể cung cấp đầy đủ thông tin các loại tham chiếu chưa chính xác trong thông báo lỗi.

Ta thử phân tích cú pháp hàm bị mã hóa ?HelloWorld@@YAXXZ

Về cơ bản, đi đôi với Name Mangling là Calling Convention (quy ước gọi hàm), ta có:

stdcall: ?funcName @@YG retType params-list Z
cdecl: ?funcName @@YA retType params-list Z 
thiscall: ?methodName @ className @@AAE retType param-list Z
static method call: ?methodName @ className @@CA retType param-list Z

Dựa vào cú pháp chuẩn, ta biết đựơc hàm function:

  • @@YA: quy ước gọi hàm là cdecl.
  • A: kiểu trả về của hàm là void
  • mã A kế tiếp: là danh sách các biến, nhưng ở đây ta thấy hàm HelloWorld không có tham số nào, nên mặc định sẽ là mã A.
  • Và cuối cùng là mã Z.

Để trả lời câu hỏi thứ 2, bạn thử viết DLL bằng cách sử dụng file .DEF:

#include 
#include 
#include 
 
void HelloWorld ()
{
    MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
 
BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason ,LPVOID reserved)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        HelloWorld();
        break;
    case DLL_PROCESS_DETACH:
        MessageBox (0, "DLL Detached!\n", "Inj Detach", MB_ICONINFORMATION);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }  
    return TRUE;
}

.DEF

LIBRARY Sample3
EXPORTS HelloWorld

Load file Sample3.DLL vào CFF, ta thấy kết quả tương tự như ta sử dụng cách 2 (dùng thêm từ khóa extern “C”)

sample3.dll_DW

Cái này sử dụng tiện và ổn định hơn bạn đã thấy rõ. Nếu dùng file .DEF bạn không cần phải quan tâm tới quy ước gọi hàm và khai báo __declspec gì cả, chỉ cần khai báo nó trong file .DEF, mà bản thân .DEF là một file text bình thường, nên việc biên dịch và viết code cho nó tương đối đơn giản. Thêm vào nữa là sử dụng file .DEF chúng ta không cần phải cân nhắc giữa thiết lập quy ước gọi hàm mặc định và quy ước gọi hàm viết trong file .CPP


Link: http://nhatphuongle.spaces.live.com/blog/cns!320FF19317F0C9A2!584.entry