3 คะแนน โดย GN⁺ 2024-06-10 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • libtree คือเครื่องมือที่เปลี่ยนเอาต์พุตของ ldd ให้เป็นรูปแบบต้นไม้ และอธิบายว่าไลบรารีแบบแชร์ถูกค้นพบได้อย่างไร หรือเหตุใดจึงหาไม่พบ
  • เอาต์พุตพื้นฐานจะซ่อน dependency มาตรฐาน บางส่วนไว้ และสามารถใช้ -v, -vv, -vvv เพื่อดูไลบรารีที่ถูกซ่อน รวมถึง dependency ของไลบรารีที่เคยพบแล้วได้แบบไล่ระดับ
  • --path หรือ -p แสดง พาธ แทน soname และสามารถใช้ --max-depth เพื่อจำกัดความลึกของการค้นหาแบบ recursive ได้
  • การติดตั้งทำได้ผ่าน ไบนารีที่ build ไว้ล่วงหน้า v3.1.1, Fedora/RHEL/CentOS, Ubuntu 22.04+ และ GNU Guix
  • การ build จากซอร์สต้องใช้คอมไพเลอร์ C ที่เข้าใจ C99 และเมื่อใช้ make แนะนำให้ใช้ LDFLAGS=-static

สิ่งที่ libtree ทำ

  • libtree คือเครื่องมือที่เปลี่ยน ldd ให้อยู่ในรูปแบบต้นไม้
  • อธิบายว่าไลบรารีแบบแชร์ถูก ค้นพบได้อย่างไร หรือเหตุใดจึงระบุตำแหน่งไม่ได้
  • README มีสกรีนช็อต doc/screenshot.png รวมอยู่ด้วย

ตัวเลือกเอาต์พุต

  • เอาต์พุตพื้นฐานจะไม่แสดง dependency มาตรฐาน บางส่วน
  • เอาต์พุตที่ละเอียดขึ้นควบคุมได้ด้วยตัวเลือก verbosity
    • libtree -v: แสดงไลบรารีที่โดยปกติจะถูกข้าม
    • libtree -vv: แสดง dependency ของไลบรารีที่โดยปกติจะถูกข้ามด้วย
    • libtree -vvv: แสดง dependency ของไลบรารีที่เคยพบแล้วด้วย
  • แฟล็ก --path หรือ -p แสดง พาธ แทน soname
    • ตัวอย่าง: libtree -p $(which tar)
  • --max-depth จำกัดความลึกของการ recursive

วิธีติดตั้ง

Build จากซอร์ส

  • libtree ต้องใช้คอมไพเลอร์ C ที่เข้าใจ C99
  • ขั้นตอน build พื้นฐานคือ clone repository แล้วรัน make
  • เมื่อใช้ make แนะนำให้ใช้ LDFLAGS=-static
  • README ยังมีคำสั่ง unsafe quick install สำหรับดาวน์โหลด libtree.c ด้วย curl แล้วคอมไพล์ แยกไว้ในส่วนที่พับได้

2 ความคิดเห็น

 
GN⁺ 2024-06-10
ความคิดเห็นจาก Hacker News
  • เครื่องมือนี้ก็ทำตาม พฤติกรรมที่คาดไม่ถึง ของ ldd ที่รันบางส่วนของไลบรารีที่ถูกตรวจสอบจริง ๆ ด้วยหรือเปล่า?
    https://catonmat.net/ldd-arbitrary-code-execution

    • เมื่อเร็ว ๆ นี้ ldd เวอร์ชันที่เก่ากว่าประมาณ 5 ปีขึ้นไป จะไม่รันไบนารีเป้าหมายแล้ว
      อ้างอิง: https://manpages.debian.org/unstable/manpages/ldd.1.en.html#...
    • ลองกวาดดูโค้ดเร็ว ๆ บนมือถือแล้ว ดูเหมือนว่าจะไม่ได้ทำแบบนั้น
      ที่จริงดูเหมือนมัน พาร์สไฟล์ ELF โดยตรง และพาร์ส dependency แบบ recursive ด้วย ค่อนข้างเจ๋งทีเดียว
    • เคยทำอะไรคล้าย ๆ กันแบบเลือน ๆ สำหรับ Python แต่สุดท้ายก็เลี่ยงปัญหานี้ไม่ได้ https://github.com/google/importlab/issues/69
    • ถ้าใช้ objdump มันจะพิมพ์ข้อมูลออกมาตามที่เข้ารหัสอยู่ในไฟล์ ELF เลย
      รวมถึงรายการไลบรารีที่ vdso จะค้นหาด้วย
  • มีเครื่องมือคล้ายกันชื่อ lddtree
    https://github.com/gentoo/pax-utils/tree/master

  • การต้อง ไล่ตาม dependency ที่หายไปแบบ recursive ซ้ำ ๆ ด้วย ldd นั้นค่อนข้างน่าเบื่อ
    ดังนั้นอันนี้ดูเป็นการปรับปรุงที่ดี และคราวหน้าถ้าเจอ not found แบบคลุมเครือจะลองใช้ดู

  • โดยพื้นฐานแล้วเหมือน depends.exe เวอร์ชัน Linux CLI

    • เครื่องมือนั้นโดยเฉพาะใช้งานกับ Windows รุ่นใหม่ ๆ ได้ไม่ดีแล้ว
      ใช้ https://github.com/lucasg/Dependencies แทนจะดีกว่า ถึงอันนี้ก็ไม่ได้ทันสมัยสุด ๆ เช่นกัน…
      ถ้าคุณติดตั้ง Visual Studio และเลือก x64/x86 build tools (latest) ในตัวติดตั้งไว้ การรัน dumpbin /dependents ใน VS Developer Command Prompt ยังคงเป็นตัวเลือกที่น่าเชื่อถือที่สุด
    • ใช่เลย ผมก็คิดแบบนั้นเป๊ะ
      [1] https://www.dependencywalker.com/
  • สำหรับคนที่สงสัยว่าสีต่าง ๆ หมายถึงอะไร ขอจดไว้ตรงนี้ เพราะหาไม่เจอใน manpage/README
    สีม่วงแดง: อยู่ในรายการยกเว้น จะแสดงเฉพาะใน -v[v[v]]
    สีน้ำเงิน: เป็นรายการที่เคยเห็นมาก่อน จึงช่วยหา dependency ที่ปรากฏหลายครั้งได้

  • “เหตุผลที่ไลบรารีถูกพบหรือไม่ถูกพบ” หมายความว่าอะไร? มันก็แค่อยู่หรือไม่อยู่ใน LD_LIBRARY_PATH ไม่ใช่เหรอ?
    ดูจากสกรีนช็อตอย่างเดียวก็ไม่ค่อยเข้าใจว่าหมายถึงอะไร

    • ในบริบทของเครื่องมือนี้ผมไม่แน่ใจนัก แต่ การค้นหาไลบรารี ซับซ้อนกว่าตัวแปรสภาพแวดล้อมตัวเดียวมาก
      มีวิธีค้นหาไดเรกทอรีหลายแบบ เช่น เส้นทางค้นหาของระบบ, runpath, rpath, LD_LIBRARY_PATH ฯลฯ
      ไลบรารีมักถูกลิงก์ด้วยชื่อสั้น ๆ อย่าง foo.so แต่ก็อาจถูก dynamic link ด้วยพาธเต็มของไลบรารีได้เช่นกัน
      เสริมอีกอย่าง โดยทั่วไปถ้าเลี่ยงได้ควรเลี่ยงการตั้ง LD_LIBRARY_PATH จะดีกว่ามาก แม้จะไม่ได้ทำได้เสมอไป แต่ถ้าตั้งแล้วมันจะขึ้นไปอยู่บนสุดของลำดับความสำคัญในการค้นหาสำหรับทุกการรัน แม้บางอย่างจะถูก dynamic link ด้วยพาธเต็มของไลบรารี LD_LIBRARY_PATH ก็ยังมีลำดับความสำคัญสูงกว่า และทำให้วิธีค้นหาถูกทำให้แบนราบไปหมด
    • ไลบรารีไม่จำเป็นต้องอยู่ใน LD_LIBRARY_PATH เสมอไปถึงจะถูกพบ
      ใจความของชื่อคือ libtree ช่วยให้หาเส้นทางจากไฟล์ executable ไปจนถึง dependency ทั้งแบบตรงและอ้อมทั้งหมดได้ง่าย หนึ่งในกรณีใช้งานคือช่วยวิเคราะห์ปัญหา dependency ที่ขาดหาย
      ในทางปฏิบัติ ถ้าคุณใช้ระบบแพ็กเกจ dependency ก็คงไม่ค่อยหาย ดังนั้นมีแนวโน้มว่าจะใช้ libtree ด้วยเหตุผลอื่นมากกว่า
    • ไม่ได้มีแค่อยู่หรือไม่อยู่ใน LD_LIBRARY_PATH เท่านั้น แต่ยังมี RPATH ที่ประเมินตอนโหลดสำหรับแต่ละไลบรารีด้วย
      ประเด็นที่ใหญ่กว่าคือ dependency ก่อตัวเป็นกราฟ และสามารถแสดงเหมือนต้นไม้ได้ การรู้ว่าไลบรารีใดเป็นต้นเหตุให้ต้องการไลบรารีบางตัวจนหาไม่เจอมีประโยชน์
    • ไม่ได้ใช้แค่ LD_LIBRARY_PATH เพื่อหาไลบรารีเท่านั้น loader ยังพิจารณาแหล่งอื่น ๆ อีกหลายแห่งเพื่อค้นหาไลบรารี
      ในการตั้งค่าปกติ จะเป็นการผสมกันระหว่าง ฟิลด์ ELF ทั่วไปของแต่ละไบนารีที่ถูกโหลด กับพาธอื่น ๆ ที่ loader รู้จัก
      บนระบบที่มีไลบรารีเดียวกันหลายเวอร์ชัน หรือมีไลบรารีหลายตัวที่ชื่อเดียวกัน การพึ่งพา LD_LIBRARY_PATH อาจเป็นมุมมองที่สั้นมาก loader จะค้นหาพาธใน LD_LIBRARY_PATH ตามลำดับสำหรับแต่ละไบนารี และเลือกไลบรารีตัวแรกที่ตรงกัน ถ้าไม่ได้ตั้งพาธที่มีลำดับความสำคัญสูงกว่าด้วยวิธีอื่น ไลบรารีนั้นอาจไม่ใช่ตัวที่ต้องการจริง ๆ และอาจนำไปสู่ข้อผิดพลาดที่ไม่คาดคิดใน runtime
      วิธีที่ดีกว่าคือตั้ง RPATH ให้ชี้ไปยังตำแหน่งที่มีไลบรารีที่ไบนารีนั้นต้องการ
      หาก environment และการตั้งค่า RPATH ไม่สอดคล้องกัน ก็อาจโหลดไลบรารีเดียวกันหลายเวอร์ชันพร้อมกันได้ เครื่องมือนี้ช่วยให้เข้าใจว่ามีปัญหาหรือไม่ และเพราะอะไร
    • อย่างน้อยก็มี RPATH, RUNPATH, LD_LIBRARY_PATH
      เครื่องมือนี้อิงกับ ldd ดังนั้นก็น่าจะตีความ DYLD_LIBRARY_PATH, DYLD_FALLBACK_FRAMEWORK_PATH, DYLD_FALLBACK_LIBRARY_PATH, @executable_path, @loader_path, @rpath ด้วย
  • มีประโยชน์มาก ปกติจะใช้ readelf อ่าน section เพื่อดูว่าความต้องการจริง ๆ คืออะไร

  • LD_DEBUG=libs ยังไม่พอเหรอ?

    • อันนั้นเป็น flag ดีบักของ loader ไม่ใช่ การประเมินแบบสแตติก ของ dependency ไลบรารี จึงไม่เหมือนกันเสียทีเดียว
  • ไม่รู้ว่าเป็นบั๊กหรือเปล่า แต่ในตัวอย่าง vim ldd กับ libtree แสดงไลบรารีคนละชุดกัน
    เช่น linux-vdso.so.1 อยู่บนสุดของรายการใน ldd แต่ไม่ปรากฏใน libtree เลย

    • linux-vdso.so.1 ไม่ใช่ไลบรารีจริง ๆ ที่จะหาเจอได้ที่ไหนสักแห่งในไฟล์ซิสเต็ม และไม่ได้ถูกอ้างอิงในไฟล์ ELF ด้วย ดังนั้น libtree จึงรู้ไม่ได้
      แต่เคอร์เนลจะ map มันเข้าไปใน address space ของโปรเซสที่เพิ่งเริ่มโดยอัตโนมัติแทน เป็นการเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยง overhead ของ system call ในฟังก์ชันอย่าง gettimeofday อ้างอิง: https://man7.org/linux/man-pages/man7/vdso.7.html
  • เคยทำ สคริปต์เล็ก ๆ ที่ยุ่งเหยิง เพื่อรัน ldd แบบ recursive สำหรับดูว่าต้องใส่อะไรบ้างถึงจะรันไบนารี closed source บน NixOS ได้
    ถ้าต้องทำแบบนั้นอีก คงจะลองใช้เครื่องมือนี้ดู

 
kayws426 2024-06-11

ดูดีนะ!!