有关Linux的可执行程序

2019-07-30

关键字:动态编译、静态编译、readelf


引言

Linux 的可执行程序可以分为两种:

1、动态编译型;

2、静态编译型。

那么,二者之间有什么区别呢?

动态编译

动态编译是指程序源码在编译时,若有需要引用外部程序接口的,如外部库或外部可执行程序,只保存对应库的相关链接。后续该程序在运行时再去系统指定的库路径下寻找所需要的库或程序接口。

这种模式在编译时只会打包自己的源代码,其好处就是编译产物体积小巧,且在加载外部接口时不会有浪费内存空间的问题。其缺点就是运行比较依赖环境的完整性。系统中缺少任何一个程序所需要的外部程序接口都会导致该程序无法运行。

静态编译

与动态编译类型相对,它是指编译时除了对自己的源码编译打包以外,若有要引用到外部库接口的,也一并打包进来。动态编译类型是我要用你我只需要记住你在哪就可以了,而静态编译则是我要用你,你就必须得过来我这边,时刻跟我呆在一起。

因此,静态编译出来的程序,它的体积会比较大,但是它的运行不需要依赖库环境。它还有一个缺点,就是可能会造成内存浪费现象。因为每个可执行程序内部都持有一份依赖库的副本,在需要使用时会将这些一起打包的库都加载到内存中去。如果系统中有多个静态编译型程序都包含有相同的依赖库,那么就会造成同样的库有着多份内存占用的情况。

如何分辨编译类型

我们可以通过 Linux 的 readelf 命令来分辨一个可执行程序是动态编译型的还是静态编译型的。

readelf -l xxx

如果是静态编译型的程序,那么可以看到类似如下的信息

Elf file type is EXEC (Executable file)
Entry point 0x8b28
There are 6 program headers, starting at offset 52
 
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x077610 0x0007f610 0x0007f610 0x00768 0x00768 R   0x4
  LOAD           0x000000 0x00008000 0x00008000 0x77d7c 0x77d7c R E 0x8000
  LOAD           0x077d7c 0x00087d7c 0x00087d7c 0x00c58 0x020dc RW  0x8000
  NOTE           0x0000f4 0x000080f4 0x000080f4 0x00020 0x00020 R   0x4
  TLS            0x077d7c 0x00087d7c 0x00087d7c 0x00010 0x00028 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
 
 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   01     .note.ABI-tag .init .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit __libc_thread_subfreeres .ARM.extab .ARM.exidx .eh_frame 
   02     .tdata .init_array .fini_array .jcr .data.rel.ro .got .data .bss __libc_freeres_ptrs 
   03     .note.ABI-tag 
   04     .tdata .tbss 
   05     

没啥很特别的地方。

如果是动态编译型的,则可以看到类似如下的信息

Elf file type is EXEC (Executable file)
Entry point 0xe5d0
There are 8 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x00bd50 0x00013d50 0x00013d50 0x00008 0x00008 R   0x4
  PHDR           0x000034 0x00008034 0x00008034 0x00100 0x00100 R E 0x4
  INTERP         0x000134 0x00008134 0x00008134 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.3]
  LOAD           0x000000 0x00008000 0x00008000 0x0bd5c 0x0bd5c R E 0x8000
  LOAD           0x00c000 0x0001c000 0x0001c000 0x00670 0x006b4 RW  0x8000
  DYNAMIC        0x00c01c 0x0001c01c 0x0001c01c 0x00130 0x00130 RW  0x4
  NOTE           0x000148 0x00008148 0x00008148 0x00020 0x00020 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   01     
   02     .interp 
   03     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame 
   04     .init_array .fini_array .jcr .dynamic .got .data .bss 
   05     .dynamic 
   06     .note.ABI-tag 
   07

可以看到上面红色加粗的字段。动态编译型的程序在执行 readelf -l 命令时,会多出一个 INTERP 段。这个字段记载的就是加载这段程序的条件。在这里,想要读取并加载这个程序,必须要有 /lib/ld-linux.so.3 库的存在。

如果系统环境没有这个 /lib/ld-linux.so.3 ,在执行这个程序时会报 No such file or directory 的错误。

动态编译型依赖查询

如果一个可执行程序是静态编译型的,那很简单啊,直接运行就好。

但如果是动态编译型的,有些时候可能要为程序所依赖的外部库费一些劲了。不过幸好我们同样可以通过 readelf 命令来查看它都依赖哪些库。

readelf -d xxx

对于动态编译型的程序,会出现类似下面类型的信息

Dynamic section at offset 0xc01c contains 33 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libqte.so.3]
 0x00000001 (NEEDED)                     Shared library: [libts-0.0.so.0]
 0x00000001 (NEEDED)                     Shared library: [librt.so.1]
 0x00000001 (NEEDED)                     Shared library: [libpthread.so.0]
 0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.so.6]
 0x00000001 (NEEDED)                     Shared library: [libm.so.6]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [/usr/qte338-target2/lib]
 0x0000000c (INIT)                       0xd684
 0x0000000d (FINI)                       0x1304c
 0x00000019 (INIT_ARRAY)                 0x1c000

上面信息中所有标记了 NEEDED 的都是这个程序运行时所要依赖的外部库。我们只需要将这些库放到系统能找得到的路径下就可以正常运行这个程序的了。

在 Linux 中,有一条系统变量是记载着库存放路径的。它就是

LD_LIBRARY_PATH

例如,在 Android 下,它的值可能是

LD_LIBRARY_PATH=/vendor/lib:/system/lib

一般而言 Linux 系统需要找外部库时,都是首先去 /lib 和 /usr/lib 目录下寻找的,如果没有找到,再去 LD_LIBRARY_PATH 记载的路径下找。


原文地址:https://www.cnblogs.com/chorm590/p/11214009.html