Merge lp:~ben-hutchings/ensoft-sextant/filter-search into lp:ensoft-sextant
- filter-search
- Merge into whiteline
Status: | Superseded |
---|---|
Proposed branch: | lp:~ben-hutchings/ensoft-sextant/filter-search |
Merge into: | lp:ensoft-sextant |
Prerequisite: | lp:~ben-hutchings/ensoft-sextant/autocomplete-fix |
Diff against target: |
607 lines (+196/-94) 7 files modified
resources/sextant/web/interface.html (+2/-2) src/sextant/__main__.py (+9/-1) src/sextant/db_api.py (+78/-46) src/sextant/objdump_parser.py (+79/-33) src/sextant/test_parser.py (+1/-1) src/sextant/update_db.py (+15/-8) src/sextant/web/server.py (+12/-3) |
To merge this branch: | bzr merge lp:~ben-hutchings/ensoft-sextant/filter-search |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert | Pending | ||
Review via email: mp+241967@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-11-18.
Commit message
Function name search within the web frontend now supports extended syntax:
'<name matches>:<file path matches>'
where name matches and file path matches are (possibly) comma separated lists, and may include wildcards '.*'. At least one of the two must be specified.
Fixed bug with inline functions being uploaded multiple times into the database.
Description of the change
Function name search within the web frontend now supports extended syntax:
'<name matches>:<file path matches>'
where name matches and file path matches are (possibly) comma separated lists, and may include wildcards '.*'. At least one of the two must be specified.
Fixed bug with inline functions being uploaded multiple times into the database.
- 43. By Ben Hutchings
-
bug from lstrip removing unwanted characters
- 44. By Ben Hutchings
-
merge to fix function stubs being added incorrectly
- 45. By Ben Hutchings
-
markup fixes
- 46. By Ben Hutchings
-
markups + small bug fixes - tests do not pass (though the functionality works).
- 47. By Ben Hutchings
-
tuple instead of list
- 48. By Ben Hutchings
-
merge from autocomplete-fix
- 49. By Ben Hutchings
-
another merge from autocomplete-fix
- 50. By Ben Hutchings
-
fixed bug causing extrac characters to be removed from the start of symbol names
Unmerged revisions
Preview Diff
1 | === modified file 'resources/sextant/web/interface.html' |
2 | --- resources/sextant/web/interface.html 2014-11-18 14:47:39 +0000 |
3 | +++ resources/sextant/web/interface.html 2014-11-18 14:47:39 +0000 |
4 | @@ -27,8 +27,8 @@ |
5 | All functions calling specific function</option> |
6 | <option value="functions_called_by"> |
7 | All functions called by a specific function</option> |
8 | - <option value="all_call_paths"> |
9 | - All function call paths between two functions</option> |
10 | + <!--option value="all_call_paths"> REMOVED AS THIS IS SLOW FOR IOS |
11 | + All function call paths between two functions</option--> |
12 | <option value="shortest_call_path"> |
13 | Shortest path between two functions</option> |
14 | <option value="function_names"> |
15 | |
16 | === modified file 'src/sextant/__main__.py' |
17 | --- src/sextant/__main__.py 2014-10-17 15:30:14 +0000 |
18 | +++ src/sextant/__main__.py 2014-11-18 14:47:39 +0000 |
19 | @@ -128,6 +128,7 @@ |
20 | alternative_name = None |
21 | |
22 | not_object_file = args.not_object_file |
23 | + add_file_paths = args.add_file_paths |
24 | # the default is "yes, this is an object file" if not-object-file was |
25 | # unsupplied |
26 | |
27 | @@ -136,7 +137,8 @@ |
28 | getpass.getuser(), |
29 | args.input_file, |
30 | alternative_name, |
31 | - not_object_file) |
32 | + not_object_file, |
33 | + add_file_paths) |
34 | except requests.exceptions.ConnectionError as e: |
35 | msg = 'Connection error to server {}: {}' |
36 | logging.error(msg.format(_displayable_url(args), e)) |
37 | @@ -221,6 +223,12 @@ |
38 | help='default False, if the input file is an ' |
39 | 'object to be disassembled', |
40 | action='store_true') |
41 | + parsers['add'].add_argument('--add-file-paths', |
42 | + help='default False, set to True to make objdump ' |
43 | + 'extract the file paths for each function. ' |
44 | + 'WARNING: this is SLOW for large object files, ' |
45 | + '~15 hours for IOS.', |
46 | + action='store_true') |
47 | |
48 | parsers['delete'] = subparsers.add_parser('delete-program', |
49 | help="delete a program from the database") |
50 | |
51 | === modified file 'src/sextant/db_api.py' |
52 | --- src/sextant/db_api.py 2014-11-18 14:47:39 +0000 |
53 | +++ src/sextant/db_api.py 2014-11-18 14:47:39 +0000 |
54 | @@ -159,7 +159,7 @@ |
55 | tmp_path = os.path.join(self._tmp_dir, '{}_{{}}'.format(program_name)) |
56 | |
57 | self.func_writer = CSVWriter(tmp_path.format('funcs'), |
58 | - headers=['name', 'type'], |
59 | + headers=['name', 'type', 'file'], |
60 | max_rows=5000) |
61 | self.call_writer = CSVWriter(tmp_path.format('calls'), |
62 | headers=['caller', 'callee'], |
63 | @@ -171,7 +171,7 @@ |
64 | ' WITH line, toInt(line.id) as lineid' |
65 | ' MATCH (n:program {{name: "{}"}})' |
66 | ' CREATE (n)-[:subject]->(m:func {{name: line.name,' |
67 | - ' id: lineid, type: line.type}})') |
68 | + ' id: lineid, type: line.type, file: line.file}})') |
69 | |
70 | self.add_call_query = (' USING PERIODIC COMMIT 250' |
71 | ' LOAD CSV WITH HEADERS FROM "file:{}" AS line' |
72 | @@ -203,7 +203,7 @@ |
73 | # Propagate the error if there is one. |
74 | return False if etype is not None else True |
75 | |
76 | - def add_function(self, name, typ='normal'): |
77 | + def add_function(self, name, typ='normal', source='unknown'): |
78 | """ |
79 | Add a function. |
80 | |
81 | @@ -219,7 +219,7 @@ |
82 | pointer: we know only that the function exists, not its |
83 | name or details. |
84 | """ |
85 | - self.func_writer.write(name, typ) |
86 | + self.func_writer.write(name, typ, source) |
87 | |
88 | def add_call(self, caller, callee): |
89 | """ |
90 | @@ -264,7 +264,11 @@ |
91 | for path in file_paths: |
92 | os.remove(path) |
93 | |
94 | - os.rmdir(self._tmp_dir) |
95 | + try: |
96 | + os.rmdir(self._tmp_dir) |
97 | + except: |
98 | + # There is other stuff there - leave it. |
99 | + pass |
100 | |
101 | try: |
102 | # If the parent sextant temp folder is empty, remove it. |
103 | @@ -290,6 +294,7 @@ |
104 | |
105 | tx.append('CREATE CONSTRAINT ON (p:program) ASSERT p.name IS UNIQUE') |
106 | tx.append('CREATE INDEX ON :func(name)') |
107 | + tx.append('CREATE INDEX ON: func(file)') |
108 | |
109 | # Apply the transaction. |
110 | tx.commit() |
111 | @@ -854,6 +859,48 @@ |
112 | .format(program_name, search, max_funcs)) |
113 | return {func[0] for func in self._db.query(q)} |
114 | |
115 | + @staticmethod |
116 | + def get_query(identifier, search, typ='func'): |
117 | + if ':' in search: |
118 | + func_subs, file_subs = search.split(':') |
119 | + else: |
120 | + func_subs, file_subs = search, '' |
121 | + |
122 | + # Remove empty strings. |
123 | + func_subs = [sub for sub in func_subs.split(',') if sub] |
124 | + file_subs = [sub for sub in file_subs.split(',') if sub] |
125 | + |
126 | + # Cases: |
127 | + # specific:<don't care> |
128 | + # wild: specific |
129 | + # wild: wild |
130 | + |
131 | + query_str = "" |
132 | + |
133 | + def get_list(subs): |
134 | + return '[{}]'.format(','.join("'{}'".format(s) for s in subs)) |
135 | + |
136 | + |
137 | + if func_subs and not any('*' in sub for sub in func_subs): |
138 | + # List of specific functions. Don't care about anything after ':' |
139 | + query_str += ('USING INDEX {0}:func(name) WHERE {0}.name IN {1} ' |
140 | + .format(identifier, get_list(func_subs))) |
141 | + else: |
142 | + if file_subs and not any('*' in sub for sub in file_subs): |
143 | + # Specific file to look in. |
144 | + query_str = ('USING INDEX {0}.func(file) WHERE {0}.file IN {1} ' |
145 | + .format(identifier, get_list(file_subs))) |
146 | + elif file_subs: |
147 | + query_str = ('WHERE ANY (s_file IN {1} WHERE {0}.file =~ s_file) ' |
148 | + .format(identifier, get_list(file_subs))) |
149 | + |
150 | + if func_subs: |
151 | + query_str += 'AND ' if file_subs else 'WHERE ' |
152 | + query_str += ('ANY (s_name IN {1} WHERE {0}.name =~ s_name) ' |
153 | + .format(identifier, get_list(func_subs))) |
154 | + |
155 | + return query_str |
156 | + |
157 | def get_all_functions_called(self, program_name, function_calling): |
158 | """ |
159 | Execute query to find all functions called by a function (indirectly). |
160 | @@ -863,14 +910,9 @@ |
161 | :param function_calling: string name of a function whose children to find |
162 | :return: FunctionQueryResult, maximal subgraph rooted at function_calling |
163 | """ |
164 | - |
165 | - if not self.check_function_exists(program_name, function_calling): |
166 | - return None |
167 | - |
168 | - q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(f:func {{name: "{}"}})' |
169 | - ' USING INDEX f:func(name)' |
170 | - ' MATCH (f)-[:calls*]->(g) RETURN distinct f, g' |
171 | - .format(program_name, function_calling)) |
172 | + q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(f:func) {}' |
173 | + ' MATCH (f)-[:calls]->(g:func) RETURN distinct f, g' |
174 | + .format(program_name, SextantConnection.get_query('f', function_calling))) |
175 | |
176 | return self._execute_query(program_name, q) |
177 | |
178 | @@ -884,14 +926,10 @@ |
179 | :return: FunctionQueryResult, maximal connected subgraph with leaf function_called |
180 | """ |
181 | |
182 | - if not self.check_function_exists(program_name, function_called): |
183 | - return None |
184 | - |
185 | - q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(g:func {{name: "{}"}})' |
186 | - ' USING INDEX g:func(name)' |
187 | - ' MATCH (f)-[:calls*]->(g) WHERE f.name <> "{}"' |
188 | - ' RETURN distinct f , g') |
189 | - q = q.format(program_name, function_called, program_name) |
190 | + q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(g:func) {}' |
191 | + ' MATCH (f)-[:calls]->(g)' |
192 | + ' RETURN distinct f, g') |
193 | + q = q.format(program_name, SextantConnection.get_query('g', function_called), program_name) |
194 | |
195 | return self._execute_query(program_name, q) |
196 | |
197 | @@ -910,22 +948,17 @@ |
198 | if not self.check_program_exists(program_name): |
199 | return None |
200 | |
201 | - if not self.check_function_exists(program_name, function_called): |
202 | - return None |
203 | - |
204 | - if not self.check_function_exists(program_name, function_calling): |
205 | - return None |
206 | - |
207 | - q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(start:func {{name: "{}"}})' |
208 | - ' USING INDEX start:func(name)' |
209 | - ' MATCH (p)-[:subject]->(end:func {{name: "{}"}})' |
210 | - ' USING INDEX end:func(name)' |
211 | + start_q = SextantConnection.get_query('start', function_calling) |
212 | + end_q = SextantConnection.get_query('end', function_called) |
213 | + |
214 | + q = (' MATCH (p:program {{name: "{}"}})' |
215 | + ' MATCH (p)-[:subject]->(start:func) {} WITH start, p' |
216 | + ' MATCH (p)-[:subject]->(end:func) {} WITH start, end' |
217 | ' MATCH path=(start)-[:calls*]->(end)' |
218 | ' WITH DISTINCT nodes(path) AS result' |
219 | ' UNWIND result AS answer' |
220 | ' RETURN answer') |
221 | - q = q.format(program_name, function_calling, function_called) |
222 | - |
223 | + q = q.format(program_name, start_q, end_q) |
224 | return self._execute_query(program_name, q) |
225 | |
226 | def get_whole_program(self, program_name): |
227 | @@ -942,7 +975,7 @@ |
228 | ' RETURN (f)'.format(program_name)) |
229 | return self._execute_query(program_name, q) |
230 | |
231 | - def get_shortest_path_between_functions(self, program_name, func1, func2): |
232 | + def get_shortest_path_between_functions(self, program_name, function_calling, function_called): |
233 | """ |
234 | Execute query to get a single, shortest, path between two functions. |
235 | :param program_name: string name of the program we wish to search under |
236 | @@ -953,17 +986,16 @@ |
237 | if not self.check_program_exists(program_name): |
238 | return None |
239 | |
240 | - if not self.check_function_exists(program_name, func1): |
241 | - return None |
242 | - |
243 | - if not self.check_function_exists(program_name, func2): |
244 | - return None |
245 | - |
246 | - q = (' MATCH (p:program {{name: "{}"}})-[:subject]->(f:func {{name: "{}"}})' |
247 | - ' USING INDEX f:func(name)' |
248 | - ' MATCH (p)-[:subject]->(g:func {{name: "{}"}})' |
249 | - ' MATCH path=shortestPath((f)-[:calls*]->(g))' |
250 | - ' UNWIND nodes(path) AS ans' |
251 | - ' RETURN ans'.format(program_name, func1, func2)) |
252 | + start_q = SextantConnection.get_query('start', function_calling) |
253 | + end_q = SextantConnection.get_query('end', function_called) |
254 | + |
255 | + q = (' MATCH (p:program {{name: "{}"}})' |
256 | + ' MATCH (p)-[:subject]->(start:func) {} WITH start, p' |
257 | + ' MATCH (p)-[:subject]->(end:func) {} WITH start, end' |
258 | + ' MATCH path=shortestPath((start)-[:calls*]->(end))' |
259 | + ' UNWIND nodes(path) AS answer' |
260 | + ' RETURN answer') |
261 | + q = q.format(program_name, start_q, end_q) |
262 | |
263 | return self._execute_query(program_name, q) |
264 | + |
265 | |
266 | === modified file 'src/sextant/objdump_parser.py' (properties changed: +x to -x) |
267 | --- src/sextant/objdump_parser.py 2014-10-23 11:15:48 +0000 |
268 | +++ src/sextant/objdump_parser.py 2014-11-18 14:47:39 +0000 |
269 | @@ -42,9 +42,12 @@ |
270 | The number of function calls that have been parsed. |
271 | function_ptr_count: |
272 | The number of function pointers that have been detected. |
273 | - _known_stubs: |
274 | - A set of the names of functions with type 'stub' that have been |
275 | - parsed - used to avoid registering a stub multiple times. |
276 | + _known_functions: |
277 | + A set of the names of functions that have been |
278 | + parsed - used to avoid registering a function multiple times. |
279 | + _partial_functions: |
280 | + A set of functions whose names we have seen but whose source |
281 | + files we don't yet know. |
282 | |
283 | """ |
284 | def __init__(self, file_path, file_object=None, |
285 | @@ -102,13 +105,14 @@ |
286 | self.call_count = 0 |
287 | self.function_ptr_count = 0 |
288 | |
289 | - # Avoid adding duplicate function stubs (as these are detected from |
290 | - # function calls so may be repeated). |
291 | - self._known_stubs = set() |
292 | + # Avoid adding duplicate functions. |
293 | + self._known_functions = set() |
294 | + # Set of partially-parsed functions. |
295 | + self._partial_functions = set() |
296 | |
297 | # By default print information to stdout. |
298 | - def print_func(name, typ): |
299 | - print('func {:25}{}'.format(name, typ)) |
300 | + def print_func(name, typ, source='unknown'): |
301 | + print('func {:25}{:15}{}'.format(name, typ, source)) |
302 | |
303 | def print_call(caller, callee): |
304 | print('call {:25}{:25}'.format(caller, callee)) |
305 | @@ -116,7 +120,6 @@ |
306 | def print_started(parser): |
307 | print('parse started: {}[{}]'.format(self.path, ', '.join(self.sections))) |
308 | |
309 | - |
310 | def print_finished(parser): |
311 | print('parsed {} functions and {} calls'.format(self.function_count, self.call_count)) |
312 | |
313 | @@ -134,12 +137,33 @@ |
314 | self.function_ptr_count += 1 |
315 | return name |
316 | |
317 | - def _add_function_normal(self, name): |
318 | - """ |
319 | - Add a function which we have full assembly code for. |
320 | - """ |
321 | - self.add_function(name, 'normal') |
322 | - self.function_count += 1 |
323 | + def _add_function(self, name, source=None): |
324 | + """ |
325 | + Add a partially known or fully known function. |
326 | + """ |
327 | + if source is None: |
328 | + # Partial definition - if do not already have a full definition |
329 | + # for this name then add it to the partials set. |
330 | + if not name in self._known_functions: |
331 | + self._partial_functions.add(name) |
332 | + elif source == 'unknown': |
333 | + # Manually adding a stub function. |
334 | + self.add_function(name, 'stub', source) |
335 | + self.function_count += 1 |
336 | + elif name not in self._known_functions: |
337 | + # A full definition - either upgrade from partial function |
338 | + # to known function, or add directly to known functions |
339 | + # (otherwise we have already seen it) |
340 | + |
341 | + try: |
342 | + self._partial_functions.remove(name) |
343 | + except KeyError: |
344 | + pass |
345 | + |
346 | + |
347 | + self._known_functions.add(name) |
348 | + self.add_function(name, 'normal', source) |
349 | + self.function_count += 1 |
350 | |
351 | def _add_function_ptr(self, name): |
352 | """ |
353 | @@ -148,15 +172,6 @@ |
354 | self.add_function(name, 'pointer') |
355 | self.function_count += 1 |
356 | |
357 | - def _add_function_stub(self, name): |
358 | - """ |
359 | - Add a function stub - we have its name but none of its internals. |
360 | - """ |
361 | - if not name in self._known_stubs: |
362 | - self._known_stubs.add(name) |
363 | - self.add_function(name, 'stub') |
364 | - self.function_count += 1 |
365 | - |
366 | def _add_call(self, caller, callee): |
367 | """ |
368 | Add a function call from caller to callee. |
369 | @@ -171,10 +186,20 @@ |
370 | self.started() |
371 | |
372 | if self._file is not None: |
373 | - in_section = False # if we are in one of self.sections |
374 | - current_function = None # track the caller for function calls |
375 | + in_section = False # If we are in one of self.sections. |
376 | + current_function = None # Track the caller for function calls. |
377 | + to_add = False |
378 | |
379 | for line in self._file: |
380 | + if to_add: |
381 | + file_line = line.startswith('/') |
382 | + source = line.split(':')[0] if file_line else None |
383 | + self._add_function(current_function, source) |
384 | + to_add = False |
385 | + |
386 | + if file_line: |
387 | + continue |
388 | + |
389 | if line.startswith('Disassembly'): |
390 | # 'Disassembly of section <name>:\n' |
391 | section = line.split(' ')[-1].rstrip(':\n') |
392 | @@ -188,13 +213,16 @@ |
393 | # with <function_identifier> of form: |
394 | # <function_name>[@plt] |
395 | function_identifier = line.split('<')[-1].split('>')[0] |
396 | + if function_identifier.startswith('__be_'): |
397 | + function_identifier = function_identifier.lstrip('__be_') |
398 | |
399 | if '@' in function_identifier: |
400 | current_function = function_identifier.split('@')[0] |
401 | - self._add_function_stub(current_function) |
402 | + self._add_function(current_function) |
403 | else: |
404 | current_function = function_identifier |
405 | - self._add_function_normal(current_function) |
406 | + # Flag function - we look for source on the next line. |
407 | + to_add = True |
408 | |
409 | elif 'call ' in line or 'callq ' in line: |
410 | # WHITESPACE to prevent picking up function names |
411 | @@ -213,9 +241,12 @@ |
412 | # from which we extract name |
413 | callee_is_ptr = False |
414 | function_identifier = callee_info.lstrip('<').rstrip('>\n') |
415 | + if function_identifier.startswith('__be_'): |
416 | + function_identifier = function_identifier.lstrip('__be_') |
417 | + |
418 | if '@' in function_identifier: |
419 | callee = function_identifier.split('@')[0] |
420 | - self._add_function_stub(callee) |
421 | + self._add_function(callee) |
422 | else: |
423 | callee = function_identifier.split('-')[-1].split('+')[0] |
424 | # Do not add this fn now - it is a normal func |
425 | @@ -231,6 +262,10 @@ |
426 | # Add the call. |
427 | if not (self.ignore_ptrs and callee_is_ptr): |
428 | self._add_call(current_function, callee) |
429 | + |
430 | + for name in self._partial_functions: |
431 | + self._add_function(name, 'unknown') |
432 | + |
433 | |
434 | self.finished() |
435 | |
436 | @@ -261,7 +296,7 @@ |
437 | return result |
438 | |
439 | |
440 | -def run_objdump(input_file): |
441 | +def run_objdump(input_file, add_file_paths=False): |
442 | """ |
443 | Run the objdump command on the file with the given path. |
444 | |
445 | @@ -271,13 +306,24 @@ |
446 | Arguments: |
447 | input_file: |
448 | The path of the file to run objdump on. |
449 | + add_file_paths: |
450 | + Whether to call with -l option to extract line numbers and source |
451 | + files from the binary. VERY SLOW on large binaries (~15 hours for ios). |
452 | |
453 | """ |
454 | + print('input file: {}'.format(input_file)) |
455 | # A single section can be specified for parsing with the -j flag, |
456 | # but it is not obviously possible to parse multiple sections like this. |
457 | - p = subprocess.Popen(['objdump', '-d', input_file, '--no-show-raw-insn'], |
458 | - stdout=subprocess.PIPE) |
459 | - g = subprocess.Popen(['egrep', 'Disassembly|call(q)? |>:$'], stdin=p.stdout, stdout=subprocess.PIPE) |
460 | + args = ['objdump', '-d', input_file, '--no-show-raw-insn'] |
461 | + if add_file_paths: |
462 | + args += ['--line-numbers'] |
463 | + |
464 | + p = subprocess.Popen(args, stdout=subprocess.PIPE) |
465 | + # Egrep filters out the section headers (Disassembly of section...), |
466 | + # the call lines (... [l]call[q] ...), the function declarations |
467 | + # (... <function>:$) and the file paths (^/file_path). |
468 | + g = subprocess.Popen(['egrep', 'Disassembly|call(q)? |>:$|^/'], |
469 | + stdin=p.stdout, stdout=subprocess.PIPE) |
470 | return input_file, g.stdout |
471 | |
472 | |
473 | |
474 | === modified file 'src/sextant/test_parser.py' |
475 | --- src/sextant/test_parser.py 2014-10-23 11:15:48 +0000 |
476 | +++ src/sextant/test_parser.py 2014-11-18 14:47:39 +0000 |
477 | @@ -23,7 +23,7 @@ |
478 | calls = defaultdict(list) |
479 | |
480 | # set the Parser to put output in local dictionaries |
481 | - add_function = lambda n, t: self.add_function(functions, n, t) |
482 | + add_function = lambda n, t, s='unknown': self.add_function(functions, n, t) |
483 | add_call = lambda a, b: self.add_call(calls, a, b) |
484 | |
485 | p = parser.Parser(path, sections=sections, ignore_ptrs=ignore_ptrs, |
486 | |
487 | === modified file 'src/sextant/test_resources/parser_test' |
488 | Binary files src/sextant/test_resources/parser_test 2014-10-13 14:10:01 +0000 and src/sextant/test_resources/parser_test 2014-11-18 14:47:39 +0000 differ |
489 | === modified file 'src/sextant/update_db.py' |
490 | --- src/sextant/update_db.py 2014-10-17 14:20:06 +0000 |
491 | +++ src/sextant/update_db.py 2014-11-18 14:47:39 +0000 |
492 | @@ -20,7 +20,7 @@ |
493 | import logging |
494 | |
495 | def upload_program(connection, user_name, file_path, program_name=None, |
496 | - not_object_file=False): |
497 | + not_object_file=False, add_file_paths=False): |
498 | """ |
499 | Upload a program's functions and call graph to the database. |
500 | |
501 | @@ -38,6 +38,9 @@ |
502 | not_object_file: |
503 | Flag controlling whether file_path is pointing to a dump file or |
504 | a binary file. |
505 | + add_file_paths: |
506 | + Flag controlling whether to call objdump with the -l option to |
507 | + extract line numbers and source files. VERY SLOW on large binaries. |
508 | """ |
509 | if not connection._ssh: |
510 | raise SSHConnectionError('An SSH connection is required for ' |
511 | @@ -59,9 +62,9 @@ |
512 | start = time() |
513 | |
514 | if not not_object_file: |
515 | - print('Generating dump file...', end='') |
516 | + print('Generating dump file with{} file paths...'.format(('out', '')[add_file_paths]), end='') |
517 | sys.stdout.flush() |
518 | - file_path, file_object = run_objdump(file_path) |
519 | + file_path, file_object = run_objdump(file_path, add_file_paths) |
520 | print('done.') |
521 | else: |
522 | file_object = None |
523 | @@ -82,15 +85,19 @@ |
524 | print('done: {} functions and {} calls.' |
525 | .format(parser.function_count, parser.call_count)) |
526 | |
527 | - parser = Parser(file_path = file_path, file_object = file_object, |
528 | + parser = Parser(file_path=file_path, file_object = file_object, |
529 | sections=[], |
530 | - add_function = program.add_function, |
531 | - add_call = program.add_call, |
532 | + add_function=program.add_function, |
533 | + add_call=program.add_call, |
534 | started=lambda parser: start_parser(program), |
535 | finished=lambda parser: finish_parser(parser, program)) |
536 | + |
537 | parser.parse() |
538 | - |
539 | - program.commit() |
540 | + |
541 | + if parser.function_count == 0: |
542 | + print('Nothing to upload. Did you mean to add the --not-object-file flag?') |
543 | + else: |
544 | + program.commit() |
545 | |
546 | end = time() |
547 | print('Finished in {:.2f}s.'.format(end-start)) |
548 | |
549 | === modified file 'src/sextant/web/server.py' |
550 | --- src/sextant/web/server.py 2014-11-18 14:47:39 +0000 |
551 | +++ src/sextant/web/server.py 2014-11-18 14:47:39 +0000 |
552 | @@ -13,6 +13,8 @@ |
553 | from twisted.internet.threads import deferToThread |
554 | from twisted.internet import defer |
555 | |
556 | +from neo4jrestclient.exceptions import TransactionException |
557 | + |
558 | import logging |
559 | import os |
560 | import json |
561 | @@ -24,6 +26,8 @@ |
562 | import tempfile |
563 | import subprocess |
564 | |
565 | +from datetime import datetime |
566 | + |
567 | from cgi import escape # deprecated in Python 3 in favour of html.escape, but we're stuck on Python 2 |
568 | |
569 | # global SextantConnection object which deals with the port forwarding |
570 | @@ -174,13 +178,15 @@ |
571 | # if we are okay here we have a valid query with all required arguments |
572 | if res_code is RESPONSE_CODE_OK: |
573 | try: |
574 | + print('running query {}'.format(datetime.now())) |
575 | program = yield defer_to_thread_with_timeout(render_timeout, fn, |
576 | name, *req_args) |
577 | - except defer.CancelledError: |
578 | + print('\tdone {}'.format(datetime.now())) |
579 | + except Exception as e: |
580 | # the timeout has fired and cancelled the request |
581 | res_code = RESPONSE_CODE_BAD_REQUEST |
582 | - res_fmt = "The request timed out after {} seconds." |
583 | - res_msg = res_fmt.format(render_timeout) |
584 | + res_msg = "{}".format(e) |
585 | + print('\tfailed {}'.format(datetime.now())) |
586 | |
587 | if res_code is RESPONSE_CODE_OK: |
588 | # we have received a response to our request |
589 | @@ -201,10 +207,12 @@ |
590 | suppress_common = suppress_common_arg in ('null', 'true') |
591 | |
592 | # we have a non-empty return - render it |
593 | + print('getting plot {}'.format(datetime.now())) |
594 | res_msg = yield deferToThread(self.get_plot, program, |
595 | suppress_common, |
596 | remove_self_calls=False) |
597 | request.setHeader('content-type', 'image/svg+xml') |
598 | + print('\tdone {}'.format(datetime.now())) |
599 | |
600 | request.setResponseCode(res_code) |
601 | request.write(res_msg) |
602 | @@ -229,6 +237,7 @@ |
603 | max_funcs = AUTOCOMPLETE_NAMES_LIMIT + 1 |
604 | programs = CONNECTION.programs_with_metadata() |
605 | result = CONNECTION.get_function_names(program_name, search, max_funcs) |
606 | + print(search, len(result)) |
607 | return result if len(result) < max_funcs else set() |
608 | |
609 |