3 Punkte von GN⁺ 2024-06-10 | 2 Kommentare | Auf WhatsApp teilen
  • libtree ist ein Tool, das die Ausgabe von ldd in einen Baum umwandelt und erklärt, wie eine Shared Library gefunden wurde oder warum sie nicht gefunden werden kann
  • In der Standardausgabe werden einige Standardabhängigkeiten ausgeblendet; mit -v, -vv, -vvv lassen sich schrittweise die ausgeblendeten Bibliotheken sowie die Abhängigkeiten bereits zuvor gesehener Bibliotheken anzeigen
  • --path oder -p zeigt Pfade statt soname an, und mit --max-depth kann die Tiefe der rekursiven Suche begrenzt werden
  • Die Installation ist über v3.1.1 Prebuilt-Binaries, Fedora/RHEL/CentOS, Ubuntu 22.04+ und GNU Guix möglich
  • Für den Build aus dem Quellcode wird ein C-Compiler benötigt, der C99 versteht; bei Verwendung von make wird LDFLAGS=-static empfohlen

Was libtree macht

  • libtree ist ein Tool, das ldd in Baumform darstellt
  • Es erklärt, wie Shared Libraries gefunden werden oder warum ihr Speicherort nicht ermittelt werden kann
  • Im README ist ein Screenshot unter doc/screenshot.png enthalten

Ausgabeoptionen

  • In der Standardausgabe werden einige Standardabhängigkeiten nicht angezeigt
  • Detailliertere Ausgabe wird über Verbosity-Optionen gesteuert
    • libtree -v: zeigt Bibliotheken an, die standardmäßig übersprungen werden
    • libtree -vv: zeigt auch die Abhängigkeiten der standardmäßig übersprungenen Bibliotheken an
    • libtree -vvv: zeigt auch die Abhängigkeiten bereits zuvor gesehener Bibliotheken an
  • Das Flag --path oder -p zeigt Pfade statt soname an
    • Beispiel: libtree -p $(which tar)
  • --max-depth begrenzt die Rekursionstiefe

Installation

Aus dem Quellcode bauen

  • libtree benötigt einen C-Compiler, der C99 versteht
  • Das Standard-Build-Verfahren besteht darin, das Repository zu klonen und dann make auszuführen
  • Bei Verwendung von make wird LDFLAGS=-static empfohlen
  • Im README gibt es außerdem in einem separaten eingeklappten Abschnitt einen Befehl für eine unsafe quick install, der libtree.c per curl herunterlädt und kompiliert

2 Kommentare

 
GN⁺ 2024-06-10
Hacker-News-Kommentare
  • Übernimmt dieses Tool auch das unerwartete Verhalten von ldd, bei dem einige der zu untersuchenden Libraries tatsächlich ausgeführt werden?
    https://catonmat.net/ldd-arbitrary-code-execution

    • Neuere, ungefähr höchstens 5 Jahre alte Versionen von ldd führen das Ziel-Binary nicht aus.
      Siehe: https://manpages.debian.org/unstable/manpages/ldd.1.en.html#...
    • Beim schnellen Überfliegen des Codes auf dem Handy sieht es nicht so aus, als würde es das tun.
      Tatsächlich scheint es ELF-Dateien direkt zu parsen und auch Abhängigkeiten rekursiv zu parsen, was ziemlich cool ist.
    • Ich hatte einmal etwas vage Ähnliches für Python gebaut, konnte dieses Problem aber letztlich nicht umgehen: https://github.com/google/importlab/issues/69
    • Wenn man objdump verwendet, gibt es die Daten so aus, wie sie in der ELF-Datei codiert sind.
      Dazu gehört sogar die Liste der Libraries, nach denen vdso suchen soll.
  • Es gibt ein ähnliches Tool namens lddtree.
    https://github.com/gentoo/pax-utils/tree/master

  • Fehlende Abhängigkeiten mit ldd rekursiv immer wieder nachzuverfolgen ist ziemlich mühsam.
    Das hier wirkt daher wie eine gute Verbesserung, und beim nächsten unklaren not found werde ich es ausprobieren.

  • Im Grunde wie eine Linux-CLI-Version von depends.exe.

    • Dieses konkrete Tool funktioniert auf aktuellen Windows-Versionen nicht mehr besonders gut.
      Besser ist stattdessen https://github.com/lucasg/Dependencies. Auch das ist nicht völlig auf dem neuesten Stand, aber …
      Wenn Visual Studio installiert ist und im Installer x64/x86 build tools (latest) ausgewählt wurde, ist dumpbin /dependents in der VS Developer Command Prompt weiterhin die verlässlichste Option.
    • Genau, das war auch mein erster Gedanke.
      [1] https://www.dependencywalker.com/
  • Falls sich jemand fragt, was die Farben bedeuten: In manpage/README habe ich es nicht gefunden.
    Magenta: steht auf der Ausschlussliste, wird nur bei -v[v[v]] angezeigt.
    Blau: wurde zuvor bereits gesehen, sodass man mehrfach auftauchende Abhängigkeiten finden kann.

  • Was heißt „warum eine Library gefunden wird oder nicht“? Ist sie nicht entweder in LD_LIBRARY_PATH oder eben nicht?
    Nur anhand des Screenshots verstehe ich nicht ganz, worauf sich das bezieht.

    • Im Kontext dieses Tools weiß ich es nicht genau, aber die Library-Suche ist deutlich komplexer als eine einzelne Umgebungsvariable.
      Es gibt mehrere unterschiedliche Arten, Verzeichnisse zu durchsuchen: System-Suchpfade, runpath, rpath, LD_LIBRARY_PATH usw.
      Libraries werden normalerweise mit einem kurzen Namen wie foo.so gelinkt, können aber auch über den vollständigen Pfad der Library dynamisch gelinkt werden.
      Außerdem sollte man LD_LIBRARY_PATH im Allgemeinen vermeiden, wenn es geht. Das ist nicht immer möglich, aber wenn es gesetzt ist, steht es bei jeder Ausführung ganz oben in der Suchpriorität. Selbst wenn etwas dynamisch mit dem vollständigen Pfad einer Library gelinkt ist, hat LD_LIBRARY_PATH Vorrang und macht die Suchlogik vollständig flach.
    • Eine Library muss nicht zwingend in LD_LIBRARY_PATH liegen, um gefunden zu werden.
      Der Punkt des Titels ist, dass libtree es einfach macht, die Pfade von einer ausführbaren Datei zu allen direkten und indirekten Abhängigkeiten zu finden. Eine der Anwendungen davon ist, Probleme mit fehlenden Abhängigkeiten besser zu verstehen.
      Wenn man tatsächlich ein Paketsystem verwendet, fehlen Abhängigkeiten vermutlich eher selten; libtree wird man also wahrscheinlich aus anderen Gründen nutzen.
    • Es geht nicht nur darum, ob etwas in LD_LIBRARY_PATH liegt oder nicht; es gibt auch RPATH, das für jede Library beim Laden ausgewertet wird.
      Der größere Punkt ist, dass Abhängigkeiten einen Graphen bilden, der sich wie ein Baum darstellen lässt. Es ist nützlich zu wissen, wegen welcher Library, die eine bestimmte Library benötigt, diese dann nicht gefunden wurde.
    • Libraries werden nicht nur über LD_LIBRARY_PATH gefunden. Der Loader berücksichtigt auch mehrere andere Quellen, um Libraries zu finden.
      In einer normalen Konfiguration ist es eine Kombination aus den üblichen ELF-Feldern jedes geladenen Binaries und anderen dem Loader bekannten Pfaden.
      Auf Systemen mit mehreren Versionen derselben Library oder mehreren Libraries mit demselben Namen kann es sehr kurzsichtig sein, sich auf LD_LIBRARY_PATH zu verlassen. Der Loader durchsucht für jedes Binary die Pfade in LD_LIBRARY_PATH der Reihe nach und nimmt die erste passende Library. Wenn nicht auf andere Weise ein Pfad mit höherer Priorität gesetzt wurde, ist diese Library möglicherweise nicht die tatsächlich gewünschte, was zur Laufzeit zu unerwarteten Fehlern führen kann.
      Besser ist es, für das betreffende Binary RPATH auf den Ort der benötigten Libraries zu setzen.
      Wenn Umgebung und RPATH-Einstellungen nicht konsistent sind, können sogar mehrere Versionen derselben Library gleichzeitig geladen werden. Dieses Tool hilft dabei herauszufinden, ob es ein Problem gibt und warum.
    • Mindestens gibt es RPATH, RUNPATH und LD_LIBRARY_PATH.
      Da dieses Tool auf ldd basiert, wird es vermutlich auch DYLD_LIBRARY_PATH, DYLD_FALLBACK_FRAMEWORK_PATH, DYLD_FALLBACK_LIBRARY_PATH, @executable_path, @loader_path und @rpath interpretieren.
  • Wirklich nützlich. Normalerweise lese ich mit readelf die Sections aus, um herauszufinden, was tatsächlich benötigt wird.

  • Reicht LD_DEBUG=libs nicht aus?

    • Das ist ein Loader-Debug-Flag und keine statische Auswertung von Library-Abhängigkeiten, also nicht ganz dasselbe.
  • Ich weiß nicht, ob es ein Bug ist, aber im vim-Beispiel zeigen ldd und libtree unterschiedliche Libraries.
    Zum Beispiel steht linux-vdso.so.1 bei ldd ganz oben in der Liste, taucht bei libtree aber gar nicht auf.

    • linux-vdso.so.1 ist keine echte Library, die man irgendwo im Dateisystem finden kann, und sie wird auch in der ELF-Datei nicht referenziert; deshalb kann libtree nichts davon wissen.
      Stattdessen mappt der Kernel sie automatisch in den Adressraum eines neu gestarteten Prozesses. Das ist eine Optimierung, um bei Funktionen wie gettimeofday den System-Call-Overhead zu vermeiden. Siehe: https://man7.org/linux/man-pages/man7/vdso.7.html
  • Ich hatte einmal ein chaotisches kleines Skript geschrieben, das ldd rekursiv ausführt, um herauszufinden, was man einbinden muss, damit Closed-Source-Binaries auf NixOS laufen.
    Wenn ich so etwas noch einmal machen muss, werde ich dieses Tool ausprobieren.

 
kayws426 2024-06-11

Sieht gut aus!!