练习5:实现函数调用堆栈跟踪函数 (需要编程)

我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。

根据要求,要得到类似如下的输出:

……
ebp:0x00007b28 eip:0x00100992 args:0x00010094 0x00010094 0x00007b58 0x00100096kern/debug/kdebug.c:305: print_stackframe+22
ebp:0x00007b38 eip:0x00100c79 args:0x00000000 0x00000000 0x00000000 0x00007ba8kern/debug/kmonitor.c:125: mon_backtrace+10
ebp:0x00007b58 eip:0x00100096 args:0x00000000 0x00007b80 0xffff0000 0x00007b84kern/init/init.c:48: grade_backtrace2+33
ebp:0x00007b78 eip:0x001000bf args:0x00000000 0xffff0000 0x00007ba4 0x00000029kern/init/init.c:53: grade_backtrace1+38
ebp:0x00007b98 eip:0x001000dd args:0x00000000 0x00100000 0xffff0000 0x0000001dkern/init/init.c:58: grade_backtrace0+23
ebp:0x00007bb8 eip:0x00100102 args:0x0010353c 0x00103520 0x00001308 0x00000000kern/init/init.c:63: grade_backtrace+34
ebp:0x00007be8 eip:0x00100059 args:0x00000000 0x00000000 0x00000000 0x00007c53kern/init/init.c:28: kern_init+88
ebp:0x00007bf8 eip:0x00007d73 args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8
<unknow>: -- 0x00007d72 –
……

一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作(由硬件完成)。几乎所有本地编译器都会在每个函数体之前插入类似如下的汇编指令:

pushl %ebp
movl %esp , %ebp

这两条汇编指令的含义是:首先将ebp寄存器入栈,然后将栈顶指针esp赋值给ebp。“mov ebp esp”这条指令表面上看是用esp覆盖ebp原来的值,其实不然。因为给ebp赋值之前,原ebp值已经被压栈(位于栈顶),而新的ebp又恰恰指向栈顶。此时ebp寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的ebp值。

+|  栈底方向        | 高位地址|    ...        ||    ...        ||  参数3        ||  参数2        ||  参数1        ||  返回地址        ||  上一层[ebp]    | <-------- [ebp]|  局部变量        |  低位地址

一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层ebp值。由于ebp中的地址处总是“上一层函数调用时的ebp值”,而在每一层函数调用中,都能通过当时的ebp值“向上(栈底方向)”能获取返回地址、参数值,“向下(栈顶方向)”能获取函数局部变量值。如此形成递归,直至到达栈底。这就是函数调用栈。

按照如下注释进行编程。

void print_stackframe(void) {/* LAB1 YOUR CODE : STEP 1 *//* (1) call read_ebp() to get the value of ebp. the type is (uint32_t);* (2) call read_eip() to get the value of eip. the type is (uint32_t);* (3) from 0 .. STACKFRAME_DEPTH*    (3.1) printf value of ebp, eip*    (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4]*    (3.3) cprintf("\n");*    (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.*    (3.5) popup a calling stackframe*           NOTICE: the calling funciton's return addr eip  = ss:[ebp+4]*                   the calling funciton's ebp = ss:[ebp]*/
}

【0x%08x】 “0x”, 普通字符 配合显示16进制格式;"%08x", 8位对齐的16进制格式,少于8位则前补0。

简单来说,这个函数的作用就是进行若干次递归函数调用,每次查看当前ebp和函数的返回地址以及4个参数。

答案如下:

void print_stackframe(void) {uint32_t ebp = read_ebp();uint32_t eip = read_eip();int i;for (i = 0; i < STACKFRAME_DEPTH; i++) {cprintf("ebp:0x%08x eip:0x%08x", ebp, eip);uint32_t* arg = (uint32_t*) ebp + 2;cprintf("args:0x%08x 0x%08x 0x%08x 0x%08x", arg[0], arg[1], arg[2], arg[3]);print_debuginfo(eip - 1);eip = *((uint32_t*)ebp + 1);ebp = *((uint32_t*)ebp);}
}

在lab1中make qemu后的结果:

练习6:完善中断初始化和处理 (需要编程)

1.中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

答:中断描述符表是一个8字节的描述符数组,因此一个表项占8个字节。

其中最开始2个字节和最末尾2个字节定义了offset,第16-31位定义了处理代码入口地址的段选择子,使用其在GDT中查找到相应段的base address,加上offset就是中断处理代码的入口。

2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。

在mmu.h中的SETGATE宏代码如下:

#define SETGATE(gate, istrap, sel, off, dpl) {            \(gate).gd_off_15_0 = (uint32_t)(off) & 0xffff;        \(gate).gd_ss = (sel);                                \(gate).gd_args = 0;                                    \(gate).gd_rsv1 = 0;                                    \(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32;    \(gate).gd_s = 0;                                    \(gate).gd_dpl = (dpl);                                \(gate).gd_p = 1;                                    \(gate).gd_off_31_16 = (uint32_t)(off) >> 16;        \
}
  • 传入的第一个参数gate是中断的描述符表项(struct gatedesc)
  • 传入的第二个参数istrap用来判断是中断还是trap
  • 传入的第三个参数sel的作用是进行段的选择
  • 传入的第四个参数off表示偏移
  • 传入的第五个参数dpl表示这个中断的优先级

idt_init函数的注释如下:

idt_init(void) {/* LAB1 YOUR CODE : STEP 2 *//* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? 中断服务程序*     All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ?*     __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c*     (try "make" command in lab1, then you will find vector.S in kern/trap DIR)*     You can use  "extern uintptr_t __vectors[];" to define this extern variable which will be used later.* (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT).*     Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT* (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction.*     You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more.*     Notice: the argument of lidt is idt_pd. try to find it!*/
}

答案如下:其中struct gatedesc是中断门描述符,因此idt的大小除以gatedesc的大小才是准确的数组大小。再建立了idt的内容之后,要让CPU知道idt在哪,调用lidt函数。

其中,中断处理函数的段选择子及偏移量的设置要参考kern/trap/vectors.S文件:由该文件可知,所有中断向量的中断处理函数地址均保存在__vectors数组中,该数组中第i个元素对应第i个中断向量的中断处理函数地址。而且由文件开头可知,中断处理函数属于.text的内容。因此,中断处理函数的段选择子即.text的段选择子GD_KTEXT。从kern/mm/pmm.c可知.text的段基址为0,因此中断处理函数地址的偏移量等于其地址本身。

void idt_init(void) {extern uintptr_t __vectors[];int i;// 在这里先把所有的中断都初始化为内核级的中断for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i ++) {SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);}// 然后再把系统调用的中断初始化为用户级的中断// set for switch from user to kernelSETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);// 把idt的位置告诉CPU// load the IDTlidt(&idt_pd);
}

LIDT(Load IDT Register)指令:使用一个包含线性地址基址和界限的内存操作数来加载IDT。操作系统创建IDT时需要执行它来设定IDT的起始地址。这条指令只能在特权级0执行。(可参见libs/x86.h中的lidt函数实现,其实就是一条汇编指令)

3.请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。

case IRQ_OFFSET + IRQ_TIMER:/* LAB1 YOUR CODE : STEP 3 *//* handle the timer interrupt *//* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c* (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks().* (3) Too Simple? Yes, I think so!*/ticks++;if (ticks % TICK_NUM == 0) {print_ticks();  }

学堂在线-清华大学-操作系统实验Lab1【练习5-6】相关推荐

  1. 学堂在线-清华大学-操作系统实验Lab1【练习1-2】

    实验手册:https://chyyuu.gitbooks.io/ucore_os_docs/content/ 练习1:理解通过make生成执行文件的过程 1. 操作系统镜像文件ucore.img是如何 ...

  2. 学堂在线-清华大学-操作系统实验Lab1【练习3-4】

    练习3:分析bootloader进入保护模式的过程. BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader.请分析bootloader是如何完成从实模式进入保护模式 ...

  3. 学堂在线_操作系统_notes_第0-2讲_OS概述、OS实验环境准备

    学堂在线_操作系统_notes_第0-2讲_OS概述.OS实验环境准备 - 20220626.No.1821 - 操作系统OS 综合了 C语言 + 数据结构与算法DSA + 计算机组成. OS 是 控 ...

  4. 北航linux内核编译及烧录实验报告,北航操作系统实验Lab1笔记

    Loading... # 北航操作系统实验Lab1 ## Exercise 1.1 - **修改交叉编译路径为 `/OSLAB/compiler/usr/bin/mips_4KC-`** ![ex1_ ...

  5. 清华操作系统实验lab1

    第一次写的lab1练习1太冗杂,没有重点,理解不到位,后续进一步研究后感觉务必重新写一篇...... [练习1.1] 操作系统镜像文件 ucore.img 是如何一步一步生成的 生成ucore.img ...

  6. 计算机控制系统期末测试,学堂在线计算机操作系统考试题及答案

    一.单项选择题(每题1分,共20分)(更多试题及答案,尽在优题宝) 1.操作系统的发展过程是(  C    ) A.原始操作系统,管理程序,操作系统 B.原始操作系统,操作系统,管理程序 C.管理程序 ...

  7. MIT操作系统实验lab1(pingpong案例:附代码、详解)

    1.题目描述:在xv6上实现pingpong程序,即两个进程在管道两侧来回通信.父进程将"ping"写入管道,子进程从管道将其读出并打印<pid>:received p ...

  8. ucore操作系统实验笔记 - Lab1

    最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...

  9. 中断处理过程示意图_ucore操作系统实验笔记 - Lab1

    最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...

最新文章

  1. oracle 条件反转,Oracle反转倒置函数
  2. windows系统中的常用网络命令
  3. AngularJS跨域问题 ajax 跨域
  4. poj 2201(RMQ+笛卡尔树)
  5. Grub4Dos 安装Ubuntu 收藏
  6. Exynos4412 中断驱动开发相关问题总结
  7. 作者:王玲玲(1978-),女,中国科学院上海天文台高级工程师
  8. 数字后端基本概念-合集
  9. 躲开Xilinx官网龟速的下载器
  10. java安卓软件开发菜鸟教程,Android 开发环境搭建
  11. DOS命令与批处理学习历程
  12. SAP WBS预算可通过二种方式配置和使用
  13. ATSHA204A加密芯片
  14. 我的河海大学计算机考研专业课总结
  15. 微信小程序 实现带刻尺度滑块
  16. 交换机工作原理/模式
  17. Android手机的12项额外功能
  18. 信号的反射(振铃、台阶、回勾、尖峰毛刺)
  19. EXCEL VBA基础:通过创建模块完成简单SUB过程
  20. Git的使用(保姆级教程)

热门文章

  1. 轻量化Json开源格式化工具-JSON Formatter
  2. 抽象函数的对称性验证
  3. 关于logrotate的使用
  4. 群里的初级工程师求助说,要采集采招数据,必须给他安排上
  5. python连通图_用python实现无向图的连通性判断
  6. Dalvik虚拟机简介
  7. 【web前端开发】CSS浮动
  8. Corona建筑作品 | 272 Hedges Ave 海景顶级豪宅建筑表现
  9. 智能工厂 | 工业4.0
  10. 机器学习常用的分类器比较