gdb動態庫延遲斷點及線程/進程創建相關事件處理(下)
阿新 • • 發佈:2019-03-07
ger ring ack sil port [1] local hash proc 一、被調試任務所有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庫,它包含了很多對線程庫調試相關的內容。
在前一篇博客中,大致說明了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)
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庫,它包含了很多對線程庫調試相關的內容。
gdb動態庫延遲斷點及線程/進程創建相關事件處理(下)