我一直在阅读有关在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
我的问题是:
到目前为止我学到的是正确的吗?
如何在x86(内核)和x64(两者)中传递6个以上的参数?使用堆栈?介意给我一个小例子?
我有一个内核模块,我愿意从ASM调用该模块中的一个函数。我应该使用什么惯例?内核或用户空间?
答案 0 :(得分: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,就像我看到编译器如何处理它一样。它应该是直截了当的。
答案 1 :(得分:2)
我只为x86编写代码,并且可以为该架构提供一些反馈(前两列)。
对于3.,如果它是一个内核函数(而不是libc函数),你将使用内核约定(第2列)。
至于1.,更正,除了你不会使用ebx作为第6个参数。假设它是实际的ebp,传统的函数序言会推动这个论点。所以截止实际上是5个论点。
对于2.,如果你有超过5个参数,你会将它们连续存储在内存中,并在ebx中传递指向该内存区域开头的指针。
答案 2 :(得分:1)
对于内核调用约定,它使用寄存器来提高效率。此外,系统调用是需要特权级别更改的特殊调用,需要特权级别更改的调用使用不同的堆栈,因此通常的函数prolog(push ebp
,mov ebp,esp
等)是没用,因为ebp
无法访问任何用户参数。
内核函数可能会查看用户堆栈以获取所需的参数,但对于某些体系结构,从内核代码访问用户内存不像x86中那样简单,容易或快速,而Linux意味着可移植到许多架构。因此,寄存器虽然在x86架构中有些限制,但它是一种方便快捷的传递参数的方法。如果系统调用需要六个以上的参数,则其中一个参数将是指向用户内存中保存的struct
的指针。如果需要,内核将使用copy_from_user()
函数将结构复制到内核内存中(例如,通常使用ioctl()
系统调用来完成。)
答案 3 :(得分: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也有一些不同的返回寄存器。您只有EAX
和RAX
列表,但也有例如XMM0
或YMMO
或ST(0)
,...
这些结果与您为ASM撰写的结果类似。