代码调试篇(5):如何调试动态链接器

Author: stormQ

Created: Monday, 08. February 2021 12:54PM

Last Modified: Sunday, 14. February 2021 10:51AM



摘要

本文描述了如何调试运行在normaltrace两种模式下的动态链接器的调试方法。掌握后,一方面有助于分析 glibc 是如何实现共享库的加载、符合解析、重定位等过程的,另一方面有助于分析诸如ldd -u命令的实现原理。


如何调试可执行目标文件的加载过程

本节我们从一个简单的程序——hello开始,研究如何在可执行目标文件运行前将断点设置到动态链接器中,并说明要调试动态链接器源码所需要做的准备工作。

研究过程:

step 1: 准备

1) 准备示例程序,并生成可执行目标文件

示例程序,hello.cpp:

#include <stdio.h>

int main()
{
  printf("hello, world\n");
  return 0;
}

生成可执行目标文件——hello:

$ g++ -o hello hello.cpp

2) 做好调试 glibc 源码所需要的准备

详细步骤见上一篇文章:《代码调试篇(4):如何调试 glibc 源码——准备篇》。

step 2: 启动动态链接器

1) 使用 gdb 启动动态链接器

$ gdb -q /lib64/ld-linux-x86-64.so.2
Reading symbols from /lib64/ld-linux-x86-64.so.2...
(No debugging symbols found in /lib64/ld-linux-x86-64.so.2)

注:/lib64/ld-linux-x86-64.so.2是一个软链接。在笔者的机器上,实际指向/usr/lib64/ld-linux-x86-64.so.2

由于系统中自带的 ld.so 删掉了符号表等调试相关的信息。因此,在用 gdb 调试时,需要我们将其符号表加载进去。否则,无法击中在_dl_start函数处设置的断点。

2) 加载符号表

(gdb) symbol-file /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so
Reading symbols from /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so...

3) 设置源码搜索路径

(gdb) directory /home/glibc-2.31/elf/
Source directories searched: /home/glibc-2.31/elf:$cdir:$cwd

注:/home/glibc-2.31目录存放着我们安装解压后的 glibc 源码,而子目录elf中存放着动态链接器的源码实现。

4) 用动态链接器启动 hello

(gdb) starti ./hello
Starting program: /usr/lib64/ld-linux-x86-64.so.2 ./hello

Program stopped.
0x00007ffff7fd0100 in _start ()

注意: 这里必须用starti而不是start命令。如果使用后者,会直接运行并结束本次调试。

上面输出结果中的_start函数就是动态链接器的入口。

到这里,我们成功地将断点设置在了动态链接器的入口处。

5) 反汇编 _start 函数(optional)

(gdb) disassemble _start
Dump of assembler code for function _start:
=> 0x00007ffff7fd0100 <+0>:    mov    %rsp,%rdi
   0x00007ffff7fd0103 <+3>:    callq  0x7ffff7fd0df0 <_dl_start>
End of assembler dump.

从上面的输出结果中可以看出,_start函数实际调用了_dl_start函数。从而,开始了动态链接器的运作过程。

6) 查看 _start 函数的源码(optional)

_start函数定义在RTLD_START宏中(dl-machine.h),如下:

/* Initial entry point code for the dynamic linker.
   The C function `_dl_start' is the real entry point;
   its return value is the user program's entry point.  */

#define RTLD_START asm ("\n\
.text\n\
    .align 16\n\
.globl _start\n\
.globl _dl_start_user\n\
_start:\n\
    movq %rsp, %rdi\n\
    call _dl_start\n\
_dl_start_user:\n\
    # Save the user entry point address in %r12.\n\
    movq %rax, %r12\n\
    # See if we were run as a command with the executable file\n\
    # name as an extra leading argument.\n\
    movl _dl_skip_args(%rip), %eax\n\
    # Pop the original argument count.\n\
    popq %rdx\n\
    # Adjust the stack pointer to skip _dl_skip_args words.\n\
    leaq (%rsp,%rax,8), %rsp\n\
    # Subtract _dl_skip_args from argc.\n\
    subl %eax, %edx\n\
    # Push argc back on the stack.\n\
    pushq %rdx\n\
    # Call _dl_init (struct link_map *main_map, int argc, char **argv, char **env)\n\
    # argc -> rsi\n\
    movq %rdx, %rsi\n\
    # Save %rsp value in %r13.\n\
    movq %rsp, %r13\n\
    # And align stack for the _dl_init call. \n\
    andq $-16, %rsp\n\
    # _dl_loaded -> rdi\n\
    movq _rtld_local(%rip), %rdi\n\
    # env -> rcx\n\
    leaq 16(%r13,%rdx,8), %rcx\n\
    # argv -> rdx\n\
    leaq 8(%r13), %rdx\n\
    # Clear %rbp to mark outermost frame obviously even for constructors.\n\
    xorl %ebp, %ebp\n\
    # Call the function to run the initializers.\n\
    call _dl_init\n\
    # Pass our finalizer function to the user in %rdx, as per ELF ABI.\n\
    leaq _dl_fini(%rip), %rdx\n\
    # And make sure %rsp points to argc stored on the stack.\n\
    movq %r13, %rsp\n\
    # Jump to the user's entry point.\n\
    jmp *%r12\n\
.previous\n\
");

而上面这段汇编代码正是动态链接器/usr/lib64/ld-linux-x86-64.so.2中的.text节的开始部分,如下:

/lib64/ld-linux-x86-64.so.2:     file format elf64-x86-64


Disassembly of section .text:

0000000000001100 <_dl_rtld_di_serinfo@@GLIBC_PRIVATE-0x9f90>:
    1100:       48 89 e7                mov    %rsp,%rdi
    1103:       e8 e8 000 00          callq  1df0 <_dl_catch_error@plt+0xd00>
    1108:       49 89 c4                mov    %rax,%r12
    110b:       805 e7 c4 02 00       mov    0x2c4e7(%rip),%eax        # 2d5f8 <_dl_catch_error@@GLIBC_PRIVATE+0xf8c8>
    1111:       5a                      pop    %rdx
    1112:       48 8d 24 c4             lea    (%rsp,%rax,8),%rsp
    1116:       29 c2                   sub    %eax,%edx
    1118:       52                      push   %rdx
    1119:       48 89 d6                mov    %rdx,%rsi
    111c:       49 89 e5                mov    %rsp,%r13
    111f:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1123:       48 83d 36 cf 02 00    mov    0x2cf36(%rip),%rdi        # 2e060 <_rtld_global@@GLIBC_PRIVATE>
    112a:       49 8d 4c d5 10          lea    0x10(%r13,%rdx,8),%rcx
    112f:       49 8d 55 08             lea    0x8(%r13),%rdx
    1133:       31 ed                   xor    %ebp,%ebp
    1135:       e8 d6 001 00          callq  11c10 <_dl_rtld_di_serinfo@@GLIBC_PRIVATE+0x6b80>
    113a:       48 8d 15 0f 001 00    lea    0x10c0f(%rip),%rdx        # 11d50 <_dl_rtld_di_serinfo@@GLIBC_PRIVATE+0x6cc0>
    1141:       489 ec                mov    %r13,%rsp
    1144:       41 ff e4                jmpq   *%r12
    1147:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)

这意味着,/usr/lib64/ld-linux-x86-64.so.2.text节的第一条指令即为动态链接器的入口。我们可以查看动态链接器的头部信息,加以佐证,如下:

$ readelf -h /lib64/ld-linux-x86-64.so.2
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1100
  Start of program headers:          64 (bytes into file)
  Start of section headers:          189680 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         11
  Size of section headers:           64 (bytes)
  Number of section headers:         28
  Section header string table index27

从上面的输出结果可以看出,Entry point address(动态链接器的入口地址)的值确实是0x1100.text节的第一条指令的地址)。

7) 在 _dl_start 函数开始处设置断点,并继续执行

(gdb) b _dl_start
Breakpoint 1 at 0x7ffff7fd0df0: file rtld.c, line 463.
(gdb) c
Continuing.

Breakpoint 1, _dl_start (arg=0x7fffffffdea0) at rtld.c:463
warning: Source file is more recent than executable.
463    {
(gdb) n
481      rtld_timer_start (&start_time);

到这里,我们成功地进入了_dl_start函数。从此,我们就可以调试共享库的加载、符号解析、重定位等过程了。

研究结论:

ld.gdb:

file /lib64/ld-linux-x86-64.so.2
set args ./hello

symbol-file /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so
directory /home/glibc-2.31/elf/

b _dl_start
starti
c

执行ld.gdb的过程如下:

$ gdb -q -x ld.gdb
Breakpoint 1 at 0x1df0: file rtld.c, line 463.

Program stopped.
0x00007ffff7fd0100 in _start ()

Breakpoint 1, _dl_start (arg=0x7fffffffde70) at rtld.c:463
warning: Source file is more recent than executable.
463    {
(gdb)

如何调试 ldd -u 命令的执行过程

动态链接器支持多种运行模式,比如:normaltrace等,其运行模式定义在rtld.cenum mode中。

上一节中,动态链接器是在默认模式normal下运行的。本节我们以调试ldd -u命令的执行过程为例,研究如何调试运行在trace模式下的动态链接器(相比而言,稍复杂些)。

研究过程:

step 1: 准备

1) 了解 ldd -u

ldd -u <object-file>命令,用于查看未使用的且直接依赖的共享库有哪些。

通过man ldd命令,查看 ldd 的 man page。截取部分内容如下:

In the usual case, ldd invokes the standard dynamic linker (see ld.so(8)) with the LD_TRACE_LOADED_OBJECTS environment variable set to 1.  This causes the
dynamic  linker  to  inspect the program's dynamic dependencies, and find (according to the rules described in ld.so(8)) and load the objects that satisfy
those dependencies.  For each dependency, ldd displays the location of the matching object and the (hexadecimal) address at  which  it  is  loaded.

通过上面的描述可以看出,ldd命令将环境变量LD_TRACE_LOADED_OBJECTS设置为 1,然后调用动态链接器,从而实现其功能。

也就是说,ldd <object-file>命令等价于LD_TRACE_LOADED_OBJECTS=1 <object-file>

查看ldd.bash.in(位于 glibc 中)的内容(部分):

if test "$unused" = yes; then
  add_env="$add_env LD_DEBUG=\"$LD_DEBUG${LD_DEBUG:+,}unused\""
fi

从上面的代码可以看出,ldd的参数-u的作用为:将unused追加到环境变量LD_DEBUG中。

因此,可以认为ldd -u <object-file>命令等价于LD_TRACE_LOADED_OBJECTS=1 LD_DEBUG=unused <object-file>

另外,如果存在未使用的且直接依赖的共享库,那么ldd会打印如下输出:

Unused direct dependencies:
// 省略 ...

上面输出结果中的Unused direct dependencies:是在rtld.c的第 1957 行打印的。相应的代码如下(部分):

1953                   if (!l->l_used)
1954                     {
1955                       if (first)
1956                         {
1957                           _dl_printf ("Unused direct dependencies:\n");
1958                           first = false;
1959                         }
1960 
1961                       _dl_printf ("\t%s\n", l->l_name);
1962                     }

因此,我们可以在其稍前面的位置(rtld.c的第 1929 行)设置断点。如果该断点击中了,那么我们成功地做到了用 gdb 调试运行在tarce模式下的动态链接器。

rtld.c的第 1929 行的代码如下:

1929           struct link_map *l = main_map;

2) 如何决定动态链接器的运行模式

动态链接器的运行模式是由环境变量(比如:LD_DEBUGLD_TRACE_LOADED_OBJECTS等)及其参数(比如:--list--verify等)共同决定的。有兴趣的可以分析下rtld.c中的dl_mainprocess_envvars函数,此处不详细展开。这里,只需要知道环境变量LD_TRACE_LOADED_OBJECTS对应的动态链接器运行模式为trace

step 2: 使用 gdb 启动动态链接器

这里,我们在上一节的调试文件ld.gdb的基础上,修改其内容以支持调试运行在trace模式下的动态链接器。

1) 第一次尝试

一种直接的想法是,将环境变量LD_TRACE_LOADED_OBJECTS=1 LD_DEBUG=unused添加到可执行目标文件的前面,就像直接执行LD_TRACE_LOADED_OBJECTS=1 LD_DEBUG=unused ./hello命令那样。从而,将这两个环境变量的值传递给动态链接器。

按照该思路修改ld.gdb,并将修改后的文件命名为ld_t.gdb。其内容如下:

file /lib64/ld-linux-x86-64.so.2
set args LD_TRACE_LOADED_OBJECTS=1 LD_DEBUG=unused ./hello

symbol-file /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so
directory /home/glibc-2.31/elf/

b _dl_start
b rtld.c:1929
starti
c

执行ld_t.gdb的过程如下:

$ gdb -q -x ld_t.gdb
Breakpoint 1 at 0x1df0: file rtld.c, line 463.
Breakpoint 2 at 0x5fd5: file rtld.c, line 1929.

Program stopped.
0x00007ffff7fd0100 in _start ()

Breakpoint 1, _dl_start (arg=0x7fffffffde40) at rtld.c:463
463    {
(gdb) c
Continuing.
LD_TRACE_LOADED_OBJECTS=1error while loading shared libraries: LD_TRACE_LOADED_OBJECTS=1: cannot open shared object file
[Inferior 1 (process 492652) exited with code 0177]
(gdb)

从上面的输出结果可以看出,执行ld_t.gdb失败了。报错信息为:LD_TRACE_LOADED_OBJECTS=1: error while loading shared libraries: LD_TRACE_LOADED_OBJECTS=1: cannot open shared object file。从字面上理解,动态链接器将LD_TRACE_LOADED_OBJECTS=1当成了目标文件。因此,通过这种方式将环境变量传递给动态链接器是行不通的。

另外,如果将环境变量放到starti的后面,即starti LD_TRACE_LOADED_OBJECTS=1 LD_DEBUG=unused ./hello。这种方式也是行不通的。

2) 第二次尝试

我们可以通过 gdb 的set env命令在执行动态链接器前设置环境变量。

按照该思路修改ld_t.gdb。其内容如下:

file /lib64/ld-linux-x86-64.so.2
set args ./hello

set env LD_TRACE_LOADED_OBJECTS=1
set env LD_DEBUG=unused

symbol-file /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so
directory /home/glibc-2.31/elf/

b _dl_start
b rtld.c:1929
starti
c

注意: 环境变量的设置必须在执行动态链接器前(即在starti命令之前)。否则,实际不会生效。

执行ld_t.gdb的过程如下:

$ gdb -q -x ld_t.gdb
Breakpoint 1 at 0x1df0: file rtld.c, line 463.
Breakpoint 2 at 0x5fd5: file rtld.c, line 1929.
ld_t.gdb:12: Error in sourced command file:
During startup program exited normally.
(gdb)

从上面的输出结果可以看出,这次执行直接退出了,甚至_dl_start函数都未执行到。

要想弄清楚为什么会出现上面的结果。关键之处在于上面我们一直未曾注意的输出Program stopped.。详细分析过程见下文的 为什么会出现 "Program stopped"

3) 第三次尝试

这里,我们采用将动态链接器的运行模式从normal修改为trace的方式。

按照该思路修改ld_t.gdb。其内容如下:

file /lib64/ld-linux-x86-64.so.2
set args ./hello

set env LD_DEBUG=unused

symbol-file /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so
directory /home/glibc-2.31/elf/

b _dl_start
b rtld.c:1929
b rtld.c:1968
b rtld.c:2704
b _exit
starti
c
c
p *modep = trace
c
c
c
c

上面的调试文件主要作了两处修改:

执行ld_t.gdb的过程如下:

 $ gdb -q -x ld_t.gdb
Breakpoint 1 at 0x1df0: file rtld.c, line 463.
Breakpoint 2 at 0x5fd5: file rtld.c, line 1929.
Breakpoint 3 at 0x5403: file rtld.c, line 1968.
Breakpoint 4 at 0x26eb: file rtld.c, line 2704.
Breakpoint 5 at 0x1f280: file ../sysdeps/unix/sysv/linux/_exit.c, line 27.

Program stopped.
0x00007ffff7fd0100 in _start ()

Breakpoint 1, _dl_start (arg=0x7fffffffde60) at rtld.c:463
463    {

Breakpoint 4, process_envvars (modep=<synthetic pointer>) at rtld.c:2704
2704      if (__builtin_expect (__libc_enable_secure, 0))
$1 = trace

Breakpoint 2, dl_main (phdr=<optimized out>, phnum=<optimized out>, user_entry=<optimized out>, auxv=<optimized out>) at rtld.c:1932
1932          struct relocate_args args = { .l = l,

Breakpoint 3, dl_main (phdr=<optimized out>, phnum=<optimized out>, user_entry=<optimized out>, auxv=<optimized out>) at rtld.c:1968
1968          _exit (first != true);

Breakpoint 5, __GI__exit (status=0) at ../sysdeps/unix/sysv/linux/_exit.c:27
27    {
[Inferior 1 (process 504483) exited normally]
(gdb)

从上面的输出结果可以看出:

研究结论:


为什么会出现 "Program stopped"

在执行ld.gdb时,gdb 的输出结果中有这样一行:Program stopped.。那么,所谓的Program指的是哪个程序呢?

研究过程:

step 1: 分析Program是指哪个程序

1) 修改ld.gdb

修改ld.gdb,并将修改后的文件命名为ld_1.gdb。其内容如下:

file /lib64/ld-linux-x86-64.so.2
set args ./hello

set env LD_DEBUG=libs

symbol-file /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.31.so
directory /home/glibc-2.31/elf/

b _dl_start
starti
c

注:设置环境变量LD_DEBUG=libs是为了跟踪动态链接器加载共享库的过程。

2) 执行ld_1.gdb

执行ld_1.gdb的过程如下:

$ gdb -q -x ld_1.gdb
Breakpoint 1 at 0x1df0: file rtld.c, line 463.
    493684:    find library=libtinfo.so.6 [0]; searching
    493684:     search cache=/etc/ld.so.cache
    493684:      trying file=/lib/x86_64-linux-gnu/libtinfo.so.6
    493684:    
    493684:    find library=libdl.so.2 [0]; searching
    493684:     search cache=/etc/ld.so.cache
    493684:      trying file=/lib/x86_64-linux-gnu/libdl.so.2
    493684:    
    493684:    find library=libc.so.6 [0]; searching
    493684:     search cache=/etc/ld.so.cache
    493684:      trying file=/lib/x86_64-linux-gnu/libc.so.6
    493684:    
    493684:    
    493684:    calling init: /lib/x86_64-linux-gnu/libc.so.6
    493684:    
    493684:    
    493684:    calling init: /lib/x86_64-linux-gnu/libdl.so.2
    493684:    
    493684:    
    493684:    calling init: /lib/x86_64-linux-gnu/libtinfo.so.6
    493684:    
    493684:    
    493684:    initialize program: /bin/bash
    493684:    
    493684:    
    493684:    transferring control: /bin/bash
    493684:    

Program stopped.
0x00007ffff7fd0100 in _start ()

Breakpoint 1, _dl_start (arg=0x7fffffffde60at rtld.c:463
463    {
(gdb)

从上面的输出结果可以看出,在打印Program stopped.之前,gdb 打印了许多信息。我们可以将这些信息可以分为两类:1)搜索共享库;2)将控制权交给/bin/bash程序。也就是说,所谓的Program程序实际就是/bin/bash 另外,那些共享库正是/bin/bash所依赖的。

查看/bin/bash所依赖的共享库:

$ ldd /bin/bash
    linux-vdso.so.1 (0x00007fff7a2f4000)
    libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f39d84b4000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f39d84ae000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f39d82bc000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f39d8627000)

值得注意的是,我们设置的环境变量LD_DEBUG=libs/bin/bash也生效了。这解释了为什么会输出/bin/bash所依赖共享库的搜索过程。

因此,我们在ld_t.gdb中设置的环境变量LD_TRACE_LOADED_OBJECTS=1LD_DEBUG=unused也会对/bin/bash生效。而这正是导致执行ld_t.gdb会直接退出的原因。接下来详细分析该过程。

step 2: 为什么执行ld_t.gdb会直接退出

1) 动态链接器是如何使用环境变量LD_TRACE_LOADED_OBJECTSLD_DEBUG

查看process_envvars函数(在 rtld.c 中),其相关代码如下:

2530 static void
2531 process_envvars (enum mode *modep)
2532 {
2533   char **runp = _environ;
2534   char *envline;
2535   enum mode mode = normal;
// 省略 ...
2542   while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
2543     {
2544       size_t len = 0;
2545 
2546       while (envline[len] != '\0' && envline[len] != '=')
2547         ++len;
2548 
2549       if (envline[len] != '=')
2550         /* This is a "LD_" variable at the end of the string without
2551            a '=' character.  Ignore it since otherwise we will access
2552            invalid memory below.  */

2553         continue;
2554 
2555       switch (len)
2556         {
// 省略 ...
2563         case 5:
2564           /* Debugging of the dynamic linker?  */
2565           if (memcmp (envline, "DEBUG"5) == 0)
2566             {
2567               process_dl_debug (&envline[6]);
2568               break;
2569             }
// 省略 ...
2683         case 20:
2684           /* The mode of the dynamic linker can be set.  */
2685           if (memcmp (envline, "TRACE_LOADED_OBJECTS"20) == 0)
2686             mode = trace;
2687           break;
// 省略 ...
2696         }
2697     }
2698 
2699   /* The caller wants this information.  */
2700   *modep = mode;
2701 
2702   /* Extra security for SUID binaries.  Remove all dangerous environment
2703      variables.  */

2704   if (__builtin_expect (__libc_enable_secure, 0))
// 省略 ...
}

从上面代码中可以看出,动态链接器解析环境变量时:

因此,当同时设置环境变量LD_TRACE_LOADED_OBJECTS=1LD_DEBUG=unused时,动态链接器的运行模式为trace。当然,动态链接器的实际运行模式还受其参数的影响,而ld_t.gdb中不涉及。所以,执行ld_t.gdb时,动态链接器的实际运行模式就是trace

2) 动态链接器是如何处理trace模式的

查看dl_main函数(在 rtld.c 中),其相关代码如下:

1085 static void
1086 dl_main (const ElfW(Phdr) *phdr,
1087          ElfW(Word) phnum,
1088          ElfW(Addr) *user_entry,
1089          ElfW(auxv_t) *auxv)
1090 {
// 省略 ...
1886   if (__builtin_expect (mode, normal) != normal)
1887     {
1888       /* We were run just to list the shared libraries.  It is
1889          important that we do this before real relocation, because the
1890          functions we call below for output may no longer work properly
1891          after relocation.  */

1892       struct link_map *l;
1893 
1894       if (GLRO(dl_debug_mask) & DL_DEBUG_PRELINK)
1895         {
// 省略 ...
1923         }
1924       else if (GLRO(dl_debug_mask) & DL_DEBUG_UNUSED)
1925         {
// 省略 ...
1941           bool first = true;
1942           while (dyn->d_tag != DT_NULL)
1943             {
// 省略 ...
1953                   if (!l->l_used)
1954                     {
1955                       if (first)
1956                         {
1957                           _dl_printf ("Unused direct dependencies:\n");
1958                           first = false;
1959                         }
// 省略 ...
1963                     }
// 省略 ...
1966             }
1967 
1968           _exit (first != true);
1969         }
1970       else if (! main_map->l_info[DT_NEEDED])
1971         _dl_printf ("\tstatically linked\n");
1972       else
1973         {
// 省略 ...
1986         }
// 省略 ...
2113       _exit (0);
2114     }
// 省略 ...
2365   /* Once we return, _dl_sysdep_start will invoke
2366      the DT_INIT functions and then *USER_ENTRY.  */

2367 }

从上面的代码可以看出,动态链接器运行在trace模式下的行为:

也就是说,在执行ld_t.gdb时,控制权首先会交给/bin/bash。而加载/bin/bash依赖的共享库时,受环境变量LD_TRACE_LOADED_OBJECTS=1的影响,动态链接器的运行模式为trace,于是在动态链接器加载./hello之前就退出了。因此,执行ld_t.gdb会直接退出。

那么,针对这种情况,有两种解决思路:

研究结论:


References


下一篇:代码调试之目录

上一篇:代码调试篇(4):如何调试 glibc 源码——准备篇

首页