精华内容
下载资源
问答
  • 这样在App Project下调试的时候,设置断点会无效,出现“The breakpoint will not currently be hit. No symbols have been loaded for this document”的错误。 究其原因,发现编译出来的DLL文件复制到APP ...

    今天调试了一个下午,终于把这个问题解决了。

    一个Solution下多个project,在DLL project编译出来的Dll文件复制到APP project目录下。这样在App Project下调试的时候,设置断点会无效,出现“The breakpoint will not currently be hit. No symbols have been loaded for this document”的错误。

    究其原因,发现编译出来的DLL文件复制到APP project目录下,调试时会无法定位源CPP文件。后来,将DLL文件不用复制,直接编译到APP Project目录下。问题解决了。

    展开全文
  • 按照我的习惯,在gdb里面设置断点,我喜欢用 b filename:line 的形式(如 b main.c:5),但此时只能显示非动态库里面的函数文件,动态库里只显示函数原型,试了一下用 函数原型:行号的方式设置断点,不好用。...

    一、材料准备:

    1、头文件:function.h

    2、.c文件: function.c  main.c

    function.h: 一个简单的头文件,声明了函数func_a(),func_b()

    #ifndef FUNCTION_H
    #define FUNCTION_H
    
    #include <stdio.h>
    
    void func_a();
    void func_b();
    
    #endif

     

    function.c: func_a(), func_b()的实现

    #include "function.h"
    
    void func_a()
    {
        printf("I am func a! step 1 \n");
        printf("I am func a! step 2 \n");
        printf("I am func a! step 3 \n");
    }
    
    void func_b()
    {
        printf("I am func b! step 1 \n");
        printf("I am func b! step 2 \n");
        printf("I am func b! step 3 \n");
    }

     

    main.c: 主函数调用func_a(), func_b()

     

    #include "function.h"
    
    int main(int argc, char *argv[])
    {
        func_a();
        func_b();
    
        return 0;
    }

    二、将function.c编译成一个动态链接库

    敲命令:gcc -c function.c -fPIC --share -o libfunc.so     //注意,这里没有写 -g 参数,为下文埋下一个伏笔

    此时当前目录下面会生成libfunc.so文件

    三、编译main.c文件

    敲命令:gcc -o main main.c -g -lfunc -L ./  // 这里添加参数 -g

    e.g 这里小小的说明一下,程序编译默认动态库搜索路径是/lib、/usr/lib,这里如果不用 -L参数(搜索非标准的库和头文件的路径)是无法编译成功的

    四、gdb调试

    敲命令: gdb main

    按照我的习惯,在gdb里面设置断点,我喜欢用 b filename:line 的形式(如 b main.c:5),但此时只能显示非动态库里面的函数文件,动态库里只显示函数原型,试了一下用 函数原型:行号的方式设置断点,不好用。

    正确的解决方法:

    在编译动态链接库的时候添加 -g参数

    --------------------------------------------------------------------------------------

    敲命令:重新生成动态链接库并重新编译程序

    gcc -c function.c -fPIC -g --share -o libfunc.so

    gcc -o main main.c -g -lfunc -L ./

    此时用gdb断点就可以显示动态库里面的文件了,在这里是function.c,就可以用 filename:line 的方式设置断点

    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    最后在啰嗦一句,如果想检查动态库编译的时候有没有打上-g 参数有一个好办法:

    敲命令:gdb libname.so //libname.so 指对应库的名字

    如果提示信息显示 Reading symbols from libname.so ... done,表示编译的时候有带-g参数,反之则没有

    展开全文
  • gdb动态库延迟断点及线程/进程创建相关事件处理(上)  2012-04-25 22:57:29| 分类: gdb源代码分析|字号 订阅 一、gdb对共享库符号的支持 当使用gdb调试一些动态链接生成的可执行文件时,我们...

    gdb动态库延迟断点及线程/进程创建相关事件处理(上)  

    2012-04-25 22:57:29|  分类: gdb源代码分析|字号 订阅

    一、gdb对共享库符号的支持
    当使用gdb调试一些动态链接生成的可执行文件时,我们可能会有意或者无意的在一些不存在的函数上打断点,此时gdb并不是提示错误,而是提示是否在之后加载的动态库中添加该断点,也就是pending断点,下面是一个典型的提示:
    (gdb) b noexisting
    Function "noexisting" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) 
    此时我们可以想一下,对于动态加载的共享库文件,它可能是在可执行文件中已经确定好的,例如大家通过ldd可以看到一个可执行文件静态依赖的共享库文件;还有一种是通过dlopen在代码中主动随机打开的一个so文件。
    无论是哪种情况,有一点是相同而确定的,那就是在设置断点的时候,gdb是不知道这个符号的位置。这一点其实也不难,难点是gdb怎么知道一个so文件被加载(包括代码中通过dlopen打开的),并且这个被加载的so文件中包含了pending的断点符号?这里有一个实实在在的限制: gdb必须第一时间知道所有so文件的加载,在该so文件中任何一个函数都没有开始执行的时候就提前打上断点,否则就可能错误唯一的一次执行机会,例如一些so文件中init节的函数。

    大家可以先思考一下gdb将如何实现这个功能,此时建议先理一下思路,不要还没有理解问题本身就开始继续看下面内容。

    二、gdb第一时间感知动态库加载方法
    1、什么样的模式和思路
    这个问题我其实是想了一下,觉得应该有比较巧妙的方法,只是我不知道。但是看了一下gdb的实现,发现此处的方法并没有巧妙之处,但是可以实实在在解决问题。这可能就是做工程和做科学的区别,很多时候,我们要让一个工程联动流畅的运行,中间可以使用协议、妥协、适配、模式等各种方法,最终把一个产品实现,这就是我们的目的。当一个产品实现之后,大家就可以在这个基础上进行优化,扩展,兼容等各种操作,这就是工程。也就是说,实现的方法可能很朴素,但是只要能很好的解决问题,它就是一个好的工程。例如,java,它效率可能没有C++高,但是它便于跨平台、C++虽然没有C那么底层、但是它可以更好的支持大规模项目协作开发,这些都是一些应用和场景决定的一些实现。
    这里说gdb对SO文件加载的第一时间感知并不是自己独立完成的,而是需要动态链接器的支持,甚至是dl库本身的支持,gdb本身可能的确没有自己完成这个功能的能力(不太确定,但是当前的Linux实现是依赖了动态链接库本身),它需要动态库操作本身的支持。这一点对于WIndows系统同样适用,windows系统下对于调试器来说,它可以通过WaitForDebugEvent来获得被调试任务的一些事件,而动态链接库的加载就在这个通知范围内(通知类型为LOAD_DLL_DEBUG_EVENT,可参考gdb-6.0\gdb\win32-nat.c get_child_debug_event)。
    2、linux下实现
    ①、动态链接库本身支持
    _dl_debug_state动态库和调试器约定好的一个接口,这个接口事实上是一个空函数,定义于glibc-2.7\elf\dl-debug.c:
    /*  This function exists solely to have a breakpoint set on it by the
       debugger.  The debugger is supposed to find this function's address by
       examining the r_brk member of struct r_debug, but GDB 4.15 in fact looks
       for this particular symbol name in the PT_INTERP file
    .  */
    void
    _dl_debug_state (void)
    {
    }
    上面的注释已经说明了这个函数的用处,可能有些同学看这个代码的时候没有在意这个空函数,更不要说注释了。它的意思就是说,这个函数单独放在这里就是为了给调试器一个下断点的机会,调试器可以在这个约定好的地方设置断点,在该函数断点命中之后,调试器可以通过搜索_r_debug符号来找到被调试任务主动反映的一些状态。大家可以在glibc中搜索一下对这个_dl_debug_state函数的调用。在调用这个函数之前,C库都会重新的给_r_debug结构赋值。例如glibc-2.7\elf\dl-load.c  _dl_map_object_from_fd
      struct r_debug *r =  _dl_debug_initialize (0, nsid);
    ……
          /* Notify the debugger we have added some objects.  We need to
         call _dl_debug_initialize in a static program in case dynamic
         linking has not been used before.  */
          r->r_state =  RT_ADD;
          _dl_debug_state ();
    而函数就是通过
    struct r_debug *
    internal_function
    _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns)
    {
      struct r_debug *r;

      if (ns == LM_ID_BASE)
        r = & _r_debug; 也就是这个函数返回的就是这个全局的_r_debug变量
      else
        r = &GL(dl_ns)[ns]._ns_debug;
    ……
      return r;
    }
    这种模式在NPTL库中也存在,该库中定义的__nptl_create_event和__nptl_death_event就是为了让调试器方便的打断点,但是当前的gdb并没有使用这个接口功能,这是后话,具体怎么实现本文最后再描述一下。
    ②、gdb对该接口的使用
    gdb-6.0\gdb\solib-svr4.c
    该文件中包含了一些符号信息,其中包含了和外部符号联动的协约式接口
    static char *solib_break_names[] =
    {
      "r_debug_state",
      "_r_debug_state",
      " _dl_debug_state", 这个就是之前和动态库约定好的_dl_debug_state 接口,在该文件初始化的开始就会给该函数打断点
      "rtld_db_dlactivity",
      "_rtld_debug_state",
    ……
      NULL
    };
    在gdb-6.0\gdb\solib-legacy.c  legacy_svr4_fetch_link_map_offsets函数中,其中设置了r_debug、link_map结构之间的一些相对关系及结构信息(实不相瞒,这些结构具体细节有待详细分析,我也没有完全分析完整,只是看个大概)。
    然后在文件初始化函数中会调用gdb-6.0\gdb\solib-svr4.c:enable_break (void)
          /* Now try to set a breakpoint in the dynamic linker.  */
          for (bkpt_namep =  solib_break_names; *bkpt_namep != NULL; bkpt_namep++)  这个数组中就包含了我们之前说的那个_r_debug_state函数
        {
          sym_addr = bfd_lookup_symbol (tmp_bfd, *bkpt_namep);
          if (sym_addr != 0)
            break;
        }
          /* We're done with the temporary bfd.  */
          bfd_close (tmp_bfd);

          if (sym_addr != 0)
        {
           create_solib_event_breakpoint (load_addr + sym_addr); 这个函数实现非常简单,只是简单转发给create_internal_breakpoint (address, bp_shlib_event)函数,注意其中的类型bp_shlib_event,后面将会用到
          return 1;
        }
    ③、当_r_debug_state命中时
    明显地,当使能动态so断点之后,系统并不会在加载一个文件之后就让程序停下来,虽然gdb在其中设置了断点。所以gdb要能够识别这个断点类型并自己默默的消化掉这个断点,然后读取新加载(卸载时删除)文件中的符号表,并判断pending断点是否存在其中,如果存在则使能断点。
    gdb-6.0\gdb\breakpoint.c:bpstat_what (bpstat bs)

    #define  shl BPSTAT_WHAT_CHECK_SHLIBS  这个类型将会决定调试器对新接收事件的处理方式,这里就是BPSTAT_WHAT_CHECK_SHLIBS

     static const enum bpstat_what_main_action
        table[(int) class_last][(int) BPSTAT_WHAT_LAST] =
      {
    /*shlib */
        {shl, shl, shl, shl, shl, shl,  shl, shl, ts, shl, shlr},

        case bp_shlib_event:
          bs_class = shlib_event;
    ……
          current_action = table[(int) bs_class][(int) current_action];

    调试器对之上类型判断的调用位置
    gdb-6.0\gdb\infrun.c:handle_inferior_event (struct execution_control_state *ecs)
        what = bpstat_what (stop_bpstat);
        switch (what.main_action)
          {
          case  BPSTAT_WHAT_CHECK_SHLIBS:
          case BPSTAT_WHAT_CHECK_SHLIBS_RESUME_FROM_HOOK:
    #ifdef SOLIB_ADD
        {
    ……
           SOLIB_ADD (NULL, 0, NULL, auto_solib_add);
    ……
    }  }
    其中的SOLIB_ADD--->>>solib_add--->>>update_solib_list--->>>TARGET_SO_CURRENT_SOS--->>>svr4_current_sos
    其中的svr4_current_sos函数将会遍历被调试任务中所有的so文件链表,对于被调试任务来说,它的所有so文件通过link_map的指针域连接在一起,下面是glibc中结构glibc-2.7\include\link.h
    struct link_map
      {
        /* These first few members are part of the protocol with the debugger.
           This is the same format used in SVR4.  */

        ElfW(Addr) l_addr;        /* Base address shared object is loaded at.  */
        char *l_name;        /* Absolute file name object was found in.  */
        ElfW(Dyn) *l_ld;        /* Dynamic section of the shared object.  */
         struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
    ……
    }
    所以当gdb知道了动态链接库对应的link_map实例,它就可以通过该链表遍历被调试任务的所有link_map,由于每个link_map都和一个加载的so文件对应,所以可以知道被调试任务所有已经加载的动态库。
    ④、读取符号后使能断点
    前面的步骤之后,gdb就可以得到了so文件加载的事件消息,然后读入被调试任务中所有的so文件的符号信息。前面的行为也说明了要忽略此次断点,继续运行。
    在handle_inferior_event--->>>keep_going--->>insert_breakpoints函数中完成对所有断点的使能,如果新加载的so文件中包含了之前的一个pending断点,对于insert_breakpoints函数的调用将会使这个断点生效。
    3、说明
    这里只是描述了一个大致的思路,里面的有些细节可能比较模糊,而且不一定完全准确,但是大致的流程和思路是没有问题的,这一点我还是能够保证的。
    三、进程/线程/系统调用相关事件处理
    新的内核中添加了一些自动调试子进程、枚举系统调用之类的功能,这些功能对动态链接库要求不多,转而依赖内核实现。可以通过
    set follow-fork-mode parent/child 来设置调试跟踪模式,这样对子进程的调试比较方便,因为Unix中进程创建时 fork+exec模式,所以fork之后的代码如果出问题,当前的调试是不好使的。
    还有就是一些catch命令来跟踪系统调用等
    (gdb) show version
    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/>.
    (gdb) help catch
    Set catchpoints to catch events.

    List of catch subcommands:

    catch assert -- Catch failed Ada assertions
    catch catch -- Catch an exception
    catch exception -- Catch Ada exceptions
    catch exec -- Catch calls to exec
    catch fork -- Catch calls to fork
    catch syscall -- Catch system calls by their names and/or numbers  这些功能不清楚是什么版本开始支持的,上面显示我用的是gdb7.0
    catch throw -- Catch an exception
    catch vfork -- Catch calls to vfork

    这些很多都需要内核支持,所以看一下内核实现。
    1、进程、线程创建及删除
    这些主要是通过 ptrace的PTRACE_SETOPTIONS选项来实现的(该文件中还有ptrace_getsiginfo借口,说明子进程信号信息也是容易被父进程获得和修改的)
    linux-2.6.21\kernel\ptrace.c
    static int ptrace_setoptions(struct task_struct *child, long data)
    {
        child->ptrace &= ~PT_TRACE_MASK;

        if (data & PTRACE_O_TRACESYSGOOD)
            child->ptrace |= PT_TRACESYSGOOD;

        if (data & PTRACE_O_TRACEFORK)
            child->ptrace |= PT_TRACE_FORK;

        if (data & PTRACE_O_TRACEVFORK)
            child->ptrace |= PT_TRACE_VFORK;

        if (data & PTRACE_O_TRACECLONE)
            child->ptrace |= PT_TRACE_CLONE;

        if (data & PTRACE_O_TRACEEXEC)
            child->ptrace |= PT_TRACE_EXEC;

        if (data & PTRACE_O_TRACEVFORKDONE)
            child->ptrace |= PT_TRACE_VFORK_DONE;

        if (data & PTRACE_O_TRACEEXIT)
            child->ptrace |= PT_TRACE_EXIT;

        return (data & ~PTRACE_O_MASK) ? -EINVAL : 0;
    }
    在进程fork时:long do_fork(unsigned long clone_flags,……)
        if (unlikely(current->ptrace)) {
            trace =  fork_traceflag (clone_flags);
            if (trace)
                clone_flags |= CLONE_PTRACE;
        }
    ……
            if (unlikely (trace)) {
                current->ptrace_message = nr;
                 ptrace_notify ( (trace << 8) | SIGTRAP);
            }
    由于这篇文章粘贴的代码已经很多了,所以就不再粘贴fork_traceflag和ptrace_notify的实现了,但是大家通过这个名字应该就可以知道这些信息是发送给了父进程。大家注意一下ptrace_notify中返回值,低8bits为SIGTRAP信号,而高8bits为trace类型,这些类型可以为PT_TRACE_VFORK、PT_TRACE_CLONE、PTRACE_EVENT_FORK类型,大家可以在gdb中搜索一下对这些事件的处理位置。其它处理,例如PT_TRACE_EXEC、PT_TRACE_EXIT实现和该实现类似,这里省略,大家搜索一下内核这些关键字即可。
    2、系统调用枚举
    这个主要是在汇编代码中设置跟踪点:
    linux-2.6.21\arch\i386\kernel\entry.S
    syscall_trace_entry:
        movl $-ENOSYS,PT_EAX(%esp)
        movl %esp, %eax
        xorl %edx,%edx
        call do_syscall_trace
      系统调用前调用do_syscall_trace,其中edx参数清零,表示是进入系统调用
        cmpl $0, %eax
        jne resume_userspace        # ret != 0 -> running under PTRACE_SYSEMU,
                        # so must skip actual syscall
        movl PT_ORIG_EAX(%esp), %eax
        cmpl $(nr_syscalls), %eax
        jnae syscall_call
        jmp syscall_exit
    END(syscall_trace_entry)

        # perform syscall exit tracing
        ALIGN
    syscall_exit_work:
        testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl
        jz work_pending
        TRACE_IRQS_ON
        ENABLE_INTERRUPTS(CLBR_ANY)    # could let do_syscall_trace() call
                        # schedule() instead
        movl %esp, %eax
        movl $1, %edx
        call do_syscall_trace
      系统调用退出执行do_syscall_trace,参数为1,表示是推出系统调用
        jmp resume_userspace
    END(syscall_exit_work)

    通知调试器代码
    linux-2.6.21\arch\i386\kernel\ptrace.c
    __attribute__((regparm(3)))
    int do_syscall_trace(struct pt_regs *regs, int entryexit)
    {
        int is_sysemu = test_thread_flag(TIF_SYSCALL_EMU);
        /*
         * With TIF_SYSCALL_EMU set we want to ignore TIF_SINGLESTEP for syscall
         * interception
         */
        int is_singlestep = !is_sysemu && test_thread_flag(TIF_SINGLESTEP);
        int ret = 0;
    ……
        /* the 0x80 provides a way for the tracing parent to distinguish
           between a syscall stop and SIGTRAP delivery */
        /* Note that the debugger could change the result of test_thread_flag!*/
         ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) ? 0x80:0));
    }
    可以看到, 这里并没有区分是系统调用进入还是退出,我想可能是需要调试器自己记录是什么,并且进入和退出不能同时跟踪,PTRACE_CONT之后两者都失效
    linux-2.6.21\arch\i386\kernel\ptrace.c
    long arch_ptrace(struct task_struct *child, long request, long addr, long data)
        case PTRACE_SYSEMU: /* continue and stop at next syscall, which will not be executed */
        case PTRACE_SYSCALL:    /* continue and stop at next (return from) syscall */
        case PTRACE_CONT:    /* restart after signal. */
            ret = -EIO;
            if (!valid_signal(data))
                break;
            if (request == PTRACE_SYSEMU) {
                set_tsk_thread_flag(child, TIF_SYSCALL_EMU);
                clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
            } else if (request == PTRACE_SYSCALL) {
                set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
                clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
            } else {
                 clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
                clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE
    );
            }
    这么看来,watch还是比较耗费CPU的,如果系统调用比较多的话。
    四、和NPTL库比较
    1、线程创建、删除
    glibc-2.7\nptl\events.c
    void
    __nptl_create_event (void)
    {
    }
    hidden_def (__nptl_create_event)

    void
    __nptl_death_event (void)
    {
    }
    hidden_def (__nptl_death_event)
    它们被C库创建和删除线程时调用,调试器同样可以设置此处为断点。
    2、glibc-2.7\nptl\allocatestack.c

    /* List of queued stack frames.  */
    static LIST_HEAD (stack_cache);

    /* List of the stacks in use.  */
    static LIST_HEAD (stack_used);
    struct pthread
    {
    ……
      /*  This descriptor's link on the `stack_used' or `__stack_user' list.  */
      list_t list;
    ……
    }
    所有的线程通过list连接在一起,所以调试器可以动态获得被调试任务的所有线程列表。
    展开全文
  • 当使用gdb调试一些动态链接生成的可执行文件时,我们可能会有意或者无意的在一些不存在的函数上打断点,此时gdb并不是提示错误,而是提示是否在之后加载的动态库中添加该断点,也就是pending断点,下面是一个典型的...
    一、gdb对共享库符号的支持
    
    当使用gdb调试一些动态链接生成的可执行文件时,我们可能会有意或者无意的在一些不存在的函数上打断点,此时gdb并不是提示错误,而是提示是否在之后加载的动态库中添加该断点,也就是pending断点,下面是一个典型的提示:
    (gdb) b noexisting
    Function "noexisting" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) 
    此时我们可以想一下,对于动态加载的共享库文件,它可能是在可执行文件中已经确定好的,例如大家通过ldd可以看到一个可执行文件静态依赖的共享库文件;还有一种是通过dlopen在代码中主动随机打开的一个so文件。(NOTE:ldd看不到动态dlopen的so)
    无论是哪种情况,有一点是相同而确定的,那就是在设置断点的时候,gdb是不知道这个符号的位置。这一点其实也不难,难点是gdb怎么知道一个so文件被加载(包括代码中通过dlopen打开的),并且这个被加载的so文件中包含了pending的断点符号?这里有一个实实在在的限制: gdb必须第一时间知道所有so文件的加载 ,在该so文件中任何一个函数都没有开始执行的时候就提前打上断点,否则就可能错误唯一的一次执行机会,例如一些so文件中init节的函数。

    大家可以先思考一下gdb将如何实现这个功能,此时建议先理一下思路,不要还没有理解问题本身就开始继续看下面内容。

    二、gdb第一时间感知动态库加载方法
    1、什么样的模式和思路
    这个问题我其实是想了一下,觉得应该有比较巧妙的方法,只是我不知道。但是看了一下gdb的实现,发现此处的方法并没有巧妙之处,但是可以实实在在解决问题。这可能就是做工程和做科学的区别,很多时候,我们要让一个工程联动流畅的运行,中间可以使用协议、妥协、适配、模式等各种方法,最终把一个产品实现,这就是我们的目的。当一个产品实现之后,大家就可以在这个基础上进行优化,扩展,兼容等各种操作,这就是工程。也就是说,实现的方法可能很朴素,但是只要能很好的解决问题,它就是一个好的工程。例如,java,它效率可能没有C++高,但是它便于跨平台、C++虽然没有C那么底层、但是它可以更好的支持大规模项目协作开发,这些都是一些应用和场景决定的一些实现。
    这里说gdb对SO文件加载的第一时间感知并不是自己独立完成的,而是需要动态链接器的支持,甚至是dl库本身的支持,gdb本身可能的确没有自己完成这个功能的能力(不太确定,但是当前的Linux实现是依赖了动态链接库本身),它需要动态库操作本身的支持。这一点对于WIndows系统同样适用,windows系统下对于调试器来说,它可以通过WaitForDebugEvent来获得被调试任务的一些事件,而动态链接库的加载就在这个通知范围内(通知类型为LOAD_DLL_DEBUG_EVENT,可参考gdb-6.0\gdb\win32-nat.c get_child_debug_event)。
    2、linux下实现
    ①、动态链接库本身支持
    _dl_debug_state动态库和调试器约定好的一个接口,这个接口事实上是一个空函数,定义于glibc-2.7\elf\dl-debug.c:
    /*  This function exists solely to have a breakpoint set on it by the
       debugger.  The debugger is supposed to find this function's address by
       examining the r_brk member of struct r_debug, but GDB 4.15 in fact looks
       for this particular symbol name in the PT_INTERP file
    .  */
    void
    _dl_debug_state  (void)
    {
    }
    上面的注释已经说明了这个函数的用处,可能有些同学看这个代码的时候没有在意这个空函数,更不要说注释了。它的意思就是说,这个函数单独放在这里就是为了给调试器一个下断点的机会,调试器可以在这个约定好的地方设置断点,在该函数断点命中之后,调试器可以通过搜索_r_debug符号来找到被调试任务主动反映的一些状态。大家可以在glibc中搜索一下对这个_dl_debug_state函数的调用。在调用这个函数之前,C库都会重新的给_r_debug结构赋值。例如glibc-2.7\elf\dl-load.c  _dl_map_object_from_fd
      struct r_debug *r =  _dl_debug_initialize  (0, nsid);
    ……
          /* Notify the debugger we have added some objects.  We need to
         call _dl_debug_initialize in a static program in case dynamic
         linking has not been used before.  */
          r->r_state =  RT_ADD ;
          _dl_debug_state ();
    而函数就是通过
    struct r_debug *
    internal_function
    _dl_debug_initialize  (ElfW(Addr) ldbase, Lmid_t ns)
    {
      struct r_debug *r;

      if (ns == LM_ID_BASE)
        r = & _r_debug ; 也就是这个函数返回的就是这个全局的_r_debug变量
      else
        r = &GL(dl_ns)[ns]._ns_debug;
    ……
      return r;
    }
    这种模式在NPTL库中也存在,该库中定义的__nptl_create_event和__nptl_death_event就是为了让调试器方便的打断点,但是当前的gdb并没有使用这个接口功能,这是后话,具体怎么实现本文最后再描述一下。
    ②、gdb对该接口的使用
    gdb-6.0\gdb\solib-svr4.c
    该文件中包含了一些符号信息,其中包含了和外部符号联动的协约式接口
    static char *solib_break_names[] =
    {
      "r_debug_state",
      "_r_debug_state",
      " _dl_debug_state ", 这个就是之前和动态库约定好的_dl_debug_state 接口,在该文件初始化的开始就会给该函数打断点
      "rtld_db_dlactivity",
      "_rtld_debug_state",
    ……
      NULL
    };
    在gdb-6.0\gdb\solib-legacy.c  legacy_svr4_fetch_link_map_offsets函数中,其中设置了r_debug、link_map结构之间的一些相对关系及结构信息(实不相瞒,这些结构具体细节有待详细分析,我也没有完全分析完整,只是看个大概)。
    然后在文件初始化函数中会调用gdb-6.0\gdb\solib-svr4.c:enable_break (void)
          /* Now try to set a breakpoint in the dynamic linker.  */
          for (bkpt_namep =  solib_break_names ; *bkpt_namep != NULL; bkpt_namep++)  这个数组中就包含了我们之前说的那个_r_debug_state函数
        {
          sym_addr = bfd_lookup_symbol (tmp_bfd, *bkpt_namep);
          if (sym_addr != 0)
            break;
        }
          /* We're done with the temporary bfd.  */
          bfd_close (tmp_bfd);

          if (sym_addr != 0)
        {
           create_solib_event_breakpoint  (load_addr + sym_addr); 这个函数实现非常简单,只是简单转发给create_internal_breakpoint (address, bp_shlib_event)函数,注意其中的类型bp_shlib_event,后面将会用到
          return 1;
        }
    ③、当_r_debug_state命中时
    明显地,当使能动态so断点之后,系统并不会在加载一个文件之后就让程序停下来,虽然gdb在其中设置了断点。所以gdb要能够识别这个断点类型并自己默默的消化掉这个断点,然后读取新加载(卸载时删除)文件中的符号表,并判断pending断点是否存在其中,如果存在则使能断点。
    gdb-6.0\gdb\breakpoint.c:bpstat_what (bpstat bs)

    #define  shl BPSTAT_WHAT_CHECK_SHLIBS   这个类型将会决定调试器对新接收事件的处理方式,这里就是BPSTAT_WHAT_CHECK_SHLIBS

     static const enum bpstat_what_main_action
        table[(int) class_last][(int) BPSTAT_WHAT_LAST] =
      {
    /*shlib */
        {shl, shl, shl, shl, shl, shl,  shl , shl, ts, shl, shlr},

        case bp_shlib_event:
          bs_class = shlib_event;
    ……
          current_action = table[(int) bs_class][(int) current_action];

    调试器对之上类型判断的调用位置
    gdb-6.0\gdb\infrun.c:handle_inferior_event (struct execution_control_state *ecs)
        what = bpstat_what (stop_bpstat);
        switch (what.main_action)
          {
          case  BPSTAT_WHAT_CHECK_SHLIBS :
          case BPSTAT_WHAT_CHECK_SHLIBS_RESUME_FROM_HOOK:
    #ifdef SOLIB_ADD
        {
    ……
           SOLIB_ADD  (NULL, 0, NULL, auto_solib_add);
    ……
    }  }
    其中的SOLIB_ADD--->>>solib_add--->>>update_solib_list--->>>TARGET_SO_CURRENT_SOS--->>>svr4_current_sos
    其中的svr4_current_sos函数将会遍历被调试任务中所有的so文件链表,对于被调试任务来说,它的所有so文件通过link_map的指针域连接在一起,下面是glibc中结构glibc-2.7\include\link.h
    struct link_map
      {
        /* These first few members are part of the protocol with the debugger.
           This is the same format used in SVR4.  */

        ElfW(Addr) l_addr;        /* Base address shared object is loaded at.  */
        char *l_name;        /* Absolute file name object was found in.  */
        ElfW(Dyn) *l_ld;        /* Dynamic section of the shared object.  */
         struct link_map *l_next, *l_prev; /* Chain of loaded objects.   */
    ……
    }
    所以当gdb知道了动态链接库对应的link_map实例,它就可以通过该链表遍历被调试任务的所有link_map,由于每个link_map都和一个加载的so文件对应,所以可以知道被调试任务所有已经加载的动态库。
    ④、读取符号后使能断点
    前面的步骤之后,gdb就可以得到了so文件加载的事件消息,然后读入被调试任务中所有的so文件的符号信息。前面的行为也说明了要忽略此次断点,继续运行。
    在handle_inferior_event--->>>keep_going--->>insert_breakpoints函数中完成对所有断点的使能,如果新加载的so文件中包含了之前的一个pending断点,对于insert_breakpoints函数的调用将会使这个断点生效。
    3、说明
    这里只是描述了一个大致的思路,里面的有些细节可能比较模糊,而且不一定完全准确,但是大致的流程和思路是没有问题的,这一点我还是能够保证的。
    三、进程/线程/系统调用相关事件处理
    新的内核中添加了一些自动调试子进程、枚举系统调用之类的功能,这些功能对动态链接库要求不多,转而依赖内核实现。可以通过
    set follow-fork-mode parent/child 来设置调试跟踪模式,这样对子进程的调试比较方便,因为Unix中进程创建时 fork+exec模式,所以fork之后的代码如果出问题,当前的调试是不好使的。
    还有就是一些catch命令来跟踪系统调用等
    (gdb) show version
    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/>.
    (gdb) help catch
    Set catchpoints to catch events.

    List of catch subcommands:

    catch assert -- Catch failed Ada assertions
    catch catch -- Catch an exception
    catch exception -- Catch Ada exceptions
    catch exec -- Catch calls to exec
    catch fork -- Catch calls to fork
    catch syscall -- Catch system calls by their names and/or numbers   这些功能不清楚是什么版本开始支持的,上面显示我用的是gdb7.0
    catch throw -- Catch an exception
    catch vfork -- Catch calls to vfork

    这些很多都需要内核支持,所以看一下内核实现。
    1、进程、线程创建及删除
    这些主要是通过 ptrace的PTRACE_SETOPTIONS 选项来实现的(该文件中还有ptrace_getsiginfo借口,说明子进程信号信息也是容易被父进程获得和修改的)
    linux-2.6.21\kernel\ptrace.c
    static int ptrace_setoptions(struct task_struct *child, long data)
    {
        child->ptrace &= ~PT_TRACE_MASK;

        if (data & PTRACE_O_TRACESYSGOOD)
            child->ptrace |= PT_TRACESYSGOOD;

        if (data & PTRACE_O_TRACEFORK)
            child->ptrace |= PT_TRACE_FORK;

        if (data & PTRACE_O_TRACEVFORK)
            child->ptrace |= PT_TRACE_VFORK;

        if (data & PTRACE_O_TRACECLONE)
            child->ptrace |= PT_TRACE_CLONE;

        if (data & PTRACE_O_TRACEEXEC)
            child->ptrace |= PT_TRACE_EXEC;

        if (data & PTRACE_O_TRACEVFORKDONE)
            child->ptrace |= PT_TRACE_VFORK_DONE;

        if (data & PTRACE_O_TRACEEXIT)
            child->ptrace |= PT_TRACE_EXIT;

        return (data & ~PTRACE_O_MASK) ? -EINVAL : 0;
    }
    在进程fork时:long do_fork(unsigned long clone_flags,……)
        if (unlikely(current->ptrace)) {
            trace =  fork_traceflag  (clone_flags);
            if (trace)
                clone_flags |= CLONE_PTRACE;
        }
    ……
            if (unlikely (trace)) {
                current->ptrace_message = nr;
                 ptrace_notify  ( (trace << 8) | SIGTRAP );
            }
    由于这篇文章粘贴的代码已经很多了,所以就不再粘贴fork_traceflag和ptrace_notify的实现了,但是大家通过这个名字应该就可以知道这些信息是发送给了父进程。大家注意一下ptrace_notify中返回值,低8bits为SIGTRAP信号,而高8bits为trace类型,这些类型可以为PT_TRACE_VFORK、PT_TRACE_CLONE、PTRACE_EVENT_FORK类型,大家可以在gdb中搜索一下对这些事件的处理位置。其它处理,例如PT_TRACE_EXEC、PT_TRACE_EXIT实现和该实现类似,这里省略,大家搜索一下内核这些关键字即可。
    2、系统调用枚举
    这个主要是在汇编代码中设置跟踪点:
    linux-2.6.21\arch\i386\kernel\entry.S
    syscall_trace_entry:
        movl $-ENOSYS,PT_EAX(%esp)
        movl %esp, %eax
        xorl %edx,%edx
        call do_syscall_trace
      系统调用前调用do_syscall_trace,其中edx参数清零,表示是进入系统调用
        cmpl $0, %eax
        jne resume_userspace        # ret != 0 -> running under PTRACE_SYSEMU,
                        # so must skip actual syscall
        movl PT_ORIG_EAX(%esp), %eax
        cmpl $(nr_syscalls), %eax
        jnae syscall_call
        jmp syscall_exit
    END(syscall_trace_entry)

        # perform syscall exit tracing
        ALIGN
    syscall_exit_work:
        testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl
        jz work_pending
        TRACE_IRQS_ON
        ENABLE_INTERRUPTS(CLBR_ANY)    # could let do_syscall_trace() call
                        # schedule() instead
        movl %esp, %eax
        movl $1, %edx
        call do_syscall_trace
      系统调用退出执行do_syscall_trace,参数为1,表示是推出系统调用
        jmp resume_userspace
    END(syscall_exit_work)

    通知调试器代码
    linux-2.6.21\arch\i386\kernel\ptrace.c
    __attribute__((regparm(3)))
    int do_syscall_trace(struct pt_regs *regs, int entryexit)
    {
        int is_sysemu = test_thread_flag(TIF_SYSCALL_EMU);
        /*
         * With TIF_SYSCALL_EMU set we want to ignore TIF_SINGLESTEP for syscall
         * interception
         */
        int is_singlestep = !is_sysemu && test_thread_flag(TIF_SINGLESTEP);
        int ret = 0;
    ……
        /* the 0x80 provides a way for the tracing parent to distinguish
           between a syscall stop and SIGTRAP delivery */
        /* Note that the debugger could change the result of test_thread_flag!*/
         ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) ? 0x80:0) );
    }
    可以看到, 这里并没有区分是系统调用进入还是退出,我想可能是需要调试器自己记录是什么,并且进入和退出不能同时跟踪,PTRACE_CONT之后两者都失效
    linux-2.6.21\arch\i386\kernel\ptrace.c
    long arch_ptrace(struct task_struct *child, long request, long addr, long data)
        case PTRACE_SYSEMU: /* continue and stop at next syscall, which will not be executed */
        case PTRACE_SYSCALL:    /* continue and stop at next (return from) syscall */
        case PTRACE_CONT:    /* restart after signal. */
            ret = -EIO;
            if (!valid_signal(data))
                break;
            if (request == PTRACE_SYSEMU) {
                set_tsk_thread_flag(child, TIF_SYSCALL_EMU);
                clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
            } else if (request == PTRACE_SYSCALL) {
                set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
                clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
            } else {
                 clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
                clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE
    );
            }
    这么看来,watch还是比较耗费CPU的,如果系统调用比较多的话。
    四、和NPTL库比较
    1、线程创建、删除
    glibc-2.7\nptl\events.c
    void
    __nptl_create_event (void)
    {
    }
    hidden_def (__nptl_create_event)

    void
    __nptl_death_event (void)
    {
    }
    hidden_def (__nptl_death_event)
    它们被C库创建和删除线程时调用,调试器同样可以设置此处为断点。
    2、glibc-2.7\nptl\allocatestack.c

    /* List of queued stack frames.  */
    static LIST_HEAD (stack_cache);

    /* List of the stacks in use.  */
    static LIST_HEAD (stack_used);
    struct pthread
    {
    ……
      /*  This descriptor's link on the `stack_used' or `__stack_user' list .  */
      list_t list;
    ……
    }
    所有的线程通过list连接在一起,所以调试器可以动态获得被调试任务的所有线程列表。
    展开全文
  • 本篇文章来详细地介绍 Delphi 2007中的动态链接DLL断点调试技术。
  • 本篇文章来详细地介绍 Delphi 2010中的动态链接DLL断点调试技术。
  • 本篇文章来详细地介绍 Delphi 7中的动态链接DLL断点调试技术。
  • 由于Delphi 7、2007和 2010在界面上乃至功能上的一些变化,所以在动态链接DLL断点调试上,有较大的变化。在今后几天的时间中,笔者会以三篇文章来分别详细地介绍Delphi 7、2007和 2010这三个版本中的DLL断点调试...
  • DELPHI 2010 动态链接DLL断点调试

    千次阅读 2013-03-04 12:12:49
    DELPHI 2010 动态链接DLL断点调试  马根峰   (广东联合电子服务股份有限公司,广州 510300)   摘要:本文详细介绍了Delphi 2010中的动态链接DLL断点调试技术 关键词:DELPHI 2010;Dll断点调试;   ...
  • 在共享设置断点

    2007-04-12 19:12:00
    From: Debugging a Program With dbx在共享设置断点dbx 对使用连接运行时链接程序的编程接口...利用对 dlopen() 和 dlclose() 的调试支持可步入函数,或在动态共享的函数中以在程序启动时在链接的设置断点
  • GDB(设置断点

    2017-03-10 12:32:48
    Linux编程基础——GDB(设置断点) 启动GDB后,首先就是要设置断点,程序中断后才能调试。在gdb中,断点通常有三种形式: 断点(BreakPoint): 在代码的指定位置中断,这个是我们用得最多的一种。设置断点的...
  • 在对C#调用的C++动态库打断点进行调试时,断点变为黄色感叹号,未进入断点,反复查找原因,重新生成的dll文件已经拷贝到C#相关目录下面,但还是无法进入断点,后来发现重新生成的动态库的pdb文件没有拷贝到C#相关...
  • gdb设置条件断点

    2021-04-11 09:28:16
    比如给某个动态库设置,动态库在执行之前还没有加载,所以有的符号就不合法. 指令: break location if condition 比如:break main if argc > 5只有在入参数量超过五个的时候才会停止. 如果地址或其他的不合法,将会...
  • vs动态库调试

    2019-12-16 21:30:36
    动态库工程中设置如下 然后直接运行动态库就可以了 如果发现运行动态库出现无效的断点了 那么就是因为动态库的代码有了新的改动,导致exe使用的动态库和代码不一致引起的 解决方法: 重新生成动态库,重新...
  • QT直接调试时,发现在动态库中打的断点无效,提示找不到动态库 设置动态库路径即可正常调试 设置位置:选项 ——> 调试器 ——> GDB ——> 额外的启动命令,添加set solib-search-path library_path ,将...
  • Linux编程基础——GDB(设置断点

    千次阅读 2015-08-17 11:56:45
    Linux编程基础——GDB(设置断点)启动GDB后,首先就是要设置断点,程序中断后才能调试。在gdb中,断点通常有三种形式:断点(BreakPoint):在代码的指定位置中断,这个是我们用得最多的一种。设置断点的命令是...
  • 1.无法硬件调试 无法调试,或是调试没反应 2.无法设置断点 能进入调试界面,但是未高亮显示当前程序运行位置。需要打开以下几个选项。 2.无法软件仿真 同1,需要根据芯片选择适合的动态库加载。 ...
  • GDB 断点设置

    2016-01-03 17:27:53
    启动GDB后,首先就是要设置断点,程序中断后才能调试。在gdb中,断点通常有三种形式: 断点(BreakPoint): 在代码的指定位置中断,这个是我们用得最多的一种。设置断点的命令是break,它通常有如下方式: ...
  • 转自:http://www.cnblogs.com/awpatp/archive/2011/01/02/1924085.html 命令 ========== ~0 bp 02sample!...在零号线程上的KBTest类的Fibonacci_stdcall函数上设置断点, 并且在触发断点时执行"r esp
  • VS动态库调试方法

    千次阅读 2018-12-04 16:49:20
    1 将动态库与调用动态库的程序放在同一解决方案下 打开调用动态库的程序,之后在“解决方案资源管理器”中,选中“解决方案...之后,就可以在动态库中下断点进行调试了。 2 将动态库文件拷贝到调用动态库的程序的文...
  • 这种方法适用于想调试android中app所用到的.so的情况...如果直接起gdb载入动态链接的符号表,这时符号地址是不对的。本文中以某app为例,因此具体函数名依实例肯定有所不同。   第一步:Dev Tools -> Developme

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,078
精华内容 12,831
关键字:

动态库设置断点