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