Closed ChrisTimperley closed 5 years ago
I was able to hack my way around this particular problem via:
diff --git a/staticx/elf.py b/staticx/elf.py
index 7e6f92f..aef6573 100644
--- a/staticx/elf.py
+++ b/staticx/elf.py
@@ -84,6 +84,7 @@ def get_shobj_deps(path):
for line in output.splitlines():
m = pat.match(line)
if not m:
+ continue
raise ToolError('ldd', "Unexpected line in ldd output: " + line)
libname = m.group(1)
libpath = m.group(2)
Did your program actually run correctly with that hack in place? It seems like it would be skipping a needed library.
I'm confused by this output:
$ staticx bin/startcli3 portable
staticx: Unexpected line in ldd output: libgfortran-ed201abd.so.3.0.0 => not found
$ ldd bin/startcli3
linux-vdso.so.1 => (0x00007fffcdbb3000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe074e09000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fe074bec000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe07480c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe07500d000)
Can you explain why staticx
saw ldd
print libgfortran-ed201abd.so.3.0.0 => not found
, but when you run ldd
that library is not present?
Surprisingly, it appears to be running correctly! not found
would suggest that the library isn't to be found on the host machine, either, so presumably the wrapped binary would inherit the limitations of the host. Still, I'm baffled at why that library isn't listed by ldd
. Are you calling ldd
directly on the executable, or are you using another library to recursively call ldd
?
I'm just running ldd
directly. If you look here, you'll see I'm referencing
tool_ldd = ExternTool('ldd', 'binutils')
where ExternTool
is just a dumb wrapper around subprocess.Popen
.
I'm not doing any sort of path manipulation; just running ldd
. Is there any reason staticx
would see a different ldd
in its PATH than you would at your shell?
Hi, I've got the same problem however I was also able to reproduce it by running ldd directly:
1- Here is what happen when running staticx:
It terminates with staticx: Unexpected line in ldd output: libffi-ae16d830.so.6.0.4 => not found
(sIPve) auto-secops@secops-scripts:~/sIP$ staticx --loglevel DEBUG ./dist/sIP ./output
DEBUG:root:Running ['patchelf', '--set-interpreter', 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', '--set-rpath', 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr', '--force-rpath', '/tmp/staticx-prog-nh6onpqo']
INFO:root:Program interpreter: /lib64/ld-linux-x86-64.so.2
INFO:root:Using XZ BCJ filter FILTER_X86
INFO:root:Adding /tmp/staticx-prog-nh6onpqo as .staticx.prog
DEBUG:root:Running ['ldd', '/tmp/staticx-prog-nh6onpqo']
INFO:root: Adding Symlink libdl.so.2 => libdl-2.23.so
INFO:root: Adding /lib/x86_64-linux-gnu/libdl-2.23.so as libdl-2.23.so
INFO:root: Adding Symlink libz.so.1 => libz.so.1.2.8
INFO:root: Adding /lib/x86_64-linux-gnu/libz.so.1.2.8 as libz.so.1.2.8
INFO:root: Adding Symlink libc.so.6 => libc-2.23.so
INFO:root: Adding /lib/x86_64-linux-gnu/libc-2.23.so as libc-2.23.so
INFO:root: Adding Symlink ld-linux-x86-64.so.2 => ld-2.23.so
INFO:root: Adding /lib/x86_64-linux-gnu/ld-2.23.so as ld-2.23.so
INFO:root:Opened PyInstaller archive!
DEBUG:root:Extracting to /tmp/staticx-pyi-26d27yzf/_bz2.cpython-35m-x86_64-linux-gnu.so
DEBUG:root:Running ['ldd', '/tmp/staticx-pyi-26d27yzf/_bz2.cpython-35m-x86_64-linux-gnu.so']
INFO:root: Adding Symlink libpthread.so.0 => libpthread-2.23.so
INFO:root: Adding /lib/x86_64-linux-gnu/libpthread-2.23.so as libpthread-2.23.so
DEBUG:root:libc.so.6 already in staticx archive
DEBUG:root:libbz2.so.1.0 already in pyinstaller archive
DEBUG:root:ld-linux-x86-64.so.2 already in staticx archive
DEBUG:root:Extracting to /tmp/staticx-pyi-26d27yzf/_cffi_backend.cpython-35m-x86_64-linux-gnu.so
DEBUG:root:Running ['ldd', '/tmp/staticx-pyi-26d27yzf/_cffi_backend.cpython-35m-x86_64-linux-gnu.so']
staticx: Unexpected line in ldd output: libffi-ae16d830.so.6.0.4 => not found
(sIPve) auto-setops@setops-scripts:~/sIP$
2- It works when running ldd against the library in its original location:
(sIPve) auto-setops@setops-scripts:~/sIP$ ldd /home/auto-setops/sIP/sIPve/lib/python3.5/site-packages/_cffi_backend.cpython-35m-x86_64-linux-gnu.so
linux-vdso.so.1 => (0x00007ffe86564000)
libffi-ae16d830.so.6.0.4 => /home/auto-setops/sIP/sIPve/lib/python3.5/site-packages/.libs_cffi_backend/libffi-ae16d830.so.6.0.4 (0x00007fb3792fb000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb3790de000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb378d14000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb37973a000)
(sIPve) auto-setops@setops-scripts:~/sIP$
3- It fails when running ldd against the same library after it was copied to the tmp directory:
(sIPve) auto-setops@setops-scripts:~/sIP$ ldd /tmp/testldd/_cffi_backend.cpython-35m-x86_64-linux-gnu.so
linux-vdso.so.1 => (0x00007fffaa186000)
libffi-ae16d830.so.6.0.4 => not found
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fee8293e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fee82574000)
/lib64/ld-linux-x86-64.so.2 (0x00007fee82d91000)
(sIPve) auto-setops@setops-scripts:~/sIP$
I will continue investigate with ldd
Okay, the difference between (2) and (3) seems to be the culprit: When the library is moved, one if its dependencies cannot be found.
I'm guessing _cffi_backend.cpython-35m-x86_64-linux-gnu.so
sets its RPATH
to $ORIGIN
, which means "look in the same directory as this .so
for other .so
files." So when staticx
copies it to the temp directory, the dependencies in the same directory can no longer be found.
Can you confirm this by posting the output of readelf -d /original/path/to/_cffi_backend.cpython-35m-x86_64-linux-gnu.so
? Specifically, I'm looking for (RPATH)
.
This should be pretty easy to fix in staticx
. I just need to refactor things so we run ldd
on the original file path, and not the temp path after it's been copied.
Sorry I was out of the office.
The result of "readelf -d" is:
readelf -d sIPve/lib/python3.5/site-packages/_cffi_backend.cpython-35m-x86_64-linux-gnu.so
Dynamic section at offset 0xc9000 contains 28 entries:
Tag Type Name/Value
0x000000000000000f (RPATH) Library rpath: [$ORIGIN/.libs_cffi_backend]
0x0000000000000001 (NEEDED) Shared library: [libffi-ae16d830.so.6.0.4]
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
0x000000000000000c (INIT) 0x6ac0
0x000000000000000d (FINI) 0x214b8
0x0000000000000019 (INIT_ARRAY) 0x229270
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x229278
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x232ee0
0x0000000000000005 (STRTAB) 0x233200
0x0000000000000006 (SYMTAB) 0x2340f8
0x000000000000000a (STRSZ) 3831 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000003 (PLTGOT) 0x229d48
0x0000000000000002 (PLTRELSZ) 4104 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x5ab8
0x0000000000000007 (RELA) 0x2890
0x0000000000000008 (RELASZ) 12840 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x2820
0x000000006fffffff (VERNEEDNUM) 3
0x000000006ffffff0 (VERSYM) 0x2654
0x000000006ffffff9 (RELACOUNT) 474
0x0000000000000000 (NULL) 0x0
Ran into this issue as well while trying to run staticx on executable produced by PyInstaller. The above suggestion did help to make it work.
@haizaar Thanks for the feedback. It's great to see this project is getting some use!
The continue
suggestion is good as a quick test, but it should be considered a temporary hack and not a permanent solution. Adding continue
there will allow staticx
to build an incomplete output executable.
I'll get back to #75 soon to finish the real fix.
@JonathonReinhart I'm also trying to use the project, same use case of Pyinstaller. @haizaar did the binary run after you inserted the workaround?
@danielguardicore yes, it did work.
But it didn't work later on for these guys: https://github.com/crossbario/crossbar/issues/1428#issuecomment-453743862
I've finally found some time to come back to this issue, and I've narrowed down a simple test case (see the add-pyinstall-cff-test
branch).
I'm specifically working in a virtualenv (named venv
) with the following relevant versions:
$ python --version
Python 3.6.7
$ pip freeze
cffi==1.12.2
...
PyInstaller==3.4
My fixes thus far in #75 are correct, but insufficient for PyInstaller apps. As I noted in https://github.com/JonathonReinhart/staticx/pull/75#issuecomment-439766244, the problem seems to be that staticx
runs ldd
on the shared object it extracted from the PyInstaller archive to a temporary directory:
DEBUG:root:Extracting to /tmp/staticx-pyi-elnfmwc6/_cffi_backend.cpython-36m-x86_64-linux-gnu.so
DEBUG:root:Running ['ldd', '/tmp/staticx-pyi-elnfmwc6/_cffi_backend.cpython-36m-x86_64-linux-gnu.so']
staticx: Unexpected line in ldd output: libffi-ae16d830.so.6.0.4 => not found
Most of the time, this would probably work okay, but _cffi_backend.cpython-36m-x86_64-linux-gnu.so
is using RPATH:
$ readelf -d venv/lib/python3.6/site-packages/_cffi_backend.cpython-36m-x86_64-linux-gnu.so
Dynamic section at offset 0xcc000 contains 28 entries:
Tag Type Name/Value
0x000000000000000f (RPATH) Library rpath: [$ORIGIN/.libs_cffi_backend]
0x0000000000000001 (NEEDED) Shared library: [libffi-ae16d830.so.6.0.4]
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
Here's directory structure where it is installed:
venv/lib/python3.6/site-packages
├── cffi
│ ├── _cffi_errors.h
│ ├── _cffi_include.h
│ ├── cffi_opcode.py
│ └── __pycache__
│ └── cffi_opcode.cpython-36.pyc
├── cffi-1.12.2.dist-info
├── _cffi_backend.cpython-36m-x86_64-linux-gnu.so
└── .libs_cffi_backend
└── libffi-ae16d830.so.6.0.4
So at first glance, it looks like I should simply ensure to extract libffi-ae16d830.so.6.0.4
to a .libs_cffi_backend
sub-directory next to _cffi_backend.cpython-36m-x86_64-linux-gnu.so
before calling ldd
on the latter.
The problem is it doesn't appear that PyInstaller is able to give me that information. Here are the relevant lines from pyi-archive_viewer
:
pos, length, uncompressed, iscompressed, type, name
[(0, 270, 348, 1, 'm', 'struct'),
(270, 1123, 1837, 1, 'm', 'pyimod01_os_path'),
(1393, 4379, 9397, 1, 'm', 'pyimod02_archive'),
(5772, 7402, 18702, 1, 'm', 'pyimod03_importers'),
(13174, 1847, 4157, 1, 's', 'pyiboot01_bootstrap'),
(15021, 1077, 1774, 1, 's', 'pyi_rth_multiprocessing'),
(16098, 210, 254, 1, 's', 'pyi_rth_pkgres'),
(16308, 331, 462, 1, 's', 'app'),
...
(55536, 281114, 845576, 1, 'b', '_cffi_backend.cpython-36m-x86_64-linux-gnu.so'),
...
(2792700, 52534, 149064, 1, 'b', 'libffi-ae16d830.so.6.0.4'),
(2845234, 17803, 38544, 1, 'b', 'libffi.so.6'),
...
(5628336, 2184807, 2184807, 0, 'z', 'PYZ-00.pyz')]
Note that there is no .libs_cffi_backend
subdirectory visible for libffi-ae16d830.so.6.0.4
. In fact, if we look in the _MEIxxxxxx
temporary directory when the application is (successfully) running, we can see that the PyInstaller bootloader hasn't even extracted it "correctly" (the structure is "flat", just as I have extracted it):
$ cd /tmp/_MEI3u5Uge
$ ls -1 *ffi*
_cffi_backend.cpython-36m-x86_64-linux-gnu.so
libffi-ae16d830.so.6.0.4
libffi.so.6
$ ldd _cffi_backend.cpython-36m-x86_64-linux-gnu.so
linux-vdso.so.1 (0x00007ffdbedf5000)
libffi-ae16d830.so.6.0.4 => not found
libpthread.so.0 => /usr/lib64/libpthread.so.0 (0x00007fa02f284000)
libc.so.6 => /usr/lib64/libc.so.6 (0x00007fa02eec5000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa02f6da000)
At this point, I'm not even sure how ld.so
even loads this PyInstaller application correctly when it runs! :flushed:
So it seems that PyInstaller isn't giving me enough info to know the correct path structure. I need to keep digging.
Whether it's right or wrong, PyInstaller will flatten the shared object directory structure when building its archive. The reason the application runs correctly is because (on Linux), the PyInstaller bootloader points LD_LIBRARY_PATH
at the _MEIxxxxxx
path:
https://github.com/pyinstaller/pyinstaller/blob/v3.4/bootloader/src/pyi_utils.c#L808-L811
Indeed, setting LD_LIBRARY_PATH
makes my previous test work:
$ cd /tmp/_MEIMszltz
$ LD_LIBRARY_PATH="$(pwd):$LD_LIBRARY_PATH" ldd _cffi_backend.cpython-36m-x86_64-linux-gnu.so
linux-vdso.so.1 (0x00007fff16b22000)
libffi-ae16d830.so.6.0.4 => /tmp/_MEIMszltz/libffi-ae16d830.so.6.0.4 (0x00007fefebe70000)
libpthread.so.0 => /usr/lib64/libpthread.so.0 (0x00007fefebc51000)
libc.so.6 => /usr/lib64/libc.so.6 (0x00007fefeb892000)
/lib64/ld-linux-x86-64.so.2 (0x00007fefec2b0000)
I think I can apply this logic in staticx to resolve our issues.
Hey everyone,
I went ahead and merged #75 without waiting on any feedback because:
Hopefully this resolves at least some of the problems you were having with staticx + PyInstaller, let me know if it works for you! I appreciate your patience and your interest in my little project. If you have any further problems, please open an issue!
If you'd like to try it out, you can install this pre-release from TestPyPI: staticx 0.6.0.182
I'll be releasing a new version to PyPI soon. I've released staticx v0.7.0
and it will be available on PyPI momentarily.
cc @cakebake @oz123
So nearly there, but still no cigar. Thank you for all the work.
In this case, it's failing when PyInstaller bundled an additional binary file. My specific case has 2 binary files added, this is the only one causing a problem.
DEBUG:root:Running ['ldd', u'/tmp/staticx-pyi-RjKceH/traceroute64'] staticx: ldd returned 1
Running ldd manually shows that ldd thinks this isn't a dynamic executable.
Would you like me to upload the debug log?
@danielguardicore Yes, please. Would you mind opening a new issue? Please include:
staticx --loglevel DEBUG ...
pyi-archive_viewer yourapp
traceroute64
, and why/how it's included in your PyInstaller appThanks!
Opened #78 thank you
This project looks great. Thanks for your work!
I'm trying to wrap a dynamically linked binary generated by PyInstaller so that it can be run on systems with a different libc and
ld.so
. Unfortunately, I encounter the following error when I try to wrap the binary:For reference, I'm running
staticx
inside a virtual environment (created bypipenv
) on the following platform: