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

天大地大妈咪最大 提交于 2021-02-06 10:00:18

问题


Ok, this is just a bit of a fun exercise, but it can't be too hard compiling programmes for some older linux systems, or can it?

I have access to a couple of ancient systems all running linux and maybe it'd be interesting to see how they perform under load. Say as an example we want to do some linear algebra using Eigen which is a nice header-only library. Any chance to compile it on the target system?

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

Maybe not... So let's compile it on a current system. Below are my attempts, mainly failed ones. Any more ideas very welcome.

  1. Compile with -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. Compile with -m32 -march=i386 -static: Runs on all fairly recent kernel versions but fails if they are slightly older with the well known error message

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

    This is a glibc error which has a minimum kernel version it supports, e.g. kernel 2.6.4 on my system:

    $ 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. Compile glibc myself with support for the oldest kernel possible. This post describes it in more detail but essentially it goes like this

    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
    

    Not sure if the --with-cpu and --host options do anything, most important is to force the use of compiler flags -m32 -march=i486 for 32-bit builds (unfortunately -march=i386 bails out with errors after a while) and --enable-kernel=2.0.0 to make the library compatible with older kernels. Incidentially, during configure I got the warning

    WARNING: minimum kernel version reset to 2.0.10
    

    which is still acceptable, I suppose. For a list of things which change with different kernels see ./sysdeps/unix/sysv/linux/kernel-features.h.

    Ok, so let's link against the newly compiled glibc library, slightly messy but here it goes:

    $ 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
    

    Since we're doing a static compile the link order is important and may well require some trial and error, but basically we learn from what options gcc gives to the linker:

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

    Note, crtbeginT.o and crtend.o are also linked against which I didn't need for my programmes so I left them out. The output also includes a line like --start-group -lgcc -lgcc_eh -lc --end-group which indicates inter-dependence between the libraries, see this post. I just mentioned -lc twice in the gcc command line which also solves inter-dependence.

    Right, the hard work has paid off and now I get

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

    Brilliant I thought, now try it on the old system:

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

    This, again, is a glibc error message from ./nptl/sysdeps/i386/tls.h. I fail to understand the details and give up.

  4. Compile on the new system g++ -c -m32 -march=i386 and link on the old. Wow, that actually works for C and simple C++ programmes (not using C++ objects), at least for the few I've tested. This is not too surprising as all I need from libc is printf (and maybe some maths) of which the interface hasn't changed but the interface to libstdc++ is very different now.

  5. Setup a virtual box with an old linux system and gcc version 2.95. Then compile gcc version 4.x.x ... sorry, but too lazy for that right now ...

  6. ???


回答1:


Have found the reason for the error message:

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

It's because glibc makes a system call to a function which is only available since kernel 2.4.20. In a way it can be seen as a bug of glibc as it wrongly claims to be compatible with kernel 2.0.10 when it requires at least kernel 2.4.20.

The details:

./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"; })
[...]

The main thing here is, it calls the assembly function int 0x80 which is a system call to the linux kernel which decides what to do based on the value of eax, which is set to __NR_set_thread_area in this case and is defined in

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

but not in any earlier kernel versions.

So the good news is that point "3. Compiling glibc with --enable-kernel=2.0.0" will probably produce executables which run on all linux kernels >= 2.4.20.

The only chance to make this work with older kernels would be to disable tls (thread-local storage) but which is not possible with glibc 2.14, despite the fact it is offered as a configure option.




回答2:


The reason you can't compile it on the original system likely has nothing to do with kernel version (it could, but 2.2 isn't generally old enough for that to be a stumbling block for most code). The problem is that the toolchain is ancient (at the very least, the compiler). However, nothing stops you from building a newer version of G++ with the egcs that is installed. You may also encounter problems with glibc once you've done that, but you should at least get that far.

What you should do will look something like this:

  • Build latest GCC with egcs
  • Rebuild latest GCC with the gcc you just built
  • Build latest binutils and ld with your new compiler

Now you have a well-built modern compiler and (most of a) toolchain with which to build your sample application. If luck is not on your side you may also need to build a newer version of glibc, but this is your problem - the toolchain - not the kernel.



来源:https://stackoverflow.com/questions/8961622/how-to-port-c-c-applications-to-legacy-linux-kernel-versions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!