zoukankan      html  css  js  c++  java
  • gdb动态库延迟断点及线程/进程创建相关事件处理(下)

    一、被调试任务所有so文件如何枚举
    在前一篇博客中,大致说明了gdb是通过一个动态库提供的回调函数(_dl_debug_state)处埋伏断点,然后通过约定好的_r_debug全局变量来得到exe程序对应的link_map,然后以该结构为队列头来遍历被调试任务中所有的so文件。当时也说了这个地方比较模糊,只是说了一个思路,所以这里再试图把这个实现相对详细的描述一下。
    二、定义被调试任务(debuggee)的link_map地址
    同样是在gdb-6.5gdbsolib-svr4.c文件中,其中包含了专门用来定位这个文件位置的函数:
    static CORE_ADDR
    elf_locate_base (void)
    {
      struct bfd_section *dyninfo_sect;
      int dyninfo_sect_size;
      CORE_ADDR dyninfo_addr;
      gdb_byte *buf;
      gdb_byte *bufend;
      int arch_size;

      /* Find the start address of the .dynamic section.  */
      dyninfo_sect = bfd_get_section_by_name (exec_bfd, ".dynamic");通过名字找到被调试程序的动态库节(节名为.dynamic)
      if (dyninfo_sect == NULL)
        return 0;
      dyninfo_addr = bfd_section_vma (exec_bfd, dyninfo_sect);找到该节被加载入内存之后的地址,这是一个动态地址

      /* Read in .dynamic section, silently ignore errors.  */
      dyninfo_sect_size = bfd_section_size (exec_bfd, dyninfo_sect);动态节大小
      buf = alloca (dyninfo_sect_size);
      if (target_read_memory (dyninfo_addr, buf, dyninfo_sect_size))将动态节所有内容读入调试器内存中
        return 0;

      /* Find the DT_DEBUG entry in the the .dynamic section.
         For mips elf we look for DT_MIPS_RLD_MAP, mips elf apparently has
         no DT_DEBUG entries.  */

      arch_size = bfd_get_arch_size (exec_bfd);
      if (arch_size == -1)    /* failure */
        return 0;

      if (arch_size == 32)  32bits系统处理。
        { /* 32-bit elf */
          for (bufend = buf + dyninfo_sect_size;
           buf < bufend;
           buf += sizeof (Elf32_External_Dyn))遍历动态节中的每个tag。
        {
          Elf32_External_Dyn *x_dynp = (Elf32_External_Dyn *) buf;
          long dyn_tag;
          CORE_ADDR dyn_ptr;

          dyn_tag = bfd_h_get_32 (exec_bfd, (bfd_byte *) x_dynp->d_tag);
          if (dyn_tag == DT_NULL)
            break;
          else if (dyn_tag == DT_DEBUG)如果某个tag标识为DT_DEBUG,返回该TAG的值。注意,这个是实现的核心
            {
              dyn_ptr = bfd_h_get_32 (exec_bfd, 
                          (bfd_byte *) x_dynp->d_un.d_ptr);
              return dyn_ptr;
            }

    我们随便找个可执行程序来看一下它的动态节
    [tsecer@Harry linux-2.6.37.1]$ readelf -d `which cat`

    Dynamic section at offset 0xa5e4 contains 24 entries:
      Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
     0x0000000c (INIT)                       0x8048cdc
     0x0000000d (FINI)                       0x805066c
     0x6ffffef5 (GNU_HASH)                   0x804818c
     0x00000005 (STRTAB)                     0x8053da4
     0x00000006 (SYMTAB)                     0x80481cc
     0x0000000a (STRSZ)                      795 (bytes)
     0x0000000b (SYMENT)                     16 (bytes)
     0x00000015 (DEBUG)                      0x0   TAG对应内容为零,因为它是在运行时由动态链接器初始化的
     0x00000003 (PLTGOT)                     0x80536dc
    三、DT_DEBUG何时初始化
    glibc-2.7elf tld.c
    static void
    dl_main (const ElfW(Phdr) *phdr,
         ElfW(Word) phnum,
         ElfW(Addr) *user_entry)
    {
    ……
      /* Initialize _r_debug.  */
      struct r_debug *= _dl_debug_initialize (GL(dl_rtld_map).l_addr,
                            LM_ID_BASE);
    ……
      /* Set up debugging before the debugger is notified for the first time.  */
    #ifdef ELF_MACHINE_DEBUG_SETUP
      /* Some machines (e.g. MIPS) don't use DT_DEBUG in this way.  */
      ELF_MACHINE_DEBUG_SETUP (main_map, r);
      ELF_MACHINE_DEBUG_SETUP (&GL(dl_rtld_map), r);
    #else
      if (main_map->l_info[DT_DEBUG] != NULL)
        /* There is a DT_DEBUG entry in the dynamic section.  Fill it in
           with the run-time address of the r_debug structure  */
        main_map->l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;

      /* Fill in the pointer in the dynamic linker's own dynamic section, in
         case you run gdb on the dynamic linker directly.  */
      if (GL(dl_rtld_map).l_info[DT_DEBUG] != NULL)
        GL(dl_rtld_map).l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;
    #endif
    ……
    }
    所以此时的方法是调试器在主程序(注意:不是动态链接器)的DT_DEBUG节中填充上程序的_r_debug变量的地址。我们看一下找个结构的定义
    glibc-2.7elflink.h
    struct r_debug
      {
        int r_version;        /* Version number for this protocol.  */

        struct link_map *r_map;    /* Head of the chain of loaded objects.  */
    }
    四、动态库布局的一些问题
    [tsecer@Harry linux-2.6.37.1]$ sleep 1234 &
    [1] 17451
    [tsecer@Harry linux-2.6.37.1]$ cat /proc/17451/maps
    001e8000-00206000 r-xp 00000000 fd:00 1280       /lib/ld-2.11.2.so
    00206000-00207000 r--p 0001d000 fd:00 1280       /lib/ld-2.11.2.so  这里横亘一个只读数据区,比较特殊,从何而来
    00207000-00208000 rw-p 0001e000 fd:00 1280       /lib/ld-2.11.2.so
    0020a000-0037c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
    0037c000-0037d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so  这个地方还有一个更惨无人道的不可访问数据区
    0037d000-0037f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
    0037f000-00380000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
    00380000-00383000 rw-p 00000000 00:00 0 
    00bef000-00bf0000 r-xp 00000000 00:00 0          [vdso]
    08048000-0804e000 r-xp 00000000 fd:00 49195      /bin/sleep
    0804e000-0804f000 rw-p 00005000 fd:00 49195      /bin/sleep
    09d16000-09d37000 rw-p 00000000 00:00 0          [heap]
    b7686000-b7886000 r--p 00000000 fd:00 100518     /usr/lib/locale/locale-archive
    b7886000-b7887000 rw-p 00000000 00:00 0 
    b789c000-b789d000 rw-p 00000000 00:00 0 
    bfafc000-bfb11000 rw-p 00000000 00:00 0          [stack]
    [tsecer@Harry linux-2.6.37.1]$ readelf -l /lib/ld-2.11.2.so 

    Elf file type is DYN (Shared object file)
    Entry point 0x1e8850
    There are 7 program headers, starting at offset 52

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x000000 0x001e8000 0x001e8000 0x1d58c 0x1d58c R E 0x1000
      LOAD           0x01dc60 0x00206c60 0x00206c60 0x00bc0 0x00c80 RW  0x1000
      DYNAMIC        0x01defc 0x00206efc 0x00206efc 0x000c8 0x000c8 RW  0x4
      NOTE           0x000114 0x001e8114 0x001e8114 0x00024 0x00024 R   0x4
      GNU_EH_FRAME   0x01aee0 0x00202ee0 0x00202ee0 0x005e4 0x005e4 R   0x4
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
      GNU_RELRO      0x01dc60 0x00206c60 0x00206c60 0x003a0 0x003a0 R   0x1

    [tsecer@Harry linux-2.6.37.1]$ readelf -l /lib/libc-2.11.2.so 

    Elf file type is DYN (Shared object file)
    Entry point 0x220d10
    There are 10 program headers, starting at offset 52

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x0020a034 0x0020a034 0x00140 0x00140 R E 0x4
      INTERP         0x13fc90 0x00349c90 0x00349c90 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /lib/ld-linux.so.2]
      LOAD           0x000000 0x0020a000 0x0020a000 0x171bcc 0x171bcc R E 0x1000
      LOAD           0x1721c0 0x0037d1c0 0x0037d1c0 0x027bc 0x057a8 RW  0x1000

      DYNAMIC        0x173d7c 0x0037ed7c 0x0037ed7c 0x000f8 0x000f8 RW  0x4
      NOTE           0x000174 0x0020a174 0x0020a174 0x00044 0x00044 R   0x4
      TLS            0x1721c0 0x0037d1c0 0x0037d1c0 0x00008 0x00040 R   0x4
      GNU_EH_FRAME   0x13fca4 0x00349ca4 0x00349ca4 0x06d5c 0x06d5c R   0x4
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
      GNU_RELRO      0x1721c0 0x0037d1c0 0x0037d1c0 0x01e40 0x01e40 R   0x1
    1、各个内存区属性设置位置
    glibc-2.7elfdl-load.c
    struct link_map *
    _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
                char *realname, struct link_map *loader, int l_type,
                int mode, void **stack_endp, Lmid_t nsid)
    其中有一个循环,就是处理program header中的各个节,其中代码为

        case PT_LOAD:这里使我们最为常见的两个映射,也就是对应上面“r-xp”对应的代码段,rw-p对应的数据段
          /* A load command tells us to map in part of the file.
             We record the load commands and process them all later.  */
    ……
        case PT_GNU_STACK:
          stack_flags = ph->p_flags;
          break;

        case PT_GNU_RELRO:这里是我们不太常见,但是能够从maps文件中体现出来的RELRO节
          l->l_relro_addr = ph->p_vaddr;
          l->l_relro_size = ph->p_memsz;
          break;
    2、不可访问数据区由来
    0037c000-0037d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so  这个地方还有一个更惨无人道的不可访问数据区
     
    我们看一下glibc的两个DT_LOAD节
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x000000 0x0020a000 0x0020a000 0x171bcc 0x171bcc R E 0x1000
      LOAD           0x1721c0 0x0037d1c0 0x0037d1c0 0x027bc 0x057a8 RW  0x1000
    第一个节结束于0x0020a000 + 0x171bcc=0x37BBCC,第二个节开始于0x0037d1c0 ,前者向上以页面为单位取整(0x1000)为0x37c000,后者向下取整为0x0037d000 ,中间相差了一个页面,然后动态连接器毫不客气的把这个区间设置为了不可访问,对应代码为
    /* Determine whether there is a gap between the last segment
             and this one.  */
          if (nloadcmds > 1 && c[-1].mapend != c->mapstart)
            has_holes = true;
    ……
        if (has_holes)
          /* Change protection on the excess portion to disallow all access;
             the portions we do not remap later will be inaccessible as if
             unallocated.  Then jump into the normal segment-mapping loop to
             handle the portion of the segment past the end of the file
             mapping.  */
          __mprotect ((caddr_t) (l->l_addr + c->mapend),
                  loadcmds[nloadcmds - 1].mapstart - c->mapend,
                  PROT_NONE);
    3、只读数据由来
    void internal_function
    _dl_protect_relro (struct link_map *l)
    {
      ElfW(Addr) start = ((l->l_addr + l->l_relro_addr)
                  & ~(GLRO(dl_pagesize) - 1));
      ElfW(Addr) end = ((l->l_addr + l->l_relro_addr + l->l_relro_size)这里的l_relro_addr和l_relro_size同样是之前对DT_RELRO节的读取,对于libc来说,这个值为0x1721c0 0x0037d1c0 0x0037d1c0 0x01e40 0x01e40 R   0x1,即地址为0x0037d1c0  、大小为0x01e40 
                & ~(GLRO(dl_pagesize) - 1));

      if (start != end
          && __mprotect ((void *) start, end - start, PROT_READ) < 0)
        {
          static const char errstring[] = N_("
    cannot apply additional memory protection after relocation");
          _dl_signal_error (errno, l->l_name, NULL, errstring);
        }
    }
    上面的流程处理比较诡异,其实地址和结束地址都是向下取整,所以对于这只读区间,其保护范围为
    0x0037d1c0向下取整0x0037d000,结束地址37F000,所以这个只读区大小为两个页面,对应内存为

    0037d000-0037f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
    五、和nptl线程库比较
    其实这个so的枚举和线程的枚举有很多类似的地方,之前说的对vfork clone之类的跟踪并不能解决线程枚举问题,因为gdb有时候需要在一个程序运行起来之后 attach到一个线程,在attach之后,它只能逐个枚举线程(而不是靠拦截clone系统调用),它有和动态库相似的模式,只是现在的gdb还没有使用,但是线程库操作始终是一个重要问题,大家可以看一下nptl_db文件夹下实现,好像应该对应的文件为pthread_db库,它包含了很多对线程库调试相关的内容。
     
     
     
     
     
  • 相关阅读:
    Git 使用vi或vim
    git 添加远程仓库后无法push
    windows下使用IIS创建git服务
    NPOI 操作office、word、excel
    delphi 模拟POST提交数据
    git 用远程覆盖本地
    Delphi中MD5实现方法(转)
    Delphi 操作Ini文件
    Spring系列之——spring security
    Spring系列之——使用模板快速搭建springboot项目
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486357.html
Copyright © 2011-2022 走看看