Lab traps: Trap

RISC-V assembly (easy)

阅读 call.asm,以及 RISC-V 指令集教程,回答问题。(学习 RISC-V 汇编)

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
Q: 哪些寄存器存储了函数调用的参数?举个例子,main 调用 printf 的时候,13 被存在了哪个寄存器中?
A: a0-a7; a2;

Q: main 中调用函数 f 对应的汇编代码在哪?对 g 的调用呢? (提示:编译器有可能会内链(inline)一些函数)
A: 没有这样的代码。 g(x) 被内链到 f(x) 中,然后 f(x) 又被进一步内链到 main() 中

Q: printf 函数所在的地址是?
A: 0x0000000000000628, main 中使用 pc 相对寻址来计算得到这个地址。

Q: 在 main 中 jalr 跳转到 printf 之后,ra 的值是什么?
A: 0x0000000000000038, jalr 指令的下一条汇编指令的地址。

Q: 运行下面的代码

unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

输出是什么?
如果 RISC-V 是大端序的,要实现同样的效果,需要将 i 设置为什么?需要将 57616 修改为别的值吗?
A: "He110 World"; 0x726c6400; 不需要,57616 的十六进制是 110,无论端序(十六进制和内存中的表示不是同个概念)

Q: 在下面的代码中,'y=' 之后会答应什么? (note: 答案不是一个具体的值) 为什么?

printf("x=%d y=%d", 3);

A: 输出的是一个受调用前的代码影响的“随机”的值。因为 printf 尝试读的参数数量比提供的参数数量多。
第二个参数 `3` 通过 a1 传递,而第三个参数对应的寄存器 a2 在调用前不会被设置为任何具体的值,而是会
包含调用发生前的任何已经在里面的值。

Backtrace (moderate)

分析

backtrace功能: 打印出调用栈,用于调试

the layout of stack frames

  • 栈的生长方向是从高地址到低地址
  • 栈帧中从高到低第一个 8 字节 fp-8 是 return address,也就是当前调用层应该返回到的地址。
  • 栈帧中从高到低第二个 8 字节 fp-16 是 previous address,指向上一层栈帧的 fp 开始地址。
  • 剩下的为保存的寄存器、局部变量等。一个栈帧的大小不固定,但是至少 16 字节。
  • 在 xv6 中,使用一个页来存储栈,如果 fp 已经到达栈页的上界,则说明已经到达栈底。

实现

defs.h 中添加声明

1
2
3
4
5
// defs.h
void printf(char*, ...);
void panic(char*) __attribute__((noreturn));
void printfinit(void);
void backtrace(void);

riscv.h 中添加获取当前 fp(frame pointer)寄存器的方法

1
2
3
4
5
6
7
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x));
return x;
}

printf.c实现backtrace函数

1
2
3
4
5
6
7
8
9
10
void
backtrace(void)
{
uint64 fp = r_fp();
uint64 high = PGROUNDUP(fp), low = PGROUNDDOWN(fp);
while(fp>=low && fp<high) {
printf("%p\n", *((uint64*)(fp - 8))); // return address
fp = *((uint64*)(fp - 16)); // previous fp
}
}

sysproc.csys_sleep 的开头调用一次 backtrace()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uint64
sys_sleep(void)
{
int n;
uint ticks0;

backtrace(); // print stack backtrace.

if(argint(0, &n) < 0)
return -1;

// ......

return 0;
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
$ bttest
0x0000000080002ccc
0x0000000080002ba6
0x0000000080002890
$ QEMU: Terminated
grand@Lubuntu ~/xv6-labs-2020 (traps)> addr2line -e kernel/kernel
0x0000000080002ccc
0x0000000080002ba6
0x0000000080002890
Ctrl-D
/home/grand/xv6-labs-2020/kernel/sysproc.c:63
/home/grand/xv6-labs-2020/kernel/syscall.c:140
/home/grand/xv6-labs-2020/kernel/trap.c:76

Alarm (hard)

任务:给 xv6 加一个功能——在进程使用CPU时间时定期发出警告。这对于限制 CPU 密集型(计算密集型)进程的占用时间,或对于在计算过程中有其他定期动作的进程可能很有用。更广泛的说,我们将实现一个用户级中断/异常的处理程序。

我们需要添加一个 sigalarm(interval, handler) 系统调用。如果一个应用调用了 sigalarm(n, fn),则该应用每耗时 n 个 ticks,内核应该使之调用 fn,fn 返回后该应用继续执行。如果一个应用调用 sigalarm(0, 0),内核应该停止产生 alarm calls。

test0: invoke handler

添加声明

  • MakefileUPROGS的最后添加$U/_alarmtest\
  • user/user.h中的系统调用函数声明int sigalarm(int ticks, void(*handler)());int sigreturn(void);
  • user/usys.pl中添加entry("sigalarm");entry("sigreturn");
  • kernel/syscall.h添加系统函数编号#define SYS_sigalarm 22#define SYS_sigreturn 23
  • kernel/syscall.c添加内核态函数声明,如下所示
    1
    2
    3
    4
    5
    6
    7
    8
    extern uint64 sys_sigalarm(void);
    extern uint64 sys_sigreturn(void);

    static uint64 (*syscalls[])(void) = {
    ...
    [SYS_sigalarm] sys_sigalarm,
    [SYS_sigreturn] sys_sigreturn,
    };

实现

test0是实现一部分的sys_sigalarm,因此为了使得可以编译运行,先将sys_sigreturn(void)的返回值设为0
在kernel/sysproc.c里面,添加如下代码

1
2
3
4
5
uint64
sys_sigreturn(void)
{
return 0;
}

接下来是关于如何实现发出alarm

  • 首先,在struct proc(in kernel/proc.h)中添加一下进程的【属性】:总 ticks 数、处理程序指针、剩余 ticks 数
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
// Per-process state
struct proc {
struct spinlock lock;

// p->lock must be held when using these:
enum procstate state; // Process state
struct proc *parent; // Parent process
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

// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)

int ticks; // Ticks between two alarms.
uint64 handler; // Alarm handler.
int remain_ticks; // Remaining ticks after last alarm.
};
  • 在 allocproc (kernel/proc.c) 中初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static struct proc*
    allocproc(void)
    {
    ...
    // Set up new context to start executing at forkret,
    // which returns to user space.
    memset(&p->context, 0, sizeof(p->context));
    p->context.ra = (uint64)forkret;
    p->context.sp = p->kstack + PGSIZE;

    // Initialize for alarm.
    p->ticks = p->remain_ticks = 0;
    p->handler = 0;

    return p;
    }
  • 在kernel/sysproc.c实现系统调用sys_sigalarm

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    uint64
    sys_sigalarm(void)
    {
    int ticks;
    uint64 handler;
    struct proc *p = myproc();

    if(argint(0, &ticks) < 0)
    return -1;
    if(argaddr(1, &handler) < 0)
    return -1;
    acquire(&p->lock);
    p->ticks = ticks;
    p->handler = handler;
    p->remain_ticks = ticks;
    release(&p->lock);
    return 0;
    }
  • 每一个 tick 过后硬件都会产生计时器中断(timer interrupt),所以我们在 kernel/trap.c 中 if(which_dev == 2) 语句下处理它,把 p->trapframe->epc 改成处理程序地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    void
    usertrap(void)
    {
    ...
    if(p->killed)
    exit(-1);

    // This is a timer interrupt.
    if(which_dev == 2){
    if(p->ticks == 0)
    yield();
    p->remain_ticks--;
    if(p->remain_ticks == 0){
    p->remain_ticks = p->ticks;
    p->trapframe->epc = p->handler;
    }
    }

    usertrapret();
    }

test1/test2(): resume interrupted code

完善sigalarm

  • 在struct proc(in kernel/proc.h)中添加一个【属性】用来保存栈帧

    1
    2
    3
    4
    5
    6
    7
    8
    // Per-process state
    struct proc {
    ...
    int ticks; // Ticks between two alarms.
    uint64 handler; // Alarm handler.
    int remain_ticks; // Remaining ticks after last alarm.
    struct trapframe *save_trapframe; // Save trapframe page when handling alarm.
    };
  • 在 allocproc (kernel/proc.c) 中初始化 栈帧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    static struct proc*
    allocproc(void)
    {
    ...
    // Set up new context to start executing at forkret,
    // which returns to user space.
    memset(&p->context, 0, sizeof(p->context));
    p->context.ra = (uint64)forkret;
    p->context.sp = p->kstack + PGSIZE;

    // Initialize for alarm.
    p->ticks = p->remain_ticks = 0;
    p->handler = 0;
    p->save_trapframe = 0;

    return p;
    }
  • 在 kernel/trap.c 中 if(which_dev == 2) 语句处 保存栈帧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // This is a timer interrupt.
    if(which_dev == 2){
    if(p->ticks == 0)
    yield();
    p->remain_ticks--;
    if(p->remain_ticks == 0 && p->save_trapframe == 0){
    p->remain_ticks = p->ticks;
    p->save_trapframe = (struct trapframe *)kalloc();
    memmove(p->save_trapframe, p->trapframe, PGSIZE);
    p->trapframe->epc = p->handler;
    }
    }

实现sys_sigreturn,还原 栈帧

1
2
3
4
5
6
7
8
9
10
11
12
13
uint64
sys_sigreturn(void)
{
struct proc *p = myproc();
acquire(&p->lock);
if(p->save_trapframe){
memmove(p->trapframe, p->save_trapframe, PGSIZE);
kfree(p->save_trapframe);
p->save_trapframe = 0;
}
release(&p->lock);
return 0;
}

评分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
grand@Lubuntu ~/xv6-labs-2020 (traps)> ./grade-lab-traps

== Test answers-traps.txt == answers-traps.txt: FAIL
Cannot read answers-traps.txt
== Test backtrace test == backtrace test: OK (5.1s)
== Test running alarmtest == (8.3s)
== Test alarmtest: test0 ==
alarmtest: test0: OK
== Test alarmtest: test1 ==
alarmtest: test1: OK
== Test alarmtest: test2 ==
alarmtest: test2: OK
== Test usertests == usertests: OK (198.8s)
== Test time ==
time: FAIL
Cannot read time.txt
Score: 79/85

tips

如果只是修改了文件,没有任何git操作,想要撤销、还原文件

  • 用暂存区的文件替换工作区的文件:
    • 全部文件:git checkout .
    • 指定文件:git checkout -- [file]

如果修改了文件,并且git add,没有git commit

  • git log —oneline // 可以省略,oneline 一条提交信息用一行展示
  • git reset HEAD // 回退到当前版本
  • git checkout — [file]

如果修改了文件,git add, git commit

  • git log —oneline // 可以省略
  • git reset HEAD^ // 回退到上一个版本,HEAD^^:上上版本,HEAD~数字:回退到数字个版本
  • git checkout — [file]

修改记录

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
diff --git a/Makefile b/Makefile
index 1fa367e..a74296b 100644
--- a/Makefile
+++ b/Makefile
@@ -175,6 +175,7 @@ UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
+ $U/_alarmtest\



diff --git a/kernel/defs.h b/kernel/defs.h
index 4b9bbc0..137c786 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -80,6 +80,7 @@ int pipewrite(struct pipe*, uint64, int);
void printf(char*, ...);
void panic(char*) __attribute__((noreturn));
void printfinit(void);
+void backtrace(void);

// proc.c
int cpuid(void);
diff --git a/kernel/printf.c b/kernel/printf.c
index e1347de..b3219b0 100644
--- a/kernel/printf.c
+++ b/kernel/printf.c
@@ -132,3 +132,14 @@ printfinit(void)
initlock(&pr.lock, "pr");
pr.locking = 1;
}
+
+void
+backtrace(void)
+{
+ uint64 fp = r_fp();
+ uint64 high = PGROUNDUP(fp), low = PGROUNDDOWN(fp);
+ while(fp >= low && fp < high) {
+ printf("%p\n", *((uint64*)(fp - 8))); // return address
+ fp = *((uint64*)(fp - 16)); // previous fp
+ }
+}
diff --git a/kernel/proc.c b/kernel/proc.c
index dab1e1d..cebdcbe 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -127,6 +127,11 @@ found:
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;

+ // initlize tick ans handler for alarm
+ p->ticks = p->remain_ticks = 0;
+ p->handler = 0;
+ p->save_trapframe = 0;
+
return p;
}

diff --git a/kernel/proc.h b/kernel/proc.h
index 9c16ea7..0afc5ef 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -103,4 +103,9 @@ struct proc {
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
-};
+
+ int ticks; // Ticks between two alarms.
+ uint64 handler; // Alarm handler.
+ int remain_ticks; // Remaining ticks after last alarm.
+ struct trapframe *save_trapframe; // Save trapframe page when handling alarm.
+ };
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 0aec003..927d9ee 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -1,3 +1,12 @@
+// 获取当前 fp(frame pointer)寄存器
+static inline uint64
+r_fp()
+{
+ uint64 x;
+ asm volatile("mv %0, s0" : "=r" (x) );
+ return x;
+}
+
// which hart (core) is this?
static inline uint64
r_mhartid()
diff --git a/kernel/syscall.c b/kernel/syscall.c
index c1b3670..24bfccd 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -104,6 +104,8 @@ extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
+extern uint64 sys_sigalarm(void);
+extern uint64 sys_sigreturn(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
@@ -127,6 +129,8 @@ static uint64 (*syscalls[])(void) = {
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
+[SYS_sigalarm] sys_sigalarm,
+[SYS_sigreturn] sys_sigreturn,
};

void
diff --git a/kernel/syscall.h b/kernel/syscall.h
index bc5f356..67ca3a4 100644
--- a/kernel/syscall.h
+++ b/kernel/syscall.h
@@ -20,3 +20,5 @@
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
+#define SYS_sigalarm 22
+#define SYS_sigreturn 23
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index e8bcda9..f349ea9 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -58,6 +58,8 @@ sys_sleep(void)
int n;
uint ticks0;

+ backtrace(); // print stack backtrace.
+
if(argint(0, &n) < 0)
return -1;
acquire(&tickslock);
@@ -95,3 +97,36 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}
+
+uint64
+sys_sigalarm(void)
+{
+ int ticks;
+ uint64 handler;
+ struct proc *p = myproc();
+
+ if(argint(0, &ticks) < 0)
+ return -1;
+ if(argaddr(1, &handler) < 0)
+ return -1;
+ acquire(&p->lock);
+ p->ticks = ticks;
+ p->handler = handler;
+ p->remain_ticks = ticks;
+ release(&p->lock);
+ return 0;
+}
+
+uint64
+sys_sigreturn(void)
+{
+ struct proc *p = myproc();
+ acquire(&p->lock);
+ if(p->save_trapframe){
+ memmove(p->trapframe, p->save_trapframe, PGSIZE);
+ kfree(p->save_trapframe);
+ p->save_trapframe = 0;
+ }
+ release(&p->lock);
+ return 0;
+}
diff --git a/kernel/trap.c b/kernel/trap.c
index a63249e..d1900fd 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -77,9 +77,18 @@ usertrap(void)
exit(-1);

// give up the CPU if this is a timer interrupt.
- if(which_dev == 2)
- yield();
-
+ if(which_dev == 2) {
+ if(p->ticks == 0)
+ yield();
+
+ p->remain_ticks--;
+ if(p->remain_ticks == 0 && p->save_trapframe == 0){
+ p->remain_ticks = p->ticks;
+ p->save_trapframe = (struct trapframe *)kalloc();
+ memmove(p->save_trapframe, p->trapframe, PGSIZE);
+ p->trapframe->epc = p->handler;
+ }
+ }
usertrapret();
}

diff --git a/user/user.h b/user/user.h
index b71ecda..675a192 100644
--- a/user/user.h
+++ b/user/user.h
@@ -24,6 +24,9 @@ char* sbrk(int);
int sleep(int);
int uptime(void);

+int sigalarm(int ticks, void (*handler)());
+int sigreturn(void);
+
// ulib.c
int stat(const char*, struct stat*);
char* strcpy(char*, const char*);
diff --git a/user/usys.pl b/user/usys.pl
index 01e426e..fa548b0 100755
--- a/user/usys.pl
+++ b/user/usys.pl
@@ -36,3 +36,5 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
+entry("sigalarm");
+entry("sigreturn");