ldcheck 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. #!/usr/bin/env python3
  2. # Dynamic linking consistency checker
  3. # hacked together by _that@xda, 2014-12 to 2022-03
  4. import os
  5. import string
  6. import argparse
  7. def ldcheck(files, libpath, printalldefined, printresolved, demangle):
  8. nmopts = "-C" if demangle else ""
  9. libs = files
  10. libswithpath = []
  11. # find all dependencies
  12. for filename in libs:
  13. if not os.path.isfile(filename) and libpath:
  14. filename = findlibraryinpath(filename, libpath)
  15. libswithpath.append(filename)
  16. with os.popen("readelf -d {0} | grep '\(NEEDED\)' | sed -r 's/.*\[(.*)\]/\\1/'".format(filename)) as f:
  17. for line in f:
  18. dependency = line.rstrip()
  19. if dependency not in libs:
  20. libs.append(dependency)
  21. print("libs:", libswithpath)
  22. libsused = set((libswithpath[0],))
  23. # read all defined symbols
  24. syms = {}
  25. for filename in libswithpath:
  26. for sym in readsymbols(filename, nmopts):
  27. if sym["type"] != "U":
  28. # TODO: support symbols defined by multiple libs properly, and weak symbols
  29. # TODO: more proper handling of symbol versions, see https://maskray.me/blog/2020-11-26-all-about-symbol-versioning
  30. if "@@" in sym["name"]:
  31. syms[sym["name"].replace("@@", "@")] = {"type": sym["type"], "file": filename}
  32. syms[sym["name"].split("@@")[0]] = {"type": sym["type"], "file": filename}
  33. else:
  34. syms[sym["name"]] = {"type": sym["type"], "file": filename}
  35. # resolve unresolved
  36. for filename in libswithpath:
  37. for sym in readsymbols(filename, nmopts):
  38. if sym["type"] == "U":
  39. resolved = syms.get(sym["name"])
  40. if resolved:
  41. if filename in libsused:
  42. libsused.add(resolved["file"])
  43. if printresolved:
  44. print("{0:25} {1:25} {2} {3}".format(filename, resolved["file"], resolved["type"], sym["name"]))
  45. else:
  46. print("{0:25} {1:25} U {2}".format(filename, "UNRESOLVED #####", sym["name"]))
  47. else:
  48. if printalldefined:
  49. print("{0:25} {1}".format(filename, sym["line"]))
  50. unused = set(libswithpath) - libsused
  51. if unused:
  52. print("unused:", unused)
  53. def findlibraryinpath(filename, searchpath):
  54. for d in searchpath.split(os.pathsep):
  55. filepath = os.path.join(d, filename)
  56. if os.path.isfile(filepath):
  57. return filepath
  58. return filename
  59. """
  60. output format of nm:
  61. 00003004 A __bss_start
  62. U __cxa_atexit
  63. """
  64. def readsymbol(line):
  65. s = line if line[0] == " " else line.lstrip(string.hexdigits)
  66. s = s.lstrip()
  67. return {"type": s[0], "name": s[2:], "line": line}
  68. def readsymbols(filename, nmopts=""):
  69. with os.popen("nm -D {0} {1}".format(nmopts, filename)) as f:
  70. for line in f:
  71. yield readsymbol(line.rstrip())
  72. if __name__ == "__main__":
  73. parser = argparse.ArgumentParser(description='Check dynamic linkage consistency by matching defined vs unresolved symbols.')
  74. parser.add_argument('files', metavar='FILE', nargs='+', help="a dynamically linked executable or library.")
  75. parser.add_argument('-p', '--path', help="Search path for libraries (use like LD_LIBRARY_PATH)")
  76. parser.add_argument('-r', '--resolved', action='store_true', help="Print resolved symbols. By default only unresolved symbols are printed.")
  77. parser.add_argument('-a', '--alldefined', action='store_true', help="Print all defined symbols")
  78. parser.add_argument('-d', '--demangle', action='store_true', help="Demangle C++ names")
  79. args = parser.parse_args()
  80. ldcheck(args.files, args.path, args.alldefined, args.resolved, args.demangle)