您的位置:首页 > 其它

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

2012-09-03 07:46 337 查看

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

2012-04-26 21:54:32| 分类:

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 *r = _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库,它包含了很多对线程库调试相关的内容。
http://tsecer.blog.163.com/blog/static/150181720123269133521/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐