1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
|
#!/usr/bin/env python
#-------------------------------------------------------------------------------
# test/run_readelf_tests.py
#
# Automatic test runner for elftools & readelf
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
import os, sys, platform
import re
from difflib import SequenceMatcher
from optparse import OptionParser
import logging
import platform
from utils import setup_syspath; setup_syspath()
from utils import run_exe, is_in_rootdir, dump_output_to_temp_files
# Create a global logger object
#
testlog = logging.getLogger('run_tests')
testlog.setLevel(logging.DEBUG)
testlog.addHandler(logging.StreamHandler(sys.stdout))
# Set the path for calling readelf. We carry our own version of readelf around,
# because binutils tend to change its output even between daily builds of the
# same minor release and keeping track is a headache.
if platform.system() == "Darwin": # MacOS
READELF_PATH = 'greadelf'
else:
READELF_PATH = 'test/external_tools/readelf'
if not os.path.exists(READELF_PATH):
READELF_PATH = 'readelf'
def discover_testfiles(rootdir):
""" Discover test files in the given directory. Yield them one by one.
"""
for filename in os.listdir(rootdir):
_, ext = os.path.splitext(filename)
if ext == '.elf':
yield os.path.join(rootdir, filename)
def run_test_on_file(filename, verbose=False):
""" Runs a test on the given input filename. Return True if all test
runs succeeded.
"""
success = True
testlog.info("Test file '%s'" % filename)
for option in [
'-e', '-d', '-s', '-n', '-r', '-x.text', '-p.shstrtab', '-V',
'--debug-dump=info', '--debug-dump=decodedline',
'--debug-dump=frames', '--debug-dump=frames-interp',
'--debug-dump=aranges']:
if verbose: testlog.info("..option='%s'" % option)
# stdouts will be a 2-element list: output of readelf and output
# of scripts/readelf.py
stdouts = []
for exe_path in [READELF_PATH, 'scripts/readelf.py']:
args = [option, filename]
if verbose: testlog.info("....executing: '%s %s'" % (
exe_path, ' '.join(args)))
rc, stdout = run_exe(exe_path, args)
if rc != 0:
testlog.error("@@ aborting - '%s' returned '%s'" % (exe_path, rc))
return False
stdouts.append(stdout)
if verbose: testlog.info('....comparing output...')
rc, errmsg = compare_output(*stdouts)
if rc:
if verbose: testlog.info('.......................SUCCESS')
else:
success = False
testlog.info('.......................FAIL')
testlog.info('....for option "%s"' % option)
testlog.info('....Output #1 is readelf, Output #2 is pyelftools')
testlog.info('@@ ' + errmsg)
dump_output_to_temp_files(testlog, *stdouts)
return success
def compare_output(s1, s2):
""" Compare stdout strings s1 and s2.
s1 is from readelf, s2 from elftools readelf.py
Return pair success, errmsg. If comparison succeeds, success is True
and errmsg is empty. Otherwise success is False and errmsg holds a
description of the mismatch.
Note: this function contains some rather horrible hacks to ignore
differences which are not important for the verification of pyelftools.
This is due to some intricacies of binutils's readelf which pyelftools
doesn't currently implement, features that binutils doesn't support,
or silly inconsistencies in the output of readelf, which I was reluctant
to replicate. Read the documentation for more details.
"""
def prepare_lines(s):
return [line for line in s.lower().splitlines() if line.strip() != '']
def filter_readelf_lines(lines):
filter_out = False
for line in lines:
if 'of the .eh_frame section' in line:
filter_out = True
elif 'of the .debug_frame section' in line or \
'of the .zdebug_frame section' in line:
filter_out = False
if not filter_out:
if not line.startswith('unknown: length'):
yield line
lines1 = prepare_lines(s1)
lines2 = prepare_lines(s2)
lines1 = list(filter_readelf_lines(lines1))
flag_after_symtable = False
if len(lines1) != len(lines2):
return False, 'Number of lines different: %s vs %s' % (
len(lines1), len(lines2))
for i in range(len(lines1)):
if 'symbol table' in lines1[i]:
flag_after_symtable = True
# Compare ignoring whitespace
lines1_parts = lines1[i].split()
lines2_parts = lines2[i].split()
if ''.join(lines1_parts) != ''.join(lines2_parts):
ok = False
try:
# Ignore difference in precision of hex representation in the
# last part (i.e. 008f3b vs 8f3b)
if (''.join(lines1_parts[:-1]) == ''.join(lines2_parts[:-1]) and
int(lines1_parts[-1], 16) == int(lines2_parts[-1], 16)):
ok = True
except ValueError:
pass
sm = SequenceMatcher()
sm.set_seqs(lines1[i], lines2[i])
changes = sm.get_opcodes()
if flag_after_symtable:
# Detect readelf's adding @ with lib and version after
# symbol name.
if ( len(changes) == 2 and changes[1][0] == 'delete' and
lines1[i][changes[1][1]] == '@'):
ok = True
elif 'at_const_value' in lines1[i]:
# On 32-bit machines, readelf doesn't correctly represent
# some boundary LEB128 numbers
val = lines2_parts[-1]
num2 = int(val, 16 if val.startswith('0x') else 10)
if num2 <= -2**31 and '32' in platform.architecture()[0]:
ok = True
elif 'os/abi' in lines1[i]:
if 'unix - gnu' in lines1[i] and 'unix - linux' in lines2[i]:
ok = True
elif ( 'unknown at value' in lines1[i] and
'dw_at_apple' in lines2[i]):
ok = True
else:
for s in ('t (tls)', 'l (large)'):
if s in lines1[i] or s in lines2[i]:
ok = True
break
if not ok:
errmsg = 'Mismatch on line #%s:\n>>%s<<\n>>%s<<\n (%r)' % (
i, lines1[i], lines2[i], changes)
return False, errmsg
return True, ''
def main():
if not is_in_rootdir():
testlog.error('Error: Please run me from the root dir of pyelftools!')
return 1
optparser = OptionParser(
usage='usage: %prog [options] [file] [file] ...',
prog='run_readelf_tests.py')
optparser.add_option('-V', '--verbose',
action='store_true', dest='verbose',
help='Verbose output')
options, args = optparser.parse_args()
if options.verbose:
testlog.info('Running in verbose mode')
testlog.info('Python executable = %s' % sys.executable)
testlog.info('readelf path = %s' % READELF_PATH)
testlog.info('Given list of files: %s' % args)
# If file names are given as command-line arguments, only these files
# are taken as inputs. Otherwise, autodiscovery is performed.
#
if len(args) > 0:
filenames = args
else:
filenames = list(discover_testfiles('test/testfiles_for_readelf'))
success = True
for filename in filenames:
if success:
success = success and run_test_on_file(
filename,
verbose=options.verbose)
if success:
testlog.info('\nConclusion: SUCCESS')
return 0
else:
testlog.info('\nConclusion: FAIL')
return 1
if __name__ == '__main__':
sys.exit(main())
|