跳过正文

链接器 ld

··6277 字
Gnu Gcc Ld
目录

GNU ld
#

链接器 ld 是 binutil 包提供的命令,gcc 不会直接调用 ld,而是通过包装器(wrapper)collect2 来调用 ld:

  1. 构造函数和析构函数的处理:

    • collect2 的主要作用是确保 C++ 程序中的全局构造函数(constructors)和析构函数(destructors)能够正确执行
    • 它会收集所有的全局构造函数和析构函数,并生成必要的代码来确保它们在程序启动和结束时被正确调用
  2. 链接时的特殊处理(特别是 gcc 和启动、退出相关的代码库链接):

    • collect2 能够处理一些特殊的链接时任务
    • 它可以添加必要的启动代码(startup code)
    • 处理一些平台特定的链接需求
  3. 实际操作:

    • collect2 在内部仍然会调用 ld
    • 它首先处理所需的特殊任务,然后将控制权传递给真正的链接器 ld

链接器的主要功能是将多个 ELF 对象文件中的 segment 根据链接器脚本(默认)合并到最终的可执行文件中,期间涉及到符号解析和冲突检测。

C 程序源码的入口是 main 函数,但是生成的 ELF 文件的执行入口是 _start,它是编译器提供的符号和代码。

gcc 在链接主程序时,除了程序自身的 object 文件外,还链接了 gcc、libc 库:

  • 链接了 gcc 库 -lgcc, -lgcc_s,crtbeginS.o,crtendS.o 等,并将 GCC 的库添加到搜索路径的最前面
    • crt 是 c runtime 的简称,其中的 _start 才是程序的真正执行入口,编译器为 C main 函数前后插入了 gcc 和 libc 库提供的代码。
  • 链接了 -lc 库
  • 开启了 –eh-frame-hdr
  • 使用动态链接器 -dynamic-linker /lib/ld-linux-aarch64.so.1

-v 和 –help 连用时除了打印 gcc 自身参数外,还打印它调用的命令,如 cpp、ld 的参数: gcc -v --help 2>&1 |less

编译时加 -v 可以打印详细的编译命令,同时添加:

  • -Wp,-v: 编译预处理详细参数;
  • -v:编译详情, cc1 编译器的执行详情
  • -Wa,-v: 汇编详情: 显示调用的 as 命令参数;
  • -Wl,-v: 链接详情,显示 collect2 调用 ld 的详情;
alizj@ubuntu:/Users/alizj/docs/lang/c$ gcc -g -v -Wp,-v -Wa,-v -Wl,-v array.c
#...
# 链接 wrapper collect2 的命令行参数,内部调用 ld
GNU assembler version 2.42 (aarch64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.42
COMPILER_PATH=/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib/:/lib/aarch64-linux-gnu/:/lib/../lib/:/usr/lib/aarch64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-g' '-v' '-mlittle-endian' '-mabi=lp64' '-dumpdir' 'a.'
 /usr/libexec/gcc/aarch64-linux-gnu/13/collect2 -plugin /usr/libexec/gcc/aarch64-linux-gnu/13/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/aarch64-linux-gnu/13/lto-wrapper -plugin-opt=-fresolution=/tmp/ccZXDhqQ.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr --hash-style=gnu --as-needed -dynamic-linker /lib/ld-linux-aarch64.so.1 -X -EL -maarch64linux --fix-cortex-a53-843419 -pie -z now -z relro /usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/Scrt1.o /usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/crti.o /usr/lib/gcc/aarch64-linux-gnu/13/crtbeginS.o -L/usr/lib/gcc/aarch64-linux-gnu/13 -L/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu -L/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib -L/lib/aarch64-linux-gnu -L/lib/../lib -L/usr/lib/aarch64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/aarch64-linux-gnu/13/../../.. -v /tmp/cc0XD2tu.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/aarch64-linux-gnu/13/crtendS.o /usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/crtn.o
collect2 version 13.3.0

# collect 2 调用的 ld 命令
/usr/bin/ld -plugin /usr/libexec/gcc/aarch64-linux-gnu/13/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/aarch64-linux-gnu/13/lto-wrapper -plugin-opt=-fresolution=/tmp/ccZXDhqQ.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr --hash-style=gnu --as-needed -dynamic-linker /lib/ld-linux-aarch64.so.1 -X -EL -maarch64linux --fix-cortex-a53-843419 -pie -z now -z relro /usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/Scrt1.o /usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/crti.o /usr/lib/gcc/aarch64-linux-gnu/13/crtbeginS.o -L/usr/lib/gcc/aarch64-linux-gnu/13 -L/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu -L/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib -L/lib/aarch64-linux-gnu -L/lib/../lib -L/usr/lib/aarch64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/aarch64-linux-gnu/13/../../.. -v /tmp/cc0XD2tu.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/aarch64-linux-gnu/13/crtendS.o /usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/crtn.o
GNU ld (GNU Binutils for Ubuntu) 2.42
COLLECT_GCC_OPTIONS='-g' '-v' '-mlittle-endian' '-mabi=lp64' '-dumpdir' 'a.'

编译和链接
#

每个 C 源文件可以单独编译, 生成一个 obj 文件, 然后再链接这些 obj 文件(其中有一个包含 main 函数)生成可执行程序:

  • 使用 -c 来编译为 lib
  • 如果不加 -c 则表示 编译并链接为可执行程序 ,对于没有 main 函数定义的源文件会报错。
  • -o 指定生成的 obj 或可执行程序文件名称。未指定时,保存到当前目录,可执行程序名为 a.out。
gcc -c foo.c     # produces foo.o
gcc -c bar.c     # produces bar.o
gcc -o foo foo.o bar.o # 链接为可执行程序
gcc main.c file1.o file2.o -o myprogram # c 源文件和 obj 文件混合

# 或者一步生成可执行程序
gcc -o foo foo.c bar.c

每次编译,生成 obj 文件是比较耗时的, 但是链接是很快的。分别生成 obj 文件的好处是可以按需只重新编译源文件发生变化的代码, 没变化的代码的 obj 文件可以复用, 从而缩短了整体编译的时间。

  • 使用 make 命令来发现变化和按需构建。

在链接生成可执行程序时,ld 先后进行符号解析(Symbol Resolution)和重定位(Relocation),需要将最通用/基础的库放到后面, 例如 libA 和 libB 都依赖 libC, 则链接时指定的顺序:

  1. 正确: -lA -lB -lC;
  2. 错误: -lC -lA -lB;

这是因为连接器是根据 -l 指定的 obj 或文件顺序来依次解析使用到的符号, 如果前一个 lib 中的符号没有被后续 lib 来引用,则前一个 lib 在处理完后, 它就会被忽略。

  • 如果在命令行上指定多个 c 源文件来编程生成可执行程序,各源文件的顺序也是如上考虑:
# 包含 main 函数的放到最前面,被依赖的库放后面
gcc -o awesomegame ui.c characters.c npc.c items.c

目前还没有自动化的方式来自动排序这些有相互依赖的 obj 文件,但可以通过如下两种方式来解决:

  1. 重复指定 obj 或 lib 文件;
  2. 使用 ld 的 –start-group archives –end-group

静态和动态库
#

在 C 语言项目中,编译多个库文件和主程序通常涉及以下几个步骤:编译源文件、创建静态库或动态库、以及链接库文件和主程序。

使用 gcc 编译多个库文件和主程序的具体步骤如下:

  1. 编译源文件:首先,使用 -c 参数将每个源文件编译成目标文件(.o 文件):
gcc -c file1.c -o file1.o # 编译单个源文件的顺序是无关紧要的,但是链接它们时要考虑链接顺序
gcc -c file2.c -o file2.o # 如果未指定 -o,默认都保存到当前工作目录

# 一次性编译多个源文件成目标文件
gcc -I./include -c lib/file1.c lib/file2.c
ar rcs lib/libmylib.a file1.o file2.o
  1. 创建静态库或动态库

创建静态库:使用 ar 工具将多个目标文件打包成一个静态库(.a 文件)

gcc -I./include -c lib/file1.c lib/file2.c
ar rcs lib/libmylib.a file1.o file2.o

创建动态库: 使用 gcc 将目标文件打包成一个动态库(.so 文件)

// 先将各源文件编译为 PIC 的独立 obj 文件
gcc -fPIC -c file1.c file2.c file3.c

// 再将个 obj 文件打包为动态库
gcc -shared -o libmylib.so file1.o file2.o file3.o

// 或者,一次性编译并创建动态库 libmylib.so
gcc -shared -o libmylib.so file1.c file2.c
  1. 编译主程序并链接库

编译主程序并链接静态库或动态库。

链接静态库: 当链接静态库时,只需要在编译主程序时指定库路径和库名称(不需要加前缀 lib 和扩展名 .a)。

gcc main.c -L. -lmylib -o myprogram

链接动态库: 当链接动态库时,除了指定库路径和库名称外,还需要确保运行时能够找到动态库。可以使用 -rpath 选项指定运行时库路径,或者设置环境变量 LD_LIBRARY_PATH。

gcc main.c -L. -lmylib -o myprogram -Wl,-rpath,.

示例:假设我们有以下文件结构:

project/
├── main.c
├── lib/
│   ├── file1.c
│   ├── file2.c
│   ├── file1.h
│   └── file2.h
└── include/
    ├── file1.h
    └── file2.h

编译库文件:

gcc -I./include -c lib/file1.c -o lib/file1.o  # 不加 -o 时默认输出到当前工作目录的 file1.o
gcc -I./include -c lib/file2.c -o lib/file2.o

创建静态库:

ar rcs lib/libmylib.a lib/file1.o lib/file2.o

或 创建动态库:

gcc -shared -o lib/libmylib.so lib/file1.o lib/file2.o

编译和链接主程序:假设 main.c 包含库头文件并调用库函数:

// main.c
#include "file1.h"
#include "file2.h"

int main() {
    function_from_file1();
    function_from_file2();
    return 0;
}

链接静态库:

gcc -I./include main.c -L./lib -lmylib -o myprogram

链接动态库:

gcc -I./include main.c -L./lib -lmylib -o myprogram -Wl,-rpath,./lib

环境变量 LD_LIBRARY_PATH: 如果没有使用 -rpath 选项,可以通过设置 LD_LIBRARY_PATH 环境变量来指定动态库的路径:

export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH
./myprogram

链接顺序
#

参考:https://akaedu.github.io/book/ch20s01.html

由库之间的依赖关系决定。通常,链接顺序应该从具体到抽象, 即先链接项目自己的库,再链接外部依赖库,后面的库需要满足前的库的符号需求。

链接顺序会影响符号解析 ,因为链接器会在遇到第一个满足未解析符号的库或对象文件时停止搜索。因此,库文件的顺序非常重要,尤其是在处理相互依赖的多个库时。

  1. 命令行顺序:链接器按照命令行中库文件和对象文件的 顺序进行处理
  2. 符号解析:链接器遇到一个对象文件或库文件时,它会尝试解析该文件中引用的 所有符号
  3. 库文件顺序:如果链接器在当前库文件中找不到某个符号,它会 继续在后续的库文件中查找 。如果后续的库文件中包含该符号,那么符号就会被解析。
    • 链接器不会反过来再在前面的 lib 文件中查找未解析的符号,所以越是特殊的库越应该放前面。

假设有两个静态库 libA.a 和 libB.a,其中 libA.a 依赖于 libB.a 中的符号:

gcc -o myprogram main.o -lA -lB

在这个例子中,libB.a 提供的符号可以用于解析 libA.a 中的未定义符号,因为 libB.a 在 libA.a 之后。反之,如果顺序颠倒,libB.a 中的符号将无法被 libA.a 使用:

gcc -o myprogram main.o -lB -lA

此时,如果 libA.a 依赖于 libB.a,则链接器会报告未定义符号错误。

交叉依赖的解决方法: 对于交叉依赖的情况,可以使用多次列出库文件或使用 --start-group 和 --end-group 选项

# 多次列出库文件
gcc -o myprogram main.o -lA -lB -lA

# 使用 --start-group 和 --end-group
gcc -o myprogram main.o --start-group -lA -lB --end-group

关于 ld 的 –start-group 和 –end-group 的参数解释:

–start-group archives –end-group

The archives should be a list of archive files. They may be either explicit file names, or ‘-l’ options.

The specified archives are searched repeatedly until no new undefined references are created . Normally, an archive is searched only once in the order that it is specified on the command line. If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference. By grouping the archives, they will all be searched repeatedly until all possible references are resolved.

还可以使用 Makefile 来显示指定它们的链接顺序:假设你有三个对象文件 main.o、a.o 和 b.o,其中 main.o依赖于 a.o,a.o 依赖于 b.o;

# Compiler and flags
CC = gcc
CFLAGS = -Wall -Iinclude

# Directories
SRC_DIR = src
LIB_DIR = lib
EXT_LIB_DIR = ext_lib

# Source files
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
LIB_FILES = $(wildcard $(LIB_DIR)/*.c)

# Object files
OBJ_FILES = $(SRC_FILES:.c=.o) $(LIB_FILES:.c=.o)

# External libraries
EXT_LIBS = -L$(EXT_LIB_DIR) -lotherlib

# Output executable
OUTPUT = myprogram

# Default target
all: $(OUTPUT)

# Link object files with specified order
$(OUTPUT): main.o a.o b.o
	$(CC) -o $@ $^ $(EXT_LIBS)  # $^ 会根据顺序包含所有的 prerequisite

# Compile source files
%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

# Clean up
clean:
	rm -f $(SRC_DIR)/*.o $(LIB_DIR)/*.o $(OUTPUT)

.PHONY: all clean

链接脚本
#

ld 在进行 Relocation 时使用链接脚本的配置来决定生成的 ELF 文件中 Section 布局, 使用 –verbose 选项查看 ld 命令的默认链接脚本:

  • ENTRY(_start): 程序的入口;
  • SEARCH_DIR: 链接搜索路径;
  • SECTIONS: ELF 文件中的 section 定义, 用于链接;
  • SEGMENTS: ELF 文件中的 segment 定义, 用于执行;
zhangjun@lima-ebpf-dev:/usr/include$ ld --verbose
GNU ld (GNU Binutils for Ubuntu) 2.38
 Supported emulations:
  elf_x86_64
  elf32_x86_64
  elf_i386
  elf_iamcu
  elf_l1om
  elf_k1om
  i386pep
  i386pe
using internal linker script:
==================================================
/* Script for -z combreloc -z separate-code */
/* Copyright (C) 2014-2022 Free Software Foundation, Inc.
  Copying and distribution of this script, with or without modification,
  are permitted in any medium without royalty provided the copyright
  notice and this notice are preserved.  */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu");
SEARCH_DIR("=/lib/x86_64-linux-gnu");
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu");
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64");
SEARCH_DIR("=/usr/local/lib64");
SEARCH_DIR("=/lib64");
SEARCH_DIR("=/usr/lib64");
SEARCH_DIR("=/usr/local/lib");
SEARCH_DIR("=/lib");
SEARCH_DIR("=/usr/lib");
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64");
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
SECTIONS
{
 PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
 .interp         : { *(.interp) }
 .note.gnu.build-id  : { *(.note.gnu.build-id) }
 .hash           : { *(.hash) }
 .gnu.hash       : { *(.gnu.hash) }
 .dynsym         : { *(.dynsym) }
 .dynstr         : { *(.dynstr) }
 .gnu.version    : { *(.gnu.version) }
 .gnu.version_d  : { *(.gnu.version_d) }
 .gnu.version_r  : { *(.gnu.version_r) }
 .rela.dyn       :
   {
     *(.rela.init)
     *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
     *(.rela.fini)
     *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
     *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
     *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
     *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
     *(.rela.ctors)
     *(.rela.dtors)
     *(.rela.got)
     *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
     *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
     *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
     *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
     *(.rela.ifunc)
   }
 .rela.plt       :
   {
     *(.rela.plt)
     PROVIDE_HIDDEN (__rela_iplt_start = .);
     *(.rela.iplt)
     PROVIDE_HIDDEN (__rela_iplt_end = .);
   }
 .relr.dyn : { *(.relr.dyn) }
 . = ALIGN(CONSTANT (MAXPAGESIZE));
 .init           :
 {
   KEEP (*(SORT_NONE(.init)))
 }
 .plt            : { *(.plt) *(.iplt) }
.plt.got        : { *(.plt.got) }
.plt.sec        : { *(.plt.sec) }
 .text           :
 {
   *(.text.unlikely .text.*_unlikely .text.unlikely.*)
   *(.text.exit .text.exit.*)
   *(.text.startup .text.startup.*)
   *(.text.hot .text.hot.*)
   *(SORT(.text.sorted.*))
   *(.text .stub .text.* .gnu.linkonce.t.*)
   /* .gnu.warning sections are handled specially by elf.em.  */
   *(.gnu.warning)
 }
 .fini           :
 {
   KEEP (*(SORT_NONE(.fini)))
 }
 PROVIDE (__etext = .);
 PROVIDE (_etext = .);
 PROVIDE (etext = .);
 . = ALIGN(CONSTANT (MAXPAGESIZE));
 /* Adjust the address for the rodata segment.  We want to adjust up to
    the same address within the page on the next page up.  */
 . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));
 .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
 .rodata1        : { *(.rodata1) }
 .eh_frame_hdr   : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
 .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
 .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
 .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
 /* These sections are generated by the Sun/Oracle C++ compiler.  */
 .exception_ranges   : ONLY_IF_RO { *(.exception_ranges*) }
 /* Adjust the address for the data segment.  We want to adjust up to
    the same address within the page on the next page up.  */
 . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
 /* Exception handling  */
 .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
 .gnu_extab      : ONLY_IF_RW { *(.gnu_extab) }
 .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
 .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
 /* Thread Local Storage sections  */
 .tdata          :
  {
    PROVIDE_HIDDEN (__tdata_start = .);
    *(.tdata .tdata.* .gnu.linkonce.td.*)
  }
 .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
 .preinit_array    :
 {
   PROVIDE_HIDDEN (__preinit_array_start = .);
   KEEP (*(.preinit_array))
   PROVIDE_HIDDEN (__preinit_array_end = .);
 }
 .init_array    :
 {
   PROVIDE_HIDDEN (__init_array_start = .);
   KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
   KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
   PROVIDE_HIDDEN (__init_array_end = .);
 }
 .fini_array    :
 {
   PROVIDE_HIDDEN (__fini_array_start = .);
   KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
   KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
   PROVIDE_HIDDEN (__fini_array_end = .);
 }
 .ctors          :
 {
   /* gcc uses crtbegin.o to find the start of
      the constructors, so we make sure it is
      first.  Because this is a wildcard, it
      doesn't matter if the user does not
      actually link against crtbegin.o; the
      linker won't look for a file to match a
      wildcard.  The wildcard also means that it
      doesn't matter which directory crtbegin.o
      is in.  */
   KEEP (*crtbegin.o(.ctors))
   KEEP (*crtbegin?.o(.ctors))
   /* We don't want to include the .ctor section from
      the crtend.o file until after the sorted ctors.
      The .ctor section from the crtend file contains the
      end of ctors marker and it must be last */
   KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
   KEEP (*(SORT(.ctors.*)))
   KEEP (*(.ctors))
 }
 .dtors          :
 {
   KEEP (*crtbegin.o(.dtors))
   KEEP (*crtbegin?.o(.dtors))
   KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
   KEEP (*(SORT(.dtors.*)))
   KEEP (*(.dtors))
 }
 .jcr            : { KEEP (*(.jcr)) }
 .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
 .dynamic        : { *(.dynamic) }
 .got            : { *(.got) *(.igot) }
 . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
 .got.plt        : { *(.got.plt) *(.igot.plt) }
 .data           :
 {
   *(.data .data.* .gnu.linkonce.d.*)
   SORT(CONSTRUCTORS)
 }
 .data1          : { *(.data1) }
 _edata = .; PROVIDE (edata = .);
 . = .;
 __bss_start = .;
 .bss            :
 {
  *(.dynbss)
  *(.bss .bss.* .gnu.linkonce.b.*)
  *(COMMON)
  /* Align here to ensure that the .bss section occupies space up to
     _end.  Align after .bss to ensure correct alignment even if the
     .bss section disappears because there are no input sections.
     FIXME: Why do we need it? When there is no .bss section, we do not
     pad the .data section.  */
  . = ALIGN(. != 0 ? 64 / 8 : 1);
 }
 .lbss   :
 {
   *(.dynlbss)
   *(.lbss .lbss.* .gnu.linkonce.lb.*)
   *(LARGE_COMMON)
 }
 . = ALIGN(64 / 8);
 . = SEGMENT_START("ldata-segment", .);
 .lrodata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
 {
   *(.lrodata .lrodata.* .gnu.linkonce.lr.*)
 }
 .ldata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
 {
   *(.ldata .ldata.* .gnu.linkonce.l.*)
   . = ALIGN(. != 0 ? 64 / 8 : 1);
 }
 . = ALIGN(64 / 8);
 _end = .; PROVIDE (end = .);
 . = DATA_SEGMENT_END (.);
 /* Stabs debugging sections.  */
 .stab          0 : { *(.stab) }
 .stabstr       0 : { *(.stabstr) }
 .stab.excl     0 : { *(.stab.excl) }
 .stab.exclstr  0 : { *(.stab.exclstr) }
 .stab.index    0 : { *(.stab.index) }
 .stab.indexstr 0 : { *(.stab.indexstr) }
 .comment       0 : { *(.comment) }
 .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
 /* DWARF debug sections.
    Symbols in the DWARF debugging sections are relative to the beginning
    of the section so we begin them at 0.  */
 /* DWARF 1.  */
 .debug          0 : { *(.debug) }
 .line           0 : { *(.line) }
 /* GNU DWARF 1 extensions.  */
 .debug_srcinfo  0 : { *(.debug_srcinfo) }
 .debug_sfnames  0 : { *(.debug_sfnames) }
 /* DWARF 1.1 and DWARF 2.  */
 .debug_aranges  0 : { *(.debug_aranges) }
 .debug_pubnames 0 : { *(.debug_pubnames) }
 /* DWARF 2.  */
 .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
 .debug_abbrev   0 : { *(.debug_abbrev) }
 .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
 .debug_frame    0 : { *(.debug_frame) }
 .debug_str      0 : { *(.debug_str) }
 .debug_loc      0 : { *(.debug_loc) }
 .debug_macinfo  0 : { *(.debug_macinfo) }
 /* SGI/MIPS DWARF 2 extensions.  */
 .debug_weaknames 0 : { *(.debug_weaknames) }
 .debug_funcnames 0 : { *(.debug_funcnames) }
 .debug_typenames 0 : { *(.debug_typenames) }
 .debug_varnames  0 : { *(.debug_varnames) }
 /* DWARF 3.  */
 .debug_pubtypes 0 : { *(.debug_pubtypes) }
 .debug_ranges   0 : { *(.debug_ranges) }
 /* DWARF 5.  */
 .debug_addr     0 : { *(.debug_addr) }
 .debug_line_str 0 : { *(.debug_line_str) }
 .debug_loclists 0 : { *(.debug_loclists) }
 .debug_macro    0 : { *(.debug_macro) }
 .debug_names    0 : { *(.debug_names) }
 .debug_rnglists 0 : { *(.debug_rnglists) }
 .debug_str_offsets 0 : { *(.debug_str_offsets) }
 .debug_sup      0 : { *(.debug_sup) }
 .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
 /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}


==================================================
zhangjun@lima-ebpf-dev:/usr/include$

链接搜索路径
#

除了 -L 指定库搜索路径外,还可以使用 LIBRARY_PATH: (but directories specified with -L come first) 来为 gcc 指定编译链接时的动态库搜索路径。

  • 注意不是 LD_LIBRARY_PATH,后者是运行动态链接的二进制时查找动态库路径。
  • 注意 /etc/ld.so.conf 配置的路径不会在构建(链接阶段)搜索(该配置文件影响运行阶段的搜索)。

除了自定义库搜索路径外,还会搜索链接器脚本中配置的默认搜索路径,以 ubunut 24.04 为例:

alizj@ubuntu:~$ ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\012
SEARCH_DIR("=/usr/local/lib/aarch64-linux-gnu")
SEARCH_DIR("=/lib/aarch64-linux-gnu")
SEARCH_DIR("=/usr/lib/aarch64-linux-gnu")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/aarch64-linux-gnu/lib")

如上配置的搜索优先级顺序为:

  1. 命令行 -L 参数指定的路径(包含用户自定义的以及 gcc 添加的)。
  2. 环境变量 LIBRARY_PATH 指定的路径。
  3. 链接器 linker script 配置的默认搜索路径。

使用命令 echo 'main(){}' | gcc -E -v - 查看上面两个头文件搜索路径(gcc -v 显示详细执行命令):

alizj@ubuntu:/Users/alizj/docs/lang/c$ echo 'main(){}' | gcc -E -v -
Using built-in specs.
COLLECT_GCC=gcc
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: aarch64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.3.0-6ubuntu2~24.04' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-offload-targets=nvptx-none=/build/gcc-13-Nz4ro4/gcc-13-13.3.0/debian/tmp-nvptx/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
COLLECT_GCC_OPTIONS='-E' '-v' '-mlittle-endian' '-mabi=lp64'
 /usr/libexec/gcc/aarch64-linux-gnu/13/cc1 -E -quiet -v -imultiarch aarch64-linux-gnu - -mlittle-endian -mabi=lp64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -dumpbase -
ignoring nonexistent directory "/usr/local/include/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/13/include-fixed/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/13/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/aarch64-linux-gnu/13/include
 /usr/local/include
 /usr/include/aarch64-linux-gnu
 /usr/include
End of search list.
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "<stdin>"
main(){}
COMPILER_PATH=/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib/:/lib/aarch64-linux-gnu/:/lib/../lib/:/usr/lib/aarch64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mlittle-endian' '-mabi=lp64'

或者命令:

alizj@ubuntu:/Users/alizj/docs/lang/c$ gcc -v -xc /dev/null -fsyntax-only
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/aarch64-linux-gnu/13/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: aarch64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.3.0-6ubuntu2~24.04' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-offload-targets=nvptx-none=/build/gcc-13-Nz4ro4/gcc-13-13.3.0/debian/tmp-nvptx/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mlittle-endian' '-mabi=lp64' '-dumpdir' 'a-'
 /usr/libexec/gcc/aarch64-linux-gnu/13/cc1 -quiet -v -imultiarch aarch64-linux-gnu /dev/null -quiet -dumpdir a- -dumpbase null -mlittle-endian -mabi=lp64 -version -fsyntax-only -o /dev/null -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection
GNU C17 (Ubuntu 13.3.0-6ubuntu2~24.04) version 13.3.0 (aarch64-linux-gnu)
        compiled by GNU C version 13.3.0, GMP version 6.3.0, MPFR version 4.2.1, MPC version 1.3.1, isl version isl-0.26-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/13/include-fixed/aarch64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/13/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/aarch64-linux-gnu/13/include
 /usr/local/include
 /usr/include/aarch64-linux-gnu
 /usr/include
End of search list.
Compiler executable checksum: 4641e2542d7b0fa864451cdc7c3485d3
COMPILER_PATH=/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib/:/lib/aarch64-linux-gnu/:/lib/../lib/:/usr/lib/aarch64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mlittle-endian' '-mabi=lp64' '-dumpdir' 'a.'

运行时动态链接&查找
#

https://www.rectcircle.cn/posts/linux-dylib-detail-3-search/

在可执行文件执行过程中,动态库的查找发生在程序装载阶段,由操作系统内核调用 ld.so (/lib64/ld-linux-x86-64.so.2) 这个 so 内的代码实现。

默认情况下, ld.so 查找 /etc/ld.so.conf (引用了 /etc/ld.so.conf.d/*.conf) 配置的默认搜索路径,以 debian 11 为例,其默认值为:

  • /usr/local/lib
  • /usr/local/lib/x86_64-linux-gnu
  • /lib/x86_64-linux-gnu
  • /usr/lib/x86_64-linux-gnu

可以通过修改 /etc/ld.so.conf (并执行 sudo ldconfig 来更新缓存 /etc/ld.so.cache,通过 ldconfig -p 可查看发现的所有库文件) 配置文件来修改系统的运行动态库搜索路径。

除了上述方式外,还可以通过如下方式添加动态库搜索路径:

  • LD_LIBRARY_PATH 环境变量。
  • 在构建(链接阶段)时,通过 -rpath 参数指定运行时动态库搜索路径并写入到可执行文件 ELF .dynamic 段的 DT_RPATH 或 DT_RUNPATH,通过 readelf -d 可查询。
  • LD_AUDIT 环境变量指定一个 xxx.so 文件,可以使用 C 语言实现更加智能灵活的动态库搜索。

如上配置的搜索优先级顺序为:

  1. 环境变量 LD_LIBRARY_PATH 指定的路径。
  2. 可执行文件中 ELF .dynamic 段 DT_RPATH 或 DT_RUNPATH 指定的路径。
  3. /etc/ld.so.conf 配置的哪些。

验证如下:01-sample/04-runtime-stage-search.sh

#!/usr/bin/env bash

cd $(dirname $(readlink -f $0))
rm -rf build/bin && mkdir -p build/bin


echo '=== 准备: 指定 -rpath'
gcc -I ./build/include -o ./build/bin/main ./main.c -L ./build/lib -l sample -Wl,-rpath,/tmp/by_-rpath
echo


echo '=== 验证: 观察查找路径'
mkdir -p /tmp/by_LD_LIBRARY_PATH /tmp/by_-rpath
LD_DEBUG=libs LD_LIBRARY_PATH=/tmp/by_LD_LIBRARY_PATH ./build/bin/main
echo
输出如下:

=== 准备: 指定 -rpath

=== 验证: 观察查找路径
   655097:     find library=libsample.so [0]; searching
   655097:      search path=/tmp/by_LD_LIBRARY_PATH/glibc-hwcaps/x86-64-v2:/tmp/by_LD_LIBRARY_PATH/tls/x86_64/x86_64:/tmp/by_LD_LIBRARY_PATH/tls/x86_64:/tmp/by_LD_LIBRARY_PATH/tls/x86_64:/tmp/by_LD_LIBRARY_PATH/tls:/tmp/by_LD_LIBRARY_PATH/x86_64/x86_64:/tmp/by_LD_LIBRARY_PATH/x86_64:/tmp/by_LD_LIBRARY_PATH/x86_64:/tmp/by_LD_LIBRARY_PATH         (LD_LIBRARY_PATH)
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/glibc-hwcaps/x86-64-v2/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/tls/x86_64/x86_64/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/tls/x86_64/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/tls/x86_64/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/tls/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/x86_64/x86_64/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/x86_64/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/x86_64/libsample.so
   655097:       trying file=/tmp/by_LD_LIBRARY_PATH/libsample.so
   655097:      search path=/tmp/by_-rpath/glibc-hwcaps/x86-64-v2:/tmp/by_-rpath/tls/x86_64/x86_64:/tmp/by_-rpath/tls/x86_64:/tmp/by_-rpath/tls/x86_64:/tmp/by_-rpath/tls:/tmp/by_-rpath/x86_64/x86_64:/tmp/by_-rpath/x86_64:/tmp/by_-rpath/x86_64:/tmp/by_-rpath (RUNPATH from file ./build/bin/main)
   655097:       trying file=/tmp/by_-rpath/glibc-hwcaps/x86-64-v2/libsample.so
   655097:       trying file=/tmp/by_-rpath/tls/x86_64/x86_64/libsample.so
   655097:       trying file=/tmp/by_-rpath/tls/x86_64/libsample.so
   655097:       trying file=/tmp/by_-rpath/tls/x86_64/libsample.so
   655097:       trying file=/tmp/by_-rpath/tls/libsample.so
   655097:       trying file=/tmp/by_-rpath/x86_64/x86_64/libsample.so
   655097:       trying file=/tmp/by_-rpath/x86_64/libsample.so
   655097:       trying file=/tmp/by_-rpath/x86_64/libsample.so
   655097:       trying file=/tmp/by_-rpath/libsample.so
   655097:      search cache=/etc/ld.so.cache
   655097:      search path=/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2:/lib/x86_64-linux-gnu/tls/x86_64/x86_64:/lib/x86_64-linux-gnu/tls/x86_64:/lib/x86_64-linux-gnu/tls/x86_64:/lib/x86_64-linux-gnu/tls:/lib/x86_64-linux-gnu/x86_64/x86_64:/lib/x86_64-linux-gnu/x86_64:/lib/x86_64-linux-gnu/x86_64:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2:/usr/lib/x86_64-linux-gnu/tls/x86_64/x86_64:/usr/lib/x86_64-linux-gnu/tls/x86_64:/usr/lib/x86_64-linux-gnu/tls/x86_64:/usr/lib/x86_64-linux-gnu/tls:/usr/lib/x86_64-linux-gnu/x86_64/x86_64:/usr/lib/x86_64-linux-gnu/x86_64:/usr/lib/x86_64-linux-gnu/x86_64:/usr/lib/x86_64-linux-gnu:/lib/glibc-hwcaps/x86-64-v2:/lib/tls/x86_64/x86_64:/lib/tls/x86_64:/lib/tls/x86_64:/lib/tls:/lib/x86_64/x86_64:/lib/x86_64:/lib/x86_64:/lib:/usr/lib/glibc-hwcaps/x86-64-v2:/usr/lib/tls/x86_64/x86_64:/usr/lib/tls/x86_64:/usr/lib/tls/x86_64:/usr/lib/tls:/usr/lib/x86_64/x86_64:/usr/lib/x86_64:/usr/lib/x86_64:/usr/lib                (system search path)
   655097:       trying file=/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/tls/x86_64/x86_64/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/tls/x86_64/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/tls/x86_64/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/tls/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/x86_64/x86_64/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/x86_64/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/x86_64/libsample.so
   655097:       trying file=/lib/x86_64-linux-gnu/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/tls/x86_64/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/tls/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/tls/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/tls/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/x86_64/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64-linux-gnu/libsample.so
   655097:       trying file=/lib/glibc-hwcaps/x86-64-v2/libsample.so
   655097:       trying file=/lib/tls/x86_64/x86_64/libsample.so
   655097:       trying file=/lib/tls/x86_64/libsample.so
   655097:       trying file=/lib/tls/x86_64/libsample.so
   655097:       trying file=/lib/tls/libsample.so
   655097:       trying file=/lib/x86_64/x86_64/libsample.so
   655097:       trying file=/lib/x86_64/libsample.so
   655097:       trying file=/lib/x86_64/libsample.so
   655097:       trying file=/lib/libsample.so
   655097:       trying file=/usr/lib/glibc-hwcaps/x86-64-v2/libsample.so
   655097:       trying file=/usr/lib/tls/x86_64/x86_64/libsample.so
   655097:       trying file=/usr/lib/tls/x86_64/libsample.so
   655097:       trying file=/usr/lib/tls/x86_64/libsample.so
   655097:       trying file=/usr/lib/tls/libsample.so
   655097:       trying file=/usr/lib/x86_64/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64/libsample.so
   655097:       trying file=/usr/lib/x86_64/libsample.so
   655097:       trying file=/usr/lib/libsample.so
   655097:
./build/bin/main: error while loading shared libraries: libsample.so: cannot open shared object file: No such file or directory

glibc 打包和运行时动态链接
#

https://www.rectcircle.cn/posts/linux-build-once-run-anywhere/

Linux 平台的软件更新的版本,对动态链接库(特别是 glibc)的最低版本要求不断提高。而超过了维护周期的 Linux 发行版(如 Debian8),其动态链接库将得不到升级,从而导致较新版本的 Linux 软件无法再遗留的 Linux 发行版中运行(当然内核版本是另一个重要原因,本文重点关注 glibc 问题,关于内核版本不多讨论)。

编译 glibc 以及其他 so

使用如下命令,编译一个 glibc 2.31,这些命令需要再较新版本的 Linux 中执行(如 debian 11)。

# 在 debian 11 中执行
wget http://ftp.gnu.org/gnu/glibc/glibc-2.31.tar.gz
tar -zxvf glibc-2.31.tar.gz
glibc_prefix=$(pwd)/glibc-2.31-target
cd glibc-2.31/
rm -rf build && mkdir -p build && cd build
sudo apt update && sudo apt install -y gcc make gdb texinfo gawk bison sed python3-dev python3-pip
../configure --prefix=$glibc_prefix
make -j4
make install
cd ../../
# nodejs 依赖 libstdc++.6 和 libgcc_s.so.1
# (最好重新编译,这里简单复制一下,要求该操作系统的 glibc 版本就是 2.31,debian11 满足需求)
cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 ./glibc-2.31-target/lib
ln -s libstdc++.so.6.0.28 ./glibc-2.31-target/lib/libstdc++.so.6
cp /lib/x86_64-linux-gnu/libgcc_s.so.1 ./glibc-2.31-target/lib

# 压缩
tar -czvf glibc-2.31-target.tar.gz glibc-2.31-target/

传输 glibc-2.31-target.tar.gz 到 debian 8 版本中验证。

修改 ELF 动态链接库加载器
#

NixOS 提供了一个实用工具 patchelf,该工具可以修改可执行文件的动态链接库加载器路径(参考: stackoverflow)。

# 在 debian 8 中执行
sudo apt install -y patchelf
patchelf --set-interpreter $(pwd)/glibc-2.31-target/lib/ld-linux-x86-64.so.2 ./node-v18.12.0-linux-x64/bin/node

# 也可以安装 github 上的静态编译版本
# wget https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-x86_64.tar.gz
# tar -zxvf patchelf-0.18.0-x86_64.tar.gz -C patchelf/

验证

# 在 debian 8 中执行
./node-v18.12.0-linux-x64/bin/node

# 可以正确进入解释器。
# Welcome to Node.js v18.12.0.
# Type ".help" for more information.
# >

链接过程中的强符号和弱符号 :noexport:
#

https://www.zhaixue.cc/c-arm/c-arm-weak-attribute.html

内联函数探究 : https://www.zhaixue.cc/c-arm/c-arm-inline.html 有一种函数,叫内建函数 : https://www.zhaixue.cc/c-arm/c-arm-builtin.html 有一种宏,叫可变参数宏 : https://www.zhaixue.cc/c-arm/c-arm-macro.html

相关文章

C 预处理器-个人参考手册
··8475 字
Gnu Cpp
这是我个人的 C 预处理器参考手册文档。
C 语言-个人参考手册
··51795 字
C
我总结和使用的 C 语言参考手册。
Makefile-个人参考手册
··8575 字
Make Makefile Tools
这是我个人的 Makefile 参考手册。