如何使 Unix 二进制文件自包含?
我有一个 Linux 二进制文件,没有源代码,可以在一台机器上运行,我想制作一个独立的包,可以在同一架构的不同机器上运行。实现这一目标的方法是什么?
就我而言,两台机器具有相同的体系结构,相同的 Ubuntu 内核,但目标机器没有 make
并且在 /lib
和 下的文件版本错误/usr
我的一个想法是使用 chroot 并重新创建二进制文件使用的文件系统的子集,可能使用 strace 来找出它需要什么。是否已经有一个工具可以做到这一点?
对于后代,以下是我如何确定进程打开哪些文件
#!/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
更新 LD_LIBRARY_PATH
解决方案的问题在于 /lib
被硬编码到解释器中并优先于 LD_LIBRARY_PATH
,因此本机版本将首先加载。解释器被硬编码到二进制文件中。一种方法可能是修补解释器并将二进制文件运行为 patched_interpreter mycommandline
问题是,当 mycommandline
以 java
开头时,这不会之所以能工作,是因为 Java 设置了 LD_LIBRARY_PATH 并自行重新启动,这会求助于旧的解释器。对我有用的解决方案是在文本编辑器中打开二进制文件,找到解释器(/lib/ld-linux-x86-64.so.2
),并将其替换为相同长度修补解释器的路径
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+(.*)
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
)
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正如其他人提到的,静态链接是一种选择。除了与 glibc 的静态链接在每个版本中都会出现一些问题(抱歉,没有参考资料;只是我的经验)。
您的 chroot 想法可能有点矫枉过正。
据我所知,大多数商业产品使用的解决方案是使它们的“应用程序”成为一个 shell 脚本,设置 LD_LIBRARY_PATH ,然后运行实际的可执行文件。大致如下:
然后,您只需将所有相关 .so 文件的副本转储到
lib/
下,将可执行文件放在bin/
下,将脚本放入.
,并运送整个树。(为了具有生产价值,如果
"$here"/lib
不为空,则将其正确添加到LD_LIBRARY_PATH
等)[编辑,以配合您的更新]
我认为您可能对什么是硬编码、什么不是感到困惑。
ld-linux-x86-64.so.2
是动态链接器本身;你是正确的,它的路径被硬编码到 ELF 标头中。但其他库不是硬编码的;它们由动态链接器搜索,该链接器将遵循LD_LIBRARY_PATH
。如果您确实需要不同的 ld-linux.so,则无需修补 ELF 标头,只需运行动态链接器本身即可:
这将使用您的链接器而不是 ELF 标头中列出的链接器。
修补可执行文件本身是邪恶的。请考虑一下在您继续前进后必须维护您的东西的可怜人...没有人会期望您手动破解 ELF 标头。 任何人都可以读取 shell 脚本正在执行的操作。
只是我的 0.02 美元。
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:Then you just dump a copy of all the relevant .so files under
lib/
, put your executable underbin/
, put the script in.
, and ship the whole tree.(To be production-worthy, properly prepend
"$here"/lib
toLD_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 honorLD_LIBRARY_PATH
.If you really need a different ld-linux.so, instead of patching the ELF header, simply run the dynamic linker itself:
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.
CDE 有一些软件旨在完全满足您的需求。这是谷歌技术讨论
http://www.youtube.com/watch?v=6XdwHo1BWwY
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
几乎肯定有更好的答案,但您可以使用 ldd 命令(例如 ls 二进制文件)找出二进制文件需要哪些库:
一旦有了这个,您就可以制作副本并将其放在目标计算机上的正确位置。
There are almost certainly better answers, but you can find out what libraries the binary needs with the
ldd
command (example for thels
binary):Once you have this, you could make copies and put them in the proper locations on the target machine.