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
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With