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\n",__FUNCTION__);
}

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

[tsecer@Harry soinit]$ cat dep21.c 
#include <stdio.h>
int __attribute__((constructor))  dep21(void)
{
    return    printf("In %s\n",__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.1\ld\ldlang.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 == ‘\0‘)
    {
      if (! bfd_set_start_address (link_info.output_bfd, val))
        einfo (_("%P%F: can‘t set start address\n"));
    }
      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\n"),
               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\n"));
        }
      else
        {
          if (warn)
        einfo (_("%P: warning: cannot find entry symbol %s;"
             " not setting start address\n"),
               entry_symbol.name);
        }
    }
    }
}
3、入口的意义
对于大部分so文件没有意义,只有so真正独立运行时有意义,例如ld.so文件,这个让内核把任务的入口设置到该位置。
四、动态文件初始化顺序
1、init的由来
binutils-2.21.1\ld\ldmain.c
main (int argc, char **argv)

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

binutils-2.21.1\bfd\elflink.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标签确定。

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

时间: 2024-08-30 11:11:35

Linux动态链接(2)so初始化执行的相关文章

再探Linux动态链接 -- 关于动态库的基础知识

  在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台相关目标文件这一层次)和链接(Linking,指目标文件到最终形成可执行文件这一层次),这个总的过程可称为编译时:就动态链接而言,还存在一个运行时,即程序在被操作系统加载的过程中,系统将该程序需要的动态库加载至内存到程序开始运行的这一段过程.明确这两个过程在一般linux开发中的地位,以及了解每个"

Linux动态链接(1)惰性链接

一.动态链接在Linux(unix族谱)下,共享目标文件称为so文件,它和windows下的DLL机制对应,该功能在节省物理内存使用量上有重要意义,但是更重要的它还是一种扩展框架,也就是很多所谓的“插件”的实现基础.从它的出现频率上来看,它和Linux下的多线程具有同等重要的地位,甚至更高.因为很多可执行文件都没有使用pthread库,但是几乎所有的Linux发行版本的可执行文件都是动态链接生成的可执行文件.从实现机制上看,两种机制也有相同之处,它们都有共享和隔离,而线程和共享库内的线程私有数据

【转】Linux动态链接(4)ldd与ldconfig

原文网址:http://tsecer.blog.163.com/blog/static/15018172012414105551345/ 一.动态链接工具ldd和ldconfig是动态链接的两个重要辅助工具,所谓“辅助”,是相对于真正的主角动态链接器ld.so,说它是工具,是只它相对于配置文件/etc/ld.so.conf文件.ldd不直接参与链接过程,它依赖于ld.so,但是ld.so不依赖于这个工具,事实上,ldd只是一个脚本,它在调用ld.so的时候传递了一些约定好的环境变量,从而使ld.

Linux动态链接之GOT与PLT

转载于:http://www.cnblogs.com/xingyun/archive/2011/12/10/2283149.html 我们知道函数名就是一个内存地址,这个地址指向函数的入口.调用函数就是压入参数,保存返回地址,然后跳转到函数名指向的代码.问题是,如果函数在共享库中,共享库加载的地址本身就不确定,函数地址也就不确定了,那如何调用共享库中的函数呢?这就是本文要回答的. 我们先来看一小段代码(test.c): #include <stdio.h>void hello_world(vo

linux动态链接

1, 编译,使用-shared和-fpic 生成动态链接库库源码:test.c #include <stdio.h> #include <string.h> #include <stdlib.h> static void printline(int len) { int i; for(i = 0;i<len;i++) { printf("="); } printf("\n"); } void print(char * s)

Delphi动态链接多个数据源执行操作例子

1.首先配置文件[SubExamDep1]DBSERVER=(local)DBNAME=herpeisweixinUSERNAME=saPWD=bjbrMSG=SubExamDep1失败[SubExamDep2]DBSERVER=(local)DBNAME=herpeisFZTJUSERNAME=saPWD=bjbrMSG=SubExamDep2失败2.工程文件加入 Inifiles 文件public:MyIni :Tinifile; glAppPath :string;create 事件增加g

打印动态链接时的初始化堆栈信息

#include <stdio.h> #include <elf.h> int main(int argc, char* argv[]) { void** p = (void**)argv; printf("%p\n", p); printf("Argument count: %d\n", *((int*)p - 1)); int i; for (i = 0; i < argc; ++i) { printf("Argument

动态链接详解

动态链接 动态链接的诞生: 动态链接产生最主要的原因就是静态链接空间浪费过于巨大,更重要的是现阶段各种软件都是模块化开发,不同模块都是由不同厂商开发的,一旦一个模块发生改变,整个软件就需要重新编译(静态链接的情况下). 动态链接主要思想: 把链接这个过程推迟到了运行时再运行,这就是动态链接(Dynamic Linking)的基本思想. 动态链接的好处: 1.动态链接将共享对象放置在内存中,不仅仅节省内存,它还可以减少物理页面的换进换出,也可以提高CPU缓存的命中率,因为不同进程间的数据与指令都集

装载与动态链接

装载与动态链接 1可执行文件的装载与进程 可执行文件只有装载到内存后才能被CPU执行.早期的程序装载十分简陋,装载的基本过程就是把程序从外部存储器中读取到内存中的某个位置. 历史有过的装载方式包括覆盖装载.页映射. 1.1 进程虚拟地址空间 程序是一个静态的概念,它就是一些预先编译好的指令和数据集合的一个文件:进程则是一个动态的概念,它是程序运行的一个过程. 每个程序被运行起来以后,都有自己的虚拟地址空间,这个虚拟地址空间的大小由计算机的硬件平台决定,具体地说是由CPU的位数决定的. 1.2 装