打印函数的相关信息,包括源文件名、行号、函数名、函数内偏移量。
修改mon_backtrace函数
In debuginfo_eip
, where do __STAB_*
come from?
为了回答这个问题,lab给了一些提示:
- 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: bootloader是否将符号表当作内核文件的一部分加载到内存中
提示1
STAB_BEGIN,STAB_END,STABSTR_BEGIN,STABSTR_END
等符号均在 kern/kern.ld
文件定义,它们分别代表 .stab
和 .stabstr
这两个段开始与结束的地址。
提示2
$ objdump -h ./obj/kern/kernel
obj/kern/kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00001ac9 f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000006f4 f0101ae0 00101ae0 00002ae0 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00003cc1 f01021d4 001021d4 000031d4 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 0000195b f0105e95 00105e95 00006e95 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 00009300 f0108000 00108000 00009000 2**12
CONTENTS, ALLOC, LOAD, DATA
猜测:
- STAB_BEGIN = 0xf01021d4, STAB_END = 0xf01021d4 + 0x00003cc1 - 1 = 0xf0105e94
- STABSTR_BEGIN = 0xf0105e95, STABSTR_END = 0xf0105e95 + 0x0000195b -1 = 0xf01077ef
提示3
$ 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 1295 0000195a 1
0 SO 0 0 f0100000 1 {standard input}
1 SOL 0 0 f010000c 18 kern/entry.S
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.
16 LSYM 0 0 00000000 64 int:t(0,1)=r(0,1);-2147483648;2147483647;
17 LSYM 0 0 00000000 106 char:t(0,2)=r(0,2);0;127;
108 FUN 0 0 f0100040 2946 test_backtrace:F(0,25)
118 FUN 0 0 f01000a6 2987 i386_init:F(0,25)
...
...
...
显示了所有的 symbol
提示4
$ gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c
# 查看 init.s 文件
.file "init.c"
.stabs "kern/init.c",100,0,2,.Ltext0
.text
.Ltext0:
.stabs "gcc2_compiled.",60,0,0,0
.stabs "int:t(0,1)=r(0,1);-2147483648;2147483647;",128,0,0,0
.stabs "char:t(0,2)=r(0,2);0;127;",128,0,0,0
.stabs "long int:t(0,3)=r(0,3);-0;4294967295;",128,0,0,0
.stabs "unsigned int:t(0,4)=r(0,4);0;4294967295;",128,0,0,0
.stabs "long unsigned int:t(0,5)=r(0,5);0;-1;",128,0,0,0
.stabs "__int128:t(0,6)=r(0,6);0;-1;",128,0,0,0
.stabs "__int128 unsigned:t(0,7)=r(0,7);0;-1;",128,0,0,0
.stabs "long long int:t(0,8)=r(0,8);-0;4294967295;",128,0,0,0
.stabs "long long unsigned int:t(0,9)=r(0,9);0;-1;",128,0,0,0
.stabs "short int:t(0,10)=r(0,10);-32768;32767;",128,0,0,0
.stabs "short unsigned int:t(0,11)=r(0,11);0;65535;",128,0,0,0
.stabs "signed char:t(0,12)=r(0,12);-128;127;",128,0,0,0
.stabs "unsigned char:t(0,13)=r(0,13);0;255;",128,0,0,0
.stabs "float:t(0,14)=r(0,1);4;0;",128,0,0,0
.stabs "double:t(0,15)=r(0,1);8;0;",128,0,0,0
.stabs "long double:t(0,16)=r(0,1);16;0;",128,0,0,0
.stabs "_Float32:t(0,17)=r(0,1);4;0;",128,0,0,0
.stabs "_Float64:t(0,18)=r(0,1);8;0;",128,0,0,0
.stabs "_Float128:t(0,19)=r(0,1);16;0;",128,0,0,0
.stabs "_Float32x:t(0,20)=r(0,1);8;0;",128,0,0,0
.stabs "_Float64x:t(0,21)=r(0,1);16;0;",128,0,0,0
.stabs "_Decimal32:t(0,22)=r(0,1);4;0;",128,0,0,0
.stabs "_Decimal64:t(0,23)=r(0,1);8;0;",128,0,0,0
.stabs "_Decimal128:t(0,24)=r(0,1);16;0;",128,0,0,0
.stabs "void:t(0,25)=(0,25)",128,0,0,0
.stabs "./inc/stdio.h",130,0,0,0
.stabs "./inc/stdarg.h",130,0,0,0
.stabs "va_list:t(2,1)=(2,2)=(2,3)=ar(2,4)=r(2,4);0;-1;;0;0;(2,5)=xs__va_list_tag:",128,0,0,0
.stabn 162,0,0,0
.stabn 162,0,0,0
.stabs "./inc/string.h",130,0,0,0
.stabs "./inc/types.h",130,0,0,0
.stabs "bool:t(4,1)=(4,2)=eFalse:0,True:1,;",128,0,0,0
.stabs " :T(4,3)=efalse:0,true:1,;",128,0,0,0
.stabs "int8_t:t(4,4)=(0,12)",128,0,0,0
.stabs "uint8_t:t(4,5)=(0,13)",128,0,0,0
.stabs "int16_t:t(4,6)=(0,10)",128,0,0,0
.stabs "uint16_t:t(4,7)=(0,11)",128,0,0,0
.stabs "int32_t:t(4,8)=(0,1)",128,0,0,0
.stabs "uint32_t:t(4,9)=(0,4)",128,0,0,0
.stabs "int64_t:t(4,10)=(0,8)",128,0,0,0
.stabs "uint64_t:t(4,11)=(0,9)",128,0,0,0
.stabs "intptr_t:t(4,12)=(4,8)",128,0,0,0
.stabs "uintptr_t:t(4,13)=(4,9)",128,0,0,0
.stabs "physaddr_t:t(4,14)=(4,9)",128,0,0,0
.stabs "ppn_t:t(4,15)=(4,9)",128,0,0,0
.stabs "size_t:t(4,16)=(4,9)",128,0,0,0
.stabs "ssize_t:t(4,17)=(4,8)",128,0,0,0
.stabs "off_t:t(4,18)=(4,8)",128,0,0,0
.stabn 162,0,0,0
.stabn 162,0,0,0
.section .rodata.str1.1,"aMS",@progbits,1
提示5
确认boot loader在加载内核时是否把符号表也加载到内存中。怎么确认呢?使用gdb
查看符号表的位置是否存储有符号信息就知道啦。首先,根据第2步的输出结果我们知道 .stabstr
段的加载内存地址为 0xf0105e95
,使用x/8s 0xf0105e95
打印前8个字符串信息,结果如下所示。可见加载内核时符号表也一起加载到内存中了。
(gdb) x/8s 0xf0105e95
0xf0105e95: ""
0xf0105e96: "{standard input}"
0xf0105ea7: "kern/entry.S"
0xf0105eb4: "kern/entrypgdir.c"
0xf0105ec6: "gcc2_compiled."
0xf0105ed5: "int:t(0,1)=r(0,1);-2147483648;2147483647;"
0xf0105eff: "char:t(0,2)=r(0,2);0;127;"
0xf0105f19: "long int:t(0,3)=r(0,3);-2147483648;2147483647;"
1. Complete the implementation of debuginfo_eip
by inserting the call to stab_binsearch
to find the line number for an address.
通过调用 stab_binsearch
来完善 debuginfo_eip
,使其能够寻找某地址的行号
Symnum n_type n_othr n_desc n_value n_strx String
118 FUN 0 0 f01000a6 2987 i386_init:F(0,25)
119 SLINE 0 24 00000000 0
120 SLINE 0 30 00000012 0
121 SLINE 0 34 00000029 0
122 SLINE 0 36 0000002e 0
123 SLINE 0 39 00000042 0
124 SLINE 0 43 00000051 0
每列的含义:
- Symnum 是符号索引,即将整个符号表当作数组,Symnum是具体某符号的下标索引值。
- n_type 是符号类型,FUN值函数名,SLINE指text段中的行号
- n_othr 目前没被使用
- n_desc 表示在源文件中的行号
- n_value 表示地址。注意,这里只有FUN类型的符号的地址是绝对地址,SLINE符号的地址是偏移量,其实际地址为函数入口地址加上偏移量。比如第3行的含义是地址f01000b8(=0xf01000a6+0x00000012)对应源文件第34行。
// kern/kdebug.c
// 由于前面的代码已经找到地址在哪个函数里面以及函数入口地址,将原地址减去函数入口地址即可得到偏移量,再根据偏移量在符号表中的指定区间查找对应的记录即可。
// 这里贴出stab_binsearch的原型
static void
stab_binsearch(const struct Stab *stabs, int *region_left, int *region_right,
int type, uintptr_t addr);
// 要自己填充的代码如下(这段基本是抄参考资料的,也不确定正确性,太难了):
// Your code here.
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr - info->eip_fn_addr);
if (lline <= rline)
{
info->eip_line = stabs[lline].n_desc;
} else {
return -1;
}
2. 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
给内核模拟监视器增加 backtrace
功能,调用 debuginfo_eip
完善 mon_backtrace
,使其在每一行打印一个栈帧,呈现如下效果:
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>
3. 优化跟踪的输出
模仿目前已有的命令
目前已经有 help, kernelinfo
两种命令,模仿他们加命令
static struct Command commands[] = {
{ "help", "Display this list of commands", mon_help },
{ "kerninfo", "Display information about the kernel", mon_kerninfo },
{ "backtrace", "Display a backtrace of the function stack", mon_backtrace },
};
完善mon_backtrace
在 mon_backtrace
中调用 debuginfo_eip
来获取文件名、函数名和行号即可。注意,返回的 Eipdebuginfo
结构体的 eip_fn_name
字段除了函数名外还有一段尾巴,比如test_backtrace:F(0,25)
,需要将”:F(0,25)”去掉,可以使用printf("%.*s", length, string)
来实现。
int mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
uint32_t *ebp;
struct Eipdebuginfo info;
int result;
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]);
memset(&info, 0, sizeof(struct Eipdebuginfo));
result = debuginfo_eip(ebp[1], &info);
if (0 != result)
{
cprintf("failed to get debuginfo for eip %x.\r\n", ebp[1]);
}
else
{
cprintf("\t%s:%d: %.*s+%u\r\n", info.eip_file, info.eip_line, info.eip_fn_namelen, info.eip_fn_name, ebp[1] - info.eip_fn_addr);
}
ebp = (uint32_t *)*ebp;
}
return 0;
}
运行结果
$ make qemu-nox-gdb
# 运行程序 mon_backtrace 子程序的输出结果:
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
Stack backtrace:
ebp f010ff18 eip f0100078 args 00000000 00000000 00000000 f010004a f0111308
kern/init.c:16: test_backtrace+56
ebp f010ff38 eip f01000a1 args 00000000 00000001 f010ff78 f010004a f0111308
kern/init.c:16: test_backtrace+97
ebp f010ff58 eip f01000a1 args 00000001 00000002 f010ff98 f010004a f0111308
kern/init.c:16: test_backtrace+97
ebp f010ff78 eip f01000a1 args 00000002 00000003 f010ffb8 f010004a f0111308
kern/init.c:16: test_backtrace+97
ebp f010ff98 eip f01000a1 args 00000003 00000004 00000000 f010004a f0111308
kern/init.c:16: test_backtrace+97
ebp f010ffb8 eip f01000a1 args 00000004 00000005 00000000 f010004a f0111308
kern/init.c:16: test_backtrace+97
ebp f010ffd8 eip f01000f4 args 00000005 00001aac 00000640 00000000 00000000
kern/init.c:43: i386_init+78
ebp f010fff8 eip f010003e args 00000003 00001003 00002003 00003003 00004003
{standard input}:0: <unknown>+0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
# 继续运行
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
# 输入backtrace来跟踪调用关系
K> backtrace
Stack backtrace:
ebp f010ff58 eip f0100aa3 args 00000001 f010ff80 00000000 f0100b07 f0100ab6
kern/monitor.c:151: monitor+332
ebp f010ffd8 eip f0100101 args 00000000 00001aac 00000640 00000000 00000000
kern/init.c:43: i386_init+91
ebp f010fff8 eip f010003e args 00000003 00001003 00002003 00003003 00004003
{standard input}:0: <unknown>+0
K>