Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repair does not handle recursive libraries correctly #48

Closed
xhochy opened this issue Aug 29, 2016 · 18 comments
Closed

Repair does not handle recursive libraries correctly #48

xhochy opened this issue Aug 29, 2016 · 18 comments

Comments

@xhochy
Copy link

xhochy commented Aug 29, 2016

Using the latest manylinux image (and so hopefully the latest auditwheel version) I have the problem that repair does not correctly relink all libraries. The dependency hierarchy is as follows:

  • parquet.so is a Python module (Cython generated) that depends on the C++ libraries libpyarrow.so and libarrow.so directly.
  • libpyarrow.so is a pure C++ helper library but also depends on libarrow.so directly.

parquet.so has the correct links after the repair but libpyarrow.so does not.

Both libs parquet.so and libpyarrow.so are part of the wheel, libarrow.so is in this case an external one).

Console output:

Repairing pyarrow-0.1.0.dev0-cp27-cp27mu-linux_x86_64.whl
Grafting: /usr/lib/libarrow.so -> pyarrow/./libarrow-17b4bbba.so
Grafting: /usr/lib/libboost_filesystem.so.1.60.0 -> pyarrow/./libboost_filesystem-d4710d70.so.1.60.0
Grafting: /usr/lib/libarrow_parquet.so -> pyarrow/./libarrow_parquet-745cd113.so
Grafting: /usr/lib/libparquet.so -> pyarrow/./libparquet-3ec6b6fd.so
Grafting: /usr/lib/libboost_system.so.1.60.0 -> pyarrow/./libboost_system-17a2ee5a.so.1.60.0
Grafting: /usr/lib/libarrow_io.so -> pyarrow/./libarrow_io-f94b9eae.so
Setting RPATH: pyarrow/schema.so to "$ORIGIN/."
Setting RPATH: pyarrow/scalar.so to "$ORIGIN/."
Setting RPATH: pyarrow/config.so to "$ORIGIN/."
Setting RPATH: pyarrow/table.so to "$ORIGIN/."
Setting RPATH: pyarrow/array.so to "$ORIGIN/."
Setting RPATH: pyarrow/error.so to "$ORIGIN/."
Setting RPATH: pyarrow/io.so to "$ORIGIN/."
Setting RPATH: pyarrow/parquet.so to "$ORIGIN/."
Previous filename tags: linux_x86_64
New filename tags: manylinux1_x86_64
Previous WHEEL info tags: cp27-cp27mu-linux_x86_64
New WHEEL info tags: cp27-cp27mu-manylinux1_x86_64

Library linkages:

% readelf --dynamic pyarrow/parquet.so | grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libpyarrow.so]
 0x0000000000000001 (NEEDED)             Shared library: [libarrow-17b4bbba.so]
 0x0000000000000001 (NEEDED)             Shared library: [libarrow_io-f94b9eae.so]
 0x0000000000000001 (NEEDED)             Shared library: [libarrow_parquet-745cd113.so]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

% readelf --dynamic pyarrow/libpyarrow.so | grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libarrow.so]
 0x0000000000000001 (NEEDED)             Shared library: [libarrow_io.so]
 0x0000000000000001 (NEEDED)             Shared library: [libarrow_parquet.so]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

Happy to fix this on my own but I would need some pointers where I should start to investigate!

@xhochy
Copy link
Author

xhochy commented Aug 31, 2016

Ok, the problem boils down to libpyarrow.so not being a python extension module and neither an external .so. Thus is it excluded from any processing.

If I replace the if-clause in https://github.com/pypa/auditwheel/blob/master/auditwheel/wheel_abi.py#L32 with a True, the resulting wheel works.

Before I do a PR that removes this if-statement: Anyone remembers why it is there in the first place?

@ogrisel
Copy link
Contributor

ogrisel commented Sep 1, 2016

I am not sure. Ping @rmcgibbo :)

BTW thanks the report and investigation @xhochy.

@rmcgibbo
Copy link
Member

rmcgibbo commented Sep 1, 2016

Man, I wish we had a better test suite. I don't remember why that if clause is there, but going through git-blame it, or something fulfilling the same function, has been there for almost the whole project. I'm a little scared to remove it.

@daa
Copy link
Contributor

daa commented Sep 1, 2016

May be libpyarrow.so should not have RPATH set because it's loaded indirectly through parquet.so and linker first will look at that's RPATH location for dependencies - see http://blog.tremily.us/posts/rpath/ and https://wiki.debian.org/RpathIssue or may be I don't understand something. How did you check that repaired wheel does not work and python extension looks to external libraries?

@njsmith
Copy link
Member

njsmith commented Sep 1, 2016

I'm a little scared to remove it.

Then I vote we go ahead and rip it out :-). (And add a test for this case, of course.) Either everything will be fine, or else, something will break and then we'll know what test case we need to add.

@ogrisel
Copy link
Contributor

ogrisel commented Sep 2, 2016

We can always rebuild the manylinux wheels for numpy, scipy and a couple other projects and check that their own tests still pass.

MathMagique added a commit to MathMagique/auditwheel that referenced this issue Sep 23, 2016
@ehashman
Copy link
Member

I believe the if statement works as follows: when hunting through various required libraries, we use the Python extension as a sort of anchor point to start looking for dependency chains of the various ELF files. If we don't do this, we get the test failure identified in #107, where repaired libs that were included in the wheel don't have any paths set and our processing logic chokes. (@geofft to correct me if I'm remembering this wrong.)

The good news is, I think this issue should be addressed in #110, which fixes this bug and safely processes binary files included in wheels that are not Python extensions.

@ehashman
Copy link
Member

I feel like any time we get a report like this we should add it to our tests 😄 Let's see if we can add one for building pyarrow if possible.

@xhochy
Copy link
Author

xhochy commented Oct 27, 2018

pyarrow is now working nicely with auditwheel as we no longer need to repair anything; all paths are already correctly set by its build system. So using it as an example will not give you a failing test as of today.

@ehashman
Copy link
Member

In that case I will just close this.

@mariusvniekerk
Copy link

So the issue is now libraries downstream from pyarrow fail audit wheel in trying to find pyarrow

@maxnoe
Copy link

maxnoe commented Jan 26, 2021

We just also ran into this issue building python bindings for a c++ shared library using pybind11. It would be great if auditwheel would also fix the rpath of included libraries.

The situation is like this:

foo-module → libfoo
foo-module → systemlib
libfoo → systemlib

libfoo is build by setup.py, auditwheel correctly only fixes the library name for the python module, not the same library dependency of libfoo

ldd output of the module:

ldd rawzfitsreader.cpython-37m-x86_64-linux-gnu.so
	linux-vdso.so.1 (0x00007ffda63af000)
	libZFitsIO.so => /home/maxnoe/.local/anaconda/envs/cta-dev/lib/python3.7/site-packages/./libZFitsIO.so (0x00007f5722270000)
	libzstd.so => /home/maxnoe/.local/anaconda/envs/cta-dev/lib/python3.7/site-packages/./libzstd.so (0x00007f57221ee000)
	libADHCore.so => /home/maxnoe/.local/anaconda/envs/cta-dev/lib/python3.7/site-packages/./libADHCore.so (0x00007f57220d8000)
	libzmq-6d47fddd.so.5.0.0 => /home/maxnoe/.local/anaconda/envs/cta-dev/lib/python3.7/site-packages/./rawzfitsreader.libs/libzmq-6d47fddd.so.5.0.0 (0x00007f5721e5c000)
	libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f5721dfa000)
	libprotobuf-cb0440da.so.8.0.0 => /home/maxnoe/.local/anaconda/envs/cta-dev/lib/python3.7/site-packages/./rawzfitsreader.libs/libprotobuf-cb0440da.so.8.0.0 (0x00007f5721a95000)

Observe the libprotobuf-cb0440da...

ldd output of the shared library also linking vs protobuf:

❯ ldd libADHCore.so
	linux-vdso.so.1 (0x00007ffe9f371000)
	libzmq.so.5 => /usr/lib/libzmq.so.5 (0x00007f171b936000)
	libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f171b914000)
	libprotobuf.so.8 => not found

@maxnoe
Copy link

maxnoe commented Jan 27, 2021

To make it clearer, the situation in the unrepaired wheel is this:

foo-module → libfoo
foo-module → systemlib
libfoo → systemlib

After repairing, systemlib gets the hash and is included in the wheel and then it looks like this:

foo-module → libfoo
foo-module → systemlib-HASH
libfoo → systemlib

Observe that libfoo still points to systemlib and not to systemlib-HASH

@maxnoe
Copy link

maxnoe commented Jan 27, 2021

Here is an example project (very artificial, but it displays the problem) which has libfoo.so, a pyfoo extension and links vs. libpng.

https://github.com/maxnoe/example_repair_fail

ldd outputs after auditwheel repair:

❯ ldd pyfoo.cpython-39-x86_64-linux-gnu.so
	linux-vdso.so.1 (0x00007fff97050000)
	libfoo.so => /home/maxnoe/Projects/example_repair_fail/wheelhouse/./libfoo.so (0x00007f979a4ed000)
	libpng15-ce838cd1.so.15.13.0 => /home/maxnoe/Projects/example_repair_fail/wheelhouse/./example_repair_fail.libs/libpng15-ce838cd1.so.15.13.0 (0x00007f979a2be000)
	libz-d8a329de.so.1.2.7 => /home/maxnoe/Projects/example_repair_fail/wheelhouse/./example_repair_fail.libs/libz-d8a329de.so.1.2.7 (0x00007f979a0a7000)
	libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f9799e8a000)
	libm.so.6 => /usr/lib/libm.so.6 (0x00007f9799d44000)
	libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f9799d28000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007f9799b5f000)
	libpng15.so.15 => not found
	libz.so.1 => /usr/lib/libz.so.1 (0x00007f9799b45000)
	/usr/lib64/ld-linux-x86-64.so.2 (0x00007f979a530000)

this is correct. But libfoo cannot find its libpng:

❯ ldd libfoo.so
	linux-vdso.so.1 (0x00007ffe671c1000)
	libpng15.so.15 => not found
	libz.so.1 => /usr/lib/libz.so.1 (0x00007f4b2f540000)
	libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f4b2f363000)
	libm.so.6 => /usr/lib/libm.so.6 (0x00007f4b2f21d000)
	libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f4b2f203000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007f4b2f03a000)
	/usr/lib64/ld-linux-x86-64.so.2 (0x00007f4b2f5a1000)

@ehashman ehashman reopened this Jan 27, 2021
@yehonatanz
Copy link

yehonatanz commented Jan 29, 2021

We encounter this problem too: https://github.com/tomlegkov/marine-python
marine-python is a python wrapper around our fork of the wireshark project, so our libraries are not even python aware.
One symptom of this issue: gnutls in patched in libmarine but not in libwsutil (which is a dependency of libmarine):
image

Everything works well when I change: https://github.com/pypa/auditwheel/blob/master/auditwheel/wheel_abi.py#L76 to if True:, but that's a workaround we'd rather avoid.

@maxnoe
Copy link

maxnoe commented Mar 3, 2021

Could it be that this line is the culprit:

if basename(fn) not in needed_libs:

Being in needed libs does not mean the dependencies have been checked if I read the code correctly.

@maxnoe
Copy link

maxnoe commented Mar 3, 2021

This is the script I am using now to fix the wheels after the intial auditwheel run:

'''
This script fixes missing patches to the library names and rpaths
after running `auditwheel repair`.

It should no longer be needed after this auditwheel issue is fixed:
https://github.com/pypa/auditwheel/issues/48
'''
import os
from zipfile import ZipFile, ZIP_DEFLATED
import subprocess as sp
import tempfile
import argparse
from pathlib import Path
import re

parser = argparse.ArgumentParser()
parser.add_argument('inwheel')
parser.add_argument('outdir')


def zip_dir(zip_file, path):
    for root, _, files in os.walk(path):
        for file in files:
            file = os.path.join(root, file)
            zip_file.write(file, os.path.relpath(file, path))

def lib_name(lib):
    return Path(lib).stem.partition('.')[0].partition('-')[0]


def get_needed_libs(lib):
    result = sp.run(['readelf', '-d', str(lib)], stdout=sp.PIPE, encoding='utf-8')
    out = result.stdout

    libs = re.findall(r'\(NEEDED\)\s+Shared library: \[(.*)\]', out)
    return libs


def fix_wheel(inwheel, outdir):

    with tempfile.TemporaryDirectory(prefix='fix_wheel_') as tmpdir:
        tmpdir = Path(tmpdir)

        print('Extracting wheel')
        ZipFile(inwheel).extractall(path=tmpdir)

        libsdir = next(tmpdir.glob('*.libs'))
        package_name = libsdir.stem

        libs = [p.name for p in libsdir.glob('*.so*')]
        without_hash = [lib_name(lib) for lib in libs]
        vendored_libs = dict(zip(without_hash, libs))


        for lib in tmpdir.glob('**/*.so'):
            print('Patching ', lib)

            rpath = os.path.relpath(libsdir, lib.parent)
            print(rpath)

            rpath = f'$ORIGIN:$ORIGIN/{rpath}'
            print('Using RPATH', rpath)
            sp.run(['patchelf', '--force-rpath', '--set-rpath', rpath , str(lib)], check=True)

            needed_libs = get_needed_libs(lib)

            for needed in needed_libs:
                name = lib_name(needed)
                new = vendored_libs.get(name)
                if new and new != needed:
                    print(f'replacing {needed} with {new} in {lib.name}')
                    sp.run(['patchelf', '--remove-needed', needed, str(lib)], check=True)
                    sp.run(['patchelf', '--add-needed', new, str(lib)], check=True)


        outdir = Path(outdir)
        outdir.mkdir(exist_ok=True, parents=True)
        outwheel = outdir / Path(inwheel).name
        print(f'Writing fixed wheel to {outwheel}')
        with ZipFile(outwheel, 'w', compression=ZIP_DEFLATED, compresslevel=7) as zip_file:
            zip_dir(zip_file, tmpdir)





def main():
    args = parser.parse_args()
    fix_wheel(args.inwheel, args.outdir)


if __name__ == '__main__':
    main()

@mayeut
Copy link
Member

mayeut commented Apr 3, 2021

#136 seems to duplicate this issue.
@maxnoe reproducer is fixed with the fix for #136 so closing this issue.

[root@d94160482d44 example_repair_fail]# ldd /opt/python/cp36-cp36m/lib/python3.6/site-packages/pyfoo.cpython-36m-x86_64-linux-gnu.so 
	linux-vdso.so.1 =>  (0x00007fff01f8b000)
	libfoo.so => /opt/python/cp36-cp36m/lib/python3.6/site-packages/libfoo.so (0x00007f89b50e6000)
	libpng12-640ca796.so.0.49.0 => /opt/python/cp36-cp36m/lib/python3.6/site-packages/example_repair_fail.libs/libpng12-640ca796.so.0.49.0 (0x00007f89b4ebc000)
	libz-eb09ad1d.so.1.2.3 => /opt/python/cp36-cp36m/lib/python3.6/site-packages/example_repair_fail.libs/libz-eb09ad1d.so.1.2.3 (0x00007f89b4ca5000)
	libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f89b499f000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f89b471b000)
	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f89b4505000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f89b4171000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f89b5523000)
[root@d94160482d44 example_repair_fail]# ldd /opt/python/cp36-cp36m/lib/python3.6/site-packages/libfoo.so 
	linux-vdso.so.1 =>  (0x00007fff19d73000)
	libpng12-640ca796.so.0.49.0 => /opt/python/cp36-cp36m/lib/python3.6/site-packages/example_repair_fail.libs/libpng12-640ca796.so.0.49.0 (0x00007f2589512000)
	libz-eb09ad1d.so.1.2.3 => /opt/python/cp36-cp36m/lib/python3.6/site-packages/example_repair_fail.libs/libz-eb09ad1d.so.1.2.3 (0x00007f25892fb000)
	libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f2588ff5000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f2588d71000)
	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f2588b5b000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f25887c7000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f2589941000)

@mayeut mayeut closed this as completed Apr 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants