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/>.
32 from picotui
.widgets
import *
33 from picotui
import editorext
as editor
34 from picotui
.screen
import Screen
35 from picotui
.editorext
import Viewer
36 from picotui
.menu
import *
37 from picotui
.dialogs
import *
59 print("%08x %s" % (p
.cmd
.ea
, p
.cmd
.disasm
))
60 p
.cmd
.ea
+= p
.cmd
.size
65 def __init__(self
, f
, ctrl
):
69 def add_line(self
, addr
, line
):
70 line
= ("%08x " % addr
) + line
.indent
+ line
.render() + "\n"
72 if self
.cnt
% 256 == 0:
73 self
.ctrl
.show_status("Writing: 0x%x" % addr
)
77 class Editor(editor
.EditorExt
):
79 def __init__(self
, *args
):
80 super().__init
__(*args
)
85 def set_model(self
, model
):
87 self
.set_lines(model
.lines())
88 # Invalidate top_line. Assuming goto_*() will be called
90 self
.top_line
= sys
.maxsize
92 def show_line(self
, l
, i
):
95 if not isinstance(l
, str):
100 b
= self
.model
.AS
.get_bytes(l
.ea
, l
.size
)
101 bin
= str(binascii
.hexlify(b
[:show_bytes
]), "ascii")
102 if l
.size
> show_bytes
:
104 res
+= idaapi
.fillstr(bin
, show_bytes
* 2 + 1)
105 res
+= l
.indent
+ l
.render()
106 super().show_line(res
, i
)
108 def goto_addr(self
, to_addr
, col
=None, from_addr
=None):
110 self
.show_status("No address-like value to go to")
113 if isinstance(to_addr
, tuple):
114 to_addr
, subno
= to_addr
115 adj_addr
= self
.model
.AS
.adjust_addr_reverse(to_addr
)
117 self
.show_status("Unknown address: 0x%x" % to_addr
)
121 # If we can position cursor within current screen, do that,
123 no
= self
.model
.addr2line_no(to_addr
, subno
)
125 if self
.line_visible(no
):
126 self
.goto_line(no
, col
=col
)
127 if from_addr
is not None:
128 self
.addr_stack
.append(from_addr
)
131 # Otherwise, re-render model around needed address, and redraw screen
133 model
= engine
.render_partial_around(to_addr
, 0, HEIGHT
* 2)
134 self
.show_status("Rendering time: %fs" % (time
.time() - t
))
136 self
.show_status("Unknown address: 0x%x" % to_addr
)
138 self
.set_model(model
)
140 no
= self
.model
.addr2line_no(to_addr
, subno
)
142 if from_addr
is not None:
143 self
.addr_stack
.append(from_addr
)
144 if not self
.goto_line(no
, col
=col
):
145 # Need to redraw always, because we changed underlying model
148 self
.show_status("Unknown address: %x" % to_addr
)
150 def update_model(self
, stay_on_real
=False):
151 """Re-render model and update screen in such way that cursor stayed
152 on the same line (as far as possible).
153 stay_on_real == False - try to stay on same relative line no. for
155 stay_on_real == True - try to stay on the line which contains real
156 bytes for the current address (use this if you know that cursor
157 stayed on such line before the update).
159 addr
, subno
= self
.cur_addr_subno()
161 model
= engine
.render_partial_around(addr
, subno
, HEIGHT
* 2)
162 self
.show_status("Rendering time: %fs" % (time
.time() - t
))
163 self
.set_model(model
)
165 self
.cur_line
= model
.target_addr_lineno_real
167 self
.cur_line
= model
.target_addr_lineno
168 self
.top_line
= self
.cur_line
- self
.row
169 #log.debug("update_model: addr=%x, row=%d, cur_line=%d, top_line=%d" % (addr, self.row, self.cur_line, self.top_line))
172 def handle_cursor_keys(self
, key
):
174 if super().handle_cursor_keys(key
):
175 if self
.cur_line
== cl
:
177 #log.debug("handle_cursor_keys: cur: %d, total: %d", self.cur_line, self.total_lines)
178 if self
.cur_line
<= HEIGHT
or self
.total_lines
- self
.cur_line
<= HEIGHT
:
179 log
.debug("handle_cursor_keys: triggering update")
187 line
= self
.get_cur_line()
190 # Address of the next line. It may be the same address as the
191 # current line, as several lines may "belong" to the same address,
192 # (virtual lines like headers, etc.)
193 def next_line_addr_subno(self
):
195 l
= self
.content
[self
.cur_line
+ 1]
196 return (l
.ea
, l
.subno
)
200 # Return next address following the current line. May need to skip
203 addr
= self
.cur_addr()
204 n
= self
.cur_line
+ 1
206 while self
.content
[n
].ea
== addr
:
208 return self
.content
[n
].ea
212 def cur_addr_subno(self
):
213 line
= self
.get_cur_line()
214 return (line
.ea
, line
.subno
)
216 def cur_operand_no(self
, line
):
217 col
= self
.col
- engine
.DisasmObj
.LEADER_SIZE
- len(line
.indent
)
218 #self.show_status("Enter pressed: %s, %s" % (col, line))
219 for i
, pos
in enumerate(line
.arg_pos
):
220 if pos
[0] <= col
<= pos
[1]:
224 def analyze_status(self
, cnt
):
225 self
.show_status("Analyzing (%d insts so far)" % cnt
)
227 def expect_flags(self
, fl
, allowed_flags
):
228 if fl
not in allowed_flags
:
229 self
.show_status("Undefine first (u key)")
233 def write_func(self
, addr
):
234 func
= self
.model
.AS
.lookup_func(addr
)
236 funcname
= self
.model
.AS
.get_label(func
.start
)
237 outfile
= funcname
+ ".lst"
238 with
open(outfile
, "w") as f
:
239 model
= TextSaveModel(f
, self
)
240 for start
, end
in func
.get_ranges():
242 start
= engine
.render_from(model
, start
, 1)
246 def handle_edit_key(self
, key
):
248 return self
.handle_key_unprotected(key
)
249 except Exception as e
:
250 log
.exception("Exception processing user command")
255 self
.dialog_box(L
, T
, W
, H
)
256 v
= Viewer(L
+ 1, T
+ 1, W
- 2, H
- 2)
259 "Exception occured processing the command. Press Esc to continue.",
260 "Recommended action is saving database, quitting and comparing",
261 "database files with backup copies for possibility of data loss",
262 "or corruption. The exception was also logged to scratchabit.log.",
263 "Please report way to reproduce it to",
264 "https://github.com/pfalcon/ScratchABit/issues",
266 ] + traceback
.format_exc().splitlines())
271 def handle_key_unprotected(self
, key
):
272 line
= self
.get_cur_line()
273 if key
== editor
.KEY_ENTER
:
274 line
= self
.get_cur_line()
275 log
.info("Enter pressed: %s" % line
)
276 op_no
= self
.cur_operand_no(line
)
277 self
.show_status("Enter pressed: %s, %s" % (self
.col
, op_no
))
279 # No longer try to jump only to addresses in args, parse
280 # textual representation below
281 if False and isinstance(line
, engine
.DisasmObj
):
284 to_addr
= o
.get_addr()
286 o
= line
.get_operand_addr()
288 to_addr
= o
.get_addr()
290 pos
= self
.col
- line
.LEADER_SIZE
- len(line
.indent
)
291 word
= utils
.get_word_at_pos(line
.cache
, pos
)
293 if word
[0].isdigit():
294 to_addr
= int(word
, 0)
296 to_addr
= self
.model
.AS
.resolve_label(word
)
298 self
.show_status("Unknown address: %s" % word
)
300 self
.goto_addr(to_addr
, from_addr
=self
.cur_addr_subno())
301 elif key
== editor
.KEY_ESC
:
303 self
.show_status("Returning")
304 self
.goto_addr(self
.addr_stack
.pop())
306 return editor
.KEY_QUIT
307 elif key
== b
"\x1b[5;5~": # Ctrl+PgUp
308 self
.goto_addr(self
.model
.AS
.min_addr(), from_addr
=line
.ea
)
309 elif key
== b
"\x1b[6;5~": # Ctrl+PgDn
310 self
.goto_addr(self
.model
.AS
.max_addr(), from_addr
=line
.ea
)
312 addr
= self
.cur_addr()
313 self
.show_status("Analyzing at %x" % addr
)
314 engine
.add_entrypoint(addr
, False)
315 engine
.analyze(self
.analyze_status
)
318 addr
= self
.cur_addr()
319 fl
= self
.model
.AS
.get_flags(addr
)
320 if not self
.expect_flags(fl
, (self
.model
.AS
.DATA
, self
.model
.AS
.UNK
)):
322 if fl
== self
.model
.AS
.UNK
:
323 self
.model
.AS
.set_flags(addr
, 1, self
.model
.AS
.DATA
, self
.model
.AS
.DATA_CONT
)
325 sz
= self
.model
.AS
.get_unit_size(addr
)
326 self
.model
.undefine_unit(addr
)
329 self
.model
.AS
.set_flags(addr
, sz
, self
.model
.AS
.DATA
, self
.model
.AS
.DATA_CONT
)
332 addr
= self
.cur_addr()
333 fl
= self
.model
.AS
.get_flags(addr
)
334 if not self
.expect_flags(fl
, (self
.model
.AS
.DATA
, self
.model
.AS
.UNK
)):
339 b
= self
.model
.AS
.get_byte(addr
)
340 fl
= self
.model
.AS
.get_flags(addr
)
341 if not (0x20 <= b
<= 0x7e or b
in (0x0a, 0x0d)):
345 if fl
not in (self
.model
.AS
.UNK
, self
.model
.AS
.DATA
, self
.model
.AS
.DATA_CONT
):
348 if c
< '0' or c
in string
.punctuation
:
354 self
.model
.AS
.set_flags(self
.cur_addr(), sz
, self
.model
.AS
.STR
, self
.model
.AS
.DATA_CONT
)
355 self
.model
.AS
.make_unique_label(self
.cur_addr(), label
)
358 addr
= self
.cur_addr()
359 fl
= self
.model
.AS
.get_flags(addr
)
360 if not self
.expect_flags(fl
, (self
.model
.AS
.UNK
,)):
366 fl
= self
.model
.AS
.get_flags(addr
)
367 except engine
.InvalidAddrException
:
369 if fl
!= self
.model
.AS
.UNK
:
371 b
= self
.model
.AS
.get_byte(addr
)
372 if b
not in (0, 0xff):
373 self
.show_status("Filler must consist of 0x00 or 0xff")
378 self
.model
.AS
.make_filler(self
.cur_addr(), sz
)
382 addr
= self
.cur_addr()
383 self
.model
.undefine_unit(addr
)
387 op_no
= self
.cur_operand_no(self
.get_cur_line())
389 addr
= self
.cur_addr()
390 subtype
= self
.model
.AS
.get_arg_prop(addr
, op_no
, "subtype")
391 if subtype
!= engine
.IMM_ADDR
:
393 engine
.IMM_UHEX
: engine
.IMM_UDEC
,
394 engine
.IMM_UDEC
: engine
.IMM_UHEX
,
396 self
.model
.AS
.set_arg_prop(addr
, op_no
, "subtype", next_subtype
[subtype
])
398 self
.show_status("Changed arg #%d to %s" % (op_no
, next_subtype
[subtype
]))
400 addr
= self
.cur_addr()
401 line
= self
.get_cur_line()
402 o
= line
.get_operand_addr()
404 self
.show_status("Cannot convert operand to offset")
406 if o
.type != idaapi
.o_imm
or not self
.model
.AS
.is_valid_addr(o
.get_addr()):
407 self
.show_status("Cannot convert operand to offset: #%s: %s" % (o
.n
, o
.type))
410 if self
.model
.AS
.get_arg_prop(addr
, o
.n
, "subtype") == engine
.IMM_ADDR
:
411 self
.model
.AS
.unmake_arg_offset(addr
, o
.n
, o
.get_addr())
413 self
.model
.AS
.make_arg_offset(addr
, o
.n
, o
.get_addr())
414 self
.update_model(True)
416 addr
= self
.cur_addr()
417 comment
= self
.model
.AS
.get_comment(addr
) or ""
418 res
= DMultiEntry(60, 5, comment
.split("\n"), title
="Comment:").result()
419 if res
!= ACTION_CANCEL
:
420 res
= "\n".join(res
).rstrip("\n")
421 self
.model
.AS
.set_comment(addr
, res
)
426 addr
= self
.cur_addr()
427 label
= self
.model
.AS
.get_label(addr
)
428 def_label
= self
.model
.AS
.get_default_label(addr
)
429 s
= label
or def_label
431 res
= DTextEntry(30, s
, title
="New label:").result()
437 if self
.model
.AS
.label_exists(res
):
439 self
.show_status("Duplicate label")
441 self
.model
.AS
.set_label(addr
, res
)
443 # If it's new label, we need to add it to model
449 d
= Dialog(4, 4, title
="Go to")
450 d
.add(1, 1, WLabel("Label/addr:"))
451 entry
= WAutoComplete(20, "", self
.model
.AS
.get_label_list())
453 entry
.finish_dialog
= ACTION_OK
455 d
.add(1, 2, WLabel("Press Down to auto-complete"))
460 value
= entry
.get_text()
461 if '0' <= value
[0] <= '9':
464 addr
= self
.model
.AS
.resolve_label(value
)
465 self
.goto_addr(addr
, from_addr
=self
.cur_addr())
467 elif key
== editor
.KEY_F1
:
471 self
.show_status("Saving...")
472 saveload
.save_state(project_dir
)
473 self
.show_status("Saved.")
474 elif key
== b
"\x11": # ^Q
475 class IssueList(WListBox
):
476 def render_line(self
, l
):
478 d
= Dialog(4, 4, title
="Problems list")
479 lw
= IssueList(40, 16, self
.model
.AS
.get_issues())
480 lw
.finish_dialog
= ACTION_OK
485 val
= lw
.get_cur_line()
487 self
.goto_addr(val
[0], from_addr
=self
.cur_addr())
490 off
, area
= self
.model
.AS
.addr2area(self
.cur_addr())
491 props
= area
[engine
.PROPS
]
492 percent
= 100 * off
/ (area
[engine
.END
] - area
[engine
.START
] + 1)
493 func
= self
.model
.AS
.lookup_func(self
.cur_addr())
494 func
= self
.model
.AS
.get_label(func
.start
) if func
else None
495 self
.show_status("Area: 0x%x %s (%s): %.1f%%, func: %s" % (
496 area
[engine
.START
], props
.get("name", "noname"), props
["access"], percent
, func
503 self
.dialog_box(L
, T
, W
, H
)
504 v
= Viewer(L
+ 1, T
+ 1, W
- 2, H
- 2)
506 for area
in self
.model
.AS
.get_areas():
507 props
= area
[engine
.PROPS
]
508 lines
.append("%08x-%08x %s:" % (area
[engine
.START
], area
[engine
.END
], props
.get("name", "noname")))
509 flags
= area
[engine
.FLAGS
]
512 for i
in range(len(flags
)):
513 if i
% 64 == 0 and l
:
516 c
= engine
.flag2char(flags
[i
])
517 # For "function's instructions", make continuation byte be
518 # clearly distinguishable too.
519 if c
== "c" and last_capital
== "F":
530 out_fname
= "out.lst"
531 with
open(out_fname
, "w") as f
:
532 engine
.render_partial(TextSaveModel(f
, self
), 0, 0, 10000000)
533 self
.show_status("Disassembly listing written: " + out_fname
)
534 elif key
== b
"\x17": # Ctrl+W
535 outfile
= self
.write_func(self
.cur_addr())
537 self
.show_status("Wrote file: %s" % outfile
)
538 elif key
== b
"\x15": # Ctrl+U
540 addr
= self
.cur_addr()
541 flags
= self
.model
.AS
.get_flags(addr
)
542 if flags
== self
.model
.AS
.UNK
:
543 # If already on undefined, skip the current stride of them,
544 # as they indeed go in batches.
546 flags
= self
.model
.AS
.get_flags(addr
)
547 if flags
!= self
.model
.AS
.UNK
:
549 addr
= self
.model
.AS
.next_addr(addr
)
555 flags
= self
.model
.AS
.get_flags(addr
)
556 if flags
== self
.model
.AS
.UNK
:
557 self
.goto_addr(addr
, from_addr
=self
.cur_addr())
559 addr
= self
.model
.AS
.next_addr(addr
)
564 self
.show_status("There're no further undefined strides")
566 elif key
in (b
"/", b
"?"): # "/" and Shift+"/"
568 class FoundException(Exception): pass
570 class TextSearchModel(engine
.Model
):
571 def __init__(self
, substr
, ctrl
, this_addr
, this_subno
):
575 self
.this_addr
= this_addr
576 self
.this_subno
= this_subno
578 def add_line(self
, addr
, line
):
579 super().add_line(addr
, line
)
580 # Skip virtual lines before the line from which we started
581 if addr
== self
.this_addr
and line
.subno
< self
.this_subno
:
584 idx
= txt
.find(self
.search
)
586 raise FoundException((addr
, line
.subno
), idx
+ line
.LEADER_SIZE
+ len(line
.indent
))
587 if self
.cnt
% 256 == 0:
588 self
.ctrl
.show_status("Searching: 0x%x" % addr
)
590 # Don't accumulate lines
595 d
= Dialog(4, 4, title
="Text Search")
596 d
.add(1, 1, WLabel("Search for:"))
597 entry
= WTextEntry(20, self
.search_str
)
598 entry
.finish_dialog
= ACTION_OK
602 self
.search_str
= entry
.get_text()
603 if res
!= ACTION_OK
or not self
.search_str
:
605 addr
, subno
= self
.cur_addr_subno()
607 addr
, subno
= self
.next_line_addr_subno()
610 engine
.render_from(TextSearchModel(self
.search_str
, self
, addr
, subno
), addr
, 10000000)
611 except FoundException
as res
:
612 self
.goto_addr(res
.args
[0], col
=res
.args
[1], from_addr
=self
.cur_addr())
614 self
.show_status("Not found: " + self
.search_str
)
616 elif key
== MENU_PREFS
:
619 elif key
== MENU_SCRIPT
:
620 res
= DTextEntry(30, "", title
="Script module name:").result()
623 self
.show_status("Script '%s' run successfully" % res
)
628 self
.show_status("Unbound key: " + repr(key
))
635 def filter_config_line(l
):
636 l
= re
.sub(r
"#.*$", "", l
)
640 def load_symbols(fname
):
641 with
open(fname
) as f
:
643 l
= filter_config_line(l
)
646 m
= re
.search(r
"\b([A-Za-z_$.][A-Za-z0-9_$.]*)\s*=\s*((0x)?[0-9A-Fa-f]+)", l
)
649 ENTRYPOINTS
.append((m
.group(1), int(m
.group(2), 0)))
651 print("Warning: cannot parse entrypoint info from: %r" % l
)
653 def parse_entrypoints(f
):
655 l
= filter_config_line(l
)
660 m
= re
.match(r
'load "(.+?)"', l
)
662 load_symbols(m
.group(1))
664 label
, addr
= [v
.strip() for v
in l
.split("=")]
665 ENTRYPOINTS
.append((label
, int(addr
, 0)))
669 def load_target_file(loader
, fname
):
670 entry
= loader
.load(engine
.ADDRESS_SPACE
, fname
)
671 log
.info("Loaded %s, entrypoint: %s", fname
, hex(entry
) if entry
is not None else None)
672 if entry
is not None:
673 ENTRYPOINTS
.append(("_ENTRY_", entry
))
676 def parse_disasm_def(fname
):
679 with
open(fname
) as f
:
681 l
= filter_config_line(l
)
691 print("Processing section: %s" % section
)
692 if section
== "entrypoints":
693 l
= parse_entrypoints(f
)
695 assert 0, "Unknown section: " + section
702 if l
.startswith("load"):
704 if args
[2][0] in string
.digits
:
705 addr
= int(args
[2], 0)
706 print("Loading %s @0x%x" % (args
[1], addr
))
707 engine
.ADDRESS_SPACE
.load_content(open(args
[1], "rb"), addr
)
709 print("Loading %s (%s plugin)" % (args
[1], args
[2]))
710 loader
= __import__(args
[2])
711 load_target_file(loader
, args
[1])
712 elif l
.startswith("cpu "):
714 CPU_PLUGIN
= __import__(args
[1])
715 print("Loading CPU plugin %s" % (args
[1]))
716 elif l
.startswith("show bytes "):
718 show_bytes
= int(args
[2])
719 elif l
.startswith("area "):
721 assert len(args
) == 4
723 # Allow undescores to separate digit groups
725 return int(s
.replace("_", ""), 0)
728 m
= re
.match(r
"(.+?)\s*\(\s*(.+?)\s*\)", args
[2])
729 start
= str2int(m
.group(1))
730 end
= start
+ str2int(m
.group(2)) - 1
732 m
= re
.match(r
"(.+)\s*-\s*(.+)", args
[2])
733 start
= str2int(m
.group(1))
734 end
= str2int(m
.group(2))
736 a
= engine
.ADDRESS_SPACE
.add_area(start
, end
, {"name": args
[1], "access": args
[3].upper()})
737 print("Adding area: %s" % engine
.str_area(a
))
739 assert 0, "Unknown directive: " + l
745 self
.screen_size
= Screen
.screen_size()
746 self
.e
= Editor(1, 2, self
.screen_size
[0] - 2, self
.screen_size
[1] - 4)
748 menu_file
= WMenuBox([
749 ("Save (Shift+s)", b
"S"), ("Write disasm (Shift+w)", b
"W"),
750 ("Write function (Ctrl+w)", b
"\x17"),
753 menu_goto
= WMenuBox([
754 ("Follow (Enter)", KEY_ENTER
), ("Return (Esc)", KEY_ESC
),
755 ("Goto... (g)", b
"g"), ("Search disasm... (/)", b
"/"),
756 ("Search next (Shift+/)", b
"?"), ("Next undefined (Ctrl+u)", b
"\x15"),
758 menu_edit
= WMenuBox([
759 ("Undefined (u)", b
"u"), ("Code (c)", b
"c"), ("Data (d)", b
"d"),
760 ("ASCII String (a)", b
"a"), ("Filler (f)", b
"f"), ("Make label (n)", b
"n"),
761 ("Number/Address (o)", b
"o"), ("Hex/dec (h)", b
"h"),
763 menu_analysis
= WMenuBox([
764 ("Info (whereami) (i)", b
"i"), ("Memory map (Shift+i)", b
"I"),
765 ("Run script...", MENU_SCRIPT
),
766 ("Preferences...", MENU_PREFS
),
768 menu_help
= WMenuBox([
769 ("Help (F1)", KEY_F1
), ("About...", "about"),
771 self
.menu_bar
= WMenuBar([
772 ("File", menu_file
), ("Goto", menu_goto
), ("Edit", menu_edit
),
773 ("Analysis", menu_analysis
), ("Help", menu_help
)
775 self
.menu_bar
.permanent
= True
778 self
.menu_bar
.redraw()
779 self
.e
.draw_box(0, 1, self
.screen_size
[0], self
.screen_size
[1] - 2)
784 key
= self
.e
.get_input()
785 if isinstance(key
, list):
787 if self
.menu_bar
.inside(x
, y
):
788 self
.menu_bar
.focus
= True
790 if self
.menu_bar
.focus
:
791 res
= self
.menu_bar
.handle_input(key
)
792 if res
== ACTION_CANCEL
:
793 self
.menu_bar
.focus
= False
794 elif res
is not None and res
is not True:
796 res
= self
.e
.handle_input(res
)
797 if res
is not None and res
is not True:
801 self
.menu_bar
.focus
= True
802 self
.menu_bar
.redraw()
804 res
= self
.e
.handle_input(key
)
805 if res
is not None and res
is not True:
809 def call_script(script
):
810 mod
= __import__(script
)
811 main_f
= getattr(mod
, "main", None)
816 if __name__
== "__main__":
818 argp
= argparse
.ArgumentParser(description
="ScratchABit interactive disassembler")
819 argp
.add_argument("file", help="Input file (binary or disassembly .def)")
820 argp
.add_argument("--script", action
="append", help="Run script from file after loading environment")
821 argp
.add_argument("--save", action
="store_true", help="Save after --script and quit; don't show UI")
822 args
= argp
.parse_args()
824 # Plugin dirs are relative to the dir where scratchabit.py resides.
825 # sys.path[0] below provide absolute path of this dir, resolved for
827 plugin_dirs
= ["plugins", "plugins/cpu", "plugins/loader"]
828 for d
in plugin_dirs
:
829 sys
.path
.append(os
.path
.join(sys
.path
[0], d
))
830 log
.basicConfig(filename
="scratchabit.log", format
='%(asctime)s %(message)s', level
=log
.DEBUG
)
833 if args
.file.endswith(".def"):
834 parse_disasm_def(args
.file)
835 project_name
= args
.file.rsplit(".", 1)[0]
837 import default_plugins
838 for loader_id
in default_plugins
.loaders
:
839 loader
= __import__(loader_id
)
840 arch_id
= loader
.detect(args
.file)
844 print("Error: file '%s' not recognized by default loaders" % args
.file)
846 if arch_id
not in default_plugins
.cpus
:
847 print("Error: no plugin for CPU '%s' as detected for file '%s'" % (arch_id
, args
.file))
849 load_target_file(loader
, args
.file)
850 CPU_PLUGIN
= __import__(default_plugins
.cpus
[arch_id
])
851 project_name
= args
.file
853 p
= CPU_PLUGIN
.PROCESSOR_ENTRY()
854 if hasattr(p
, "config"):
856 engine
.set_processor(p
)
857 if hasattr(p
, "help_text"):
858 help.set_cpu_help(p
.help_text
)
860 APP
.aspace
= engine
.ADDRESS_SPACE
863 engine
.DisasmObj
.LEADER_SIZE
= 8 + 1
865 engine
.DisasmObj
.LEADER_SIZE
+= show_bytes
* 2 + 1
867 # Strip suffix if any from def filename
868 project_dir
= project_name
+ ".scratchabit"
870 if saveload
.save_exists(project_dir
):
871 saveload
.load_state(project_dir
)
873 for label
, addr
in ENTRYPOINTS
:
874 if engine
.ADDRESS_SPACE
.is_exec(addr
):
875 engine
.add_entrypoint(addr
)
876 engine
.ADDRESS_SPACE
.make_unique_label(addr
, label
)
878 sys
.stdout
.write("Performing initial analysis... %d\r" % cnt
)
879 engine
.analyze(_progress
)
882 #engine.print_address_map()
885 for script
in args
.script
:
888 saveload
.save_state(project_dir
)
892 if os
.path
.exists(project_dir
+ "/session.addr_stack"):
893 addr_stack
= saveload
.load_addr_stack(project_dir
)
895 show_addr
= addr_stack
.pop()
898 show_addr
= ENTRYPOINTS
[0][1]
900 show_addr
= engine
.ADDRESS_SPACE
.min_addr()
903 #_model = engine.render()
904 _model
= engine
.render_partial_around(show_addr
, 0, HEIGHT
* 2)
905 print("Rendering time: %fs" % (time
.time() - t
))
906 #print(_model.lines())
912 Screen
.enable_mouse()
913 main_screen
= MainScreen()
914 APP
.main_screen
= main_screen
916 main_screen
.e
.set_model(_model
)
917 main_screen
.e
.addr_stack
= addr_stack
918 main_screen
.e
.goto_addr(show_addr
)
919 Screen
.set_screen_redraw(main_screen
.redraw
)
923 log
.exception("Unhandled exception")
926 Screen
.goto(0, main_screen
.screen_size
[1])
928 Screen
.disable_mouse()
931 saveload
.save_session(project_dir
, main_screen
.e
)