基于ldd快速遍历应用/库的依赖关系
对于复杂的应用或库文件,要了解其运作原理、架构,通过了解其库依赖关系不失为一种手段。
ldd可以查看单个可执行文件或库文件以来的库,但是库比较多的话不容易有个全局概念。
所以基于ldd编解Python脚本,做个小工具,提供集中查看方式:
- 直接文本查看类似tree命令输出的属性结构。
- xml格式的树形结构。
- dot格式数据,然后转换成png查看调用关系。
第一种最简单,可以嵌入式设备直接查看;最后一种最直观,最有效。
1 ldd
ldd可以显示一个应用程序或者库文件以来的库文件,基于这个命令可以遍历所有被依赖的库文件。
重点看ldd结果输出的依赖库文件的路径,可以被ldd再次调用,递归遍历所有的库文件。
ldd /usr/bin/weston linux-vdso.so.1 (0x0000007f8f9e5000) libexec_weston.so.0 => /usr/lib/weston/libexec_weston.so.0 (0x0000007f8f960000) libc.so.6 => /lib64/libc.so.6 (0x0000007f8f7f0000) libweston-11.so.0 => /usr/lib64/libweston-11.so.0 (0x0000007f8f780000) libwayland-client.so.0 => /usr/lib64/libwayland-client.so.0 (0x0000007f8f760000) libwayland-server.so.0 => /usr/lib64/libwayland-server.so.0 (0x0000007f8f730000) libinput.so.10 => /usr/lib64/libinput.so.10 (0x0000007f8f6d0000) libevdev.so.2 => /usr/lib64/libevdev.so.2 (0x0000007f8f6a0000) /lib/ld-linux-aarch64.so.1 (0x0000007f8f9b2000) libpixman-1.so.0 => /usr/lib64/libpixman-1.so.0 (0x0000007f8f630000) libdrm.so.2 => /usr/lib64/libdrm.so.2 (0x0000007f8f600000) libxkbcommon.so.0 => /usr/lib64/libxkbcommon.so.0 (0x0000007f8f5a0000) libmali_hook.so.1 => /usr/lib64/libmali_hook.so.1 (0x0000007f8f580000) libmali.so.1 => /usr/lib64/libmali.so.1 (0x0000007f88b60000) libffi.so.8 => /usr/lib64/libffi.so.8 (0x0000007f88b40000) libmtdev.so.1 => /usr/lib64/libmtdev.so.1 (0x0000007f88b20000) libudev.so.1 => /lib64/libudev.so.1 (0x0000007f88ae0000) libm.so.6 => /lib64/libm.so.6 (0x0000007f88a50000) librga.so.2 => /usr/lib64/librga.so.2 (0x0000007f88a20000) libdl.so.2 => /lib64/libdl.so.2 (0x0000007f88a00000) libpthread.so.0 => /lib64/libpthread.so.0 (0x0000007f889e0000) libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000007f88850000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000007f88820000)
2 基于ldd编写python脚本
2.1 生成类似tree库调用关系
#!/usr/bin/python import sys, os def interate_elf_libraries(header, elf_name): result = os.popen("ldd %s"%(elf_name)) header = header + " " for library in result.readlines(): library_split= library.split() if len(library_split) == 4: elf_name = library_split[2] if( not ("libm.so" in elf_name or "libc.so" in elf_name or "libgcc_s.so" in elf_name or "libdl.so" in elf_name or "libpthread.so" in elf_name or "libstdc++.so" in elf_name)): print(header + "|-" + elf_name) interate_elf_libraries(header, elf_name) result.close() if __name__ == '__main__': if len(sys.argv) < 2: print("No input argument.") sys.exit(1) header = "" elf_name = sys.argv[1] print(header + elf_name) interate_elf_libraries(header, elf_name)
执行./ldd-ext.py /usr/bin/weston,结果如下:
/usr/bin/weston |-/usr/lib/weston/libexec_weston.so.0 |-/usr/lib64/libweston-11.so.0 |-/usr/lib64/libwayland-server.so.0 |-/usr/lib64/libffi.so.8 |-/usr/lib64/libpixman-1.so.0 |-/usr/lib64/librga.so.2 |-/usr/lib64/libdrm.so.2 |-/usr/lib64/libdrm.so.2 |-/usr/lib64/libdrm.so.2 |-/usr/lib64/libxkbcommon.so.0 |-/usr/lib64/libmali_hook.so.1 |-/usr/lib64/libmali.so.1 |-/usr/lib64/libdrm.so.2 |-/usr/lib64/libwayland-client.so.0 |-/usr/lib64/libffi.so.8 |-/usr/lib64/libwayland-server.so.0 |-/usr/lib64/libffi.so.8 |-/usr/lib64/libffi.so.8 ...
2.2 生成xml格式的库调用关系
#!/usr/bin/python import sys, os def interate_elf_libraries(header, elf_name): result = os.popen("ldd %s"%(elf_name)) result_lines = result.readlines() elf_has_sub=0 for library in result_lines: library_split= library.split() if len(library_split) == 4: elf_name = library_split[2] if( not ("libm.so" in elf_name or "libc.so" in elf_name or "libgcc_s.so" in elf_name or "libdl.so" in elf_name or "libpthread.so" in elf_name or "libstdc++.so" in elf_name)): elf_has_sub=1 if( elf_has_sub == 1): print(header + "<sub>") header_sub = header + "\t" for library in result_lines: library_split= library.split() if len(library_split) == 4: elf_name = library_split[2] if( not ("libm.so" in elf_name or "libc.so" in elf_name or "libgcc_s.so" in elf_name or "libdl.so" in elf_name or "libpthread.so" in elf_name or "libstdc++.so" in elf_name)): print(header_sub + "<name>" + elf_name + "</name>") interate_elf_libraries(header_sub, elf_name) print(header + "</sub>") result.close() if __name__ == '__main__': if len(sys.argv) < 2: print("No input argument.") sys.exit(1) print("<?xml version='1.0' encoding='UTF-8'?>") header = "" elf_name = sys.argv[1] print("<app>") print(header + "<name>" + elf_name + "</name>") interate_elf_libraries(header, elf_name) print("</app>")
执行./ldd-ext.py /usr/bin/weston > weston.xml,然后再浏览器中查看如下:
2.3 生成dot格式库调用关系,然后转成png图片
大概流程如下:
- ldd获取被调用的库文件路径。
- python递归调用ldd,并保存成dot格式的文件。
- 使用dot命令根据dot数据生成png等格式。
#!/usr/bin/python import sys, os, re #exclude_libs = ["libm.so", "libc.so", "libgcc_s.so", "libpthread.so", "libstdc++.so", "libdl.so", "libogg.so", "libffi.so", "librga.so", "libglib-2.0.so", "libz.so", "libgthread-2.0.so", "libpcre.so", "libpcre2-16.so"] exclude_libs = ["libm.so", "libc.so", "libgcc_s.so", "libpthread.so", "libstdc++.so", "libdl.so", "libz.so"] def interate_elf_libraries(header, elf_name): result = os.popen("ldd %s"%(elf_name)) regex_pattern = '|'.join(map(re.escape, exclude_libs)) header = header + " " for library in result.readlines(): library_split= library.split() if len(library_split) == 4:#这种情况才会包含库文件路径。 sub_elf_name = library_split[2] if not re.search(regex_pattern, sub_elf_name, re.IGNORECASE):#排除某些不重要,或者不想查看的库文件。 print("\"" + elf_name + "\"->\"" + sub_elf_name + "\";") interate_elf_libraries(header, sub_elf_name)#递归遍历被依赖的库文件。 result.close() if __name__ == '__main__': if len(sys.argv) < 2: print("No input argument.") sys.exit(1) header = "" elf_name = sys.argv[1] print("strict digraph lib_call {")#一定要用strict,起到去重作用;使用digraph表示需要方向。 interate_elf_libraries(header, elf_name) print("}")
执行./ldd-ext.py /usr/bin/weston > weston.dot,结果如下:
strict digraph lib_call { "/usr/bin/weston"->"/usr/lib/weston/libexec_weston.so.0"; "/usr/lib/weston/libexec_weston.so.0"->"/usr/lib64/libweston-11.so.0"; ..."/usr/bin/weston"->"/usr/lib64/librga.so.2"; "/usr/lib64/librga.so.2"->"/usr/lib64/libdrm.so.2"; }
执行命令dot -Tpng weston.dot -o weston.png,生成png图片:
关于Graphviz/dot更多参考《Documentation | Graphviz》。
联系方式:arnoldlu@qq.com