scratchabit: Differentiate between next_line_addr() and next_addr().
[ScratchABit.git] / scratchabit.py
blobded5d9c7b9964f98b35ba7e28fdd99cb8177319c
1 #!/usr/bin/env python3
2 # ScratchABit - interactive disassembler
4 # Copyright (c) 2015 Paul Sokolovsky
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import sys
19 import os
20 import os.path
21 import time
22 import re
23 import string
24 import binascii
25 import logging as log
27 import engine
28 import idaapi
30 import curses
31 from picotui.widgets import *
32 from picotui import editorext as editor
33 from picotui.editorext import Viewer
34 import utils
35 import help
36 import saveload
39 HEIGHT = 21
41 def disasm_one(p):
42 insnsz = p.ana()
43 p.out()
44 print("%08x %s" % (p.cmd.ea, p.cmd.disasm))
45 p.cmd.ea += p.cmd.size
46 p.cmd.size = 0
49 class TextSaveModel:
50 def __init__(self, f, ctrl):
51 self.f = f
52 self.ctrl = ctrl
53 self.cnt = 0
54 def add_line(self, addr, line):
55 line = ("%08x " % addr) + line.indent + line.render() + "\n"
56 self.f.write(line)
57 if self.cnt % 256 == 0:
58 self.ctrl.show_status("Writing: 0x%x" % addr)
59 self.cnt += 1
62 class Editor(editor.EditorExt):
64 def __init__(self, *args):
65 super().__init__(*args)
66 self.model = None
67 self.addr_stack = []
68 self.search_str = ""
70 def set_model(self, model):
71 self.model = model
72 self.set_lines(model.lines())
73 # Invalidate top_line. Assuming goto_*() will be called
74 # after set_model().
75 self.top_line = sys.maxsize
77 def show_line(self, l, i):
78 global show_bytes
79 if not isinstance(l, str):
80 res = "%08x " % l.ea
81 if show_bytes > 0:
82 bin = ""
83 if not l.virtual:
84 b = self.model.AS.get_bytes(l.ea, l.size)
85 bin = str(binascii.hexlify(b[:show_bytes]), "ascii")
86 if l.size > show_bytes:
87 bin += "+"
88 res += idaapi.fillstr(bin, show_bytes * 2 + 1)
89 res += l.indent + l.render()
90 super().show_line(res, i)
92 def goto_addr(self, to_addr, from_addr=None):
93 if to_addr is None:
94 self.show_status("No address-like value to go to")
95 return
96 subno = -1
97 if isinstance(to_addr, tuple):
98 to_addr, subno = to_addr
99 adj_addr = self.model.AS.adjust_addr_reverse(to_addr)
100 if adj_addr is None:
101 self.show_status("Unknown address: 0x%x" % to_addr)
102 return
103 to_addr = adj_addr
105 # If we can position cursor within current screen, do that,
106 # to avoid jumpy UI
107 no = self.model.addr2line_no(to_addr, subno)
108 if no is not None:
109 if self.line_visible(no):
110 self.goto_line(no)
111 if from_addr is not None:
112 self.addr_stack.append(from_addr)
113 return
115 # Otherwise, re-render model around needed address, and redraw screen
116 t = time.time()
117 model = engine.render_partial_around(to_addr, 0, HEIGHT * 2)
118 self.show_status("Rendering time: %fs" % (time.time() - t))
119 if not model:
120 self.show_status("Unknown address: 0x%x" % to_addr)
121 return
122 self.set_model(model)
124 no = self.model.addr2line_no(to_addr, subno)
125 if no is not None:
126 if from_addr is not None:
127 self.addr_stack.append(from_addr)
128 if not self.goto_line(no):
129 # Need to redraw always, because we changed underlying model
130 self.redraw()
131 else:
132 self.show_status("Unknown address: %x" % to_addr)
134 def update_model(self, stay_on_real=False):
135 """Re-render model and update screen in such way that cursor stayed
136 on the same line (as far as possible).
137 stay_on_real == False - try to stay on same relative line no. for
138 the current address.
139 stay_on_real == True - try to stay on the line which contains real
140 bytes for the current address (use this if you know that cursor
141 stayed on such line before the update).
143 addr, subno = self.cur_addr_subno()
144 t = time.time()
145 model = engine.render_partial_around(addr, subno, HEIGHT * 2)
146 self.show_status("Rendering time: %fs" % (time.time() - t))
147 self.set_model(model)
148 if stay_on_real:
149 self.cur_line = model.target_addr_lineno_real
150 else:
151 self.cur_line = model.target_addr_lineno
152 self.top_line = self.cur_line - self.row
153 #log.debug("update_model: addr=%x, row=%d, cur_line=%d, top_line=%d" % (addr, self.row, self.cur_line, self.top_line))
154 self.redraw()
156 def handle_cursor_keys(self, key):
157 cl = self.cur_line
158 if super().handle_cursor_keys(key):
159 if self.cur_line == cl:
160 return True
161 #log.debug("handle_cursor_keys: cur: %d, total: %d", self.cur_line, self.total_lines)
162 if self.cur_line <= HEIGHT or self.total_lines - self.cur_line <= HEIGHT:
163 log.debug("handle_cursor_keys: triggering update")
164 self.update_model()
166 return True
167 else:
168 return False
170 def cur_addr(self):
171 line = self.get_cur_line()
172 return line.ea
174 # Address of the next line. It may be the same address as the
175 # current line, as several lines may "belong" to the same address,
176 # (virtual lines like headers, etc.)
177 def next_line_addr(self):
178 try:
179 return self.content[self.cur_line + 1].ea
180 except:
181 return None
183 # Return next address following the current line. May need to skip
184 # few virtual lines.
185 def next_addr(self):
186 addr = self.cur_addr()
187 n = self.cur_line + 1
188 try:
189 while self.content[n].ea == addr:
190 n += 1
191 return self.content[n].ea
192 except:
193 return None
195 def cur_addr_subno(self):
196 line = self.get_cur_line()
197 return (line.ea, line.subno)
199 def cur_operand_no(self, line):
200 col = self.col - engine.DisasmObj.LEADER_SIZE - len(line.indent)
201 #self.show_status("Enter pressed: %s, %s" % (col, line))
202 for i, pos in enumerate(line.arg_pos):
203 if pos[0] <= col <= pos[1]:
204 return i
205 return -1
207 def analyze_status(self, cnt):
208 self.show_status("Analyzing (%d insts so far)" % cnt)
210 def write_func(self, addr):
211 func = self.model.AS.lookup_func(addr)
212 if func:
213 funcname = self.model.AS.get_label(func.start)
214 outfile = funcname + ".lst"
215 with open(outfile, "w") as f:
216 model = TextSaveModel(f, self)
217 for start, end in func.get_ranges():
218 while start < end:
219 start = engine.render_from(model, start, 1)
220 return outfile
223 def handle_edit_key(self, key):
224 try:
225 return self.handle_key_unprotected(key)
226 except Exception as e:
227 log.exception("Exception processing user command")
228 L = 5
229 T = 2
230 W = 70
231 H = 20
232 self.dialog_box(L, T, W, H)
233 v = Viewer(L + 1, T + 1, W - 2, H - 2)
234 import traceback
235 v.set_lines([
236 "Exception occured processing the command. Press Esc to continue.",
237 "Recommended action is saving database, quitting and comparing",
238 "database files with backup copies for possibility of data loss",
239 "or corruption. The exception was also logged to scratchabit.log.",
240 "Please report way to reproduce it to",
241 "https://github.com/pfalcon/ScratchABit/issues",
243 ] + traceback.format_exc().splitlines())
244 v.loop()
245 self.redraw()
248 def handle_key_unprotected(self, key):
249 line = self.get_cur_line()
250 if key == editor.KEY_ENTER:
251 line = self.get_cur_line()
252 log.info("Enter pressed: %s" % line)
253 op_no = self.cur_operand_no(line)
254 self.show_status("Enter pressed: %s, %s" % (self.col, op_no))
255 to_addr = None
256 # No longer try to jump only to addresses in args, parse
257 # textual representation below
258 if False and isinstance(line, engine.DisasmObj):
259 if op_no >= 0:
260 o = line[op_no]
261 to_addr = o.get_addr()
262 if to_addr is None:
263 o = line.get_operand_addr()
264 if o:
265 to_addr = o.get_addr()
266 if to_addr is None:
267 pos = self.col - line.LEADER_SIZE - len(line.indent)
268 word = utils.get_word_at_pos(line.cache, pos)
269 if word:
270 if word[0].isdigit():
271 to_addr = int(word, 0)
272 else:
273 to_addr = self.model.AS.resolve_label(word)
274 if to_addr is None:
275 self.show_status("Unknown address: %s" % word)
276 return
277 self.goto_addr(to_addr, from_addr=self.cur_addr_subno())
278 elif key == editor.KEY_ESC:
279 if self.addr_stack:
280 self.show_status("Returning")
281 self.goto_addr(self.addr_stack.pop())
282 elif key == b"q":
283 return editor.KEY_QUIT
284 elif key == b"\x1b[5;5~": # Ctrl+PgUp
285 self.goto_addr(self.model.AS.min_addr(), from_addr=line.ea)
286 elif key == b"\x1b[6;5~": # Ctrl+PgDn
287 self.goto_addr(self.model.AS.max_addr(), from_addr=line.ea)
288 elif key == b"c":
289 addr = self.cur_addr()
290 self.show_status("Analyzing at %x" % addr)
291 engine.add_entrypoint(addr, False)
292 engine.analyze(self.analyze_status)
293 self.update_model()
294 elif key == b"d":
295 addr = self.cur_addr()
296 fl = self.model.AS.get_flags(addr)
297 if fl not in (self.model.AS.DATA, self.model.AS.UNK):
298 self.show_status("Undefine first")
299 return
300 if fl == self.model.AS.UNK:
301 self.model.AS.set_flags(addr, 1, self.model.AS.DATA, self.model.AS.DATA_CONT)
302 else:
303 sz = self.model.AS.get_unit_size(addr)
304 self.model.undefine_unit(addr)
305 sz *= 2
306 if sz > 4: sz = 1
307 self.model.AS.set_flags(addr, sz, self.model.AS.DATA, self.model.AS.DATA_CONT)
308 self.update_model()
309 elif key == b"a":
310 addr = self.cur_addr()
311 fl = self.model.AS.get_flags(addr)
312 if fl not in (self.model.AS.UNK, self.model.AS.DATA):
313 self.show_status("Undefine first")
314 return
315 sz = 0
316 label = "s_"
317 while True:
318 b = self.model.AS.get_byte(addr)
319 fl = self.model.AS.get_flags(addr)
320 if not (0x20 <= b <= 0x7e or b in (0x0a, 0x0d)):
321 if b == 0:
322 sz += 1
323 break
324 if fl not in (self.model.AS.UNK, self.model.AS.DATA, self.model.AS.DATA_CONT):
325 break
326 c = chr(b)
327 if c < '0' or c in string.punctuation:
328 c = '_'
329 label += c
330 addr += 1
331 sz += 1
332 if sz > 0:
333 self.model.AS.set_flags(self.cur_addr(), sz, self.model.AS.STR, self.model.AS.DATA_CONT)
334 self.model.AS.make_unique_label(self.cur_addr(), label)
335 self.update_model()
336 elif key == b"u":
337 addr = self.cur_addr()
338 self.model.undefine_unit(addr)
339 self.update_model()
341 elif key == b"h":
342 op_no = self.cur_operand_no(self.get_cur_line())
343 if op_no >= 0:
344 addr = self.cur_addr()
345 subtype = self.model.AS.get_arg_prop(addr, op_no, "subtype")
346 if subtype != engine.IMM_ADDR:
347 next_subtype = {
348 engine.IMM_UHEX: engine.IMM_UDEC,
349 engine.IMM_UDEC: engine.IMM_UHEX,
351 self.model.AS.set_arg_prop(addr, op_no, "subtype", next_subtype[subtype])
352 self.redraw()
353 self.show_status("Changed arg #%d to %s" % (op_no, next_subtype[subtype]))
354 elif key == b"o":
355 addr = self.cur_addr()
356 line = self.get_cur_line()
357 o = line.get_operand_addr()
358 if not o:
359 self.show_status("Cannot convert operand to offset")
360 return
361 if o.type != idaapi.o_imm or not self.model.AS.is_valid_addr(o.get_addr()):
362 self.show_status("Cannot convert operand to offset: #%s: %s" % (o.n, o.type))
363 return
365 if self.model.AS.get_arg_prop(addr, o.n, "subtype") == engine.IMM_ADDR:
366 self.model.AS.unmake_arg_offset(addr, o.n, o.get_addr())
367 else:
368 self.model.AS.make_arg_offset(addr, o.n, o.get_addr())
369 self.update_model(True)
370 elif key == b";":
371 addr = self.cur_addr()
372 comment = self.model.AS.get_comment(addr) or ""
373 res = self.dialog_edit_line(line=comment, width=60)
374 if res is not None:
375 self.model.AS.set_comment(addr, res)
376 self.update_model()
377 else:
378 self.redraw()
379 elif key == b"n":
380 addr = self.cur_addr()
381 label = self.model.AS.get_label(addr)
382 def_label = self.model.AS.get_default_label(addr)
383 s = label or def_label
384 while True:
385 res = self.dialog_edit_line(line=s)
386 if not res:
387 break
388 if res == def_label:
389 res = addr
390 else:
391 if self.model.AS.label_exists(res):
392 s = res
393 self.show_status("Duplicate label")
394 continue
395 self.model.AS.set_label(addr, res)
396 if not label:
397 # If it's new label, we need to add it to model
398 self.update_model()
399 return
400 break
401 self.redraw()
402 elif key == b"g":
403 d = Dialog(4, 4, title="Go to")
404 d.add(1, 1, WLabel("Label/addr:"))
405 entry = WAutoComplete(20, "", self.model.AS.get_label_list())
406 entry.popup_h = 12
407 entry.finish_dialog = ACTION_OK
408 d.add(13, 1, entry)
409 d.add(1, 2, WLabel("Press Down to auto-complete"))
410 res = d.loop()
411 self.redraw()
413 if res == ACTION_OK:
414 value = entry.get_text()
415 if '0' <= value[0] <= '9':
416 addr = int(value, 0)
417 else:
418 addr = self.model.AS.resolve_label(value)
419 self.goto_addr(addr, from_addr=self.cur_addr())
421 elif key == editor.KEY_F1:
422 help.help(self)
423 self.redraw()
424 elif key == b"S":
425 saveload.save_state(project_dir)
426 self.show_status("Saved.")
427 elif key == b"\x11": # ^Q
428 class IssueList(WListBox):
429 def render_line(self, l):
430 return "%08x %s" % l
431 d = Dialog(4, 4, title="Problems list")
432 lw = IssueList(40, 16, self.model.AS.get_issues())
433 lw.finish_dialog = ACTION_OK
434 d.add(1, 1, lw)
435 res = d.loop()
436 self.redraw()
437 if res == ACTION_OK:
438 val = lw.get_cur_line()
439 if val:
440 self.goto_addr(val[0], from_addr=self.cur_addr())
442 elif key == b"i":
443 off, area = self.model.AS.addr2area(self.cur_addr())
444 props = area[engine.PROPS]
445 percent = 100 * off / (area[engine.END] - area[engine.START] + 1)
446 func = self.model.AS.lookup_func(self.cur_addr())
447 func = self.model.AS.get_label(func.start) if func else None
448 self.show_status("Area: 0x%x %s (%s): %.1f%%, func: %s" % (
449 area[engine.START], props.get("name", "noname"), props["access"], percent, func
451 elif key == b"I":
452 L = 5
453 T = 2
454 W = 66
455 H = 20
456 self.dialog_box(L, T, W, H)
457 v = Viewer(L + 1, T + 1, W - 2, H - 2)
458 lines = []
459 for area in self.model.AS.get_areas():
460 props = area[engine.PROPS]
461 lines.append("%08x-%08x %s:" % (area[engine.START], area[engine.END], props.get("name", "noname")))
462 flags = area[engine.FLAGS]
463 last_capital = None
464 l = ""
465 for i in range(len(flags)):
466 if i % 64 == 0 and l:
467 lines.append(l)
468 l = ""
469 c = engine.flag2char(flags[i])
470 # For "function's instructions", make continuation byte be
471 # clearly distinguishable too.
472 if c == "c" and last_capital == "F":
473 c = "f"
474 l += c
475 if c < "a":
476 last_capital = c
477 if l:
478 lines.append(l)
479 v.set_lines(lines)
480 v.loop()
481 self.redraw()
482 elif key == b"W":
483 out_fname = "out.lst"
484 with open(out_fname, "w") as f:
485 engine.render_partial(TextSaveModel(f, self), 0, 0, 10000000)
486 self.show_status("Disassembly listing written: " + out_fname)
487 elif key == b"\x17": # Ctrl+W
488 outfile = self.write_func(self.cur_addr())
489 if outfile:
490 self.show_status("Wrote file: %s" % outfile)
491 elif key == b"\x15": # Ctrl+U
492 # Next undefined
493 addr = self.next_addr()
494 while True:
495 flags = self.model.AS.get_flags(addr)
496 if flags == self.model.AS.UNK:
497 self.goto_addr(addr, from_addr=self.cur_addr())
498 break
499 addr += 1
500 elif key in (b"/", b"?"): # "/" and Shift+"/"
501 class FoundException(Exception): pass
502 class TextSearchModel:
503 def __init__(self, substr, ctrl):
504 self.search = substr
505 self.ctrl = ctrl
506 self.cnt = 0
507 def add_line(self, addr, line):
508 line = line.render()
509 if self.search in line:
510 raise FoundException(addr)
511 if self.cnt % 256 == 0:
512 self.ctrl.show_status("Searching: 0x%x" % addr)
513 self.cnt += 1
514 if key == b"/":
515 d = Dialog(4, 4, title="Text Search")
516 d.add(1, 1, WLabel("Search for:"))
517 entry = WTextEntry(20, self.search_str)
518 entry.finish_dialog = ACTION_OK
519 d.add(13, 1, entry)
520 res = d.loop()
521 self.redraw()
522 self.search_str = entry.get_text()
523 if res != ACTION_OK or not self.search_str:
524 return
525 addr = self.cur_addr()
526 else:
527 addr = self.next_line_addr()
529 try:
530 engine.render_from(TextSearchModel(self.search_str, self), addr, 10000000)
531 except FoundException as res:
532 self.goto_addr(res.args[0], from_addr=self.cur_addr())
533 else:
534 self.show_status("Not found: " + self.search_str)
536 else:
537 self.show_status("Unbound key: " + repr(key))
540 CPU_PLUGIN = None
541 ENTRYPOINTS = []
542 show_bytes = 0
544 def filter_config_line(l):
545 l = re.sub(r"#.*$", "", l)
546 l = l.strip()
547 return l
549 def load_symbols(fname):
550 with open(fname) as f:
551 for l in f:
552 l = filter_config_line(l)
553 if not l:
554 continue
555 m = re.search(r"\b([A-Za-z_$.][A-Za-z0-9_$.]*)\s*=\s*((0x)?[0-9A-Fa-f]+)", l)
556 if m:
557 #print(m.groups())
558 ENTRYPOINTS.append((m.group(1), int(m.group(2), 0)))
559 else:
560 print("Warning: cannot parse entrypoint info from: %r" % l)
562 def parse_entrypoints(f):
563 for l in f:
564 l = filter_config_line(l)
565 if not l:
566 continue
567 if l[0] == "[":
568 return l
569 m = re.match(r'load "(.+?)"', l)
570 if m:
571 load_symbols(m.group(1))
572 else:
573 label, addr = [v.strip() for v in l.split("=")]
574 ENTRYPOINTS.append((label, int(addr, 0)))
575 return ""
578 def load_target_file(loader, fname):
579 entry = loader.load(engine.ADDRESS_SPACE, fname)
580 log.info("Loaded %s, entrypoint: %s", fname, hex(entry) if entry is not None else None)
581 if entry is not None:
582 ENTRYPOINTS.append(("_ENTRY_", entry))
585 def parse_disasm_def(fname):
586 global CPU_PLUGIN
587 global show_bytes
588 with open(fname) as f:
589 for l in f:
590 l = filter_config_line(l)
591 if not l:
592 continue
593 #print(l)
594 while True:
595 if not l:
596 #return
597 break
598 if l[0] == "[":
599 section = l[1:-1]
600 print("Processing section: %s" % section)
601 if section == "entrypoints":
602 l = parse_entrypoints(f)
603 else:
604 assert 0, "Unknown section: " + section
605 else:
606 break
608 if not l:
609 break
611 if l.startswith("load"):
612 args = l.split()
613 if args[2][0] in string.digits:
614 addr = int(args[2], 0)
615 print("Loading %s @0x%x" % (args[1], addr))
616 engine.ADDRESS_SPACE.load_content(open(args[1], "rb"), addr)
617 else:
618 print("Loading %s (%s plugin)" % (args[1], args[2]))
619 loader = __import__(args[2])
620 load_target_file(loader, args[1])
621 elif l.startswith("cpu "):
622 args = l.split()
623 CPU_PLUGIN = __import__(args[1])
624 print("Loading CPU plugin %s" % (args[1]))
625 elif l.startswith("show bytes "):
626 args = l.split()
627 show_bytes = int(args[2])
628 elif l.startswith("area "):
629 args = l.split()
630 assert len(args) == 4
632 # Allow undescores to separate digit groups
633 def str2int(s):
634 return int(s.replace("_", ""), 0)
636 if "(" in args[2]:
637 m = re.match(r"(.+?)\s*\(\s*(.+?)\s*\)", args[2])
638 start = str2int(m.group(1))
639 end = start + str2int(m.group(2)) - 1
640 else:
641 m = re.match(r"(.+)\s*-\s*(.+)", args[2])
642 start = str2int(m.group(1))
643 end = str2int(m.group(2))
645 a = engine.ADDRESS_SPACE.add_area(start, end, {"name": args[1], "access": args[3].upper()})
646 print("Adding area: %s" % engine.str_area(a))
647 else:
648 assert 0, "Unknown directive: " + l
651 if __name__ == "__main__":
652 # Plugin dirs are relative to the dir where scratchabit.py resides.
653 # sys.path[0] below provide absolute path of this dir, resolved for
654 # symlinks.
655 plugin_dirs = ["plugins", "plugins/cpu", "plugins/loader"]
656 for d in plugin_dirs:
657 sys.path.append(os.path.join(sys.path[0], d))
658 log.basicConfig(filename="scratchabit.log", format='%(asctime)s %(message)s', level=log.DEBUG)
659 log.info("Started")
661 if sys.argv[1].endswith(".def"):
662 parse_disasm_def(sys.argv[1])
663 project_name = sys.argv[1].rsplit(".", 1)[0]
664 else:
665 import default_plugins
666 for loader_id in default_plugins.loaders:
667 loader = __import__(loader_id)
668 arch_id = loader.detect(sys.argv[1])
669 if arch_id:
670 break
671 if not arch_id:
672 print("Error: file '%s' not recognized by default loaders" % sys.argv[1])
673 sys.exit(1)
674 if arch_id not in default_plugins.cpus:
675 print("Error: no plugin for CPU '%s' as detected for file '%s'" % (arch_id, sys.argv[1]))
676 sys.exit(1)
677 load_target_file(loader, sys.argv[1])
678 CPU_PLUGIN = __import__(default_plugins.cpus[arch_id])
679 project_name = sys.argv[1]
681 p = CPU_PLUGIN.PROCESSOR_ENTRY()
682 engine.set_processor(p)
683 if hasattr(p, "help_text"):
684 help.set_cpu_help(p.help_text)
686 engine.DisasmObj.LEADER_SIZE = 8 + 1
687 if show_bytes:
688 engine.DisasmObj.LEADER_SIZE += show_bytes * 2 + 1
690 # Strip suffix if any from def filename
691 project_dir = project_name + ".scratchabit"
693 if saveload.save_exists(project_dir):
694 saveload.load_state(project_dir)
695 else:
696 for label, addr in ENTRYPOINTS:
697 if engine.ADDRESS_SPACE.is_exec(addr):
698 engine.add_entrypoint(addr)
699 engine.ADDRESS_SPACE.make_unique_label(addr, label)
700 def _progress(cnt):
701 sys.stdout.write("Performing initial analysis... %d\r" % cnt)
702 engine.analyze(_progress)
703 print()
705 #engine.print_address_map()
707 addr_stack = []
708 if os.path.exists(project_dir + "/session.addr_stack"):
709 addr_stack = saveload.load_addr_stack(project_dir)
710 print(addr_stack)
711 show_addr = addr_stack.pop()
712 else:
713 if ENTRYPOINTS:
714 show_addr = ENTRYPOINTS[0][1]
715 else:
716 show_addr = engine.ADDRESS_SPACE.min_addr()
718 t = time.time()
719 #_model = engine.render()
720 _model = engine.render_partial_around(show_addr, 0, HEIGHT * 2)
721 print("Rendering time: %fs" % (time.time() - t))
722 #print(_model.lines())
723 #sys.exit()
725 Editor.init_tty()
726 try:
727 screen_size = Editor.screen_size()
728 e = Editor(1, 1, screen_size[0] - 2, screen_size[1] - 3)
729 e.cls()
730 e.enable_mouse()
731 e.draw_box(0, 0, screen_size[0], screen_size[1] - 1)
732 e.set_model(_model)
733 e.addr_stack = addr_stack
734 e.goto_addr(show_addr)
735 e.loop()
736 except:
737 log.exception("Unhandled exception")
738 raise
739 finally:
740 e.cursor(True)
741 e.deinit_tty()
742 e.wr("\n\n")
743 saveload.save_session(project_dir, e)