zoukankan      html  css  js  c++  java
  • gdb如何调用被调试任务的特定函数

    一、gdb中函数调用
    在gdb中,可以通过
    call function(args……)
    来调用一个函数,当然也可以使用print之类的函数来间接的调用一个函数,但是不管如何,它们最终都要求gdb来调用一个函数,执行该函数,取函数返回值等基本逻辑处理。现在想一下gdb是如何让被调试进程执行特定函数的,这里包含了参数的传递,返回值的提取,并且最为重要的是要保证只执行这个函数(也就是函数返回之后它如何收场)。特别是最后一个,函数如何结束的问题是一个比较棘手的问题,因为一个函数的返回点可能并不唯一,gdb没有办法在函数的最后加一个断点来等待执行完成,如果gdb不能在函数执行结束之后及时断住被调试任务,可能造成不可预知的灾难性后果。
    二、gdb实现
    gdb中关于这个函数的实现位于
    gdb-6.0gdbinfcall.c
    文件中的
    call_function_by_hand (struct value *function, int nargs, struct value **args)
    函数,具体函数细节我们就不详细分析了(当然不是因为我不懂),这里只是大致说一下gdb是如何解决上面说的问题的。
    1、堆栈及参数如何确定
    对于函数来说,它首先要有一个堆栈,这个栈不仅包含了它自己使用的函数内堆栈,还要有自己的参数以及返回地址等信息,这些一般是由调用者代劳完成的,这是函数调用的规则。现在是gdb人工培育的一个函数调用,所以这个工作就理所当然的要由gdb来完成这个参数列表以及返回地址等函数使用的上下文的设置。这一点并不复杂,因为每种体系结构有自己的函数调用约定,例如,对于386来说,在进入函数之前,esp指向返回地址,esp+4为第一个参数,esp+8为第二个参数一次类推,当然有些比较特殊的调用需要在编译时声明,例如一些使用寄存器传递参数的函数调用,这些都会在可执行文件对应的调试信息中有保存,所以也不是什么难题,就像协议一样,照着协议的内容来做就不会有问题,因为关键就是一致嘛。
    那么这个堆栈在哪里开呢?最为直观和简单的就是在当前调试的任务(线程)使用的堆栈栈顶之上开栈,这一点您也别大惊小怪,因为在Linux下的信号处理函数就是这么搞的,它就是使用了被中断线程的堆栈顶端的堆栈,所以这里gdb在被调试任务的栈顶建立自己的堆栈也是有情可原的,而且是最为直接的一种实现,这里不是高潮。
    2、如何优雅的执行并只执行这一个函数
    难点依然在于第一节中说的那个,函数执行完之后如何收场?在上面提到过,gdb要为函数设置参数和返回地址,不论函数内部有几个返回点,它返回之后总是会跳转到调用者给它提供的一个地址上来,所以我们可以在这个返回值上做文章,让它返回到一个特定的地址,然后我们在这个地址上打断点,从而可以知道该函数执行结束。使用gdb的断点功能,断点要设置到代码段中,即使设置到其它地方,我们如何保证这个断点不是其它线程正常路径下的一个函数调用而是我们手动调用函数的实现呢?所以这个返回地址的选择就非常重要,大家可以自己想一下设置到哪里。当然我是看了代码知道它,它是设置在了整个用户态程序的入口处,也就是从内核态到达用户态之后执行的第一条指令的地址,或者说这个程序的入口位置,这个位置在程序运行起来之后只会被被运行一次,而不会被其它任何的任务再调动,因为它是最为原始的入口,如果有人执行到这里,那么整个程序都要重新执行一次,所以这个是最为合适的一个位置。那么gdb如何知道这个位置呢?在elf文件中,在ELF文件的头部保存了这个文件的入口,我们随便找一个可执行文件,看一下它的入口
    [tsecer@Harry localstatic]$ readelf -h  /bin/cat 
    ELF Header:
      Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              EXEC (Executable file)
      Machine:                           Intel 80386
      Version:                           0x1
      Entry point address:               0x80490f0
    这里就是这个入口地址,gdb源代码中对于该地址的读取代码为
    #define CALL_DUMMY_ADDRESS() (entry_point_address ())
    /* Get current entry point address.  */

    CORE_ADDR
    entry_point_address (void)
    {
      return symfile_objfile ? symfile_objfile->ei.entry_point : 0;
    }
    三、验证gdb实现
    [tsecer@Harry gdbentry]$ cat gdbentry.c
    #include <stdlib.h>
    #include <stdio.h>
    void dumpmem(int * start, int len)/*显示从start开始的len个整数*/
    {
    int i ;
    for(i =0 ; i < len ; i++)
    {
        if(!(i&0x3))
            printf(" %#-08x: ",start+i);
        printf("%08x ",start[i]);
    }
    printf(" ");
    }
    int handy(int * addr,int naptime)
    {
    int* myesp;
    __asm__ __volatile__ (
    "movl %%esp,%0 "
    :"=&r"(myesp)
    );/*该汇编指令将esp地址放入myesp中,供下面打印堆栈内容*/
    printf("Dumping start from esp: ");/*这里显示gdb调动的堆栈内容*/
    dumpmem(myesp,16);
    printf("Dumping entry from %#x is %#x end ",addr,addr[0]);/*显示整个可执行文件的入口处内容,修改为断点指令0xcc*/
    dumpmem(addr,1);
    printf("enddumping");
    sleep(naptime);
    }
    int main()
    {
    sleep(1000000);
    }
    [tsecer@Harry gdbentry]$ gcc -g -o gdbentry.c.exe gdbentry.c 
    [tsecer@Harry gdbentry]$ readelf -h gdbentry.c.exe 
    ELF Header:
      Magic:   7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - Linux
      ABI Version:                       0
      Type:                              EXEC (Executable file)
      Machine:                           Intel 80386
      Version:                           0x1
      Entry point address:               0x80483a0 整个可执行文件的入口地址,这将会作为手动调用函数的返回地址
      Start of program headers:          52 (bytes into file)
      Start of section headers:          3780 (bytes into file)
      Flags:                             0x0
      Size of this header:               52 (bytes)
      Size of program headers:           32 (bytes)
      Number of program headers:         8
      Size of section headers:           40 (bytes)
      Number of section headers:         38
      Section header string table index: 35
    [tsecer@Harry gdbentry]$ gdb gdbentry.c.exe 
    GNU gdb (GDB) Fedora (7.0-3.fc12)
    Copyright (C) 2009 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
    and "show warranty" for details.
    This GDB was configured as "i686-redhat-linux-gnu".
    For bug reporting instructions, please see:
    <http://www.gnu.org/software/gdb/bugs/>...
    Reading symbols from /home/tsecer/CodeTest/gdbentry/gdbentry.c.exe...done.
    (gdb) b main
    Breakpoint 1 at 0x804853c: file gdbentry.c, line 30.
    (gdb) r
    Starting program: /home/tsecer/CodeTest/gdbentry/gdbentry.c.exe 

    Breakpoint 1, main () at gdbentry.c:30
    30    sleep(1000000);
    Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686
    (gdb) call hand
    handle_amd    handle_intel  handy         
    (gdb) call handy(0x80483a0,2) 手动调用handy函数,该函数会显示函数入口处内容,可以看到,其中入口处已经被修改为intel的调试中断指令0xcc(int 3)
    Dumping start from esp:

    0xbffff2b8:    bffff2b8    00000010    bffff2e8    080497ec    
    0xbffff2c8:    bffff2d8    08048320    bffff318    bffff2b8    
    0xbffff2d8:    bffff308    08048579    bffff2e4    080483a0    
    0xbffff2e8:    080483a0    00000002    08048560    080483a0    
    Dumping entry from 0x80483a0 is 0x895eedcc end

    0x80483a0:    895eedcc    
    $1 = 0
    (gdb) x/x 0x80483a0
    0x80483a0 <_start>:    0x895eed31函数执行结束之后,这里可以看到此处恢复为代码段的原始值
    (gdb) 
    四、为什么不是用/proc/pid/mem来看而是由handy来显示?
    因为内核中限制了对进程mem文件的读取非常苛刻,只有进程自己或者它的调试进程可以读取,普通进程即使是root也不能读取其它进程的mem文件
    linux-2.6.21fsprocase.c
    static ssize_t mem_read(struct file * file, char __user * buf,
                size_t count, loff_t *ppos)
    ……
        if (!MAY_PTRACE(task) || !ptrace_may_attach(task))
            goto out;
    #define MAY_PTRACE(task)
        (task == current ||
        (task->parent == current &&
        (task->ptrace & PT_PTRACED) &&
         (task->state == TASK_STOPPED || task->state == TASK_TRACED) &&
         security_ptrace(current,task) == 0))

  • 相关阅读:
    ABAP接口用法
    监听textarea数值变化
    The first step in solving any problem is recognizing there is one.
    Wrinkles should merely indicate where smiles have been.
    God made relatives.Thank God we can choose our friends.
    Home is where your heart is
    ABAP跳转屏幕
    Python 工具包 werkzeug 初探
    atom通过remote ftp同步本地文件到远程主机的方法
    Mongodb学习笔记一
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486224.html
Copyright © 2011-2022 走看看