作业要求:
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
一、选择系统调用
本人学号尾数为31,但是查找syscall_32.tbl
表后发现31号系统调用为stty
,进一步搜素在系统调用描述文件里面找到此系统调用和32号gtty
都为sys_ni_syscall
,进一步查资料发现上述两个系统调用已经被淘汰,所以它所对应的服务例程就要被指定为sys_ni_syscall
。
知识拓展:
即使31号和32号系统调用已经被淘汰了,但是我们并不能将它们的位置分配给其他的系统调用,因为一些老的代码可能还会使用到它们。否则,如果某个用户应用试图调用这些已经被淘汰的系统调用,所得到的结果,比如打开了一个文件,就会与预期完全不同,这将令人感到非常奇怪。其实,sys_ni_syscall中的"ni"即表示"not implemented(没有实现)
下面转而分析31号上面的系统调用,即30号utime
。
# The format is:
# <number> <abi> <name> <entry point> <compat entry point>
30 i386 utime sys_utime32 __ia32_sys_utime32
utime
的作用为修改文件的访问时间和修改时间。其对应的32位entry point
为sys_utime32
,搜索sys_utime32
在utimes.c文件中找到了其实现,它是通过调用do_utimes
来实现的。do_utimes
的代码实现如下:
/*
* do_utimes - change times on filename or file descriptor
* @dfd: open file descriptor, -1 or AT_FDCWD
* @filename: path name or NULL
* @times: new times or NULL
* @flags: zero or more flags (only AT_SYMLINK_NOFOLLOW for the moment)
*
* If filename is NULL and dfd refers to an open file, then operate on
* the file. Otherwise look up filename, possibly using dfd as a
* starting point.
*
* If times==NULL, set access and modification to current time,
* must be owner or have write permission.
* Else, update from *times, must be owner or super user.
*/
long do_utimes(int dfd, const char __user *filename, struct timespec64 *times,
int flags)
{
int error = -EINVAL;
if (times && (!nsec_valid(times[0].tv_nsec) ||
!nsec_valid(times[1].tv_nsec))) {
goto out;
}
if (flags & ~AT_SYMLINK_NOFOLLOW)
goto out;
if (filename == NULL && dfd != AT_FDCWD) {
struct fd f;
if (flags & AT_SYMLINK_NOFOLLOW)
goto out;
f = fdget(dfd);
error = -EBADF;
if (!f.file)
goto out;
error = utimes_common(&f.file->f_path, times);
fdput(f);
} else {
struct path path;
int lookup_flags = 0;
if (!(flags & AT_SYMLINK_NOFOLLOW))
lookup_flags |= LOOKUP_FOLLOW;
retry:
error = user_path_at(dfd, filename, lookup_flags, &path);
if (error)
goto out;
error = utimes_common(&path, times);
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
}
out:
return error;
}
二、 触发系统调用(直接触发+汇编触发)
使用下面的代码直接触发utime系统调用:
#include <sys/stat.h>
#include <utime.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char *pathname;
struct stat sb;
struct utimbuf utb;
if (argc != 2 || strcmp(argv[1], "--help") == 0){
printf("%s file\n", argv[0]);
return 1;
}
pathname = argv[1];
//获取当前文件时间
if (stat(pathname, &sb) == -1)
return 1;
//把最近修改时间改成访问时间
utb.actime = sb.st_atime;
utb.modtime = sb.st_atime; /* Make modify time same as access time */
// 调用utime
if (utime(pathname, &utb) == -1) /* Update file times */
return 1;
return 0;
}
对上述的程序进行修改,使用汇编来调用utime,其实就是使用汇编指令传递utime的参数,并使用系统调用通过软中断0x80陷入内核,跳转到系统调用处理程序system_call(sys_utime32)函数,并执行相应的服务例程,但由于是代表用户进程,所以这个执行过程并不属于中断上下文,而是处于进程上下文:
#include <sys/stat.h>
#include <utime.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *pathname;
struct stat sb;
struct utimbuf utb;
if (argc != 2 || strcmp(argv[1], "--help") == 0){
printf("%s file\n", argv[0]);
return 1;
}
pathname = argv[1];
//获取当前文件时间
if (stat(pathname, &sb) == -1)
return 1;
//把最近修改时间改成访问时间
utb.actime = sb.st_atime;
utb.modtime = sb.st_atime; /* Make modify time same as access time */
int flag;
asm volatile(
"movl %1, %%ebx\n\t" // 将pathname放入ebx
"movl %2, %%ecx\n\t" // 将utimbuf 的引用放入ecx
"movl $30, %%eax\n\t" //通过EAX寄存器返回系统调用值
"int $0x80\n\t" // 通过软中断0x80陷入内核
"movl %%eax, %0\n\t" // 将输出通过eax赋值给flag
:"=m"(flag)
:"b"(pathname),"c"(&utb)
);
if (flag == -1) /* Update file times */
return 1;
return 0;
}
三、 通过gdb跟踪该系统调用的内核处理过程
四、 分析总结
参考文章: