symbols-check.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. #!/usr/bin/env python3
  2. import argparse
  3. import os
  4. import platform
  5. import subprocess
  6. # This list contains symbols that _might_ be exported for some platforms
  7. PLATFORM_SYMBOLS = [
  8. '_GLOBAL_OFFSET_TABLE_',
  9. '__bss_end__',
  10. '__bss_start__',
  11. '__bss_start',
  12. '__end__',
  13. '_bss_end__',
  14. '_edata',
  15. '_end',
  16. '_fini',
  17. '_init',
  18. '_fbss',
  19. '_fdata',
  20. '_ftext',
  21. ]
  22. def get_symbols(nm, lib):
  23. '''
  24. List all the (non platform-specific) symbols exported by the library
  25. '''
  26. symbols = []
  27. platform_name = platform.system()
  28. output = subprocess.check_output([nm, '-gP', lib],
  29. stderr=open(os.devnull, 'w')).decode("ascii")
  30. for line in output.splitlines():
  31. fields = line.split()
  32. if len(fields) == 2 or fields[1] == 'U':
  33. continue
  34. symbol_name = fields[0]
  35. if platform_name == 'Linux':
  36. if symbol_name in PLATFORM_SYMBOLS:
  37. continue
  38. elif platform_name == 'Darwin':
  39. assert symbol_name[0] == '_'
  40. symbol_name = symbol_name[1:]
  41. symbols.append(symbol_name)
  42. return symbols
  43. def main():
  44. parser = argparse.ArgumentParser()
  45. parser.add_argument('--symbols-file',
  46. action='store',
  47. required=True,
  48. help='path to file containing symbols')
  49. parser.add_argument('--lib',
  50. action='store',
  51. required=True,
  52. help='path to library')
  53. parser.add_argument('--nm',
  54. action='store',
  55. required=True,
  56. help='path to binary (or name in $PATH)')
  57. args = parser.parse_args()
  58. try:
  59. lib_symbols = get_symbols(args.nm, args.lib)
  60. except:
  61. # We can't run this test, but we haven't technically failed it either
  62. # Return the GNU "skip" error code
  63. exit(77)
  64. mandatory_symbols = []
  65. optional_symbols = []
  66. with open(args.symbols_file) as symbols_file:
  67. qualifier_optional = '(optional)'
  68. for line in symbols_file.readlines():
  69. # Strip comments
  70. line = line.split('#')[0]
  71. line = line.strip()
  72. if not line:
  73. continue
  74. # Line format:
  75. # [qualifier] symbol
  76. qualifier = None
  77. symbol = None
  78. fields = line.split()
  79. if len(fields) == 1:
  80. symbol = fields[0]
  81. elif len(fields) == 2:
  82. qualifier = fields[0]
  83. symbol = fields[1]
  84. else:
  85. print(args.symbols_file + ': invalid format: ' + line)
  86. exit(1)
  87. # The only supported qualifier is 'optional', which means the
  88. # symbol doesn't have to be exported by the library
  89. if qualifier and not qualifier == qualifier_optional:
  90. print(args.symbols_file + ': invalid qualifier: ' + qualifier)
  91. exit(1)
  92. if qualifier == qualifier_optional:
  93. optional_symbols.append(symbol)
  94. else:
  95. mandatory_symbols.append(symbol)
  96. unknown_symbols = []
  97. for symbol in lib_symbols:
  98. if symbol in mandatory_symbols:
  99. continue
  100. if symbol in optional_symbols:
  101. continue
  102. unknown_symbols.append(symbol)
  103. missing_symbols = [
  104. sym for sym in mandatory_symbols if sym not in lib_symbols
  105. ]
  106. for symbol in unknown_symbols:
  107. print(args.lib + ': unknown symbol exported: ' + symbol)
  108. for symbol in missing_symbols:
  109. print(args.lib + ': missing symbol: ' + symbol)
  110. if unknown_symbols or missing_symbols:
  111. exit(1)
  112. exit(0)
  113. if __name__ == '__main__':
  114. main()