如何使用自定义版本的glibc和静态链接构建C程序?

我已经构建了glibc 2.14并将其安装在目录~/GLIBC/glibc_install 。 现在我想使用这个C库而不是系统的默认C库来构建和运行程序。

  • 为了确保我正在使用我的自定义glibc,我添加了一个调用glibc/stdio-common/printf.c:__printfglibc/stdio-common/printf.c:__printf来打印消息。

  • 然后我重建并重新安装了glibc。

  • 然后我写了一个“Hello,World”程序,并尝试编译并链接如下:

     gcc -nodefaultlibs -static -lgcc -L~/GLIBC/glibc_install/lib -o myprog myprog.c 

但是我得到以下链接器错误报告:

 /usr/lib/gcc/x86_64-linux-gnu/4.4.3/../../../../lib/crt1.o: In function `_start': (.text+0x19): undefined reference to `__libc_csu_init' /usr/lib/gcc/x86_64-linux-gnu/4.4.3/../../../../lib/crt1.o: In function `_start': (.text+0x25): undefined reference to `__libc_start_main' /tmp/ccACTQEp.o: In function `main': c1.c:(.text+0xa): undefined reference to `puts' collect2: ld returned 1 exit status 

我究竟做错了什么?

根据glibc帮助邮件列表(libc-help@sourceware.org)的一些建议,我有一个解决方案。 事实certificate,这个任务有点棘手,因为你必须告诉链接器省略它通常自动(和静默)包含的所有内容,然后包括它需要的所有东西,包括一堆开始和结束文件。 一些开始和结束文件来自libc,一些来自gcc,因此make规则有点复杂。 下面是一个通用示例makefile来说明该方法。 我将假设您正在从名为prog.c的源文件构建一个名为prog的程序 ,并且已在目录/ home / my_acct / glibc_install中安装了自定义glibc。

 TARGET = prog OBJ = $(TARGET).o SRC = $(TARGET).c CC = gcc CFLAGS = -g LDFLAGS = -nostdlib -nostartfiles -static GLIBCDIR = /home/my_acct/glibc_install/lib STARTFILES = $(GLIBCDIR)/crt1.o $(GLIBCDIR)/crti.o `gcc --print-file-name=crtbegin.o` ENDFILES = `gcc --print-file-name=crtend.o` $(GLIBCDIR)/crtn.o LIBGROUP = -Wl,--start-group $(GLIBCDIR)/libc.a -lgcc -lgcc_eh -Wl,--end-group $(TARGET): $(OBJ) $(CC) $(LDFLAGS) -o $@ $(STARTFILES) $^ $(LIBGROUP) $(ENDFILES) $(OBJ): $(SRC) $(CC) $(CFLAGS) -c $^ clean: rm -f *.o *.~ $(TARGET) 

你的命令行只是假的。 尝试:

 gcc -nodefaultlibs -static -L~/GLIBC/glibc_install/lib -o myprog myprog.c -lgcc -lc -lgcc -lc 

或类似的。 您省略了-lc ,并且在输入文件之前错误地拥有了库。

你正在寻找一个名为libibgcc而不是libgcc的库…

设置1:在没有专用GCC的情况下编译自己的glibc并使用它

没有静态也工作: 单个主机上的多个glibc库

这种设置可能会很快,因为它不会重新编译整个GCC工具链,只需要glibc。

但它不可靠,因为它使用glibc提供的主机C运行时对象,如crt1.ocrti.ocrtn.o 这可以参考: https : //sourceware.org/glibc/wiki/Testing/Builds? action = recall & rev = 21#Compile_against_glibc_in_an_installed_location那些对象做了glibc所依赖的早期设置,所以如果事情崩溃的话,我不会感到惊讶和令人敬畏的微妙方式。

有关更可靠的设置,请参阅下面的设置2。

构建glibc并在本地安装:

 export glibc_install="$(pwd)/glibc/build/install" git clone git://sourceware.org/git/glibc.git cd glibc git checkout glibc-2.28 mkdir build cd build ../configure --prefix "$glibc_install" make -j `nproc` make install -j `nproc` 

设置1:validation构建

test_glibc.c

 #define _GNU_SOURCE #include  #include  #include  #include  #include  atomic_int acnt; int cnt; int f(void* thr_data) { for(int n = 0; n < 1000; ++n) { ++cnt; ++acnt; } return 0; } int main(int argc, char **argv) { /* Basic library version check. */ printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version()); /* Exercise thrd_create from -pthread, * which is not present in glibc 2.27 in Ubuntu 18.04. * https://stackoverflow.com/questions/56810/how-do-i-start-threads-in-plain-c/52453291#52453291 */ thrd_t thr[10]; for(int n = 0; n < 10; ++n) thrd_create(&thr[n], f, NULL); for(int n = 0; n < 10; ++n) thrd_join(thr[n], NULL); printf("The atomic counter is %u\n", acnt); printf("The non-atomic counter is %u\n", cnt); } 

使用test_glibc.sh编译并运行:

 #!/usr/bin/env bash set -eux rm -rf tmp mkdir tmp gcc \ -L "${glibc_install}/lib" \ -I "${glibc_install}/include" \ -Wl,--rpath="${glibc_install}/lib" \ -Wl,--dynamic-linker="${glibc_install}/lib/ld-linux-x86-64.so.2" \ -static \ -std=c11 \ -o tmp/test_glibc.out \ -v \ test_glibc.c \ -pthread \ ; sudo chroot tmp /test_glibc.out 

该计划输出预期的:

 gnu_get_libc_version() = 2.28 The atomic counter is 10000 The non-atomic counter is 8674 

即使我们在一个干净的chroot上运行它,所以-static必须有效。

命令改编自https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location但--sysroot使其失败:

 cannot find /home/ciro/glibc/build/install/lib/libc.so.6 inside /home/ciro/glibc/build/install 

所以我删除了它。

ldd输出确认我们刚刚构建的ldd和库实际上正在按预期使用:

 + ldd test_glibc.out linux-vdso.so.1 (0x00007ffe4bfd3000) libpthread.so.0 => /home/ciro/glibc/build/install/lib/libpthread.so.0 (0x00007fc12ed92000) libc.so.6 => /home/ciro/glibc/build/install/lib/libc.so.6 (0x00007fc12e9dc000) /home/ciro/glibc/build/install/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc12f1b3000) 

gcc编译调试输出显示我的主机运行时对象被使用,如前所述这是不好的,但我不知道如何解决它,例如它包含:

 COLLECT_GCC_OPTIONS=/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o 

设置1:修改glibc

现在让我们修改glibc:

 diff --git a/nptl/thrd_create.cb/nptl/thrd_create.c index 113ba0d93e..b00f088abb 100644 --- a/nptl/thrd_create.c +++ b/nptl/thrd_create.c @@ -16,11 +16,14 @@ License along with the GNU C Library; if not, see . */ +#include  + #include "thrd_priv.h" int thrd_create (thrd_t *thr, thrd_start_t func, void *arg) { + puts("hacked"); _Static_assert (sizeof (thr) == sizeof (pthread_t), "sizeof (thr) != sizeof (pthread_t)"); 

然后重新编译并重新安装glibc,并重新编译并重新运行我们的程序:

 cd glibc/build make -j `nproc` make -j `nproc` install ./test_glibc.sh 

我们看到hacked按预期打印了几次。

这进一步证实我们实际上使用了我们编译的glibc而不是主机。

在Ubuntu 18.04上测试过。

设置2:crosstool-NG原始设置

这是设置1的替代方案,它是我已经实现的最正确的设置:就我所能观察到的一切都是正确的,包括C运行时对象,如crt1.ocrti.ocrtn.o

在这个设置中,我们将编译一个完整的专用GCC工具链,它使用我们想要的glibc。

这种方法唯一的缺点是构建需要更长的时间。 但我不会冒任何风险的生产设置。

crosstool-NG是一组脚本,可以从我们的源代码下载和编译所有内容,包括GCC,glibc和binutils。

是的,GCC构建系统非常糟糕,我们需要一个单独的项目。

这种设置只是不完美,因为crosstool-NG不支持在没有额外的-Wl标志的情况下构建可执行文件 ,因为我们自己构建了GCC,所以感觉很奇怪。 但一切似乎都有效,所以这只是一个不便之处。

获取crosstool-NG并配置它:

 git clone https://github.com/crosstool-ng/crosstool-ng cd crosstool-ng git checkout a6580b8e8b55345a5a342b5bd96e42c83e640ac5 export CT_PREFIX="$(pwd)/.build/install" export PATH="/usr/lib/ccache:${PATH}" ./bootstrap ./configure --enable-local make -j `nproc` ./ct-ng x86_64-unknown-linux-gnu ./ct-ng menuconfig 

我能看到的唯一强制选项是使其与主机内核版本匹配,以使用正确的内核头文件。 找到您的主机内核版本:

 uname -a 

这告诉我:

 4.15.0-34-generic 

所以在menuconfig我做:

  • Operating System
    • Version of linux

所以我选择:

 4.14.71 

这是第一个相同或更旧的版本。 它必须更旧,因为内核是向后兼容的。

现在你可以建立:

 env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc` 

现在等待大约30分钟到2个小时进行编译。

设置2:可选配置

我们用./ct-ng x86_64-unknown-linux-gnu生成的.config有:

 CT_GLIBC_V_2_27=y 

要更改它,在menuconfig执行:

  • C-library
  • Version of glibc

保存.config ,继续构建。

或者,如果你想使用自己的glibc源代码,例如使用最新git中的glibc,请按以下步骤操作:

  • Paths and misc options
    • Try features marked as EXPERIMENTAL :设置为true
  • C-library
    • Source of glibc
      • Custom location :说是
      • Custom location
        • Custom source location :指向包含glibc源的目录

glibc被克隆为:

 git clone git://sourceware.org/git/glibc.git cd glibc git checkout glibc-2.28 

设置2:测试它

一旦你构建了你想要的工具链,就用以下方法测试它:

 #!/usr/bin/env bash set -eux install_dir="${CT_PREFIX}/x86_64-unknown-linux-gnu" rm -rf tmp mkdir tmp PATH="${PATH}:${install_dir}/bin" \ x86_64-unknown-linux-gnu-gcc \ -Wl,--dynamic-linker="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib/ld-linux-x86-64.so.2" \ -Wl,--rpath="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib" \ -static \ -v \ -o tmp/test_glibc.out \ test_glibc.c \ -pthread \ ; sudo chroot tmp /test_glibc.out 

除了现在使用了正确的运行时对象之外,一切似乎都像在安装程序1中一样工作:

 COLLECT_GCC_OPTIONS=/home/ciro/crosstool-ng/.build/install/x86_64-unknown-linux-gnu/bin/../x86_64-unknown-linux-gnu/sysroot/usr/lib/../lib64/crt1.o 

设置2:高效的glibc重新编译尝试失败

如下所述,使用crosstool-NG似乎不可能。

如果你只是重建;

 env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc` 

然后考虑对自定义glibc源位置的更改,但它从头开始构建所有内容,使其无法用于迭代开发。

如果我们这样做:

 ./ct-ng list-steps 

它给出了构建步骤的一个很好的概述:

 Available build steps, in order: - companion_tools_for_build - companion_libs_for_build - binutils_for_build - companion_tools_for_host - companion_libs_for_host - binutils_for_host - cc_core_pass_1 - kernel_headers - libc_start_files - cc_core_pass_2 - libc - cc_for_build - cc_for_host - libc_post_cc - companion_libs_for_target - binutils_for_target - debug - test_suite - finish Use "" as action to execute only that step. Use "+" as action to execute up to that step. Use "+" as action to execute from that step onward. 

因此,我们看到glibc步骤与几个GCC步骤交织在一起,最明显的是libc_start_files之前,这可能是与cc_core_pass_1一起最昂贵的步骤。

要构建一个步骤,必须首先在.config选项中为初始构建设置“保存中间步骤”:

  • Paths and misc options
    • Debug crosstool-NG
      • Save intermediate steps

然后你可以尝试:

 env -u LD_LIBRARY_PATH time ./ct-ng libc+ -j`nproc` 

但不幸的是, +所需的内容如下所述: https : //github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

但请注意,在中间步骤重新启动会将安装目录重置为该步骤中的状态。 即,你将有一个重建的libc - 但没有使用这个libc构建的最终编译器(因此,没有像libstdc ++这样的编译器库)。

并且基本上仍然使重建太慢而不适合开发,我不知道如何在不修补crosstool-NG的情况下克服这个问题。

此外,从libc步骤开始似乎没有从Custom source location再次复制Custom source location ,进一步使此方法无法使用。

额外奖励:stdlibc ++

如果您对C ++标准库也感兴趣,可以获得奖励: 如何编辑和重新构建GCC libstdc ++ C ++标准库源代码?