GDB
如何启动gdb
- 启动 make CPUs=1 qemu-gdb
- 在另一个终端启动 gdb-multiarch kernel/kernel
- 进入gdb后输入 target remote localhost:26000
GDB 的一些命令
- shell clear # 清屏
- layout src # 显示源码
- layout regs # 显示寄存器
- layout split # 同时显示源码和寄存器
- backtrace # 显示函数调用栈
在XV6 如何添加系统调用
- 在
user/user.h
中添加系统调用函数的定义. 比如:
struct stat;
// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
- 在
user/usys.pl
中添加入口, 这个文件会在make
后生成user/usys.S
文件, 在该汇编文件中,每个函数就只有三行,将系统调用号通过li(load imm)
存入 a7 寄存器,之后使用ecall
进入内核态,最后返回。
sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n";
print " ecall\n";
print " ret\n";
}
entry("fork");
entry("exit");
- 在
kernel/syscall.h
中定义系统调用号
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_trace 22
- 在
kernel/syscall.c
的syscalls
函数指针数值中添加对应的函数。
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
// 这里的static uint64 (*syscalls[])(void)
// 表示定义一个 静态函数指针的数组
// 函数指针参数是void, 返回值是 uint64
// [SYS_fork] sys_fork, 表示将第 SYS_fork 设置为 sys_fork
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,
};
- 在
syscall
函数中,先读取trapframe->a7
获取系统调用号之后,根据系统调用号查找syscalls
数组中的对应的处理函数并调用。 - 获取传入的参数。通过往
a0
寄存器写入,通过一组专门得API读取出 - 返回参数
p->trapframe->a0 = syscalls[num]();
System call tracing (moderate)
需要添加一个系统调用 trace
来控制追踪系统调用。
输入参数: mask
使用位来指定需要跟踪的系统调用。比如,跟踪fork
, 系统调用可以是 trace(1<SYS_fork)
在这里,使用每一个位来标识是否启用跟踪,比如 SYS_fork
是 4 那么 1 << SYS_fork 的二进制就是: 10000
需要修改 xv6的内核,当相关的系统调用返回时,需要打印一行信息。这行信息包括线程ID,系统调用名字以及返回的值。不需要打印系统调用的参数。trace
系统调用应该为其子线程开启跟踪,但是不能影响到无关的线程。
输出示例:
trace 32 grep hello README
threadID:syscall syscall Name -> returen value
- 添加一个系统调用的步骤,添加好接口
trace
和系统调用sys_trace
uint64
sys_trace(void)
{
int mask;
argint(0, &mask);
// 这一行在第二点里面解释
myproc()->trace_mask = mask;
return 0;
}
这里测试一下拿到的mask是否是对的。
2. 保存mask
在PCB结构里面增加 trace_mask
字段
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
int trace_mask; // trace syscall mas
...
};
这样就可以使用 myproc()->trace_mask = mask;
来存取mask值
为了在子线程里面把mask拷贝到子线程,所以,我们需要在fork()
函数里把 trace_mask
拷贝给子线程。
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
// 将 父线程的trace_mask给子线程
np->trace_mask = p->trace_mask;
.....
}
- 如何根据mask来打印跟踪的信息
- 添加名字数组
const static 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]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
// 通过 `&`运算来判断是否是目标进程
// 比如 fork 的num 是 1 对应的二进制为
// 1<<num: 00000000 00000000 00000000 00000000 00000001
// mask : 11111111 11111111 11111111 11111111 00000000
// (mask) & (1 << num) = 00000000 00000000 00000000 00000000 00000000
// 所以不输出
if(p->trace_mask & (1 << num)) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num],p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
Sysinfo
添加一个 sysinfo
的系统调用来手机正在运行的系统的信息。输入参数: strcut sysinfo* pSysInfo
。 内核需要填充这些字段。
- 添加系统调用