罗冲 + 原创 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
利用系统调用
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
int main()
{
int tt;
char* path = "/home/lch/study/chapter4/test";
unsigned short mod = 0750;
tt = mkdir(path, mod);
printf("tt:%d\n",tt);
}
利用汇编:
#include <stdio.h>
int main()
{
int tt;
char* path = "/home/lch/study/chapter4/test";
unsigned short mod = 0750;
asm volatile(
"mov $0x27, %%eax\n\t"
"int $0x80\n\t"
"mov %%eax, %0\n\t"
:"=m"(tt)
:"b"(path),"c"(mod)
);
printf("tt:%x\n",tt);
}
首先将代码进行编译,在这里使用静态编译的方式:
在测试用例中,是调用函数
int mkdir(const char *pathname, mode_t mode);
mkdir()函数是由libc.a提供,使用静态编译的主要原因是为避免动态调用时,对于我们代码分析的影响。
利用gdb,分析程序在调用mkdir的过程,并在main函数处打上断点。然后查看其汇编语言:
结合C语言与汇编语言,可以知道:
char* path = "/home/lch/study/chapter4/test";
与汇编程序
movl $0x80b366c,0x18(%esp)
是一致的。这也说明了。这是因为path被赋的值为一个常量,因此操作系统会直接把常量的地址给程序。这里我们需在关注是的mkdir()两个参数的寄存器赋值。即path的地址被保存在(ebx + 18)这个地址,而mod的值被保存在(ebx+1e)这个地方。然后通过eax寄存,调整其它堆栈中的位置。在执行
call 0x8053d50 <mkdir>
之前,堆栈的值为:
继续查看mkdir的汇编语言:
分析其汇编语言,再结合在调用call 函数之前的堆栈信息
0x08053d52 <+2>: mov 0x8(%esp),%ecx
0x08053d56 <+6>: mov 0x4(%esp),%ebx
可以知道ecx中保存的是mod的值,也就是0755,而ebx则保存的是path的值。
0x08053d5a <+10>: mov $0x27,%eax
接着将0x27这个值保存在eax中,而0x27这个立即数对应的中断号就是mkdir的中断号。当中断号与参数保存之后,就是开始准备中断了。也就是
0x08053d5f <+15>: call *0x80d66b4
它是调用 *0x80d66b4这个函数地址,查看一下其值与所对应的汇编:
而这里开始系统调用的中断是则是sysenter
0x00110419 <+5>: sysenter
通过查询资料可知,“在 2.6 内核中,内核代码同时包含了对 int 0x80 中断方式和 sysenter 指令方式调用的支持,因此内核会给用户空间提供一段入口代码,内核启动时根据 CPU 类型,决定这段代码采取哪种系统调用方式。对于 glibc 来说,无需考虑系统调用方式,直接调用这段入口代码,即可完成系统调用。”
对于sysenter的实现是通过汇编语言:entry_32.S
sysenter_do_call:
cmpl $(NR_syscalls), %eax
jae sysenter_badsys
call *sys_call_table(,%eax,4)
eax中存储系统调用号,此时它再通过sys_call_table列表,找到对应的系统函数
通过objdump查看其编译后的汇编语言:
[lch@bogon chapter4]$ objdump -S mkdir_asm
mkdir_asm: file format elf32-i386
... ....
080483c4 <main>:
#include <stdio.h>
#include <time.h>
int main()
{
80483c4: 55 push %ebp
80483c5: 89 e5 mov %esp,%ebp
80483c7: 83 e4 f0 and $0xfffffff0,%esp
80483ca: 53 push %ebx
80483cb: 83 ec 2c sub $0x2c,%esp int tt;
char* path = "/home/lch/study/chapter4/test";
80483ce: c7 44 24 18 e4 84 04 movl $0x80484e4,0x18(%esp)
80483d5: 08
unsigned short mod = 0750;
80483d6: 66 c7 44 24 1e e8 01 movw $0x1e8,0x1e(%esp)
asm volatile(
80483dd: 8b 44 24 18 mov 0x18(%esp),%eax
80483e1: 0f b7 54 24 1e movzwl 0x1e(%esp),%edx
80483e6: 89 c3 mov %eax,%ebx
80483e8: 89 d1 mov %edx,%ecx
80483ea: b8 27 00 00 00 mov $0x27,%eax
80483ef: cd 80 int $0x80
80483f1: 89 44 24 14 mov %eax,0x14(%esp)
"int $0x80\n\t"
"mov %%eax, %0\n\t"
:"=m"(tt)
:"b"(path),"c"(mod)
);
printf("tt:%x\n",tt);
相比与之前的汇编语言,只有一条不相同,即是int 0x80。系统通过int 0x80的方式进入内核态,然后,执行过程与系统函数mkdir相同。
1)sysenter与int 0x80所起的作用相同
2) 系统调用是通过eax传递系统调用号。这是由entry_32.S里面的汇编语言决定的。
3)整个系统调用流程如下:
参考资料:
1. Linux 2.6 对新型 CPU 快速系统调用的支持 http://oss.org.cn/html/16/n-3916.html
2. 使用 Linux 系统调用的内核命令 http://www.ibm.com/developerworks/cn/linux/l-system-calls/