函数调用约定
调用约定从字面上理解,他是调用函数的一套约定。主要体现在一下三个方面
1. 参数的传递方式,参数是存放在寄存器中还是栈中
2. 参数的传递顺序,是从左到右传递还是从右到左传递
3. 是调用者保存寄存器环境还是被调用者保存
在进行函数调用的时候,函数所需要传递的参数往往是不固定的。在计算机中并没有专门储存参数的硬件,因为参数的不确定性,该硬件的容量并不好确定,而且如果传递参数的过程中,函数被换下CPU,新的进程进行参数调用时还会覆盖之前的参数。
考虑到这些方方面面,最后决定在栈中存储函数的参数。其优点如下
1. 每个进程都有自己的栈
2. 参数的内存地址不用花精力去维护,已经有栈机制自动帮我们维护
参数储存的问题解决了,那么接下来讨论另外两个问题
1. 参数存储在栈中,那么谁来负责回收参数所占的栈空间,是调用者?还是被调用者
2. 当参数很多的时候后,主调函数将参数以什么样的顺序传递
上面两个问题就涉及到具体的调用规定了
在这些调用约定中,我们最常用是以下几种约定
1. cdecl
2. stdcall
3. thiscall
cdecl 是c默认的调用约定。
stdcall 他是微软Win32 API的一准标准,我们常用的回调函数就是通过这种调用方式
thiscall 是c++中非静态类成员函数的默认调用约定
系统调用
首先简单的介绍一下系统调用是什么,等后面需要真正实现系统调用的时候再来详细的说明
系统调用是linux内核提供的一套子程序,主要是为了实现在用户态不能实现的功能,比如说最常见的读写硬盘文件,这些读写的方法肯定不能由用户程序来编写,而且用户程序也没有权限去直接操控硬件,这就需要操作系统的支持,需要操作系统提供读写硬盘的接口
系统调用的入口只有一个,即第0x80号中断,通过eax指定子功能号。在linux中,系统调用是定义在 /usr/include/asm-generic-unistd.h 中
调用系统调用有两种方式
1. 通过操作系统提供的库函数进行系统调用
2. 直接通过0x80中断与系统通信
我们想要自制kernel的话第一条路肯定走不通,因为没有库函数供我们调用,库函数都是我们自己写的。
通过中断的方式进行系统调用需要了解一下系统调用输入参数的传递方式
当输入的参数小于等于5个时,linux用寄存器传递参数。当输入的参数大于5个时,把参数按照顺序放入连续的内存中,并把这块内存的首地址放入ebx中
通过寄存器传递参数时
eax存放子功能号
ebx存放第一个参数
ecx存放第二个参数
edx存放第三个参数
esi存放第四个参数
edi存放地五个参数
下面是一个简单的syscall_wirte的简单实现
section .data
str_c_lib: db "c library says: hello world!", 0xa
str_c_lib_len equ $-str_c_lib
str_syscall: db "syscall says: hello world!", 0xa
str_syscall_len equ $-str_syscall
section .text
global _start
_start:
; 方式 1 :模拟 c 语言中系统调用库函数 write ;
push str_c_lib_len
push str_c_lib
push 1
call simu_write
add esp, 12
; 方式 2 :跨过库函数,直接进行系统调用
mov eax,4 ;第4号子功能是 write 系统调用(不是 c 库函数 write)
mov ebx, 1
mov ecx, str_syscall
mov edx, str_syscall_len
int Ox80 ;发起中断,通知 Linux 完成请求的功能
; 退出程序
mov eax, 1 ;第1号子功能是exit
int 0x80
sumu_write:
push ebp
mov ebp, esp
mov eax, 4
mov ebx, [ebp + 8]
mov ecx, [ebp + 12]
mov edx, [ebp + 16]
int 0x80
pop ebp
ret