一、拨云见日
身为程序员,我们绕不开系统调用,但是我们往往都是通过一个”中间人”—-库函数与其打交道。
我们调用一个库函数也许看起来非常简单,但是其真正的实现细节,并非我们看起来那样的”风平浪静”,其内部往往”暗流涌动”。
用户态:Linux以 0级来表示用户态,在用户态中,我们的行为受到系统限制。
内核态:Linux以 3级来表示用户态,在内核态,我们可以访问任意的物理地址。
二、实验与分析
实验目的 : 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用。
实验步骤 : 分别编写调用库函数API的C代码以及嵌入汇编代码。
1.系统调用time获取当前时间
time.c
#include <stdio.h>
#include <time.h>
int main()
{
time_t tt;
struct tm *t;
tt = time(NULL);
t = localtime(&tt);
printf("time : %d / %02d / %02d : %02d : %02d : %02d\n",t->tm_year+1900,t->tm_mon+1, t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
return 0;
}
编译运行
time_asm.c
#include <stdio.h>
#include <time.h>
int main()
{
time_t tt;
struct tm *t;
asm volatile(
"mov $0,%%ebx\n\t" //系统调用传递的第一参数使用
//ebx,此处为NULL
"mov $0xd,%%eax\n\t" //使用eax传递系统调用号
//此处为13
"int $0x80\n\t" //int 0x80 软中断实现用户空间
//与内核空间的交互
"mov %%eax,%0\n\t" //将调用返回的eax赋给m
:"=m" (tt)
);
t = localtime (&tt);
printf("time : %d / %02d / %02d : %02d : %02d : %02d\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
return 0;
}
编译运行
2.系统调用mkdir创建文件夹
mkdir.c
#include <stdio.h>
#include <stdlib.h>
#include <direct.h>
int main()
{
int flag=mkdir("E:\\test");
printf("%d\n",flag);
if(flag == 0){
printf("success");
}else{
printf("failed");
}
return 0;
}
编译运行
结果如下
mkdir_asm.c
#include <stdio.h>
#include <time.h>
#include <direct.h>
#define M 10
int main()
{
char path[M] ="E:\\test";
int flag;
asm volatile(
"mov $0,%%ebx\n\t" //传参
"mov $0x27,%%eax\n\t" //系统调用号 39
"int $0x80\n\t" //int 0x80 软中断实现用户空间
"mov %%eax,%1\n\t" //将调用返回的eax赋给flag
:"=m" (path)
:"d" (flag)
);
printf("%d\n",flag);
if(flag == 0){
printf("success");
}else{
printf("failed");
}
return 0;
}
编译运行
结果如下
其它系统调用
结果与分析:
使用库函数API和嵌入汇编的C代码运行结果一样,两种方法都能够获取当前的时间。API对系统调用进行了封装。
三、总结
用户在调用某些涉及到系统调用的库函数时,CPU会从用户态切换到内核态,同时会在堆栈保存一些寄存器的值,如用户的栈顶地址,当时的状态字,当时的cs:eip的值。然后执行内核函数,在此之前需通过eax传入系统调号用号参数指明需要哪个系统调用。当调用结束后,需要进行现场恢复,会由内核态切换到用户态,同时通过eax返回结果。
writen by
江明星