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

时间:2012-01-22 14:06:59

标签: c++ linux gcc linux-kernel glibc

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

我可以访问所有运行Linux的几个古老系统,也许看看它们在负载下的表现会很有趣。举个例子,我们想用Eigen做一些线性代数,This post是一个很好的只有头的库。有没有机会在目标系统上编译它?

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,支持最老的内核。 link order更详细地描述了它,但基本上就像这样

    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 bails一段时间后出错,并--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
    

    由于我们正在进行静态编译,this post很重要,可能需要一些试验和错误,但基本上我们可以了解gcc给链接器的选项:

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

    注意,crtbeginT.ocrtend.o也与我不需要的程序相关联,所以我将它们排除在外。输出还包括类似--start-group -lgcc -lgcc_eh -lc --end-group的行,表示库之间的相互依赖关系,请参阅{{3}}。我刚刚在-lc命令行中提到gcc两次,这也解决了相互依赖。

    是的,努力工作得到了回报,现在我得到了

    $ 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
    

    这又是来自glibc的{​​{1}}错误消息。我无法理解细节并放弃。

  4. 在新系统./nptl/sysdeps/i386/tls.h上编译并链接旧系统。哇,这实际上适用于C和简单的C ++程序(不使用C ++对象),至少对于我测试过的少数几个。这并不太令人惊讶,因为g++ -c -m32 -march=i386所需要的只是libc(也许是一些数学),其界面没有改变但是printf的界面现在非常不同。 / p>

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

  6. ???

2 个答案:

答案 0 :(得分:9)

找到了错误消息的原因:

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无法实现,尽管它是以configure提供的选项。

答案 1 :(得分:3)

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

你应该做的事情看起来像这样:

  • 使用egcs
  • 构建最新的GCC
  • 使用您刚刚构建的gcc重建最新的GCC
  • 使用新编译器构建最新的binutils和ld

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