• ucorelab15堆栈练习


    该篇文章为知识总结 , 大部分的内容来自 : https://www.cnblogs.com/whileskies/p/13427861.html , 非原创

    题目

    练习5:实现函数调用堆栈跟踪函数 (需要编程)
    我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。在如果能够正确实现此函数,可在lab1中执行 “make qemu”后,在qemu模拟器中得到类似如下的输出:

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

    请完成实验,看看输出是否与上述显示大致一致,并解释最后一行各个数值的含义。

    知识点

        知识点总结来自 : https://www.cnblogs.com/whileskies/p/13427861.html , 非原创 
    

    堆栈是函数运行时的内存空间,由高地址向低地址增长,ebp寄存器存储栈底地址,esp寄存器存储栈顶地址,始终指向栈顶元素;栈从高地址向地址增长。

    入栈指令:push S,相当于R[%esp] = R[%esp] - 4; M[R[%esp]] = S
    出栈指令:pop D,相当于D = M[R[%esp]]; R[%esp] = R[%esp] + 4
    函数调用时主要经过以下步骤:

    调用者:

    • 将被调用函数的参数从右向左依次入栈
    • 执行call命令,将call命令的下一条指令地址,也即返回地址,压栈,同时跳转到被调用函数执行
      被调用函数:
    • pushl %ebp:将调用者的栈底地址入栈,便于返回到调用者继续执行
    • movl %esp, %ebp:%ebp指向当前栈顶,也即与%esp指向相同,此函数的堆栈就此建立
    • 函数执行,临时变量压入堆栈
    • movl %ebp, %esp:%esp指向该函数的栈底
    • popl %ebp:将%ebp栈底指针重新指向调用者函数的栈底地址
    • ret:被调用函数从栈顶弹出返回地址,返回到调用函数继续执行
      堆栈示意图如下所示:
    +|  栈底方向     | 高位地址
     |    ...      |
     |    ...      |
     |  参数3       |
     |  参数2       |
     |  参数1       |
     |  返回地址     |
     |  上一层[ebp]  | <-------- [ebp]
     |  局部变量     |  低位地址
    
    

    上面讲的知识点总结可以结合上面的来说明 .

    1297993-20220109114724144-660974239.png

    1297993-20220109115020494-1266243661.png

    以上为了实际上一直在讲的一个东西就是 : 我们可以通过 ebp 的位移推导出入参返回地址 , 也即是 :

    ss:[ebp]处为上一级函数的ebp地址,ss:[ebp+4]为返回地址;
    ss:[ebp + 8]为函数第一个参数地址,ss:[ebp + 12]为第二个参数地址;
    
    

    解题

    代码

    
    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 (uint32_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]
          *  
          *  这里解释一下为什么需要 eip , 我们的目的是为了打印调用函数的堆栈信息 , 要是知道每一层的 eip 就好了, 我们就可以知道堆栈信息 ,而如何知道
          *  eip 信息的, 一个重要的信息来自 ebp , 为什么? 因为调用函数的时候会把 “返回地址” 压栈 
          * 
          */
        uint32_t ebp = read_ebp();
        uint32_t eip = read_eip();
        // 同样的方法可以读到 esp 
        // uint32_t esp = read_esp();
    
        // for 循环 , 向上打印堆栈信息
        for (int i = 0; i < STACKFRAME_DEPTH; i++) {
            if (ebp == 0) break;
            // 打印这两个值
            cprintf("ebp:0x%08x eip:0x%08x ", ebp, eip);
            // 打印参数 
            cprintf("args:0x%08x 0x%08x 0x%08x 0x%08x", *(uint32_t *)(ebp + 8), 
            cprintf("\n");
            // 这里打印的是该 eip , ebp 对应的函数名称和代码函数 
            print_debuginfo(eip - 1);
    
            // 指针要指向上一层
            eip = *(uint32_t *)(ebp + 4);  // ebp + 4 就是上一次调用的地方,即"返回地址" 
            ebp = *(uint32_t *)(ebp); //  ebp 里面存放的是上一层的 ebp 
        }
    
    }
    
    

    首先通过函数读取ebp、eip寄存器值,分别表示指向栈底的地址、当前指令的地址;

    ss:[ebp + 8]为函数第一个参数地址,ss:[ebp + 12]为第二个参数地址;

    ss:[ebp]处为上一级函数的ebp地址,ss:[ebp+4]为返回地址;

    可通过指针索引的方式访问指针所指内容。

    获取当前的eip值较为巧妙,代码如下:

    static __noinline uint32_t
    read_eip(void) {
        uint32_t eip;
        asm volatile("movl 4(%%ebp), %0" : "=r" (eip));
        return eip;
    }
    

    在调用该函数时会创建相应堆栈,通过创建函数时压入的上一级函数返回地址来间接得到当前的eip。

    参考资料

    看这一个

  • 相关阅读:
    数据库设计步骤 java程序员
    (1) 语法结构 java程序员
    (3)JavaScript学习笔记 函数、对象、数组 java程序员
    JavaScript 学习计划 java程序员
    (5)JavaScript学习笔记 变量 java程序员
    (2) 数据类型、值 及 字符串 java程序员
    (4)JavaScript学习笔记 数据类型和值(续) java程序员
    J2EE、J2SE、J2ME是什么意思? java程序员
    80端口(该端口是Tomcat的监听端口)已经被其他程序占用 java程序员
    (17) string 和 stringbuilder java程序员
  • 原文地址:https://www.cnblogs.com/Benjious/p/16125507.html
Copyright © 2020-2023  润新知