本人学号最后两位为30,根据 syscall_32.tbl 中的相应映射,选择 utime 系统调用展开分析。
utime 系统调用的功能为“更改一个文件的访问和修改时间”,实际使用的效果如下:
可以看到,在 03:11 创建的 file1 和 file2 文件,未经过 utime 系统调用的处理,最后修改时间、最后访问时间、更改状态时间均为 03:11。
经过 ./utime file1 的命令后,可以看到 file1 的最后修改时间和最后访问时间未变,但是更改状态时间则更改为程序运行时的时间,程序成功运行。
以下为通过汇编指令触发的具体的代码
#include#include #include #include #include #include #include int main (int argc, char **argv) { int i, fd; struct stat statbuf; struct utimbuf timebuf; for(i = 1; i < argc; i++) { if(stat(argv[i], &statbuf) < 0) { printf ("%s: stat error\n", argv[i]); continue; } if((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) { printf ("%s: open error\n", argv[i]); continue; } close(fd); timebuf.actime = statbuf.st_atime; timebuf.modtime = statbuf.st_mtime; int res; // 存储函数运算结果 asm volatile( "movl %1, %%ebx\n\t" // 将第一个参数 argv[i] 放入 ebx 寄存器 "movl %2, %%ecx\n\t" // 将第二个参数 &timebuf 放入 ecx 寄存器 "movl $30, %%eax\n\t" // utime 的系统调用号为30,将其放入 eax 寄存器 "int $0x80\n\t" // 触发系统中断 "movl %%eax, %0\n\t" // 将函数处理结果返回给 res 变量中 :"=m"(res) :"b"(argv[i]),"c"(&timebuf) ); // if(utime(argv[i], &timebuf) < 0) if(res < 0) { printf ("%s: utime error\n", argv[i]); continue; } } exit(0); }
通过gdb跟踪该系统调用的内核处理过程如下:
(1) 启动qemu,并进入gdb调试状态
(2) 在指定的系统调用处打断点,并在qemu中运行写好的程序
进入 utime 的系统调用 syscall 处的入口,接下来单步调试,看具体过程
(3) 保存现场
将 utime 系统调用相关参数存入寄存器,加载运行,最后恢复现场
直至系统调用结束,无其他步骤执行。
总结:
即便是最简单的程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。
在 Linux 平台下有两种方式来使用系统调用:利用封装后的 C 库(libc)或者通过汇编直接调用。
在32位环境中(也是本文的实验环境),
Linux 下的系统调用是通过中断(int 0x80)来实现的。在执行 int 80 指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得所有的系统调用功能号都可以在文件 /usr/include/bits/syscall.h 中找到,为了便于使用,它们是用 SYS_