diff --git a/chapter1_riscv.md b/chapter1_riscv.md
index 0899131..5870295 100644
--- a/chapter1_riscv.md
+++ b/chapter1_riscv.md
@@ -298,6 +298,8 @@ clobbered_regs(破坏描述部分)
我们仍然以例1.1为例分析RISC-V程序执行过程中,栈的变化。RISC-V并未提供push和pop指令,所以函数开始时对sp寄存器的操作都是RISC-V版本的入栈动作。例如,main函数中的“addi sp,sp,-16”指令,将sp减去16然后赋值给sp,其实质是在栈顶“空出”16个字节的空间,然后将必要的内容进行压栈。如后续的“sd ra,8(sp)”就是将返回地址(ra寄存器中的内容)压栈;而“sd s0,0(sp)”是将fp寄存器中的内容压栈(s0和fp是同一个寄存器,参见表1.1)。例1.1中程序从main函数到bar函数的函数调用过程中,它的程序栈将形成图1.2中所示的结构:
+
+
图1.2 例1.1程序的函数调用栈结构(从main函数到bar函数)
diff --git a/chapter3_traps.md b/chapter3_traps.md
index 12c5b05..604276a 100644
--- a/chapter3_traps.md
+++ b/chapter3_traps.md
@@ -1539,3 +1539,112 @@ System is shutting down with exit code 0.
那么handle_mtimer_trap()需要完成哪些“后续动作”呢?首先,我们看到在该函数上面定义了一个全局变量g_ticks,用它来对时钟中断的次数进行计数,而第31行会输出该计数。为了确保我们的系统持续正常运行,该计数应每次都会完成加一操作。所以,handle_mtimer_trap()首先需要对g_ticks进行加一;其次,由于处理完中断后,SIP(Supervisor Interrupt Pending,即S模式的中断等待寄存器)寄存器中的SIP_SSIP位仍然为1(由M态的中断处理函数设置),如果该位持续为1的话会导致我们的模拟RISC-V机器始终处于中断状态。所以,handle_mtimer_trap()还需要对SIP的SIP_SSIP位清零,以保证下次再发生时钟中断时,M态的函数将该位置一会导致S模式的下一次中断。
+
+
+## 3.4 lab1_challenge1 挑战一:打印用户程序调用栈
+
+
+
+#### **给定应用**
+
+- user/app_print_backtrace.c
+
+```c
+ 1 /*
+ 2 * Below is the given application for lab1_challenge1_backtrace.
+ 3 * This app prints all functions before calling print_backtrace().
+ 4 */
+ 5
+ 6 #include "user_lib.h"
+ 7 #include "util/types.h"
+ 8
+ 9 void f8() { print_backtrace(7); }
+ 10 void f7() { f8(); }
+ 11 void f6() { f7(); }
+ 12 void f5() { f6(); }
+ 13 void f4() { f5(); }
+ 14 void f3() { f4(); }
+ 15 void f2() { f3(); }
+ 16 void f1() { f2(); }
+ 17
+ 18 int main(void) {
+ 19 printu("back trace the user app in the following:\n");
+ 20 f1();
+ 21 exit(0);
+ 22 return 0;
+ 23 }
+```
+
+以上程序在真正调用系统调用print_backtrace(7)之前的函数调用关系比复杂,图示起来有以下关系:
+
+main -> f1 -> f2 -> f3 -> f4 -> f5 -> f6 -> f7 -> f8
+
+print_backtrace(7)的作用是将以上用户程序的函数调用关系,从最后的f8向上打印7层,预期的输出为:
+
+```
+In m_start, hartid:0
+HTIF is available!
+(Emulated) memory size: 2048 MB
+Enter supervisor mode...
+Application: obj/app_print_backtrace
+Application program entry point (virtual address): 0x0000000081000072
+Switching to user mode...
+back trace the user app in the following:
+f8
+f7
+f6
+f5
+f4
+f3
+f2
+User exit with code:0.
+System is shutting down with exit code 0.
+```
+
+
+
+#### 实验内容
+
+本实验为挑战实验,基础代码将继承和使用lab1_3完成后的代码:
+
+- 切换到lab1_3、继承lab1_2中所做修改:
+
+```bash
+//切换到lab1_challenge1_backtrace
+$ git checkout lab1_challenge1_backtrace
+
+//继承lab1_3以及之前的答案
+$ git merge lab1_3_irq -m "continue to work on lab1_challenge1"
+```
+
+注意:**不同于基础实验,挑战实验的基础代码具有更大的不完整性,可能无法直接通过构造过程。**例如,由于以上的用户代码中print_backtrace()系统调用并未实现,所以构造时就会报错。同样,不同于基础实验,我们在代码中也并未专门地哪些地方的代码需要填写,哪些地方的代码无须填写。这样,我们留个读者更大的“想象空间”。
+
+- 本实验的具体要求为:
+
+通过修改PKE内核,来实现从给定应用(user/app_print_backtrace.c)到预期输出的转换。
+
+对于print_backtrace()函数的实现要求:
+
+应用程序调用print_backtrace()时,应能够通过控制输入的参数(如例子user/app_print_backtrace.c中的7)控制回溯的层数。例如,如果调用print_backtrace(5)则只输出5层回溯;如果调用print_backtrace(100),则应只回溯到main函数就停止回溯(因为调用的深度小于100)。
+
+- 讨论的简化
+
+实验可以对中间函数,如user/app_print_backtrace.c中的f1到f8,进行讨论上的简化,可假设它们都不带参数。
+
+
+
+
+
+#### 实验指导
+
+为完成该挑战,PKE内核的完善应包含以下内容:
+
+- 系统调用路径上的完善,可参见[3.2](#syscall)中的知识;
+- 在操作系统内核中获取用户程序的栈。这里需要注意的是,PKE系统中当用户程序通过系统调用陷入到内核时,会切换到S模式的“用户内核”栈,而不是在用户栈上继续操作。我们的print_backtrace()函数的设计目标是回溯并打印用户进程的函数调用情况,所以,进入操作系统内核后,需要找到用户进程的用户态栈来开始回溯;
+- 找到用户态栈后,我们需要了解用户态栈的结构。实际上,这一点在我们的第一章就有[举例](chapter1_riscv.md#call_stack_structure)来说明,读者可以回顾一下第一章的例子。另外,由于我们可以对讨论进行简化(即假设中间函数无参数),那么单次函数调用的栈深度我们也可以进行相应的假设;
+- 通过用户栈找到函数的返回地址后,需要将虚拟地址转换为源程序中的符号。这一点,读者需要了解ELF文件中的符号节(.symtab section),以及字符串节(.strtab section)的相关知识,了解这两个节(section)里存储的内容以及存储的格式等内容。对ELF的这两个节,网上有大量的介绍,例如[这里](https://blog.csdn.net/edonlii/article/details/8779075)。
+
+**注意:完成实验内容后,请读者另外编写应用,通过调用print_backtrace()函数,并带入不同的深度参数,对自己的实现进行检测。**
+
+
+