11

I have a Linux binary, without sources, that works on one machine, and I'd like to make a self-contained package that would run on a different machine of the same architecture. What is a way of achieving this?

In my case, both machines have the same architecture, same Ubuntu kernel, but target machine doesn't have make and has wrong version of files under /lib and /usr

One idea I had was to use chroot and recreate a subset of the filesystem that the binary uses, possibly using strace to figure out what it needs. Is there a tool that does this already?

For posterity, here's how I figure out which files a process opens

#!/usr/bin/python
# source of trace_fileopen.py
# Runs command and prints all files that have been successfully opened with mode O_RDONLY
# example: trace_fileopen.py ls -l
import re, sys, subprocess, os

if __name__=='__main__':
  strace_fn = '/tmp/strace.out'
  strace_re = re.compile(r'([^(]+?)\((.*)\)\s*=\s*(\S+?)\s+(.*)$')

  cmd = sys.argv[1]
  nowhere = open('/dev/null','w')#
  p = subprocess.Popen(['strace','-o', strace_fn]+sys.argv[1:], stdout=nowhere, stderr=nowhere)
  sts = os.waitpid(p.pid, 0)[1]

  output = []
  for line in open(strace_fn):
    # ignore lines like --- SIGCHLD (Child exited) @ 0 (0) ---
    if not strace_re.match(line):
      continue
    (function,args,returnval,msg) = strace_re.findall(line)[0]
    if function=='open' and returnval!='-1':
      (fname,mode)=args.split(',',1)
      if mode.strip()=='O_RDONLY':
        if fname.startswith('"') and fname.endswith('"') and len(fname)>=2:
          fname = fname[1:-1]
        output.append(fname)
  prev_line = ""
  for line in sorted(output):
    if line==prev_line:
      continue
    print line
    prev_line = line

Update The problem with LD_LIBRARY_PATH solutions is that /lib is hardcoded into interpreter and takes precedence over LD_LIBRARY_PATH, so native versions will get loaded first. The interpreter is hardcoded into the binary. One approach might be to patch the interpreter and run the binary as patched_interpreter mycommandline Problem is that when mycommandline is starts with java, this doesn't work because Java sets-up LD_LIBRARY_PATH and restarts itself, which resorts to the old interpreter. A solution that worked for me was to open the binary in the text editor, find the interpreter (/lib/ld-linux-x86-64.so.2), and replace it with same-length path to the patched interpreter

7
  • 4
    Sounds like you want a static binary. It builds in all libraries it uses. Commented Jul 15, 2011 at 1:03
  • What @VLC said, you have to mark it for static linking though which might be a bit of a pain depending. Commented Jul 15, 2011 at 1:04
  • Did you build the binary in question? If you did, you might well be able to just rebuild it statically. Commented Jul 15, 2011 at 1:06
  • I missed an important detail: sources are not available, I just have the binary Commented Jul 15, 2011 at 1:17
  • possible duplicate of How can I know if my executable will also to run on other computers (linux)? Commented Jul 15, 2011 at 1:20

3 Answers 3

5

As others have mentioned, static linking is one option. Except static linking with glibc gets a little more broken with every release (sorry, no reference; just my experience).

Your chroot idea is probably overkill.

The solution most commercial products use, as far as I can tell, is to make their "application" a shell script that sets LD_LIBRARY_PATH and then runs the actual executable. Something along these lines:

#!/bin/sh
here=`dirname "$0"`
export LD_LIBRARY_PATH="$here"/lib
exec "$here"/bin/my_app "$@"

Then you just dump a copy of all the relevant .so files under lib/, put your executable under bin/, put the script in ., and ship the whole tree.

(To be production-worthy, properly prepend "$here"/lib to LD_LIBRARY_PATH if it is non-empty, etc.)

[edit, to go with your update]

I think you may be confused about what is hard-coded and what is not. ld-linux-x86-64.so.2 is the dynamic linker itself; and you are correct that its path is hard-coded into the ELF header. But the other libraries are not hard-coded; they are searched for by the dynamic linker, which will honor LD_LIBRARY_PATH.

If you really need a different ld-linux.so, instead of patching the ELF header, simply run the dynamic linker itself:

/path/to/my-ld-linux.so my_program <args>

This will use your linker instead of the one listed in the ELF header.

Patching the executable itself is evil. Please consider the poor person who has to maintain your stuff after you move on... Nobody is going to expect you to have hacked the ELF header by hand. Anybody can read what a shell script is doing.

Just my $0.02.

Sign up to request clarification or add additional context in comments.

3 Comments

The issue here is that things in /lib will get loaded before things in LD_LIBRARY_PATH. I got around this without using chroot, updated question body
@Yaroslav: Been there, done that... Note that you can also run the binary as "/lib/my-ld-linux.so <binary> <arguments>". When you do this, my-ld-linux.so will ignore the version in the ELF header. It is arguably cleaner to run your executable like this from a script (so anybody can see what you are doing) than to patch the ELF header in the executable (which nobody will ever figure out).
Yea, you really can't do entirely static linking with glibc at all due the Name Service Switch mechanism for allowing stuff to include alternate kinds of hostname resolution.
2

There's CDE a bit of software designed to do exactly what you want. Here's a google tech talk about it http://www.youtube.com/watch?v=6XdwHo1BWwY

Comments

2

There are almost certainly better answers, but you can find out what libraries the binary needs with the ldd command (example for the ls binary):

$ ldd /bin/ls
linux-vdso.so.1 =>  (0x00007ffffff18000)
librt.so.1 => /lib/librt.so.1 (0x00007f5ae565c000)
libselinux.so.1 => /lib/libselinux.so.1 (0x00007f5ae543e000)
libacl.so.1 => /lib/libacl.so.1 (0x00007f5ae5235000)
libc.so.6 => /lib/libc.so.6 (0x00007f5ae4eb2000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00007f5ae4c95000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5ae588b000)
libdl.so.2 => /lib/libdl.so.2 (0x00007f5ae4a90000)
libattr.so.1 => /lib/libattr.so.1 (0x00007f5ae488b000)

Once you have this, you could make copies and put them in the proper locations on the target machine.

1 Comment

Actually I've done something like that (using strace to find additional required libraries not reported by ldd), but I don't like the idea of my program potentially breaking because of things outside of ~ which I don't have control over. chroot looks good, but it would take work to figure out how to reconstruct the filesystem

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.