Last active
April 10, 2019 19:49
-
-
Save omajid/766aef59857de9fc1eab173119143abc to your computer and use it in GitHub Desktop.
Script to check debug symbols are present and can identify code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
""" | |
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 | |
""" | |
# This technique was explained to me by Mark Wielaard (mjw). | |
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." | |
# Test for .debug_* sections in the shared object. This is the main test. | |
# Stripped objects will not contain these. | |
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) | |
# Test FILE symbols. These will most likely be removed by anyting that | |
# manipulates symbol tables because it's generally useless. So a nice test | |
# that nothing has messed with symbols. | |
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)) | |
# Test that there are no .gnu_debuglink sections pointing to another | |
# debuginfo file. There shouldn't be any debuginfo files, so the link makes | |
# no sense either. | |
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): | |
# print(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): | |
# print(results) | |
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:])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment