| |
| |
| """ |
| Check debug symbols are present in shared object and can identify |
| code. |
| |
| It starts scanning from a directory and recursively scans all ELF |
| files found in it for various symbols to ensure all debuginfo is |
| present and nothing has been stripped. |
| |
| Usage: |
| |
| ./check-debug-symbols /path/of/dir/to/scan/ |
| |
| |
| Example: |
| |
| ./check-debug-symbols /usr/lib64 |
| """ |
| |
| |
| |
| import collections |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| ScanResult = collections.namedtuple('ScanResult', |
| 'file_name debug_info debug_abbrev file_symbols gnu_debuglink') |
| |
| |
| def scan_file(file): |
| "Scan the provided file and return a ScanResult containing results of the scan." |
| |
| |
| |
| readelf_S_result = subprocess.run(['eu-readelf', '-S', file], |
| stdout=subprocess.PIPE, encoding='utf-8', check=True) |
| has_debug_info = any(line for line in readelf_S_result.stdout.split('\n') if '] .debug_info' in line) |
| |
| has_debug_abbrev = any(line for line in readelf_S_result.stdout.split('\n') if '] .debug_abbrev' in line) |
| |
| |
| |
| |
| def contains_file_symbols(line): |
| parts = line.split() |
| if len(parts) < 8: |
| return False |
| return \ |
| parts[2] == '0' and parts[3] == 'FILE' and parts[4] == 'LOCAL' and parts[5] == 'DEFAULT' and \ |
| parts[6] == 'ABS' and re.match(r'((.*/)?[-_a-zA-Z0-9]+\.(c|cc|cpp|cxx))?', parts[7]) |
| |
| readelf_s_result = subprocess.run(["eu-readelf", '-s', file], |
| stdout=subprocess.PIPE, encoding='utf-8', check=True) |
| has_file_symbols = any(line for line in readelf_s_result.stdout.split('\n') if contains_file_symbols(line)) |
| |
| |
| |
| |
| has_gnu_debuglink = any(line for line in readelf_s_result.stdout.split('\n') if '] .gnu_debuglink' in line) |
| |
| return ScanResult(file, has_debug_info, has_debug_abbrev, has_file_symbols, has_gnu_debuglink) |
| |
| def is_elf(file): |
| result = subprocess.run(['file', file], stdout=subprocess.PIPE, encoding='utf-8', check=True) |
| return re.search('ELF 64-bit LSB (?:executable|shared object)', result.stdout) |
| |
| def scan_file_if_sensible(file): |
| if is_elf(file): |
| |
| return scan_file(file) |
| return None |
| |
| def scan_dir(dir): |
| results = [] |
| for root, _, files in os.walk(dir): |
| for name in files: |
| result = scan_file_if_sensible(os.path.join(root, name)) |
| if result: |
| results.append(result) |
| return results |
| |
| def scan(file): |
| file = os.path.abspath(file) |
| if os.path.isdir(file): |
| return scan_dir(file) |
| elif os.path.isfile(file): |
| return [scan_file_if_sensible(file)] |
| |
| def is_bad_result(result): |
| return not result.debug_info or not result.debug_abbrev or not result.file_symbols or result.gnu_debuglink |
| |
| def print_scan_results(results, verbose): |
| |
| for result in results: |
| file_name = result.file_name |
| found_issue = False |
| if not result.debug_info: |
| found_issue = True |
| print('error: missing .debug_info section in', file_name) |
| if not result.debug_abbrev: |
| found_issue = True |
| print('error: missing .debug_abbrev section in', file_name) |
| if not result.file_symbols: |
| found_issue = True |
| print('error: missing FILE symbols in', file_name) |
| if result.gnu_debuglink: |
| found_issue = True |
| print('error: unexpected .gnu_debuglink section in', file_name) |
| if verbose and not found_issue: |
| print('OK: ', file_name) |
| |
| def main(args): |
| verbose = False |
| files = [] |
| for arg in args: |
| if arg == '--verbose' or arg == '-v': |
| verbose = True |
| else: |
| files.append(arg) |
| |
| results = [] |
| for file in files: |
| results.extend(scan(file)) |
| |
| print_scan_results(results, verbose) |
| |
| if any(is_bad_result(result) for result in results): |
| return 1 |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |