如何使用自定义版本的glibc和静态链接构建C程序?
我已经构建了glibc 2.14并将其安装在目录~/GLIBC/glibc_install
。 现在我想使用这个C库而不是系统的默认C库来构建和运行程序。
-
为了确保我正在使用我的自定义glibc,我添加了一个调用
glibc/stdio-common/printf.c:__printf
到glibc/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.o
, crti.o
和crtn.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.o
, crti.o
和crtn.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 ++标准库源代码?