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

一、被调试任务所有so文件如何枚举
在前一篇博客中,大致说明了gdb是通过一个动态库提供的回调函数(_dl_debug_state)处埋伏断点,然后通过约定好的_r_debug全局变量来得到exe程序对应的link_map,然后以该结构为队列头来遍历被调试任务中所有的so文件。当时也说了这个地方比较模糊,只是说了一个思路,所以这里再试图把这个实现相对详细的描述一下。
二、定义被调试任务(debuggee)的link_map地址
同样是在gdb-6.5\gdb\solib-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.7\elf\rtld.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.7\elf\link.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.7\elf\dl-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库,它包含了很多对线程库调试相关的内容。

原文地址:https://www.cnblogs.com/tsecer/p/10486357.html

时间: 2024-11-09 01:49:35

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

GDB动态库搜索路径

当GDB无法显示so动态库的信息或者显示信息有误时,通常是由于库搜索路径错误导致的,可使用set sysroot.set solib-absolute-prefix.set solib-search-path来指定库搜索路径. 1. set sysroot 与 set solib-absolute-prefix 是同一条命令,实际上,set sysroot是set solib-absolute-prefix 的别名. 2. set solib-search-path设置动态库的搜索路径,该命令可

动态库注入--远程线程

现在一般很少有人使用远程线程这种方法来注入动态库或者ShellCode,一般就用来作为一种注入方法来学习. 实现流程 a根据进程ID得到进程句柄. b在kernel32.dll中得到LoadLibrary()函数的地址. c在目标进程中申请内存,并写入动态库的地址(作为LoadLibrary函数的参数). d调用CreateRemoteThread()创建远程线程. CreateRemoteThread(ProcessHandle,None,0,LoadlibraryAddr,DLL_PATH_

【转】分析Linux和windows动态库

原文地址:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html 摘要:动态链接库技术实现和设计程序常用的技术,在Windows和Linux系 统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理.但不同操作系统的动态库由 于格式不同,在需要不同操作系统调用时需要进行动态库程序移植.本文分析和比较了两种操作系统动态库技术,并给出了将Visual C++编制的动态库移植到Lin

linux成长之路(gcc编译器、静态库、动态库)

Jeremy Lin GCC简介 GCC(GNU Complier Collection)是GNU推出的功能强大.性能优越的多平台编译器套件,它包括了C.C++.Objective-C.Fortran.Java.Ada和Go语言的前端,也包括了这些语言的库,当前最新的版本是GCC 5.1.GCC可以在多种硬件平台上编译出可执行程序,其执行效率与一般的编译器相比平均效率要高20%-30%.GCC编译器能将C.C++语言源程序.汇程式程序和目标程序编译.连接成可执行文件,如果没有给出可执行文件的名字

Android WebView加载Chromium动态库的过程分析

Chromium动态库的体积比较大,有27M左右,其中程序段和数据段分别占据25.65M和1.35M.如果按照通常方式加载Chromium动态库,那么当有N个正在运行的App使用WebView时,系统需要为Chromium动态库分配的内存为(25.65 + N x 1.35)M.这是非常可观的.为此,Android使用了特殊的方式加载Chromium动态库.本文接下来就详细分析这种特殊的加载方式. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 为什么当有

Linux和windows动态库

转载:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html 态链接库技术实现和设计程序常用的技术,在Windows和Linux系 统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理.但不同操作系统的动态库由 于格式不同,在需要不同操作系统调用时需要进行动态库程序移植.本文分析和比较了两种操作系统动态库技术,并给出了将Visual C++编制的动态库移植到Linux上的方法

C/C++ (函数、变量和类)动态库的创建、导出和使用(图文+示例代码)

 一 Windows库 1引入库的原因: a.项目的复杂程度大 b.提高代码的利益利用率 2库的分类 2.1静态库: *.lib,不能被加载的程序,可以理解为目标程序的归档. 2.2动态库:*.dll,可以被应用程序加载的程序. 二 动态库 1动态库优点 1.1可以提供模块化的方式,方便协调开发(对于大项目,每个人写的东西编译为动态库,直接链接即可) 1.2对源代码保护 1.3减小可执行文件大小 1.4提高代码重用率 2动态库的基本使用方法 2.1动态库的创建 2.2加载动态库 2.3获取并

Linux动态库相关知识整理

动态库和静态库在C/C++开发中很常见,相比静态库直接被编译到可执行程序, 动态库运行时加载使得可执行程序的体积更小,更新动态库可以不用重新编译可执 行程序等诸多好处.作者是一个Linux后台开发,这些知识经常用到,所以 整理了一下这方面的知识.静态库相对简单,本文只关心Linux平台下的动态库. 创建动态库 这里我把一个短小却很有用的哈希函数编译成动态库做为示例,ELFhash用于对字符串做哈希,返回一个无符号整数. //elfhash.h #include <stdio.h> unsign

Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf 动态库的后缀为*.so 静态库的后缀为 libxxx.a ldconfig 目录名

Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf  动态库的后缀为*.so  静态库的后缀为 libxxx.a   ldconfig   目录名 转载自:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在这之前,我们需要了解一下源代码到可执行程序之间到底发生了什么神奇而美妙的事情. 在Linux操作系统中,普遍使用ELF格