好难啊,去年做的时候就很难,难到直接不知道怎么出来的,现在能搞出来了。。。。。。
Exercise 12. Modify your stack backtrace function to display, for each eip, the function name, source file name, and line number corresponding to that eip.
In debuginfo_eip, where do __STAB_* come from? This question has a long answer; to help you to discover the answer, here are some things you might want to do:
look in the file kern/kernel.ld for __STAB_*
run objdump -h obj/kern/kernel
run objdump -G obj/kern/kernel
run gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s.
see if the bootloader loads the symbol table in memory as part of loading the kernel binary
Complete the implementation of debuginfo_eip by inserting the call to stab_binsearch to find the line number for an address.
Add a backtrace command to the kernel monitor, and extend your implementation of mon_backtrace to call debuginfo_eip and print a line for each stack frame of the form:
K> backtrace
Stack backtrace:
ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143: monitor+106
ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49: i386_init+59
ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70: <unknown>+0
K>
Each line gives the file name and line within that file of the stack frame's eip, followed by the name of the function and the offset of the eip from the first instruction of the function (e.g., monitor+106 means the return eip is 106 bytes past the beginning of monitor).
Be sure to print the file and function names on a separate line, to avoid confusing the grading script.
Tip: printf format strings provide an easy, albeit obscure, way to print non-null-terminated strings like those in STABS tables. printf("%.*s", length, string) prints at most length characters of string. Take a look at the printf man page to find out why this works.
You may find that some functions are missing from the backtrace. For example, you will probably see a call to monitor() but not to runcmd(). This is because the compiler in-lines some function calls. Other optimizations may cause you to see unexpected line numbers. If you get rid of the -O2 from GNUMakefile, the backtraces may make more sense (but your kernel will run more slowly).
要完成好几个任务
- 找出__STAB_*都来自哪里
- 确定bootloader是否将符号表作为加载内核的一部分加载到内存里了
- 补全debuginfo_eip函数的功能,让这个函数能查找行号
- 补全mon_backtrace函数的功能,使得其能够得到像上面那样的输出
1. 找出__STAB_*都来自哪里
这个比较容易,按照要求中的提示打开kern/kernel.ld,包含__STAB_*的部分如下
/* Include debugging information in kernel memory */
.stab : {
PROVIDE(__STAB_BEGIN__ = .);
*(.stab);
PROVIDE(__STAB_END__ = .);
BYTE(0) /* Force the linker to allocate space
for this section */
}
可以看到这个begin和end分别是.stab段的开始和结束。
ld文件是链接器脚本文件,链接时就按照这个脚本文件将目标文件和库链接到一起。它的语法见链接脚本文件ld语法
2. 确定bootloader是否将符号表作为加载内核的一部分加载到内存里了
这个我没想出来,看别人的答案是使用objdump -h找到stabstr段的地址,gdb调试时直接用x命令打印stabstr位置处有没有信息,如果有信息,就是加载成功了。
3. 补全debuginfo_eip函数的功能,让这个函数能查找行号
一开始也是始终没想明白,后来使用
objdump -G obj/kern/kernel # -G, --stabs Display (in raw form) any STABS info in the file
打印出来所有的调试信息,看了很多遍,才明白过来。怀疑-G是综合把.stab和.stabstr的文件一起输出的,所以才会包含字符串什么的。
截取一小部分。
yichuan@ubuntu:~/6.828/lab$ objdump -G obj/kern/kernel
obj/kern/kernel: file format elf32-i386
Contents of .stab section:
Symnum n_type n_othr n_desc n_value n_strx String
-1 HdrSym 0 1241 000018fc 1
0 SO 0 0 f0100000 1 {standard input}
1 SOL 0 0 f010000c 18 kern/entry.S
2 SLINE 0 44 f010000c 0
3 SLINE 0 57 f0100015 0
4 SLINE 0 58 f010001a 0
5 SLINE 0 60 f010001d 0
6 SLINE 0 61 f0100020 0
7 SLINE 0 62 f0100025 0
8 SLINE 0 67 f0100028 0
9 SLINE 0 68 f010002d 0
10 SLINE 0 74 f010002f 0
11 SLINE 0 77 f0100034 0
12 SLINE 0 80 f0100039 0
13 SLINE 0 83 f010003e 0
14 SO 0 2 f0100040 31 kern/entrypgdir.c
15 OPT 0 0 00000000 49 gcc2_compiled.
每个条目有7个参数
- symnum:条目序号
- n_type:条目类型,各个缩写的含义不知道是啥。
- n_othr:一直都为空,不知为何
- n_desc:后来才知道这是符号在文件中的行号
- n_value:符号的地址。FUN类型的地址是绝对地址,而对其他类型,如果前面有出现过FUN,那么SLINE指的就是这个函数内部的偏移地址,是相对地址。例子中的SLINE类型等前面都没出现过FUN,所以都是绝对地址。
- n_strx:字符串表的索引,指的是这个符号名字首字母在字符串表中的索引位置。比如{standard input}的n_strx为1,因为其首字母位置1,结尾是17,可能有一个终止符,所以kern/entry.S的n_strx为18。后面同理。
- String:即符号的字符串名。
debuginfo_eip函数使用了结构体Eipdebuginfo,定义在kdebug.h
int
debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info)
查看该结构体定义
struct Eipdebuginfo {
const char *eip_file; // Source code filename for EIP
int eip_line; // Source code linenumber for EIP
const char *eip_fn_name; // Name of function containing EIP
// - Note: not null terminated!
int eip_fn_namelen; // Length of function name
uintptr_t eip_fn_addr; // Address of start of function
int eip_fn_narg; // Number of function arguments
};
可以看到由当前eip能够得到这么多调试信息。其中有些已实现,而得到行号要我们来完成。
按照kdebug.c的写法,查找各个变量的含义,结合上下文,最后得到如下写法。
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline <= rline) {
info->eip_line = stabs[lline].n_desc;
}
else return -1;
kdebug.c的注释里面提示已经很详细了。n_desc就是我们要的行号。
4. 补全mon_backtrace函数的功能,使得其能够得到像题目中那样的输出
有了kdebug的补全提示,这个写起来就很容易了。
int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
uint32_t *ebp;
struct Eipdebuginfo tinfo;
ebp = (uint32_t *)read_ebp();
cprintf("Stack backtrace:\r\n");
while (ebp)
{
cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\r\n",
ebp, ebp[1], ebp[2], ebp[3], ebp[4], ebp[5], ebp[6]);
int res = debuginfo_eip(ebp[1], &tinfo); //因为原型里指定的是指针,所以此处取地址
if(res != 0) cprintf("Error!\n");
else {
cprintf("\t\t%s:%d: %.*s+%u\n", tinfo.eip_file, tinfo.eip_line, tinfo.eip_fn_namelen, tinfo.eip_fn_name, ebp[1] - tinfo.eip_fn_addr);
}
ebp = (uint32_t *)*ebp;
}
return 0;
}
由于debuginfo_eip写的是如果正常运行就返回0,如果出错就返回负数,所以有这个条件判断。其他信息都已经在Eipdebuginfo结构体中包含,上面已说过。
注意输出格式符 %.*s,表示这个位置要对应两个变量,第一个是一个整型数,表示最大字符串位数,第二个是字符串。如果字符串长度超过位数限制,就只输出位数规定的字符数。
最后使用了减法,ebp[1] - tinfo.eip_fn_addr,因为这个位置输出的是在文件中的行号,所以要用当前的eip指向的位置减去所在函数的起始地址才可以。
为了得到运行结果看是否正确,要在适当的位置加断点。
之前的kernel.asm
test_backtrace(5);
f01000c8: c7 04 24 05 00 00 00 movl $0x5,(%esp)
f01000cf: e8 6c ff ff ff call f0100040 <test_backtrace>
f01000d4: 83 c4 10 add $0x10,%esp
// Drop into the kernel monitor.
while (1)
monitor(NULL);
f01000d7: 83 ec 0c sub $0xc,%esp
f01000da: 6a 00 push $0x0
f01000dc: e8 37 07 00 00 call f0100818 <monitor>
我们写的函数被test_backtrace调用,运行结果(就是cprintf语句)会表现出正确性,所以尝试在0xf01000d7处加断点。
运行结果如下,可以使用make grade检测一下看看能得多少分。
yichuan@ubuntu:~/6.828/lab$ make grade
make clean
make[1]: Entering directory '/home/yichuan/6.828/lab'
rm -rf obj .gdbinit jos.in qemu.log
make[1]: Leaving directory '/home/yichuan/6.828/lab'
./grade-lab1
make[1]: Entering directory '/home/yichuan/6.828/lab'
+ as kern/entry.S
+ cc kern/entrypgdir.c
sh: echo: I/O error
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc kern/kdebug.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
sh: echo: I/O error
+ ld obj/kern/kernel
ld: warning: section `.bss' type changed to PROGBITS
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 390 bytes (max 510)
+ mk obj/kern/kernel.img
make[1]: Leaving directory '/home/yichuan/6.828/lab'
running JOS: (0.6s)
printf: FAIL
AssertionError: ...
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
qemu: terminating on signal 15 from pid 21790
MISSING '6828 decimal is 15254 octal!'
backtrace count: OK
backtrace arguments: OK
backtrace symbols: OK
backtrace lines: OK
Score: 30/50
GNUmakefile:200: recipe for target 'grade' failed
make: *** [grade] Error 1