c++ - 如何将 c/c++ 应用程序移植到旧版 linux 内核版本

标签 c++ linux gcc linux-kernel glibc

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

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

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,尽可能支持最旧的内核。 This post更详细地描述它,但本质上是这样的

    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 选项是否有任何作用,最重要的是强制使用编译器标志 -m32 - March=i486 用于 32 位构建(不幸的是 -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
    

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

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

    注意,crtbeginT.ocrtend.o 也与我的程序不需要的链接,因此我将它们排除在外。输出还包括类似 --start-group -lgcc -lgcc_eh -lc --end-group 的行,表示库之间的相互依赖关系,参见 this post .我刚刚在 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 (也许还有一些数学),其中的接口(interface)没有改变,但是 libstdc++< 的接口(interface) 现在大不一样了。

  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.4.20 时,它错误地声称与内核 2.0.10 兼容。

详情:

./glibc-2.14/nptl/sysdeps/i386/tls.h
[...]
     /* Install the TLS.  */                                                  \
     asm volatile (TLS_LOAD_EBX                                               \
                   "int $0x80\n\t"                                            \
                   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 storage\n"; })
[...]

这里主要的是,它调用汇编函数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 中是不可能的,尽管它是作为 配置选项。

关于c++ - 如何将 c/c++ 应用程序移植到旧版 linux 内核版本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8961622/

相关文章:

linux - 带有 RT Linux(RT-PREEMPT 补丁)的 ZeroMQ 是实时的吗?

C++:没有断点工作

c++ - 链接器似乎忽略了库并默认为旧版本

c++ - 使用 std::string 导致 Windows "Entry Point Not Found"

c++ - “new Foo()” 和 “&Foo()” 作为参数的区别

linux - ELF 可执行问题

c++ - 从 'BYTE *' 到 'ULONG' 的指针截断

python - 如何使用 RedHat Enterprise Linux 上的软件集合调用 python 3 cgi 脚本?

c++ - C++ STL 中是否有任何数据结构用于执行 log(n) 中第 k 个元素的插入、搜索和检索?

c++ - 使用 typeid 获取派生类的名称