Merge lp:~ben-hutchings/ensoft-sextant/wierd-names-clean into lp:ensoft-sextant
- wierd-names-clean
- Merge into whiteline
Status: | Merged |
---|---|
Approved by: | Robert |
Approved revision: | 69 |
Merged at revision: | 36 |
Proposed branch: | lp:~ben-hutchings/ensoft-sextant/wierd-names-clean |
Merge into: | lp:ensoft-sextant |
Prerequisite: | lp:~ben-hutchings/ensoft-sextant/rel-merge |
Diff against target: |
474 lines (+190/-48) 10 files modified
src/sextant/db_api.py (+7/-3) src/sextant/objdump_parser.py (+22/-8) src/sextant/test_all.py (+39/-0) src/sextant/test_all.sh (+0/-4) src/sextant/test_db.py (+39/-13) src/sextant/test_parser.py (+36/-10) src/sextant/test_resources/parser_file2.c (+24/-0) src/sextant/test_resources/parser_header.h (+10/-0) src/sextant/test_resources/parser_test.c (+3/-9) src/sextant/test_sshmanager.py (+10/-1) |
To merge this branch: | bzr merge lp:~ben-hutchings/ensoft-sextant/wierd-names-clean |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert | Approve | ||
Review via email: mp+242752@code.launchpad.net |
This proposal supersedes a proposal from 2014-11-25.
Commit message
Function names are now cleaned up by a helper function, which removes __be_ prefixes if they are there (bi-endian builds) and converts names like <name>.<other stuff> to just <name>.
Tests extended to check that this works.
Description of the change
Function names are now cleaned up by a helper function, which removes __be_ prefixes if they are there (bi-endian builds) and converts names like <name>.<other stuff> to just <name>.
Tests extended to check that this works.
- 64. By Ben Hutchings
-
merge from rel-merge markups
- 65. By Ben Hutchings
-
extended tests, replaced test_all.sh with test_all.py which also generates code coverage reports for the tested modeuls
- 66. By Ben Hutchings
-
whitespace fix
- 67. By Ben Hutchings
-
fixed bug with get_all_
functions_ calling - 68. By Ben Hutchings
-
very minor change to the default objdump_parser print output format to include more information added since this was originally written
- 69. By Ben Hutchings
-
doc comment fix
Robert (rjwills) : | # |
Preview Diff
1 | === modified file 'src/sextant/db_api.py' |
2 | --- src/sextant/db_api.py 2014-11-25 11:40:03 +0000 |
3 | +++ src/sextant/db_api.py 2014-12-12 11:30:28 +0000 |
4 | @@ -996,7 +996,7 @@ |
5 | |
6 | return self._execute_query(program_name, q) |
7 | |
8 | - def get_all_functions_calling(self, program_name, function_called, |
9 | + def get_all_functions_calling(self, program_name, function_calling, |
10 | limit_internal=False, max_depth=1): |
11 | """ |
12 | Return functions calling the specified functions. |
13 | @@ -1014,12 +1014,16 @@ |
14 | program_name: |
15 | The name of the program to query. |
16 | |
17 | - function_called: |
18 | + function_calling: |
19 | A string of form <name_match>:<file_match>, where at least |
20 | one of name_match and file_match is provided, and each may be a |
21 | comma separated list of strings containing wildcard '.*' |
22 | sequences. Specifies the list of functions to match. |
23 | |
24 | + NOTE: the name is a bit of a hack to work with the javascript. |
25 | + Should really be function_called but that would require more |
26 | + js fiddling. |
27 | + |
28 | limit_internal: |
29 | If true, only explore internal calls. |
30 | |
31 | @@ -1034,7 +1038,7 @@ |
32 | q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(g:func) {}' |
33 | ' MATCH (f)-[{}*0..{}]->(g)' |
34 | ' RETURN distinct f, g') |
35 | - q = q.format(program_name, SextantConnection.get_query('g', function_called), |
36 | + q = q.format(program_name, SextantConnection.get_query('g', function_calling), |
37 | ':internal' if limit_internal else ':internal|external', |
38 | max_depth or '') |
39 | |
40 | |
41 | === modified file 'src/sextant/objdump_parser.py' |
42 | --- src/sextant/objdump_parser.py 2014-11-25 11:40:03 +0000 |
43 | +++ src/sextant/objdump_parser.py 2014-12-12 11:30:28 +0000 |
44 | @@ -121,7 +121,7 @@ |
45 | print('func {:25} {:15}{}'.format(name, typ, source)) |
46 | |
47 | def print_call(caller, callee, is_internal): |
48 | - print('call {:25} {:25}'.format(caller, callee)) |
49 | + print('call {} {:25} {:25}'.format('EI'[is_internal], caller, callee)) |
50 | |
51 | def print_started(parser): |
52 | print('parse started: {}[{}]'.format(self.path, ', '.join(self.sections))) |
53 | @@ -199,10 +199,27 @@ |
54 | self.add_call(caller, callee, False) |
55 | self._known_calls.add((caller, callee)) |
56 | self.call_count += 1 |
57 | - print(caller, callee) |
58 | else: |
59 | self._partial_calls.add((caller, callee)) |
60 | |
61 | + @staticmethod |
62 | + def clean_id(function_identifier): |
63 | + """ |
64 | + Clean the funciton identifier string. |
65 | + """ |
66 | + # Bi-endian builds add a __be_ prefix to all functions, |
67 | + # get rid of it if it is there, |
68 | + if function_identifier.startswith('__be_'): |
69 | + function_identifier = function_identifier[len('__be_'):] |
70 | + |
71 | + # Some functions look like <identifier>. or <identifier>..<digit> |
72 | + # - get rid of the extra bits here: |
73 | + if '.' in function_identifier: |
74 | + function_identifier = function_identifier.split('.')[0] |
75 | + |
76 | + return function_identifier |
77 | + |
78 | + |
79 | def parse(self): |
80 | """ |
81 | Parse self._file. |
82 | @@ -242,10 +259,7 @@ |
83 | # <function_name>[@plt] |
84 | function_identifier = line.split('<')[-1].split('>')[0] |
85 | |
86 | - # IOS builds add a __be_ (big endian) prefix to all functions, |
87 | - # get rid of it if it is there, |
88 | - if function_identifier.startswith('__be_'): |
89 | - function_identifier = function_identifier[len('__be_'):] |
90 | + function_identifier = self.clean_id(function_identifier) |
91 | |
92 | if '@' in function_identifier: |
93 | # Of form <function name>@<other stuff>. |
94 | @@ -277,8 +291,8 @@ |
95 | # from which we extract name |
96 | callee_is_ptr = False |
97 | function_identifier = callee_info.lstrip('<').rstrip('>\n') |
98 | - if function_identifier.startswith('__be_'): |
99 | - function_identifier = function_identifier[len('__be_'):] |
100 | + |
101 | + function_identifier = self.clean_id(function_identifier) |
102 | |
103 | if '@' in function_identifier: |
104 | callee = function_identifier.split('@')[0] |
105 | |
106 | === added file 'src/sextant/test_all.py' |
107 | --- src/sextant/test_all.py 1970-01-01 00:00:00 +0000 |
108 | +++ src/sextant/test_all.py 2014-12-12 11:30:28 +0000 |
109 | @@ -0,0 +1,39 @@ |
110 | +#!/usr/bin/python |
111 | + |
112 | +from __future__ import print_function |
113 | + |
114 | +import subprocess |
115 | +import shlex |
116 | + |
117 | +tests = (('test_parser.py', 'objdump_parser.py'), |
118 | + ('test_csvwriter.py', 'csvwriter.py'), |
119 | + ('test_sshmanager.py', 'sshmanager.py'), |
120 | + ('test_db.py', 'db_api.py')) |
121 | + |
122 | +pycmd = 'coverage run {}' |
123 | +covcmd = 'coverage report -m {}' |
124 | + |
125 | +if __name__ == '__main__': |
126 | + Popen = subprocess.Popen |
127 | + |
128 | + for test, name in tests: |
129 | + print('Running tests: {}'.format(test)) |
130 | + do_print = False |
131 | + |
132 | + |
133 | + pyproc = Popen(shlex.split(pycmd.format(test)), stdout=subprocess.PIPE) |
134 | + for line in pyproc.stdout: |
135 | + if '----------' in line or '=========' in line: |
136 | + do_print = True |
137 | + |
138 | + if do_print: |
139 | + print(line.rstrip()) |
140 | + |
141 | + covproc = Popen(shlex.split(covcmd.format(name))) |
142 | + covproc.wait() |
143 | + |
144 | + |
145 | + |
146 | + |
147 | + |
148 | + |
149 | |
150 | === removed file 'src/sextant/test_all.sh' |
151 | --- src/sextant/test_all.sh 2014-10-13 16:01:59 +0000 |
152 | +++ src/sextant/test_all.sh 1970-01-01 00:00:00 +0000 |
153 | @@ -1,4 +0,0 @@ |
154 | -#!/usr/bin/bash |
155 | - |
156 | -PYTHONPATH=$PYTHONPATH:~/. |
157 | -python -m unittest discover --pattern=test_*.py |
158 | |
159 | === modified file 'src/sextant/test_db.py' |
160 | --- src/sextant/test_db.py 2014-11-25 11:40:03 +0000 |
161 | +++ src/sextant/test_db.py 2014-12-12 11:30:28 +0000 |
162 | @@ -12,7 +12,7 @@ |
163 | import update_db |
164 | |
165 | PNAME = 'tester-parser_test' |
166 | -NORMAL = {'main', 'normal', 'wierd$name', 'duplicates'} |
167 | +NORMAL = {'main', 'normal', 'wierd$name', 'duplicates', 'name', 'puts'} |
168 | |
169 | |
170 | class TestFunctionQueryResults(unittest.TestCase): |
171 | @@ -37,10 +37,12 @@ |
172 | names = get_names(PNAME) |
173 | # Test file wildcard search |
174 | parser_names = get_names(PNAME, search=':.*parser_test.c') |
175 | + file2_names = get_names(PNAME, search=':.*parser_file2.c') |
176 | |
177 | self.assertTrue(names.issuperset(NORMAL)) |
178 | - self.assertEquals(len(names), 24) |
179 | - self.assertEquals(parser_names, {u'main', u'normal', u'duplicates', u'wierd$name'}) |
180 | + self.assertEquals(len(names), 27) |
181 | + self.assertEquals(parser_names, {u'main', u'normal', u'duplicates'}) |
182 | + self.assertEquals(file2_names, {u'wierd$name', u'name'}) |
183 | |
184 | # Test the wildcard matching |
185 | search = self.connection.get_function_names(PNAME, search='.*libc.*') |
186 | @@ -58,13 +60,12 @@ |
187 | def test_get_all_functions_called(self): |
188 | get_fns = self.connection.get_all_functions_called |
189 | |
190 | - for depth, num in zip([0, 1, 2, 3], [8, 3, 8, 8]): |
191 | + for depth, num in zip(range(5), [11, 3, 9, 10, 11]): |
192 | result = get_fns(PNAME, 'main', False, depth).functions |
193 | - self.assertEquals(len(result), num, str(result)) |
194 | + self.assertEquals(len(result), num) |
195 | |
196 | - for depth, num in zip([0, 1, 2, 3], [8, 4, 8, 8]): |
197 | + for depth, num in zip(range(5), [9, 6, 9, 9, 9]): |
198 | # Limit to internal functions |
199 | - # TODO this isn't a great test - need greater call depth |
200 | result = get_fns(PNAME, 'main', True, depth).functions |
201 | self.assertEquals(len(result), num) |
202 | |
203 | @@ -80,18 +81,43 @@ |
204 | self.assertEquals(len(result), num) |
205 | |
206 | def test_get_all_paths_between(self): |
207 | - get_paths = self.connection.get_call_paths |
208 | - |
209 | - result = {f.name for f in get_paths(PNAME, 'main', 'wierd$name', True, 0).functions} |
210 | - exp = {'main', 'normal', 'duplicates', 'wierd$name'} |
211 | - self.assertEquals(result, exp) |
212 | + get_paths = self.connection.get_call_paths |
213 | + |
214 | + result = {f.name for f in get_paths(PNAME, 'main', 'wierd$name', False, 0).functions} |
215 | + exp = {'main', 'normal', 'duplicates', 'wierd$name'} |
216 | + self.assertEquals(result, exp) |
217 | + |
218 | + self.assertFalse(get_paths(PNAME, 'main', 'wierd$name', True, 0).functions) |
219 | |
220 | def test_get_shortest_paths_between(self): |
221 | get_paths = self.connection.get_shortest_path_between_functions |
222 | |
223 | - result = {f.name for f in get_paths(PNAME, 'main', 'wierd$name', True, 0).functions} |
224 | + result = {f.name for f in get_paths(PNAME, 'main', 'wierd$name', False, 0).functions} |
225 | exp = {u'main', u'normal', u'wierd$name'} |
226 | self.assertEquals(result, exp) |
227 | + |
228 | + self.assertFalse(get_paths(PNAME, 'main', 'wierd$name', True, 0).functions) |
229 | + |
230 | + def test_programs_with_metadata(self): |
231 | + result = self.connection.programs_with_metadata() |
232 | + found = False |
233 | + |
234 | + for program in result: |
235 | + if program.program_name == PNAME: |
236 | + found = True |
237 | + |
238 | + self.assertEquals(program.number_of_funcs, 27) |
239 | + self.assertEquals(program.number_of_calls, 26) |
240 | + |
241 | + break |
242 | + |
243 | + self.assertTrue(found) |
244 | + |
245 | + def test_get_whole_program(self): |
246 | + result = self.connection.get_whole_program(PNAME) |
247 | + self.assertEquals(len(result.functions), 27) |
248 | + |
249 | + self.assertFalse(self.connection.get_whole_program('no such program')) |
250 | |
251 | if __name__ == '__main__': |
252 | unittest.main() |
253 | |
254 | === modified file 'src/sextant/test_parser.py' |
255 | --- src/sextant/test_parser.py 2014-11-20 17:25:59 +0000 |
256 | +++ src/sextant/test_parser.py 2014-12-12 11:30:28 +0000 |
257 | @@ -5,6 +5,7 @@ |
258 | |
259 | import objdump_parser as parser |
260 | |
261 | +OBJ_FILE = 'test_resources/parser_test' |
262 | DUMP_FILE = 'test_resources/parser_test.dump' |
263 | |
264 | class TestSequence(unittest.TestCase): |
265 | @@ -12,7 +13,8 @@ |
266 | pass |
267 | |
268 | def add_function(self, dct, name, typ, source): |
269 | - self.assertFalse(name in dct, "duplicate function added: {} into {}".format(name, dct.keys())) |
270 | + self.assertFalse(name in dct, ("duplicate function added: {} into {}" |
271 | + .format(name, dct.keys()))) |
272 | dct[name] = (typ, source) |
273 | |
274 | def add_call(self, dct, caller, callee, is_internal): |
275 | @@ -26,7 +28,9 @@ |
276 | add_function = lambda n, t, s: self.add_function(functions, n, t, s) |
277 | add_call = lambda a, b, i: self.add_call(calls, a, b, i) |
278 | |
279 | - p = parser.Parser(path, sections=sections, ignore_ptrs=ignore_ptrs, |
280 | + file_path, file_object = parser.run_objdump(OBJ_FILE, add_file_paths=True) |
281 | + p = parser.Parser(file_path=None, file_object=file_object, |
282 | + sections=sections, ignore_ptrs=ignore_ptrs, |
283 | add_function=add_function, add_call=add_call) |
284 | res = p.parse() |
285 | |
286 | @@ -44,22 +48,26 @@ |
287 | res, funcs, calls = self.do_parse() |
288 | |
289 | known = 'parser_test.c' |
290 | + files = 'parser_file2.c' |
291 | unknown = 'unknown' |
292 | |
293 | - for name, typ, fle in zip(['normal', 'duplicates', 'wierd$name', 'printf', 'func_ptr_3'], |
294 | - ['normal', 'normal', 'normal', 'stub', 'pointer'], |
295 | - [known, known, known, unknown, unknown]): |
296 | + for name, typ, fle in zip(['normal', 'duplicates', 'wierd$name', 'printf', 'func_ptr_3', |
297 | + 'name', 'puts', 'inl_func'], |
298 | + ['normal', 'normal', 'normal', 'stub', 'pointer', |
299 | + 'normal', 'stub', 'normal'], |
300 | + [known, known, files, unknown, unknown, |
301 | + files, unknown, 'parser_header.h']): |
302 | self.assertTrue(name in funcs, "'{}' not found in function dictionary".format(name)) |
303 | self.assertEquals(funcs[name][0], typ) |
304 | - self.assertTrue(funcs[name][1].endswith(fle)) |
305 | + self.assertTrue(funcs[name][1].endswith(fle), "{}-{}".format(name, fle)) |
306 | |
307 | |
308 | def test_no_ptrs(self): |
309 | # ensure that the ignore_ptrs flags is working |
310 | res, funcs, calls = self.do_parse(ignore_ptrs=True) |
311 | |
312 | - self.assertFalse('pointer' in funcs.values()) |
313 | - self.assertEqual(len(calls['normal']), 2) |
314 | + self.assertFalse('pointer' in (typ for typ, src in funcs.values())) |
315 | + self.assertEqual(len(calls['normal']), 3) |
316 | |
317 | |
318 | def test_calls(self): |
319 | @@ -68,7 +76,11 @@ |
320 | self.assertTrue(('normal', True) in calls['main']) |
321 | self.assertTrue(('duplicates', True) in calls['main']) |
322 | |
323 | - normal_calls = sorted(['wierd$name', 'printf', 'func_ptr_3']) |
324 | + self.assertTrue(('wierd$name', False) in calls['normal']) |
325 | + self.assertTrue(('name', True) in calls['wierd$name']) |
326 | + self.assertTrue(('puts', False) in calls['name']) |
327 | + |
328 | + normal_calls = sorted(['wierd$name', 'printf', 'inl_func', 'func_ptr_3']) |
329 | self.assertEquals(sorted(zip(*calls['normal'])[0]), normal_calls) |
330 | |
331 | self.assertEquals(calls['duplicates'].count(('normal', True)), 1) |
332 | @@ -77,6 +89,9 @@ |
333 | self.assertTrue(('func_ptr_4', True) in calls['duplicates']) |
334 | self.assertTrue(('func_ptr_5', True) in calls['duplicates']) |
335 | |
336 | + # A function that should only be visible in the plt section |
337 | + self.assertFalse('__gmon_start__' in funcs) |
338 | + |
339 | def test_sections(self): |
340 | res, funcs, calls = self.do_parse(sections=['.plt', '.text']) |
341 | |
342 | @@ -84,6 +99,17 @@ |
343 | self.assertTrue('@' not in ''.join(funcs.keys()), "check names are extracted correctly") |
344 | self.assertTrue('__gmon_start__' in funcs, "see a function defined only in .plt") |
345 | |
346 | - |
347 | + def test_clean_names(self): |
348 | + clean_id = parser.Parser.clean_id |
349 | + # Stripping of ios be prefixes |
350 | + self.assertEquals(clean_id('__be_test__be_name'), 'test__be_name') |
351 | + self.assertEquals(clean_id('_be_test_name'), '_be_test_name') |
352 | + self.assertEquals(clean_id('__betest_name'), '__betest_name') |
353 | + |
354 | + # Removing of extra bits at end |
355 | + self.assertEquals(clean_id('test_name.'), 'test_name') |
356 | + self.assertEquals(clean_id('test_name..0'), 'test_name') |
357 | + |
358 | + |
359 | if __name__ == '__main__': |
360 | unittest.main() |
361 | |
362 | === added file 'src/sextant/test_resources/parser_file2.c' |
363 | --- src/sextant/test_resources/parser_file2.c 1970-01-01 00:00:00 +0000 |
364 | +++ src/sextant/test_resources/parser_file2.c 2014-12-12 11:30:28 +0000 |
365 | @@ -0,0 +1,24 @@ |
366 | +#include <stdio.h> |
367 | +#include "parser_header.h" |
368 | + |
369 | +static int |
370 | +wierd$name(int a); |
371 | + |
372 | +static int |
373 | +__be_name(int a); |
374 | + |
375 | +static int |
376 | +wierd$name(int a) |
377 | +{ |
378 | + a = __be_name(inl_func(a)); |
379 | + return (a); |
380 | +} |
381 | + |
382 | +static int |
383 | +__be_name(int a) |
384 | +{ |
385 | + printf("In __be_name\n"); |
386 | + return (a+1); |
387 | +} |
388 | + |
389 | + |
390 | |
391 | === added file 'src/sextant/test_resources/parser_header.h' |
392 | --- src/sextant/test_resources/parser_header.h 1970-01-01 00:00:00 +0000 |
393 | +++ src/sextant/test_resources/parser_header.h 2014-12-12 11:30:28 +0000 |
394 | @@ -0,0 +1,10 @@ |
395 | +#ifndef PARSER_H |
396 | +#define PARSER_H |
397 | + |
398 | +static inline int |
399 | +inl_func(int a) |
400 | +{ |
401 | + return (2*a); |
402 | +} |
403 | + |
404 | +#endif |
405 | |
406 | === modified file 'src/sextant/test_resources/parser_test' |
407 | Binary files src/sextant/test_resources/parser_test 2014-11-17 13:53:27 +0000 and src/sextant/test_resources/parser_test 2014-12-12 11:30:28 +0000 differ |
408 | === modified file 'src/sextant/test_resources/parser_test.c' |
409 | --- src/sextant/test_resources/parser_test.c 2014-10-13 14:10:01 +0000 |
410 | +++ src/sextant/test_resources/parser_test.c 2014-12-12 11:30:28 +0000 |
411 | @@ -1,11 +1,11 @@ |
412 | // COMMENT |
413 | #include<stdio.h> |
414 | +#include "parser_header.h" |
415 | +#include "parser_file2.c" |
416 | |
417 | static int |
418 | normal(int a); |
419 | |
420 | -static int |
421 | -wierd$name(int a); |
422 | |
423 | typedef int (*pointer)(int); |
424 | |
425 | @@ -18,19 +18,13 @@ |
426 | pointer ptr = wierd$name; |
427 | |
428 | wierd$name(a); |
429 | - printf("%d\n", a); |
430 | + printf("%d\n", inl_func(a)); |
431 | ptr(a); |
432 | |
433 | return (a); |
434 | } |
435 | |
436 | static int |
437 | -wierd$name(int a) |
438 | -{ |
439 | - return (a); |
440 | -} |
441 | - |
442 | -static int |
443 | duplicates(int a) |
444 | { |
445 | pointer ptr1 = wierd$name; |
446 | |
447 | === modified file 'src/sextant/test_sshmanager.py' |
448 | --- src/sextant/test_sshmanager.py 2014-10-17 14:35:01 +0000 |
449 | +++ src/sextant/test_sshmanager.py 2014-12-12 11:30:28 +0000 |
450 | @@ -16,7 +16,7 @@ |
451 | self.manager = None |
452 | |
453 | def get_manager(self, local_port=9643, remote_host='localhost', |
454 | - remote_port=9643, ssh_user=None): |
455 | + remote_port=9643, ssh_user=None, is_localhost=False): |
456 | return sshmanager.SSHManager(local_port, remote_host, remote_port, ssh_user) |
457 | |
458 | def test_init(self): |
459 | @@ -34,6 +34,15 @@ |
460 | # check connecion failure |
461 | self.assertRaises(sshmanager.SSHConnectionError, self.get_manager, remote_host='invalid host') |
462 | |
463 | + def test_localhost(self): |
464 | + self.manager = self.get_manager(is_localhost=True) |
465 | + self.assertTrue(os.path.isdir(self.manager._tmp_dir)) |
466 | + self.manager.close() |
467 | + self.assertFalse(os.path.isdir(self.manager._tmp_dir)) |
468 | + self.manager = None |
469 | + |
470 | + |
471 | + |
472 | def test_files(self): |
473 | genuine_file = 'test_resources/parser_test.c' |
474 | genuine_file2 = 'test_resources/parser_test' |