0%

HIT-OSLab2

HIT-OSLab2

实验目标:在 Linux-0.11 上添加两个系统调用:(原始只有72个系统调用)

1
int iam(const char * name);
  • name 中存放的字符串拷贝到内核中并保存下来,要求 name 的长度不能超过 23 个字符,若超过了,返回 -1 并置 errnoEINVAL,如果没有超过就返回拷贝的字符个数
1
int whoami(char * name,unsigned int size);
  • 将内核中存放的字符串(由 iam 拷贝进去的)重新复制到 name 中,size 是指 name 指向的内存空间允许存放的最大长度,如果 size 小于需要的空间,就返回 -1,并置 errnoEINVAL,否则返回拷贝的字节数

实验过程

系统调用执行的详细过程如下:

  • 调用中断处理程序:当进程需要执行系统调用时,进程会主动调用中断处理程序 interrupt handler
  • 保存现场信息:中断处理程序会保存当前进程的寄存器值到堆栈中,以便在系统调用结束后恢复进程状态
  • 切换控制权:将 CPU 控制权交给操作系统,操作系统开始处理系统调用请求
  • 解析系统调用请求:操作系统根据系统调用请求的代码指针等信息,解析出需要调用的系统服务函数的地址
  • 调用系统服务函数:操作系统将解析出的系统服务函数地址作为参数,调用系统服务函数执行系统调用请求
  • 处理系统调用结果:系统服务函数处理完系统调用请求后,会将结果返回给操作系统
  • 恢复现场信息:操作系统将保存的寄存器值从堆栈中取出,恢复当前进程的原始状态
  • 切换控制权:操作系统将 CPU 控制权还给进程,进程继续执行

在代码层中的实现如下:

  • 把系统调用的编号存入 ax
  • 把函数参数存入其他通用寄存器
  • 触发 0x80 中断(int 0x80

在 linux-0.11 中,系统调用的源码存放在两处地方:

  • linux-0.11/lib/
  • linux-0.11/kernel/(实验要求我们把文件放入这里)

在系统调用中用户态程序不能直接和内核态程序交换数据,必须使用内核提供的 API,在 linux-0.11 中的 API 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static inline unsigned char get_fs_byte(const char * addr)
{
unsigned register char _v;

__asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));
return _v;
}

static inline unsigned short get_fs_word(const unsigned short *addr)
{
unsigned short _v;

__asm__ ("movw %%fs:%1,%0":"=r" (_v):"m" (*addr));
return _v;
}

static inline unsigned long get_fs_long(const unsigned long *addr)
{
unsigned long _v;

__asm__ ("movl %%fs:%1,%0":"=r" (_v):"m" (*addr)); \
return _v;
}

static inline void put_fs_byte(char val,char *addr)
{
__asm__ ("movb %0,%%fs:%1"::"r" (val),"m" (*addr));
}

static inline void put_fs_word(short val,short * addr)
{
__asm__ ("movw %0,%%fs:%1"::"r" (val),"m" (*addr));
}

static inline void put_fs_long(unsigned long val,unsigned long * addr)
{
__asm__ ("movl %0,%%fs:%1"::"r" (val),"m" (*addr));
}

/*
* Someone who knows GNU asm better than I should double check the followig.
* It seems to work, but I don't know if I'm doing something subtly wrong.
* --- TYT, 11/24/91
* [ nothing wrong here, Linus ]
*/

static inline unsigned long get_fs()
{
unsigned short _v;
__asm__("mov %%fs,%%ax":"=a" (_v):);
return _v;
}

static inline unsigned long get_ds()
{
unsigned short _v;
__asm__("mov %%ds,%%ax":"=a" (_v):);
return _v;
}

static inline void set_fs(unsigned long val)
{
__asm__("mov %0,%%fs"::"a" ((unsigned short) val));
}

编写 sys_iam 和 sys_whoami 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>

char msg[24];

int sys_iam(const char *name){
char tmp[24];
int i;
for(i=0;i<24;i++){
tmp[i] = get_fs_byte(name+i);
if(tmp[i] == '\0')
break;
}
if(i>=23){
return -1;
}
strcpy(msg,tmp);
return i;
}

int sys_whoami(char *name,unsigned int size){
int len;
int i;
len = strlen(msg);
if(len > size){
return -1;
}
for(i=0;i<len;i++){
put_fs_byte(msg[i],name+i);
if(msg[i] == '\0')
break;
}
return i;
}

系统调用号在 linux-0.11/include/unistd.h 文件中,我们可以在其末尾添加新的系统调用号:

1
2
#define __NR_iam	72
#define __NR_woiam 73
  • 需要注意的是:这里同时需要修改 hdc-0.11.img 中的 hdc/usr/include/unistd.h 文件,如果想在虚拟机中使用 gcc 编译的话,会导入虚拟机 hdc/usr/include/ 中的文件为头文件

系统调用总数量则记录在 linux-0.11/kernel/system_call.s 中,将其改为74:

1
nr_system_calls = 74

接下来需要在系统调用中添加我们实现的程序,为此我们需要先分析一下系统调用的代码

在 linux-0.11 中,system_call.s 文件为 Linux 内核中所有系统调用的实现提供了框架,主要包含以下内容:

  • 系统调用函数原型声明:定义了每个系统调用的函数原型,以便其他汇编文件可以方便地调用这些函数
  • 系统调用处理函数:定义了实际处理每个系统调用的函数,这些函数由内核驱动,并通过中断处理机制执行
  • 系统调用参数转换函数:在调用用户空间应用程序之前,系统调用参数需要进行转换,这些函数负责将内核提供的参数转换为应用程序需要的格式

所有系统调用的入口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
system_call:
cmpl $nr_system_calls-1,%eax # 系统调用号不存在
ja bad_sys_call
push %ds # 将段寄存器和目的寄存器压栈保存
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call sys_call_table(,%eax,4) # 执行系统调用

在 sys_call_table 中记录了所有的系统调用,其位置在 linux-0.11/include/linux/sys.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern int sys_iam();
extern int sys_whoami();

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid,sys_iam,sys_whoami };
  • 在其中添加 sys_iam,sys_whoami 即可完成注册

最后需要在 makefile 中添加 sys_iam,sys_whoami 的链接,不然就会引发如下报错:

1
2
3
ld: kernel/kernel.o:(.data+0x120): undefined reference to `sys_iam'
ld: kernel/kernel.o:(.data+0x124): undefined reference to `sys_whoami'
make: *** [Makefile:67:tools/system] 错误 1

修改 makefile:

1
2
3
OBJS  = sched.o system_call.o traps.o asm.o fork.o \
panic.o printk.o vsprintf.o sys.o exit.o \
signal.o mktime.o whoami.o
1
2
### Dependencies:
whoami.s whoami.o: whoami.c ../include/linux/kernel.h ../include/unistd.h

为了测试系统调用,我们可以将 hdc-0.11.img 挂载(使用 mount-hdc 脚本),然后再往其中写入测试脚本的代码

不过在此之前我们需要先了解一下 _syscallN 宏:

1
2
3
4
5
6
7
8
9
10
11
12
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}

#endif /* __LIBRARY__ */
  • N 是一个整数(表示系统调用的编号),... 是一个可变参数列表(用于传递系统调用的参数),## 是一个 C 语言宏展开的符号(##name 将被替换 name
  • "=a" 用于指定返回寄存器 __res 的输出模式(返回寄存器 __res 用于存储系统调用的返回值),这段代码的作用是:在执行完汇编代码后应该将结果存储在 eax 寄存器中
  • 将系统调用号 __NR_##name 和参数 a b c 压入栈中,最后执行 int 0x80

在 linux-0.11 上函数调用和系统调用的底层代码是不同的,_syscallN 宏的出现可以使我们以函数调用的方式去写系统调用的C代码

下面是利用 _syscallN 执行系统调用的测试脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <errno.h>
#define __LIBRARY__
#include <stdio.h>
#include <unistd.h>

_syscall2(int,whoami,char *,name,unsigned int,size)

int main(int argc, char *argv[]){
char name[24] = {0};
int re = whoami(name,24);
if(re != -1){
printf("output successfully!\n",name);
printf("my name is %s\n",name);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <errno.h>
#define __LIBRARY__
#include <stdio.h>
#include <unistd.h>

_syscall1(int,iam,const char *,name)

int main(int argc, char **argv){
char name[24] = "iam yhellow";
int re = iam(name);
if(re != -1){
printf("input successfully!\n");
}
return 0;
}

使用如下命令进行编译:(最好在 linux-0.11 中进行编译)

1
2
gcc -o whoami whoami.c -Wall
gcc -o iam iam.c -Wall

最终效果如下: