通过0x80软件中断执行系统调用
参考书《Linux内核模块开发技术指南》
1.Linux的0x80软件中断
在X86的32位系统下,系统调用是通过0x80软件中断实现的。X86的64位操作系统也支持该方式执行系统调用(需要在编译内核时,配置CONFIG_IA32_EMULATION选项)。
执行0x80软件中断的方式是:在执行软件中断前,将系统调用号放入eax寄存器中,参数1到参数6分别放入:ebx、ecx、edx、esi、edi、ebp寄存器。然后调用int $0x80汇编指令执行系统调用,返回值将保存在eax寄存器中。对于使用0x80软件中断,系统调用号及执行函数等信息保存在内核源码的arch/x86/entry/syscalls/syscall_32.tbl文件中。文件arch/x86/entry/syscalls/syscall_32.tbl的内容如下图所示(截取了syscall_32.tbl的一小部分):
上图的第1列为系统调用号;第2列对应平台信息,i386表示x86平台;第3列为系统调用的名称;第4列是系统调用在内核的执行函数。可以看出:系统调用exit的系统调用号是1,fork的系统调用号是2,read的系统调用号是3…
上图中,一个系统调用可能有两个执行函数。例如:对于open系统调用,其中一个执行函数是sys_open,另一个执行函数是compat_sys_open。分别对应32位操作系统和64位操作系统下执行open系统调用应该使用的函数。
2.0x80软件中断执行示例
下面将实现一个示例程序,使用软件中断0x80执行系统调用write,将字符串“hello”输出到标准输出。源码如下:
int main()
{char *buf = "hello"; //buf保存了将要输出的字符串“hello”asm volatile("mov $4, %%eax;" //将立即数4放入eax,eax保存的是系统调用号"mov $1, %%ebx;" //将立即数1放入ebx,ebx保存系统调用的第一个参数"mov %0, %%ecx;" //将buf放入ebx,ecx保存系统调用的第二个参数"mov $5, %%edx;" //将立即数5放入edx,edx保存系统调用的第三个参数"int $0x80"::"g"(buf)); //执行0x80软件中断进入系统调用return 0;
}
上述源码使用内联汇编执行int $0x80指令(关于内联汇编,见我的文章:一文弄懂GCC内联汇编)。在执行软件中断前,将系统调用号4放入eax寄存器,系统调用号4对应的是write系统调用;将立即数1放入ebx寄存器,ebx保存了write系统调用的第一个参数,即文件描述符,1是标准输出;将参数buf放入ecx寄存器,ecx保存了write系统调用的第二个参数,即字符串“hello”;将立即数5放入edx寄存器,edx保存了write系统调用的第三个参数,即数据长度,“hello”字符串的长度是5字节。
编译、执行上述源码,将打印出“hello”字符串。