能显示函数调用关系的ucore

操作系统中存在很多函数,通过函数间的调用来完成各种功能。在操作系统运行过程中,维持函数之间的调用关系,以及函数内部的局部变量是栈(stack)的基本功能。我们需要能够理解在操作系统中栈的实现细节和功能,这样能够更好地理解操作系统中函数如何相互调用,发现可能存在的问题。

实验目标

为了理解操作系统中的函数调用关系、传递参数和函数局部变量,我们设计了proj3.1,在ucore中增加了一个monitor子功能模块,能够分析出ucore在执行过程中的函数调用关系和函数传递的参数。通过分析proj3.1的实现,读者可了解基本栈结构,栈处理流程,GCC编译器的参数传递约定和构建函数调用关系的具体实现。

proj3.1概述

  1. 实现描述
    proj3.1建立在proj3的基础上,通过增加了一个monitor功能子模块,实现了一个能显示函数调用关系的ucore。简单地说proj3.1根据GCC生成的栈构建代码、函数参数压栈约定和实际函数调用过程中的栈结构内存空间,分析并显示函数调用关系。具体完成此工作的是print_stackframe函数。

  2. 项目组成
    proj3.1整体目录结构中新增加的主要内容如下所示:

    1. proj3.1
    2. |-- kern
    3. | |-- debug
    4. | | |-- assert.h
    5. | | |-- kdebug.c
    6. | | |-- kdebug.h
    7. | | |-- monitor.c
    8. | | |-- monitor.h
    9. | | |-- panic.c
    10. | | `-- stab.h
    11. | |-- driver
    12. | | `-- kbdreg.h
    13. | |-- init
    14. | | `-- init.c
    15. | |-- libs
    16. | | |-- readline.c
    17. | | `-- stdio.c
    18. | `-- trap
    19. | `-- trap.h
    20. |-- Makefile
    21. `-- tools
    22. |-- kernel.ld
    23. ……

    proj3.1是基于proj3进一步扩展完成的。相对于proj3,一共增加了10个文件,主要集中在debug目录下,这一个比较大的跨越。不过仔细看来主要增加和修改的文件不多,具体新增内容如下所示:

    • kern/debug/monitor.[ch]:监视系统运行的monitor交互子模块
    • kern/debug/debug.[ch]:实现内存地址到函数名的映射和分析显示函数调用关系;
    • kern/libs/readline.c:实现monitor的接收字符输入的功能;
    • kern/driver/kbdreg.h:定义了键盘的键值;
    • kern/trap/trap.h:为了向后兼容中断的处理,先在此处写了一个空的trapframe结构;
    • tools/kernel.ld:指导ld工具软件链接各个.o目标文件形成ucore的kernel的链接脚本;
  3. 编译运行
    编译并运行proj3.1的命令如下:

    1. cd proj3.1
    2. make
    3. make qemu

    当“K>”提示符出现后,可以敲入“help”字符串,可以看到当前的monitor有三个命令可以输入:help、kerninfo、backtrace。我们敲入“backtrace”字符串,则可以得到如下显示界面:

    1. (THU.CST) os is loading ...
    2. Welcome to the kernel debug monitor!!
    3. Type 'help' for a list of commands.
    4. K> help
    5. help - Display this list of commands.
    6. kerninfo - Display information about the kernel.
    7. backtrace - Print backtrace of stack frame.
    8. K> backtrace
    9. ebp:0x00007b08 eip:0x0010073a args:0x00010094 0x00000000 0x00007b88 0x00100985
    10. kern/debug/kdebug.c:216: print_stackframe+25
    11. ebp:0x00007b18 eip:0x00100a76 args:0x00000000 0x00007b3c 0x00000000 0x0000000a
    12. kern/debug/monitor.c:94: mon_backtrace+11
    13. ebp:0x00007b88 eip:0x00100985 args:0x00108560 0x00000000 0x00007bb8 0x0010124c
    14. kern/debug/monitor.c:55: runcmd+135
    15. ebp:0x00007bb8 eip:0x001009f8 args:0x00000000 0x00101ef0 0x0000065c 0x00000000
    16. kern/debug/monitor.c:70: monitor+75
    17. ebp:0x00007be8 eip:0x00100059 args:0x00000000 0x00000000 0x00000000 0x00007c4f
    18. kern/init/init.c:22: kern_init+89
    19. ebp:0x00007bf8 eip:0x00007d5b args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8

    通过上图可以看到monitor能够把当前的函数调用关系给显示出来,而且不仅仅给出函数调用返回处的eip地址,还显示出了在实际源代码处的文件名和行号。如果ucore在执行过程中由于某种异常错误激发monitor执行(以后有这样的实验),我们就可以很容易找到问题出现在什么地方了。下面我们将从栈的基本概念、栈结构和栈处理过程等方面来分析上图中背后的东西。