如何将 C/C++ 应用程序移植到旧版 Linux 内核版本

how to port c/c++ applications to legacy linux kernel versions

本文关键字:Linux 版本 内核 应用程序 C++      更新时间:2023-10-16

好吧,这只是一个有趣的练习,但是对于一些较旧的Linux系统编译程序不会太难,或者可以吗?

我可以访问几个运行 Linux 的古老系统,也许看看它们在负载下的表现会很有趣。例如,我们想使用 Eigen 做一些线性代数,这是一个很好的仅标题库。有机会在目标系统上编译它吗?

user@ancient:~ $ uname -a
Linux local 2.2.16 #5 Sat Jul 8 20:36:25 MEST 2000 i586 unknown
user@ancient:~ $ gcc --version
egcs-2.91.66

也许不是...因此,让我们在当前系统上编译它。以下是我的尝试,主要是失败的尝试。任何更多的想法非常欢迎。

  1. 使用-m32 -march=i386编译

    user@ancient:~ $ ./a.out
    BUG IN DYNAMIC LINKER ld.so: dynamic-link.h: 53: elf_get_dynamic_info: Assertion `! "bad dynamic tag"' failed!
    
  2. 使用 -m32 -march=i386 -static 编译:在所有相当新的内核版本上运行,但如果它们稍旧并显示众所周知的错误消息,则失败

    user@ancient:~ $ ./a.out
    FATAL: kernel too old
    Segmentation fault
    

    这是一个glibc错误,它支持的最低内核版本,例如我的系统上的内核 2.6.4:

    $ file a.out
    a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
    statically linked, for GNU/Linux 2.6.4, not stripped
    
  3. glibc自己编译,支持最旧的内核。这篇文章更详细地描述了它,但本质上是这样的

    wget ftp://ftp.gnu.org/gnu/glibc/glibc-2.14.tar.bz2
    tar -xjf glibc-2.14.tar.bz2
    cd glibc-2.14
    mkdir build; cd build
    ../configure --prefix=/usr/local/glibc_32  
                 --enable-kernel=2.0.0 
                 --with-cpu=i486 --host=i486-linux-gnu 
                 CC="gcc -m32 -march=i486"  CXX="g++ -m32 -march=i486"
    make -j 4
    make intall
    

    不确定 --with-cpu--host 选项是否有什么作用,最重要的是强制使用 32 位构建-m32 -march=i486编译器标志(不幸的是,-march=i386一段时间后会因错误而摆脱困境(和--enable-kernel=2.0.0使库与旧内核兼容。偶然地,在configure期间我收到了警告

    WARNING: minimum kernel version reset to 2.0.10
    

    我想这仍然是可以接受的。有关随不同内核而变化的内容的列表,请参阅 ./sysdeps/unix/sysv/linux/kernel-features.h

    好的,让我们链接到新编译的glibc库,有点混乱,但在这里:

    $ export LIBC_PATH=/usr/local/glibc_32
    $ export LIBC_FLAGS=-nostdlib -L${LIBC_PATH} 
                        ${LIBC_PATH}/crt1.o ${LIBC_PATH}/crti.o 
                        -lm -lc -lgcc -lgcc_eh -lstdc++ -lc 
                        ${LIBC_PATH}/crtn.o
    $ g++ -m32 -static prog.o ${LIBC_FLAGS} -o prog
    

    由于我们正在进行静态编译,因此链接顺序很重要,并且可能需要一些试验和错误,但基本上我们从gcc给链接器的选项中学习:

    $ g++ -m32 -static -Wl,-v file.o
    

    请注意,crtbeginT.ocrtend.o也是链接的,我的程序不需要,所以我把它们排除在外。输出还包括类似--start-group -lgcc -lgcc_eh -lc --end-group的行,指示库之间的相互依赖性,请参阅这篇文章。我刚刚在gcc命令行中提到了两次-lc,这也解决了相互依赖问题。

    是的,辛勤工作得到了回报,现在我得到了

    $ file ./prog
    ./prog: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
    statically linked, for GNU/Linux 2.0.10, not stripped
    

    我想太棒了,现在在旧系统上尝试一下:

    user@ancient:~ $ ./prog
    set_thread_area failed when setting up thread-local storage
    Segmentation fault
    

    这又是来自./nptl/sysdeps/i386/tls.hglibc错误消息。我不明白细节,放弃了。

  4. 在新系统上编译g++ -c -m32 -march=i386并在旧系统上链接。哇,这实际上适用于 C 和简单的 C++ 程序(不使用C++对象(,至少对于我测试过的少数几个。这并不奇怪,因为我只需要libc printf(也许还有一些数学(,其中的界面没有改变,但libstdc++的界面现在非常不同。

  5. 使用旧的 Linux 系统和 gcc 版本 2.95 设置虚拟盒子。然后编译 gcc 版本 4.x.x ...对不起,但现在懒得了...

  6. ???

已找到错误消息的原因:

user@ancient $ ./prog
set_thread_area failed when setting up thread-local storage
Segmentation fault

这是因为glibc对仅从内核 2.4.20 开始可用的函数进行系统调用。在某种程度上,它可以被视为glibc的错误,因为它错误地声称与内核 2.0.10 兼容,而它至少需要内核 2.4.20。

详情:

./glibc-2.14/nptl/sysdeps/i386/tls.h
[...]
     /* Install the TLS.  */                                                  
     asm volatile (TLS_LOAD_EBX                                               
                   "int $0x80nt"                                            
                   TLS_LOAD_EBX                                               
                   : "=a" (_result), "=m" (_segdescr.desc.entry_number)       
                   : "0" (__NR_set_thread_area),                              
                     TLS_EBX_ARG (&_segdescr.desc), "m" (_segdescr.desc));    
[...]
     _result == 0 ? NULL                                                      
     : "set_thread_area failed when setting up thread-local storagen"; })
[...]

这里最主要的是,它调用汇编函数int 0x80这是对 linux 内核的系统调用,它根据 eax 的值决定做什么,设置为 __NR_set_thread_area在这种情况下,定义在

$ grep __NR_set_thread_area /usr/src/linux-2.4.20/include/asm-i386/unistd.h
#define __NR_set_thread_area    243

但不在任何早期的内核版本中。

所以好消息是"3.使用 --enable-kernel=2.0.0 编译 glibc 可能会产生在所有 Linux 内核上运行的可执行文件>= 2.4.20。

使用旧内核进行此操作的唯一机会是禁用tls(线程本地存储(,但这在 glibc 2.14 中是不可能的,尽管它是作为configure选项提供的。

您无法在

原始系统上编译它的原因可能与内核版本无关(可以,但 2.2 通常还不够旧,不会成为大多数代码的绊脚石(。 问题是工具链是古老的(至少是编译器(。 但是,没有什么能阻止您使用已安装的 egcs 生成较新版本的 G++。 一旦你这样做了,你也可能会遇到glibc的问题,但你至少应该走那么远。

您应该执行的操作将如下所示:

  • 使用egcs构建最新的 GCC
  • 使用刚刚构建的gcc重建最新的 GCC
  • 使用新编译器构建最新的二进制文件并ld

现在,您有一个构建良好的现代编译器和(大部分(工具链,可用于构建示例应用程序。 如果运气不好,您可能还需要构建一个较新版本的glibc,但这是您的问题 - 工具链 - 而不是内核。