haampie / libtree

ldd as a tree
MIT License
2.63k stars 60 forks source link

documentation request: comparison with lddtree #88

Closed bmillwood closed 4 months ago

bmillwood commented 5 months ago

While looking for tools like this, I happened across this one and also lddtree, a part of pax-utils.

I think it could be interesting / useful to mention that tool here too, and describe whether libtree differs in philosophy or purpose or functionality in any non-obvious ways. Superficially, it looks like libtree makes more effort to have pretty and visually clear output, whereas lddtree appears to have more features targeted around copying a binary together with its libraries to a new location, but I haven't looked super closely at either of them.

For your convenience, I provide the output of lddtree --help below. One other thing I notice is that the lddtree help specifically mentions that it doesn't load or run the supplied code, just reads it. I expect this is also true of libtree, which has led e.g. this blog post to recommend using libtree instead of ldd. Note though that kernel.org mailing list thread "ldd: Do not recommend binutils as the safer option" suggests that running anything on an untrusted binary is risky outside of a properly sandboxed environment anyway. (In case you're curious, I found both the blog post and mailing list thread while reading this gist about the xz backdoor.)

usage: lddtree [-h] [-a] [-l] [-x] [-v] [--skip-non-elfs] [--skip-missing] [-V]
               [-R ROOT] [--no-auto-root] [-C CWD] [-P PREFIX] [--copy-to-tree DEST]
               [--bindir BINDIR] [--libdir LIBDIR] [--generate-wrappers]
               [--copy-non-elfs]
               path [path ...]

Read the ELF dependency tree and show it

This does not work like `ldd` in that we do not execute/load code (only read
files on disk), and we show the ELFs as a tree rather than a flat list.

Paths may be globs that lddtree will take care of expanding.
Useful when you want to glob a path under the ROOT path.

When using the --root option, all paths are implicitly prefixed by that.
  e.g. lddtree -R /my/magic/root /bin/bash
This will load up the ELF found at /my/magic/root/bin/bash and then resolve
all libraries via that path.  If you wish to actually read /bin/bash (and
so use the ROOT path as an alternative library tree), you can specify the
--no-auto-root option.

When pairing --root with --copy-to-tree, the ROOT path will be stripped.
  e.g. lddtree -R /my/magic/root --copy-to-tree /foo /bin/bash
You will see /foo/bin/bash and /foo/lib/libc.so.6 and not paths like
/foo/my/magic/root/bin/bash.  If you want that, you'll have to manually
add the ROOT path to the output path.

The --bindir and --libdir flags are used to normalize the output subdirs
when used with --copy-to-tree.
  e.g. lddtree --copy-to-tree /foo /bin/bash /usr/sbin/lspci /usr/bin/lsof
This will mirror the input paths in the output.  So you will end up with
/foo/bin/bash and /foo/usr/sbin/lspci and /foo/usr/bin/lsof.  Similarly,
the libraries needed will be scattered among /foo/lib/ and /foo/usr/lib/
and perhaps other paths (like /foo/lib64/ and /usr/lib/gcc/...).  You can
collapse all that down into nice directory structure.
  e.g. lddtree --copy-to-tree /foo /bin/bash /usr/sbin/lspci /usr/bin/lsof \
               --bindir /bin --libdir /lib
This will place bash, lspci, and lsof into /foo/bin/.  All the libraries
they need will be placed into /foo/lib/ only.

positional arguments:
  path

options:
  -h, --help            show this help message and exit
  -a, --all             Show all duplicated dependencies
  -l, --list            Display output in a simple list (easy for copying)
  -x, --debug           Run with debugging
  -v, --verbose         Be verbose
  --skip-non-elfs       Skip plain (non-ELF) files instead of warning
  --skip-missing        Skip missing files instead of failing
  -V, --version         Show version information

Path options:
  -R ROOT, --root ROOT  Search for all files/dependencies in ROOT
  --no-auto-root        Do not automatically prefix input ELFs with ROOT
  -C CWD, --cwd CWD     Path to resolve relative paths against
  -P PREFIX, --prefix PREFIX
                        Specify EPREFIX for binaries (for Gentoo Prefix)

Copying options:
  --copy-to-tree DEST   Copy all files to the specified tree
  --bindir BINDIR       Dir to store all ELFs specified on the command line
  --libdir LIBDIR       Dir to store all ELF libs
  --generate-wrappers   Wrap executable ELFs with scripts for local ldso
  --copy-non-elfs       Copy over plain (non-ELF) files instead of warn+ignore
haampie commented 4 months ago

I didn't see this issue until now. I don't think it's really necessary to have an extensive comparison documented somewhere. It's just a few points:

  1. libtree is about 100 - 200x faster than lddtree:

    ~ $ time libtree /usr/bin/* > /dev/null 2>&1
    real 0m0.255s
    user 0m0.048s
    sys  0m0.206s
    
    ~ $ time /usr/bin/python3 /usr/bin/lddtree /usr/bin/* > /dev/null 2>&1
    real 0m47.448s
    user 0m45.647s
    sys  0m1.508s
  2. libtree is trivial to install (from source all you need is a c99 compiler, or you use prebuilt binaries); lddtree uses bash, python, and binutils (?). For me apt install pax-utils didn't even work because apparently you have to run it like /usr/bin/python3 /usr/bin/lddtree because I happened to have a different python in my PATH -- not exactly user friendly.
  3. I removed library bundling from libtree in v3, so libtree is single-purpose: show (static) dependencies of executables and libraries in a pretty way. *

And yeah, libtree was motivated partly because of that odd behavior of ldd. It's a re-implementation of the resolver with prettier printing. That means that libtree just parses the dynamic section of ELF files. I don't think there is any risk in attempting to parse an untrusted / malformed ELF file, it will just fail.

* Bundling was removed because it's more complicated and also lddtree gets it wrong. There is another tool (I forgot which) that does a much better job at bundling dynamically opened libraries and files by tracing the executable as it runs, and it also bundles libc by copying the dynamic linker and adding wrapper scripts for executables. Even that is no guarantee for it to work. Point is I wanted to have a tool that does one thing really well, and not add features that will cause infinite maintenance.

bmillwood commented 4 months ago

Thanks, that reply is exactly what I wanted :)

(I'm tempted to say you should just link this issue from the README or something, but I guess I shouldn't assume that every question I have is an FAQ)