zoukankan      html  css  js  c++  java
  • Linux动态链接(2)so初始化执行

    一、so文件和exe文件
    这两种文件其实具有很多相似自出,或者说so文件是介于obj文件和exe文件的一种中间过渡形式,它虽然不能直接运行(但是经过特殊编写的so文件内核是支持加载运行的,例如ld.so),但是具有了自己的一些更为高级的内容,例如一些初始化节,got表等内容,虽然孱弱,但是它具有了更加完善的生物形态。但是大家不要用进化论的观点来认为so文件比exe文件出现的早,事实上so是比较新的一个概念。我们看一下这些文件类型的标识类型说明
    #define ET_NONE   0
    #define ET_REL    1
    #define ET_EXEC   2
    #define ET_DYN    3
    可以看到动态so文件的数值要比exe数值大。
    这里说明这么多,就是so可以被通用的动态链接器识别和加载,由于so已经是一个exe的雏形,所以在加载一个so文件的时候可能除了完成动态链接之外还需要进行一些额外的操作,其中我们最为关心的就是个个so中定义的一些全局变量的初始化。这个概念对于通常的C文件生成的so没有直接意义,但是对于包含了全局变量的C++文件可能比较有用,也就是在这个so文件被加载之后、该文件中任何代码执行之前需要执行so中所有的初始化函数。
    二、测试工程代码
    //总共包含四个源文件,三个dep??.c生成各自的libdep??.so,main.c生成主程序。其中main.exe依赖dep11.so 和 dep12.so,而dep11.so进而依赖dep21.so,主要看这些文件的打开顺序以及初始化函数的执行顺序
    [tsecer@Harry soinit]$ ls
    dep11.c  dep12.c  dep21.c  main.c  Makefile

    [tsecer@Harry soinit]$ cat dep11.c 
    #include <stdio.h>
    int __attribute__((constructor)) dep11(void)
    {
        return printf("In %s ",__FUNCTION__);
    }

    [tsecer@Harry soinit]$ cat dep12.c 
    #include <stdio.h>
    int __attribute__((constructor))  dep12(void)
    {
         return     printf("In %s ",__FUNCTION__);
    }

    [tsecer@Harry soinit]$ cat dep21.c 
    #include <stdio.h>
    int __attribute__((constructor))  dep21(void)
    {
        return    printf("In %s ",__FUNCTION__);
    }

    [tsecer@Harry soinit]$ cat Makefile 
    main.exe:main.c libdep11.so libdep12.so
        gcc -fPIC main.c -o $@ -L. -ldep11 -ldep12
        LD_LIBRARY_PATH=. ./main.exe
    libdep12.so libdep21.so:lib%.so:%.c
        gcc -fPIC -shared -o $@ $<
    libdep11.so:libdep21.so
        gcc -fPIC -shared -o $@ -L. -ldep21 dep11.c 
    clean:
        rm -f *.so *.exe *.o

    //执行make之后输出的执行顺序,这个顺序我们稍后解释
    [tsecer@Harry soinit]$ make
    gcc -fPIC -shared -o libdep21.so dep21.c
    gcc -fPIC -shared -o libdep11.so -L. -ldep21 dep11.c 
    gcc -fPIC -shared -o libdep12.so dep12.c
    gcc -fPIC main.c -o main.exe -L. -ldep11 -ldep12
    /usr/bin/ld: warning: libdep21.so, needed by ./libdep11.so, not found (try using -rpath or -rpath-link)
    LD_LIBRARY_PATH=. ./main.exe
    In dep21
    In dep12
    In dep11
    In dep00
    三、动态库入口位置由来及意义
    1、入口的形式
    [tsecer@Harry soinit]$ readelf -a libdep11.so 
    ELF Header:
      Magic:   7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - Linux
      ABI Version:                       0
      Type:                              DYN (Shared object file)
      Machine:                           Intel 80386
      Version:                           0x1
      Entry point address:               0x370
      Start of program headers:          52 (bytes into file)
      Start of section headers:          1956 (bytes into file)
      Flags:                             0x0
      Size of this header:               52 (bytes)
      Size of program headers:           32 (bytes)
      Number of program headers:         5
      Size of section headers:           40 (bytes)
      Number of section headers:         27
      Section header string table index: 24

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .note.gnu.build-i NOTE            000000d4 0000d4 000024 00   A  0   0  4
      [ 2] .gnu.hash         GNU_HASH        000000f8 0000f8 00003c 04   A  3   0  4
      [ 3] .dynsym           DYNSYM          00000134 000134 0000b0 10   A  4   1  4
      [ 4] .dynstr           STRTAB          000001e4 0001e4 000090 00   A  0   0  1
      [ 5] .gnu.version      VERSYM          00000274 000274 000016 02   A  3   0  2
      [ 6] .gnu.version_r    VERNEED         0000028c 00028c 000030 00   A  4   1  4
      [ 7] .rel.dyn          REL             000002bc 0002bc 000028 08   A  3   0  4
      [ 8] .rel.plt          REL             000002e4 0002e4 000018 08   A  3  10  4
      [ 9] .init             PROGBITS        000002fc 0002fc 000030 00  AX  0   0  4
      [10] .plt              PROGBITS        0000032c 00032c 000040 04  AX  0   0  4
      [11] .text             PROGBITS        00000370 000370 000138 00  AX  0   0 16
      [12] .fini             PROGBITS        000004a8 0004a8 00001c 00  AX  0   0  4
      这里一个比较有意思的现象就是so文件也有自己的入口地址,这个地址位于.text节的开始。但是明显的我们没有指定其实地址,对于通常的可执行程序,我们使用的是内置连接脚本,通过ld --verbose可以看到脚本内容。我们使用libdep11.so生成的命令添加 -v显示连接器使用的脚本:
    [tsecer@Harry soinit]$ ld -shared -verbose
    GNU ld version 2.19.51.0.14-34.fc12 20090722
      Supported emulations:
       elf_i386
       i386linux
       elf_x86_64
    using internal linker script:
    ==================================================
    /* Script for --shared -z combreloc: shared library, combine & sort relocs */
    OUTPUT_FORMAT("elf32-i386", "elf32-i386",
              "elf32-i386")
    OUTPUT_ARCH(i386)
    ENTRY(_start)
    SEARCH_DIR("/usr/i686-redhat-linux/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");
    内置链接脚本同样使用的是_start符号,但是我们并没有定义这个符号,因为这个符号是在/usr/lib/gcc/i686-redhat-linux/4.4.2/../../../crt1.o中定义的,而生成动态库的时候并没有链接这个文件,下面是通过gcc -v显示的动态链接命令
     /usr/libexec/gcc/i686-redhat-linux/4.4.2/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -shared -o libdep11.so /usr/lib/gcc/i686-redhat-linux/4.4.2/../../../crti.o /usr/lib/gcc/i686-redhat-linux/4.4.2/crtbeginS.o -L. -L/usr/lib/gcc/i686-redhat-linux/4.4.2 -L/usr/lib/gcc/i686-redhat-linux/4.4.2 -L/usr/lib/gcc/i686-redhat-linux/4.4.2/../../.. -ldep21 /tmp/cc3PSy9f.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-redhat-linux/4.4.2/crtendS.o /usr/lib/gcc/i686-redhat-linux/4.4.2/../../../crtn.o
    2、入口的由来
    在链接器代码中,其中对于这个内容的处理比较特殊,链接器内部特地为so文件的生成做了兼容,所以此时不会出现链接错误:
    binutils-2.21.1ldldlang.c
    static void
    lang_end (void)
    {
    ……
      if ((link_info.relocatable && !link_info.gc_sections)
          || (link_info.shared && !link_info.executable
    ))
        warn = entry_from_cmdline对于动态链接文件来说,是否警告根据entry是否是命令行确定,由于我们例子中位于内置脚本中,所以不告警
      else
        warn = TRUE;
    ……
    {
          bfd_vma val;
          const char *send;

          /* We couldn't find the entry symbol.  Try parsing it as a
         number.  */
          val = bfd_scan_vma (entry_symbol.name, &send, 0);
          if (*send == '')
        {
          if (! bfd_set_start_address (link_info.output_bfd, val))
            einfo (_("%P%F: can't set start address "));
        }
          else
        {
          asection *ts;

          /* Can't find the entry symbol, and it's not a number.  Use
             the first address in the text section.  */
          ts = bfd_get_section_by_name (link_info.output_bfd, entry_section);如果找不到入口符号(即_start符号),则用entry_section地址代替。其定义为const char *entry_section = ".text";,所以就是text节作为共享库文件的入口,也就是我们看到和.text起始地址一致的原因

          if (ts != NULL)
            {
              if (warn)//这里的warn为false,所以没有告警
            einfo (_("%P: warning: cannot find entry symbol %s;"
                 " defaulting to %V "),
                   entry_symbol.name,
                   bfd_get_section_vma (link_info.output_bfd, ts));
              if (!(bfd_set_start_address
                (link_info.output_bfd,
                 bfd_get_section_vma (link_info.output_bfd, ts))))
            einfo (_("%P%F: can't set start address "));
            }
          else
            {
              if (warn)
            einfo (_("%P: warning: cannot find entry symbol %s;"
                 " not setting start address "),
                   entry_symbol.name);
            }
        }
        }
    }
    3、入口的意义
    对于大部分so文件没有意义,只有so真正独立运行时有意义,例如ld.so文件,这个让内核把任务的入口设置到该位置。
    四、动态文件初始化顺序
    1、init的由来
    binutils-2.21.1ldldmain.c
    main (int argc, char **argv)

      link_info.init_function = "_init";
      link_info.fini_function = "_fini";

    binutils-2.21.1fdelflink.c
          /* Add some entries to the .dynamic section.  We fill in some of the
         values later, in bfd_elf_final_link, but we must add the entries
         now so that we know the final size of the .dynamic section.  */

          /* If there are initialization and/or finalization functions to
         call then add the corresponding DT_INIT/DT_FINI entries.  */
          h = (info->init_function
           ? elf_link_hash_lookup (elf_hash_table (info),
                       info->init_function, FALSE,
                       FALSE, FALSE)
           : NULL);
          if (h != NULL
          && (h->ref_regular
              || h->def_regular))
        {
          if (!_bfd_elf_add_dynamic_entry (info, DT_INIT, 0))
            return FALSE;
        }
    对于共享文件链接,通过之前命令可以看到,它的确链接了crti.o,其中也定义了_init函数
    [tsecer@Harry soinit]$ nm /usr/lib/gcc/i686-redhat-linux/4.4.2/../../../crt1.o 
    00000000 R _IO_stdin_used
    00000000 D __data_start
             U __libc_csu_fini
             U __libc_csu_init
             U __libc_start_main
    00000000 R _fp_hw
    00000000 T _start
    00000000 W data_start
             U main
    [tsecer@Harry soinit]$ nm /usr/lib/gcc/i686-redhat-linux/4.4.2/../../../crti.o 
             U _GLOBAL_OFFSET_TABLE_
             w __gmon_start__
    00000000 T _fini
    00000000 T _init
    最后定义了_init符号。
    2、动态链接器打开共享库顺序
    在dlopen--->>>dl_open_worker函数中,它执行大致流程为
    _dl_map_object
    _dl_map_object_deps
     _dl_init (new, args->argc, args->argv, args->env)
    也就是首先打开所有的直接依赖,这是一个递归过程,深度优先,其关键代码为
    _dl_map_object_deps
      for (d = l->l_ld; d->d_tag != DT_NULL; ++d)
            if (__builtin_expect (d->d_tag, DT_NEEDED) == DT_NEEDED)
              {
            /* Map in the needed object.  */
            struct link_map *dep;

            /* Recognize DSTs.  */
            name = expand_dst (l, strtab + d->d_un.d_val, 0);
            /* Store the tag in the argument structure.  */
            args.name = name;

            bool malloced;
            int err = _dl_catch_error (&objname, &errstring, &malloced,
                           openaux, &args);
    }
    在一个so文件的所有依赖被加载完成之后,open函数将会一次调用所有的直接依赖的初始化入口函数,这个函数再负责遍历可能存在的、生成so时使用的各个obj文件中的init节中的函数指针数组。
    _dl_init (struct link_map *main_map, int argc, char **argv, char **env)
    {
      i = main_map->l_searchlist.r_nlist;
      while (i-- > 0)
        call_init (main_map->l_initfini[i], argc, argv, env);
    }
    这里可以看到,此处函数执行的时候,它是执行的while(i--)指令,这意味着直接依赖的初始化函数的执行顺序是和在依赖中出现的顺序相反。所以我们看main.exe依次依赖了dep11.so和dep12.so,但是dep12.so的初始化函数的执行要早于dep11.so
    [tsecer@Harry soinit]$ readelf -d main.exe 

    Dynamic section at offset 0x650 contains 22 entries:
      Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libdep11.so]
     0x00000001 (NEEDED)                     Shared library: [libdep12.so]
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
    3、动态链接器如何知道各个SO的初始化函数入口
    [tsecer@Harry soinit]$ readelf -d main.exe 

    Dynamic section at offset 0x650 contains 22 entries:
      Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libdep11.so]
     0x00000001 (NEEDED)                     Shared library: [libdep12.so]
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
     0x0000000c (INIT)                       0x804836c
     0x0000000d (FINI)                       0x804857c
    动过动态节中的DT_INIT和DT_FINI标签确定。
     
     
     
     
     
  • 相关阅读:
    dumpsys
    阿里云云效流水线体验
    停车入场城市排行榜1
    第三方企业号对接工作
    PHP搭建(windows64+apache2.4.7+mysql-5.6+php5.5)
    十大编程算法助程序员走上高手之路
    数据库的最简单实现
    JavaScript 开发的45个经典技巧
    常用meta整理
    Shell脚本编程初体验
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486374.html
Copyright © 2011-2022 走看看