Running 64bit binaries on 32bit userland with 64bit kernel

The problem

Some time ago I wrote about how my Gentoo Linux is installed (blogpost in pt_BR) on my laptop. Long story short: I have a hybrid system, 32bit userland running on a 64bit kernel. I opted to do this type of install because I still have an impression that a full 64bit system still has some key problems (in my past adventures flash was one of them). Anyway, this setup works very well. I can use all 8GB (not by one same process) for example. But not long after the installation I discovered one thing that got me confused.

How come even with a 64bit kernel I still coudn’t run any 64bit ELF binary?

I thought that just having a 64bit kernel was enough to run 32 and 64bit binaries, in fact it is! But is not so trivial do run a 64bit binary in a 32bit userland.

The first attempt, no luck

The first thing I tried was obviously call the binary directly on the command line:

daltonmatos@jetta ~/src [158]$ ./m64
-bash: ./m64: No such file or directory
daltonmatos@jetta ~/src [159]$

This is, indeed, a very cryptic error message.

Enter the dynamic loader

All binaries (at least the massive majority) on your system is a dynamically linked ELF. And you can check this with the file command.

daltonmatos@jetta ~/src [179]$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.9, stripped
daltonmatos@jetta ~/src [180]$

Remember, 32bit userland. A bit further and we will see this for 64 bit binaries.

By dynamic linked you can understand that all libraries that the binary depends on are loaded at runtime. You can check which are these libraries with the ldd command.

daltonmatos@jetta ~/src [180]$ ldd /bin/ls
 linux-gate.so.1 => (0xffffe000)
 librt.so.1 => /lib/librt.so.1 (0xf776c000)
 libacl.so.1 => /lib/libacl.so.1 (0xf7763000)
 libc.so.6 => /lib/libc.so.6 (0xf7605000)
 libpthread.so.0 => /lib/libpthread.so.0 (0xf75eb000)
 /lib/ld-linux.so.2 (0xf7796000)
 libattr.so.1 => /lib/libattr.so.1 (0xf75e4000)
daltonmatos@jetta ~/src [181]$

Note: Thee ldd command is just a wrapper around the real dynamic loader, usually /lib/ld.so or /lib/ld-<version>.so. I’m running glibc-2.13 so here I have /lib/ld-2.13.so. Running ldd <some-binary> is the same as running /lib/ld-2.13.so --list <some-binary>. So for now on we will be using the second.

Here begins the differencies. The 32 bit ld.so (or the ldd wrapper) tool does not know how to handle a 64 bit binary, as we can see:

daltonmatos@jetta ~/src [229]$ /lib/ld-2.13.so --list ./m64
./m64: error while loading shared libraries: ./m64: wrong ELF class: ELFCLASS64
daltonmatos@jetta ~/src [230]$

And the ldd command:

 
daltonmatos@jetta ~/src [190]$ ldd ./m64
 not a dynamic executable

which is clearly not true, since I compiled m64 myself using a regular gcc that outputs 64bit binaries. So we need a 64bit-capable ld.so. Since I have two compilers (one that generates 32bit binaries and another that generates 64bit) I need to have two complete tool chains (basically: binutils, gcc, gdb, glibc). So my second tool chain has what I need.

Specifically on Gentoo Linux, the cross compiling infrastructure creates /usr/<arch>-pc-linux-gnu folder and stores all files under it so I have a /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so, that is, the dynamic loader for 64bit binaries. Yes, I have a different version of the glibc for 64bit (2.14.1).

So let’s re-check our binaries, but now with this new tool:

daltonmatos@jetta ~/src [191]$ file ./m64 
./m64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [192]$

Alright, our brand new 64bit program. Now, the shared libraries:

daltonmatos@jetta ~/src [194]$ /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so --list ./m64
 linux-vdso.so.1 => (0x00007fffa90fb000)
 libc.so.6 => /usr/x86_64-pc-linux-gnu/lib/libc.so.6 (0x00007f103f367000)
 /lib64/ld-linux-x86-64.so.2 => /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so (0x00007f103f6f7000)
daltonmatos@jetta ~/src [195]$

That’s awesome! You maybe asking yourself: “How is it possible to run the ld.so binary directly but not other 64bit binaries?” And you’re right, because the ld.so itself is a 64bit binary:

daltonmatos@jetta ~/src [197]$ file /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so 
/usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), 
dynamically linked, stripped
daltonmatos@jetta ~/src [198]$

If you know this, let me know because I don’t! =) I just discovered that the dynamic loader itself is runnable directly from the command line and can be used to run other binaries.

daltonmatos@jetta ~/src [200]$ file ./m64
./m64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), 
for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [201]$ /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so ./m64 
Hello World
daltonmatos@jetta ~/src [202]$

Like magic! =)

If you tried these exact steps I  just described you probably got an error like this one:

daltonmatos@jetta ~/src [202]$ /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so ./m64 
./m64: error while loading shared libraries: libc.so.6: wrong ELF class: ELFCLASS32
daltonmatos@jetta ~/src [203]$

What does this mean? Remember that we have two glibc’s installed? Both binaries depend on a shared library with the name libc.so.6. So shouldn’t the SO handle this automatically? Yes, but it doesn’t and now I have to tell you that I’ve been hiding one piece of important information from you. Is the second key to be able to run 64bit binaries, being the first the discovery of ld.so as a generic binary runner.

Go back in the output of the ld.so --list command and see how the libc.so.6‘s path is resolved in both cases. See that they have different paths? 32 bit resloves to /lib/libc.so.6 and 64bit resolves to /usr/x86_64-pc-linux-gnu/lib/libc.so.6. So how do we handle this?

It turns out that we can change where the dynamic loader looks for shared objects with the LD_LIBRARY_PATH environment variable. So all commands using the 64bit loader need to be prepended with LD_LIBRARY_PATH=<new-path>, like this:

daltonmatos@jetta ~/src [203]$ LD_LIBRARY_PATH=/usr/x86_64-pc-linux-gnu/lib /usr/x86_64-pc-linux-gnu/lib/ld-2.14.1.so ./m64 
Hello World
daltonmatos@jetta ~/src [204]$

One of the key advantages of having the ability to do this is that you can compile some programs to 64bit code and this program will be able to use more that 4GB of RAM, if needed.

An easier, but not optimal way

There is another way to do this that will not require tweaking the library loading path or anythin. You can compile all your 64bit binaries as statically linked ELF’s, like this:

daltonmatos@jetta ~/src [218]$ x86_64-pc-linux-gnu-gcc -static -o m64s main.c
daltonmatos@jetta ~/src [219]$ file m64s 
m64s: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [220]$

This way you won’t need the dynamic loader and then you can run your binary directly from the command line:

daltonmatos@jetta ~/src [220]$ ./m64s
Hello World
daltonmatos@jetta ~/src [221]$

But this comes with a cost. Extra disk space:

daltonmatos@jetta ~/src [222]$ file m64*
m64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, not stripped
m64s: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.9, not stripped
daltonmatos@jetta ~/src [223]$ ls -lh m64*
-rwxr-xr-x 1 daltonmatos daltonmatos 7,7K Abr 8 17:15 m64
-rwxr-xr-x 1 daltonmatos daltonmatos 771K Abr 10 20:32 m64s
daltonmatos@jetta ~/src [224]$

The statically linked binary is 100 times bigger than the dynamic one. This is because you have all that’s needed bundled into the static binary. You can reduce the final size using the strip command but you won’t get a significant reduction.

That’s it! Thanks for reading!

, , , ,

  1. #1 por Denilson Sá em 19/04/2012 - 00:55

    It says that ld.so is dynamically linked. What is it linked to? Maybe nothing, since it is a basic library? Probably discovering that is the missing piece for your puzzle.

    • #2 por daltonmatos em 19/04/2012 - 08:12

      Sure, yeah! Actually the file command told me it’s a dynamically linked binary. But the curious is that running ld.so (64bits) to list its own shared libs returns: loader cannot load itself. And using the ld.so (32bits) to show it own shared libs returns Statically linked . Although file continues to see some sort of dynamically linked header on these binaries. It’s really a mistery! =)

      Maybe using a third tool will be the key to discover this.

      • #3 por Denilson Sá em 19/04/2012 - 23:28

        On my 64-bit Gentoo, running “ldd /lib/ld-2.13.so” says “statically linked”.

        So, I believe this is the reason why it works.
        And it makes a lot of sense… The library/binary responsible for loading other dynamic libraries should be statically linked.

      • #4 por daltonmatos em 20/04/2012 - 09:38

        Yeah, I agree. Bu why file reports that it is a dynamically linked binary? This got me confused. =)

  2. #5 por jogodobicho.pw em 03/06/2013 - 05:26

    Hi there! I could have sworn I’ve visited your blog before but after browsing through a few of the articles I realized it’s new to me.
    Anyways, I’m certainly delighted I found it and I’ll be book-marking it and checking back regularly!

  1. Livresoftware | As novidades do Linux 3.4

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: