2013-12-11 59 views
9

我一直在閱讀有關調用約定在ASM這是我走到這一步:ASM調用約定

  x86(userland) x86(kernel) x64(userland) x64(kernel) 

1st arg   Stack   EBX    RDI   RDI 
2nd arg   Stack   ECX    RSI   RSI 
3rd arg   Stack   EDX    RDX   RDX 
4th arg   Stack   ESI    RCX   R10 
5th arg   Stack   EDI    R8    R8 
6th arg   Stack   EBP    R9    R9 

result   EAX    EAX    RAX   RAX 

我的問題是:

  1. 是我到目前爲止正確的教訓?

  2. 如何在x86(內核)和x64(兩者)中傳遞多於6個參數?使用堆棧?想給我看一個小例子嗎?

  3. 我有一個內核模塊,我願意從ASM調用該模塊中的一個函數。我應該使用什麼約定?內核還是用戶空間?

+0

我的理解是在用戶和內核之間的syscall接口中使用第四列(標記爲「x64(kernel)」)。對於內核中函數之間的調用,使用標準ABI(標記爲「x64(userland)」)。但是,我不是這方面的專家,所以如果我錯了,請有人糾正我。 –

+1

對於一些特殊情況,研究__syscall –

+0

@MarcovandeVoort:你能否詳細說明一下? –

回答

2

我只針對x86編碼,並且可以爲您提供該架構(前兩列)的一些反饋。對於3.,如果它是一個內核函數(與libc函數相對),那麼您可以使用內核約定(您的列2)。

至於1.,正確,除了你不會使用ebx作爲第6個參數。傳統的函數序言會假設它是實際的ebp,會推動這個論點。所以截止點實際上是5個參數。對於2.,如果你有5個以上的參數,你可以將它們連續地存儲在內存中,然後把一個指針指向ebx中這個內存區域的開始位置。

4

1)是的,它似乎但僅限於Linux。我想你可以依靠這裏描述的Linux約定:http://www.x86-64.org/documentation/abi.pdf。但實際上,你可以自由地傳遞參數它在intel assembly manual章6.3.3

2)使用堆棧描述的方法是在編譯器的方式:

int func(int i, int j, int k, int l, int m, int n, int o, int p, int q) { return q; } 
void func2() { func(1, 2, 3, 4, 5, 6, 7, 8, 9); } 

然後:

$ gcc -c func.c && objdump -d func.o 

我的哪個x86_64的機器上輸出:

0000000000000000 <func>: 
    0: 55      push %rbp 
    1: 48 89 e5    mov %rsp,%rbp 
    4: 89 7d fc    mov %edi,-0x4(%rbp) 
    7: 89 75 f8    mov %esi,-0x8(%rbp) 
    a: 89 55 f4    mov %edx,-0xc(%rbp) 
    d: 89 4d f0    mov %ecx,-0x10(%rbp) 
    10: 44 89 45 ec    mov %r8d,-0x14(%rbp) 
    14: 44 89 4d e8    mov %r9d,-0x18(%rbp) 
    18: 8b 45 20    mov 0x20(%rbp),%eax 
    1b: 5d      pop %rbp 
    1c: c3      retq 

000000000000001d <func2>: 
    1d: 55      push %rbp 
    1e: 48 89 e5    mov %rsp,%rbp 
    21: 48 83 ec 18    sub $0x18,%rsp 
    25: c7 44 24 10 09 00 00 movl $0x9,0x10(%rsp) 
    2c: 00 
    2d: c7 44 24 08 08 00 00 movl $0x8,0x8(%rsp) 
    34: 00 
    35: c7 04 24 07 00 00 00 movl $0x7,(%rsp) 
    3c: 41 b9 06 00 00 00  mov $0x6,%r9d 
    42: 41 b8 05 00 00 00  mov $0x5,%r8d 
    48: b9 04 00 00 00   mov $0x4,%ecx 
    4d: ba 03 00 00 00   mov $0x3,%edx 
    52: be 02 00 00 00   mov $0x2,%esi 
    57: bf 01 00 00 00   mov $0x1,%edi 
    5c: e8 00 00 00 00   callq 61 <func2+0x44> 
    61: c9      leaveq 
    62: c3      retq 

3)我想說的內核因爲你正在調用內核模塊中的函數。要獲得完整的有效示例,可以在模塊中從C中調用您的函數,並以與我一樣的方式反彙編.ko,以查看編譯器如何處理它。它應該是直截了當的。

+0

如果我可以的話,我會投票幾次你的答案:) – alexandernst

+0

Humm ...我有一個問題。那個'''callq 61 '''是如何翻轉成e8 00 00 00 00'''?我的意思是'''e8'''確實意味着'''call''',但''''''''61''' – alexandernst

+0

在這種情況下,objdump在二進制對象上運行,因此編譯器不知道func1的最終虛擬地址。它將在鏈接過程中解決。在這種情況下,在此之前填充0。所以objdump只是向您展示了61,61的調用指針。這與生成的二進制文件無關(使用hexdump嘗試)。但是,如果在最終的a.out上運行objdump -d,則會注意到func1的實際虛擬地址,而不是0. 在您的示例中顯示gcc生成的彙編代碼可能更有趣,方法是運行gcc -S func .c && cat func.s – Ervadac

1

至於內核調用約定,它使用寄存器來提高效率。此外,系統調用是需要特權級別更改的特殊調用,需要特權級別更改的調用使用不同的堆棧,所以通常的函數序言(push ebp,mov ebp,esp等)無用,因爲ebp無法訪問用戶參數。

內核函數可能會窺探用戶堆棧以獲取它所需的參數,但對於某些體系結構,從內核代碼訪問用戶內存並不像X86那樣直接,簡單或快速,而Linux是指可以移植到許多架構。因此,寄存器雖然在x86架構上有所限制,但卻是傳遞參數的一種方便且快速的方法。如果一個系統調用需要多於六個參數,其中一個將是一個指向保存在用戶存儲器中的struct的指針。如果需要,內核將使用copy_from_user()函數將結構複製到內核內存中(例如,通常使用ioctl()系統調用完成)。

+0

我將從我的內核模塊中運行該ASM代碼,因此代碼將擁有特權。 – alexandernst

+0

然後,afaik,您將使用標準的C調用約定:將參數從右向左推入堆棧。這就是內核模塊中的函數的定義。 –

+0

所以我只需將我所有的地址都從右到左依次變爲變量,然後在每次需要變量時調用myfunc然後調用pop。 ? – alexandernst

1

我不知道這是否有幫助,但請查看Agner Fog的Calling conventions for different C++ compilers and operating systems中的表4和表5。這些給出了C++不同編譯器和操作系統的寄存器使用和調用約定的一個很好的總結。

對於x86-64:Windows和Linux只有一個調用約定,但它們是不同的。 Windows使用6個寄存器和Linux 14寄存器。

對於x86:Windows和Linux使用相同的調用約定,但有幾種調用約定:cdecl, stdcall, pascal and fastcall。約定cdecl, stdcall, and pascal僅使用堆棧,而fastcall使用2(或三個取決於編譯器的)整數寄存器。大會cdecl是默認的。

Windows和Linux也有一些不同的返回寄存器。您只列出了EAXRAX,但也有例如XMM0YMMOST(0),...

這些結果與您爲ASM編寫的結果類似。