64位驅動中加入彙編代碼

來源:互聯網
上載者:User

本文介紹如何在驅動程式的C檔案中調用ASM檔案中匯出的彙編函數
引言
    Windows驅動程式使用DDK或者IFSDDK(以下簡稱DDK)中的Build.exe程式對源檔案進行編譯和連結,操作時只需啟動相應的DDK命令列程式,進入待編譯的源檔案目錄,執行Build命令即可得到.sys檔案。
    Build命令實際上是調用了一個nmake程式,nmake程式隨後調用cl.exe和link.exe,並指定所有的編譯和連結選項,與應用程式不同,程式員在使用build命令時無法對編譯連結選項進行修改,從而無法在build命令中幹先行編譯或者連結過程。
    64位平台不再支援內嵌式的彙編代碼,為了使驅動程式中仍然可以使用組合語言,通常的做法是將彙編代碼寫成獨立的函數放入單獨的彙編檔案中,並對彙編檔案進行編譯形成obj檔案,而後在連結期對所有目標檔案(包括驅動C代碼的目標檔案)進行連結。但是DDK內建的Build程式並不直接支援對彙編檔案的編譯和連結(也可能我還沒有發現這樣的方法)。因此需要通過其他手段將彙編代碼目標檔案和C代碼目標檔案進行連結。本文講述的方法是通過修改SOURCE檔案和強制指定外部函數呼叫慣例的方式來達到在Windows驅動程式中調用獨立彙編函數的目的。
需要注意的問題
1.32位平台和64位平台中呼叫慣例的不同
    32位平台下,驅動程式預設採用__stdcall呼叫慣例,該約定編譯產生的函數名均帶有尾碼@xxx,而彙編代碼採用__cdecl呼叫慣例,用彙編器編譯產生的函數名並不帶尾碼,從而導致C代碼無法引用彙編檔案中的函數(連結器會報錯:unresolved external asm_rng_available@xxx)。解決這一問題的方法是強制指定調用外部的彙編函數為__cdecl呼叫慣例,即如果在C檔案中調用彙編檔案中的asm_rng_available函數,聲明方式應如下:
    extern int          __cdecl asm_rng_available() ;
強制指定__cdecl呼叫慣例後可以保證C編譯器產生的asm_rng_available函數名和彙編檔案中產生的函數名是一樣的。
    64位體繫結構中只有一個本機呼叫慣例和一個__cdecl約定可以被編譯器忽略,其他的呼叫慣例都已經被清除,所以64位下的DDK編譯器產生的函數名和彙編器產生的函數名是一致的,函數名不會帶有尾碼,因此不必修改。即如果在C檔案中調用彙編檔案中的asm_rng_available函數,聲明方式如下:
    extern int          asm_rng_available() ;


2.SOURCE檔案的修改
    由於Build命令不會自動關聯彙編檔案的目標檔案,為了將編譯後的彙編目標檔案和C目標檔案進行連結,可以在SOURCE檔案的TARGETLIBS宏中進行指定。具體方法如下:
    TARGETLIBS=.\instr_32.obj
這樣就可以使用Build命令直接對所有目標檔案進行連結了。
3.64位和32位中一些系統調用的差異
    64位DDK中的lib庫並沒有匯出KeInitializeSpinLock, KeQueryInterruptTime函數,因此不能直接調用,必須通過MmGetSystemRoutineAddress得到函數的指標,利用函數指標進行調用。
驅動中調用彙編函數
    下面對整個過程進行完整的描述:
    1. 將彙編檔案和驅動程式的C檔案放在同一個目錄下(簡稱驅動目錄);
    2. 修改SOURCE檔案,在SOURCE檔案中添加TARGETLIBS = *.obj,其中*表示彙編檔案的檔案名稱(不加副檔名);
    3. 在驅動程式C檔案中聲明外部彙編檔案的函數名,如果是32位體繫結構,聲明時需強制指定彙編函數為__cdecl呼叫慣例;
    4. 編譯彙編檔案,在驅動目錄下產生obj目標檔案(編譯器可以採用nasm或者masm);
    5. 在DDK編譯環境下對整個驅動工程執行Build命令,從而產生.sys檔案。
OK...

因為對於C語言,在vs2005.net環境下可直接進行64位平台的編譯,其中注意事項已有許多文章涉及,這裡不再複述。

A、而對於組合語言,首先要注意,必須為純彙編格式(*.asm檔案)或intrinsic指令格式。其次,在64位平台上,不建議使用nasm編譯器(我沒找到其對於64位編譯的相關支援),而建議採用yasm,這個彙編編譯器是在nasm的基礎上產生的,可以說對nasm的功能相容,並且支援64位編譯,詳細地介紹及相關下載見:http://www.tortall.net/projects/yasm/

說明:可以使用Visual Studio中內建的ml64.exe對64位彙編源檔案進行編譯:

樣本圖:

B、x86-64較x86-32多了8個通用寄存器,而且,每個通用寄存器都是64位寬,它們是:
rax,rbx,rcx,rdx,rsi,rdi,rsp,rbp
r8,r9,r10,r11,r12,r13,r14,r15

同時,x86-64全面支援x86-32和x86-16的通用寄存器:
eax,ax,al,ah,
ebx,bx,bl,bh,
但是,在對寄存器進行入/出棧操作時只能對相應的64位寄存器入/出棧,即:
( Instructions that modify the stack (push, pop, call, ret, enter, and leave) are implicitly 64-bit. Their 32-bit counterparts are not available, but their 16-bit counterparts are. Examples in NASM syntax: 
    push eax  ; illegal instruction
    push rbx  ; 1-byte instruction
    push r11  ; 2-byte instruction with REX prefix)

C、關於x64的呼叫慣例:

在設計呼叫慣例時,x64 體繫結構利用機會清除了現有 Win32 呼叫慣例(如 __stdcall、__cdecl、__fastcall、_thiscall 等)的混亂。在 Win64 中,只有一個本機呼叫慣例和 __cdecl 之類的修飾符被編譯器忽略。除此之外,減少呼叫慣例行為還為可調試性帶來了好處。

您需要瞭解的有關 x64 呼叫慣例的主要內容是:它與 x86 fastcall 約定的相似之處。使用 x64 約定,會將前 4 個整數參數(從左至右)傳入指定的 64 位元寄存器:

RCX: 1st integer argument第一個參數在RCX寄存器存放
RDX: 2nd integer argument第二個參數在RCX寄存器存放
R8: 3rd integer argument第三個參數在RCX寄存器存放
R9: 4th integer argument第四個參數在RCX寄存器存放

前 4 個以外的整數參數將傳遞到堆棧。該指標被視為整數參數,因此始終位於 RCX 寄存器內。對於浮點參數,前 4 個參數將傳入 XMM0 到
XMM3 的寄存器,後續的浮點參數將放置到線程堆棧上。

更進一步探究呼叫慣例,即使參數可以傳入寄存器,編譯器仍然可以通過消耗 RSP 寄存器在堆棧上為其預留空間。至少,每個函數必須在堆棧上預留 32 個位元組(4 個 64 位元值)。該空間允許將傳入函數的寄存器輕鬆地複製到已知的堆棧位置。不要求被調用函數將輸入寄存器參數溢出至堆棧,但需要時,堆棧空間預留確保它可以這樣做。當然,如果要傳遞
4 個以上的整數參數,則必須預留相應的額外堆棧空間。

讓我們看一個樣本。請考慮一個將兩個整數參數傳遞給子函數的函數。編譯器不僅會將值賦給 RCX 和 RDX,還會從 RSP 堆棧指標寄存器中減去 32 個位元組。在被調用函數中,可以在寄存器(RCX 和 RDX)中訪問參數。如果被調用代碼因其他目的而需要寄存器,可將寄存器複製到預留的 32 位元組堆棧地區中。圖 6 顯示在傳遞 6 個整數參數之後的寄存器和堆棧。

圖 6 傳遞整數

對這裡的參數壓棧,需要說明的是(對於整形參數):
1、第四個參數後的參數按順序反向壓棧(8位元組)
2、為前四個參數預留的32位元組占空間是固定的、預分配的,不用被調用函數的編寫者負責,也就是說,不管你是否用到,這部分棧空間都佔用了,rsp所指向的位置如。
所以對於上面的例子,如果在被調用函數重要訪問p5,p6正確的方法是:
mov eax,[esp+32+8];p5
mov ebx,[esp+32+16];p6

 D、一個很特別的寄存器 rip,相當於x86-32的eip. 在x86-32是不可直接存取的,如mov eax,eip是錯的,但在x86-64位下卻可以,如 mov,rax,qword
ptr [rip+100]是對的
。而且,它除了是個程式計數器外,也是個“資料基地址”,有此可見,它現在是身兼兩職!為什麼在x86-64位下要用rip做訪問資料的基地址呢?因為,在x86-64下,DS,ES,CS,SS都沒有實際意義了,也就是說,它們不再參與地址計算,只是為了相容x86-32。FS,GS還是參與地址計算,它們兩個和x86-32的意義相同。由於DS,ES,CS,SS沒有意義了,所以在編譯動態庫時,符號變數是不允許直接出現在彙編代碼中的,而必須與rip一起使用,即
mov rax,[symb wrt rip]或者lea rbx,[symb wrt rip]

參考網頁:
AMD64 Architecture ---http://www.tortall.net/projects/yasm/wiki/AMD64
開始進行 64 位元 Windows 系統編程之前需要瞭解的所有資訊---http://www.microsoft.com/china/MSDN/library/Windev/64bit/issuesx64.mspx?mfr=true
The history of calling conventions, part 5: amd64---http://blogs.msdn.com/oldnewthing/archive/2004/01/14/58579.aspx



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1371665

下面給出一個範例程式碼:

彙編中實現一個加法函數,實現四個參數傳入,分別對應上面介結的RCX第一個參數,RDX第二個參數,R8第三個參數,R9第四個參數

x64asm.asm檔案名稱

.code
AddSub proc
push rcx

add rcx,rdx
add rcx,r8
add rcx,r9
mov rax,rcx

pop rcx
ret
AddSub endp
end

在驅動中調用彙編代碼中提供的加法函數:AddSub

x64AsmDriver.c源檔案名稱

#include <ntddk.h>

extern int AddSub(PVOID, PVOID, PVOID, PVOID);

VOID DriverUnload( IN PDRIVER_OBJECT DriverObject )
{
KdPrint(("DriverUnload OK!\n"));
}

#pragma code_seg("INIT")
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING pRegPath
)
{
ULONG32 nResult = 0;
ULONG32 a = 10;
ULONG32 b = 20;
ULONG32 c = 30;
ULONG32 d = 40;

DriverObject->DriverUnload = DriverUnload;

nResult = AddSub(a, b, c, d);
KdPrint(("nResult = %d\n", nResult));

return STATUS_SUCCESS;
}

說明:關於驅動Source檔案配置:將彙編產生的OBJ檔案加到這裡TARGETLIBS=.\x64Asm.obj

TARGETNAME=x64AsmDriver
TARGETTYPE=DRIVER
TARGETLIBS=.\x64Asm.obj

SOURCES=x64AsmDriver.c

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.