MIT6.828-Lab1-Part3-Exercise12-修改mon_backtrace的代码

 

打印函数的相关信息,包括源文件名、行号、函数名、函数内偏移量。

修改mon_backtrace函数

In debuginfo_eip, where do __STAB_* come from?

为了回答这个问题,lab给了一些提示:

  1. look in the file kern/kernel.ld for __STAB_*
  2. run objdump -h obj/kern/kernel
  3. run objdump -G obj/kern/kernel
  4. 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.
  5. 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

猜测:

  1. STAB_BEGIN = 0xf01021d4, STAB_END = 0xf01021d4 + 0x00003cc1 - 1 = 0xf0105e94
  2. 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,使其能够寻找某地址的行号

关键是要理解`.stabs`段记录了什么信息
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> 

参考资料

  1. cnblog-whl1729
  2. The “stabs” representation of debugging information