System call tracing
低配 strace
还是很复杂的,很多 stub code 要修改,要严格按照 hint 里面的顺序,不然会乱
感觉可以搜 sys_
然后看着改
要理解 mask
The user-space code for systems calls is in user/user.h and user/usys.pl.
The kernel-space code is kernel/syscall.h, kernel/syscall.c.
The process-related code is kernel/proc.h and kernel/proc.c.
先添加 kernel space 和 user space 的函数入口
// kernel/syscall.c
extern uint64 sys_trace ( void );
// kernel/syscall.h
#define SYS_trace 22
// user/user.h
int trace ( int );
// user/usys.pl
entry ( "trace" );
修改 proc 来储存要追踪的那些 syscall,注意 alignment,加到最后最好,这样比较无脑,xv6 理论上 32 位就够了,Linux 就不够:D。另外在新建 proc 时候最好 initialize 一下
// kernel/proc.h
// Per-process state
struct proc {
...
uint32 trace_mask;
};
// kernel/proc.c, initialize
static struct proc * allocproc ( void ) {
...
p->trace_mask = 0 ;
return p;
}
实现,参照其他 syscall 即可。为了追逐 child 还要修改 fork
// kernel/sysproc.c
uint64 sys_trace ( void ) {
int mask;
if ( argint ( 0 , & mask) != 0 ) { // 用于获取syscall参数
return - 1 ;
}
myproc ()->trace_mask = mask;
return 0 ;
}
// kernel/proc.c
int fork ( void ) {
int i, pid;
struct proc * np;
struct proc * p = myproc ();
// Allocate process.
if ((np = allocproc ()) == 0 ) {
return - 1 ;
}
np->trace_mask = p->trace_mask;
...
}
由于 syscall 函数是所有 syscall 的入口,在这里输出即可,用一个类 map 来实现索引并输出名字
static const char * syscall_names [] = {
[SYS_fork] "fork" , [SYS_exit] "exit" , [SYS_wait] "wait" ,
[SYS_pipe] "pipe" , [SYS_read] "read" , [SYS_kill] "kill" ,
[SYS_exec] "exec" , [SYS_fstat] "fstat" , [SYS_chdir] "chdir" ,
[SYS_dup] "dup" , [SYS_getpid] "getpid" , [SYS_sbrk] "sbrk" ,
[SYS_sleep] "sleep" , [SYS_uptime] "uptime" , [SYS_open] "open" ,
[SYS_write] "write" , [SYS_mknod] "mknod" , [SYS_unlink] "unlink" ,
[SYS_link] "link" , [SYS_mkdir] "mkdir" , [SYS_close] "close" ,
[SYS_trace] "trace" ,
};
void syscall ( void ) {
int num;
struct proc * p = myproc ();
num = p->trapframe->a7;
if (num > 0 && num < NELEM (syscalls) && syscalls [num]) {
p->trapframe->a0 = syscalls [num]();
int pid = p->pid;
int return_code = p->trapframe->a0;
if ((p->trace_mask >> num) & 1 ) {
printf ( " %d : syscall %s -> %d\n " , pid, syscall_names [num], return_code);
}
} else {
printf ( " %d %s : unknown syscall %d\n " , p->pid, p->name, num);
p->trapframe->a0 = - 1 ;
}
}
$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
sysinfo
一样的思路,不同的是
要知道如何获取 freemem
和 nproc
(kalloc.c
and proc.c
)
copy memory from kernel space to user space (copyout
), since the page table are different.
添加 syscall 的方式同上
// user/user.h
// make it compile
struct sysinfo;
int sysinfo ( struct sysinfo * );
// user/usys.pl
entry ( "sysinfo" );
// kernel/syscall.h
#define SYS_sysinfo 23
// kernel/defs.h
uint64 proc_count ( void );
uint64 kfree_mem_count ( void );
// kernel/kalloc.c
uint64 kfree_mem_count () {
uint64 mem_bytes = 0 ;
struct run * r;
acquire ( & kmem.lock);
r = kmem.freelist;
while (r) {
mem_bytes += PGSIZE;
r = r->next;
}
release ( & kmem.lock);
return mem_bytes;
}
// kernel/proc.c
uint64 proc_count () {
uint64 cnt = 0 ;
for ( struct proc * p = proc; p < & proc [NPROC]; p ++ ) {
acquire ( & p->lock);
if (p->state != UNUSED) {
cnt ++ ;
}
release ( & p->lock);
}
return cnt;
}
// kernel/syscall.c
extern uint64 sys_sysinfo ( void );
static uint64 ( * syscalls [] )( void ) = {
[SYS_fork] sys_fork, [SYS_exit] sys_exit, [SYS_wait] sys_wait,
[SYS_pipe] sys_pipe, [SYS_read] sys_read, [SYS_kill] sys_kill,
[SYS_exec] sys_exec, [SYS_fstat] sys_fstat, [SYS_chdir] sys_chdir,
[SYS_dup] sys_dup, [SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, [SYS_open] sys_open,
[SYS_write] sys_write, [SYS_mknod] sys_mknod, [SYS_unlink] sys_unlink,
[SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close,
[SYS_trace] sys_trace, [SYS_sysinfo] sys_sysinfo
};
uint64 sys_sysinfo () {
uint64 addr;
if ( argaddr ( 0 , & addr) < 0 ) {
return - 1 ; // questionable -1 in unit but i guess it's intentional
}
struct sysinfo sinfo;
sinfo.freemem = kfree_mem_count ();
sinfo.nproc = proc_count ();
if ( copyout ( myproc ()->pagetable, addr, ( char * ) & sinfo, sizeof (sinfo)) < 0 ) {
return - 1 ;
}
return 0 ;
}
result:
$ sysinfotest
sysinfotest: start
sysinfotest: OK
autograde
== Test trace 32 grep ==
$ make qemu-gdb
trace 32 grep: OK (3.1s)
== Test trace all grep ==
$ make qemu-gdb
trace all grep: OK (1.2s)
== Test trace nothing ==
$ make qemu-gdb
trace nothing: OK (1.1s)
== Test trace children ==
$ make qemu-gdb
trace children: OK (18.2s)
== Test sysinfotest ==
$ make qemu-gdb
sysinfotest: OK (2.8s)
== Test time ==
time: FAIL
Cannot read time.txt
Score: 34/35
感想
为了隔离真的好麻烦。。操作系统和 libc 真是抽象完了.
整体感觉难不是很难,但是很繁琐,还要对操作系统有个整体认识。