|
|
1.1 ! root 1: #!/usr/bin/env python ! 2: # ! 3: # A Debug UI for the Hatari, part of PyGtk Hatari UI ! 4: # ! 5: # Copyright (C) 2008 by Eero Tamminen <[email protected]> ! 6: # ! 7: # This program is free software; you can redistribute it and/or modify ! 8: # it under the terms of the GNU General Public License as published by ! 9: # the Free Software Foundation; either version 2 of the License, or ! 10: # (at your option) any later version. ! 11: # ! 12: # This program is distributed in the hope that it will be useful, ! 13: # but WITHOUT ANY WARRANTY; without even the implied warranty of ! 14: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 15: # GNU General Public License for more details. ! 16: ! 17: import os ! 18: # use correct version of pygtk/gtk ! 19: import pygtk ! 20: pygtk.require('2.0') ! 21: import gtk ! 22: import pango ! 23: ! 24: from config import ConfigStore ! 25: from uihelpers import UInfo, create_button, create_toggle, \ ! 26: create_table_dialog, table_add_entry_row, table_add_widget_row, \ ! 27: get_save_filename, FselEntry ! 28: from dialogs import TodoDialog, ErrorDialog, AskDialog, KillDialog ! 29: ! 30: ! 31: def dialog_apply_cb(widget, dialog): ! 32: dialog.response(gtk.RESPONSE_APPLY) ! 33: ! 34: ! 35: # ------------- ! 36: # Table dialogs ! 37: ! 38: class SaveDialog: ! 39: def __init__(self, parent): ! 40: table, self.dialog = create_table_dialog(parent, "Save from memory", 3) ! 41: self.file = FselEntry(self.dialog) ! 42: table_add_widget_row(table, 0, "File name:", self.file.get_container()) ! 43: self.address = table_add_entry_row(table, 1, "Save address:", 6) ! 44: self.address.connect("activate", dialog_apply_cb, self.dialog) ! 45: self.length = table_add_entry_row(table, 2, "Number of bytes:", 6) ! 46: self.length.connect("activate", dialog_apply_cb, self.dialog) ! 47: ! 48: def run(self, address): ! 49: "run(address) -> (filename,address,length), all as strings" ! 50: if address: ! 51: self.address.set_text("%06X" % address) ! 52: self.dialog.show_all() ! 53: filename = length = None ! 54: while 1: ! 55: response = self.dialog.run() ! 56: if response == gtk.RESPONSE_APPLY: ! 57: filename = self.file.get_filename() ! 58: address_txt = self.address.get_text() ! 59: length_txt = self.length.get_text() ! 60: if filename and address_txt and length_txt: ! 61: try: ! 62: address = int(address_txt, 16) ! 63: except ValueError: ! 64: ErrorDialog(self.dialog).run("address needs to be in hex") ! 65: continue ! 66: try: ! 67: length = int(length_txt) ! 68: except ValueError: ! 69: ErrorDialog(self.dialog).run("length needs to be a number") ! 70: continue ! 71: if os.path.exists(filename): ! 72: question = "File:\n%s\nexists, replace?" % filename ! 73: if not AskDialog(self.dialog).run(question): ! 74: continue ! 75: break ! 76: else: ! 77: ErrorDialog(self.dialog).run("please fill the field(s)") ! 78: else: ! 79: break ! 80: self.dialog.hide() ! 81: return (filename, address, length) ! 82: ! 83: ! 84: class LoadDialog: ! 85: def __init__(self, parent): ! 86: chooser = gtk.FileChooserButton('Select a File') ! 87: chooser.set_local_only(True) # Hatari cannot access URIs ! 88: chooser.set_width_chars(12) ! 89: table, self.dialog = create_table_dialog(parent, "Load to memory", 2) ! 90: self.file = table_add_widget_row(table, 0, "File name:", chooser) ! 91: self.address = table_add_entry_row(table, 1, "Load address:", 6) ! 92: self.address.connect("activate", dialog_apply_cb, self.dialog) ! 93: ! 94: def run(self, address): ! 95: "run(address) -> (filename,address), all as strings" ! 96: if address: ! 97: self.address.set_text("%06X" % address) ! 98: self.dialog.show_all() ! 99: filename = None ! 100: while 1: ! 101: response = self.dialog.run() ! 102: if response == gtk.RESPONSE_APPLY: ! 103: filename = self.file.get_filename() ! 104: address_txt = self.address.get_text() ! 105: if filename and address_txt: ! 106: try: ! 107: address = int(address_txt, 16) ! 108: except ValueError: ! 109: ErrorDialog(self.dialog).run("address needs to be in hex") ! 110: continue ! 111: break ! 112: else: ! 113: ErrorDialog(self.dialog).run("please fill the field(s)") ! 114: else: ! 115: break ! 116: self.dialog.hide() ! 117: return (filename, address) ! 118: ! 119: ! 120: class OptionsDialog: ! 121: def __init__(self, parent): ! 122: table, self.dialog = create_table_dialog(parent, "Debugger UI options", 1) ! 123: self.lines = table_add_entry_row(table, 0, "Memdump/disasm lines:", 2) ! 124: self.lines.connect("activate", dialog_apply_cb, self.dialog) ! 125: ! 126: def run(self, lines): ! 127: "run(lines) -> lines, as integers" ! 128: self.lines.set_text(str(lines)) ! 129: self.dialog.show_all() ! 130: while 1: ! 131: lines = None ! 132: response = self.dialog.run() ! 133: if response == gtk.RESPONSE_APPLY: ! 134: text = self.lines.get_text() ! 135: if text: ! 136: try: ! 137: lines = int(text) ! 138: except ValueError: ! 139: ErrorDialog(self.dialog).run("lines needs an integer number") ! 140: continue ! 141: break ! 142: else: ! 143: ErrorDialog(self.dialog).run("please fill the field(s)") ! 144: else: ! 145: break ! 146: self.dialog.hide() ! 147: return lines ! 148: ! 149: ! 150: # ---------------------------------------------------- ! 151: ! 152: # constants for the other classes ! 153: class Constants: ! 154: # dump modes ! 155: DISASM = 1 ! 156: MEMDUMP = 2 ! 157: REGISTERS = 3 ! 158: # move IDs ! 159: MOVE_MIN = 1 ! 160: MOVE_MED = 2 ! 161: MOVE_MAX = 3 ! 162: ! 163: ! 164: # class for the memory address entry, view (label) and ! 165: # the logic for memory dump modes and moving in memory ! 166: class MemoryAddress: ! 167: # class variables ! 168: debug_output = None ! 169: hatari = None ! 170: ! 171: def __init__(self, hatariobj): ! 172: # hatari ! 173: self.debug_output = hatariobj.open_debug_output() ! 174: self.hatari = hatariobj ! 175: # widgets ! 176: self.entry, self.memory = self.create_widgets() ! 177: # settings ! 178: self.dumpmode = Constants.REGISTERS ! 179: self.lines = 12 ! 180: # addresses ! 181: self.first = None ! 182: self.second = None ! 183: self.last = None ! 184: ! 185: def clear(self): ! 186: self.first = None ! 187: self.second = None ! 188: self.last = None ! 189: ! 190: def create_widgets(self): ! 191: entry = gtk.Entry(6) ! 192: entry.set_width_chars(6) ! 193: entry.connect("activate", self._entry_cb) ! 194: memory = gtk.Label() ! 195: mono = pango.FontDescription("monospace") ! 196: memory.modify_font(mono) ! 197: entry.modify_font(mono) ! 198: return (entry, memory) ! 199: ! 200: def _entry_cb(self, widget): ! 201: try: ! 202: address = int(widget.get_text(), 16) ! 203: except ValueError: ! 204: ErrorDialog(widget.get_toplevel()).run("invalid address") ! 205: return ! 206: self.dump(address) ! 207: ! 208: def reset_entry(self): ! 209: self.entry.set_text("%06X" % self.first) ! 210: ! 211: def get(self): ! 212: return self.first ! 213: ! 214: def get_memory_label(self): ! 215: return self.memory ! 216: ! 217: def get_address_entry(self): ! 218: return self.entry ! 219: ! 220: def get_lines(self): ! 221: return self.lines ! 222: ! 223: def set_lines(self, lines): ! 224: self.lines = lines ! 225: ! 226: def set_dumpmode(self, mode): ! 227: self.dumpmode = mode ! 228: self.dump() ! 229: ! 230: def dump(self, address = None, move_idx = 0): ! 231: if not address: ! 232: address = self.first ! 233: ! 234: if self.dumpmode == Constants.REGISTERS: ! 235: output = self._get_registers() ! 236: self.memory.set_label("".join(output)) ! 237: return ! 238: ! 239: if not address: ! 240: print "ERROR: address needed" ! 241: return ! 242: ! 243: if self.dumpmode == Constants.MEMDUMP: ! 244: output = self._get_memdump(address, move_idx) ! 245: elif self.dumpmode == Constants.DISASM: ! 246: output = self._get_disasm(address, move_idx) ! 247: else: ! 248: print "ERROR: unknown dumpmode:", self.dumpmode ! 249: return ! 250: self.memory.set_label("".join(output)) ! 251: if move_idx: ! 252: self.reset_entry() ! 253: ! 254: def _get_registers(self): ! 255: self.hatari.debug_command("r") ! 256: output = self.hatari.get_lines(self.debug_output) ! 257: if not self.first: ! 258: # second last line has first PC, last line next PC in next column ! 259: self.first = int(output[-2][:output[-2].find(":")], 16) ! 260: self.second = int(output[-1][output[-1].find(":")+2:], 16) ! 261: self.reset_entry() ! 262: return output ! 263: ! 264: def _get_memdump(self, address, move_idx): ! 265: linewidth = 16 ! 266: screenful = self.lines*linewidth ! 267: # no move, left/right, up/down, page up/down (no overlap) ! 268: offsets = [0, 2, linewidth, screenful] ! 269: offset = offsets[abs(move_idx)] ! 270: if move_idx < 0: ! 271: address -= offset ! 272: else: ! 273: address += offset ! 274: self._set_clamped(address, address+screenful) ! 275: self.hatari.debug_command("m %06x-%06x" % (self.first, self.last)) ! 276: # get & set debugger command results ! 277: output = self.hatari.get_lines(self.debug_output) ! 278: self.second = address + linewidth ! 279: return output ! 280: ! 281: def _get_disasm(self, address, move_idx): ! 282: # TODO: uses brute force i.e. ask for more lines that user has ! 283: # requested to be sure that the window is filled, assuming ! 284: # 6 bytes is largest possible instruction+args size ! 285: # (I don't remember anymore my m68k asm...) ! 286: screenful = 6*self.lines ! 287: # no move, left/right, up/down, page up/down ! 288: offsets = [0, 2, 4, screenful] ! 289: offset = offsets[abs(move_idx)] ! 290: # force one line of overlap in page up/down ! 291: if move_idx < 0: ! 292: address -= offset ! 293: if address < 0: ! 294: address = 0 ! 295: if move_idx == -Constants.MOVE_MAX and self.second: ! 296: screenful = self.second - address ! 297: else: ! 298: if move_idx == Constants.MOVE_MED and self.second: ! 299: address = self.second ! 300: elif move_idx == Constants.MOVE_MAX and self.last: ! 301: address = self.last ! 302: else: ! 303: address += offset ! 304: self._set_clamped(address, address+screenful) ! 305: self.hatari.debug_command("d %06x-%06x" % (self.first, self.last)) ! 306: # get & set debugger command results ! 307: output = self.hatari.get_lines(self.debug_output) ! 308: # cut output to desired length and check new addresses ! 309: if len(output) > self.lines: ! 310: if move_idx < 0: ! 311: output = output[-self.lines:] ! 312: else: ! 313: output = output[:self.lines] ! 314: # with disasm need to re-get the addresses from the output ! 315: self.first = int(output[0][:output[0].find(":")], 16) ! 316: self.second = int(output[1][:output[1].find(":")], 16) ! 317: self.last = int(output[-1][:output[-1].find(":")], 16) ! 318: return output ! 319: ! 320: def _set_clamped(self, first, last): ! 321: "set_clamped(first,last), clamp addresses to valid address range and set them" ! 322: assert(first < last) ! 323: if first < 0: ! 324: last = last-first ! 325: first = 0 ! 326: if last > 0xffffff: ! 327: first = 0xffffff - (last-first) ! 328: last = 0xffffff ! 329: self.first = first ! 330: self.last = last ! 331: ! 332: ! 333: # the Hatari debugger UI class and methods ! 334: class HatariDebugUI: ! 335: ! 336: def __init__(self, hatariobj, do_destroy = False): ! 337: self.address = MemoryAddress(hatariobj) ! 338: self.hatari = hatariobj ! 339: # set when needed/created ! 340: self.dialog_load = None ! 341: self.dialog_save = None ! 342: self.dialog_options = None ! 343: # set when UI created ! 344: self.keys = None ! 345: self.stop_button = None ! 346: # set on option load ! 347: self.config = None ! 348: self.load_options() ! 349: # UI initialization/creation ! 350: self.window = self.create_ui("Hatari Debug UI", do_destroy) ! 351: ! 352: def create_ui(self, title, do_destroy): ! 353: # buttons at top ! 354: hbox1 = gtk.HBox() ! 355: self.create_top_buttons(hbox1) ! 356: ! 357: # disasm/memory dump at the middle ! 358: align = gtk.Alignment() ! 359: # top, bottom, left, right padding ! 360: align.set_padding(8, 0, 8, 8) ! 361: align.add(self.address.get_memory_label()) ! 362: ! 363: # buttons at bottom ! 364: hbox2 = gtk.HBox() ! 365: self.create_bottom_buttons(hbox2) ! 366: ! 367: # their container ! 368: vbox = gtk.VBox() ! 369: vbox.pack_start(hbox1, False) ! 370: vbox.pack_start(align, True, True) ! 371: vbox.pack_start(hbox2, False) ! 372: ! 373: # and the window for all of this ! 374: window = gtk.Window(gtk.WINDOW_TOPLEVEL) ! 375: window.set_events(gtk.gdk.KEY_RELEASE_MASK) ! 376: window.connect("key_release_event", self.key_event_cb) ! 377: if do_destroy: ! 378: window.connect("delete_event", self.quit) ! 379: else: ! 380: window.connect("delete_event", self.hide) ! 381: window.set_icon_from_file(UInfo.icon) ! 382: window.set_title(title) ! 383: window.add(vbox) ! 384: return window ! 385: ! 386: def create_top_buttons(self, box): ! 387: self.stop_button = create_toggle("Stop", self.stop_cb) ! 388: box.add(self.stop_button) ! 389: ! 390: monitor = create_button("Monitor...", self.monitor_cb) ! 391: box.add(monitor) ! 392: ! 393: buttons = ( ! 394: ("<<<", "Page_Up", -Constants.MOVE_MAX), ! 395: ("<<", "Up", -Constants.MOVE_MED), ! 396: ("<", "Left", -Constants.MOVE_MIN), ! 397: (">", "Right", Constants.MOVE_MIN), ! 398: (">>", "Down", Constants.MOVE_MED), ! 399: (">>>", "Page_Down", Constants.MOVE_MAX) ! 400: ) ! 401: self.keys = {} ! 402: for label, keyname, offset in buttons: ! 403: button = create_button(label, self.set_address_offset, offset) ! 404: keyval = gtk.gdk.keyval_from_name(keyname) ! 405: self.keys[keyval] = offset ! 406: box.add(button) ! 407: ! 408: # to middle of <<>> buttons ! 409: address_entry = self.address.get_address_entry() ! 410: box.pack_start(address_entry, False) ! 411: box.reorder_child(address_entry, 5) ! 412: ! 413: def create_bottom_buttons(self, box): ! 414: radios = ( ! 415: ("Registers", Constants.REGISTERS), ! 416: ("Memdump", Constants.MEMDUMP), ! 417: ("Disasm", Constants.DISASM) ! 418: ) ! 419: group = None ! 420: for label, mode in radios: ! 421: button = gtk.RadioButton(group, label) ! 422: if not group: ! 423: group = button ! 424: button.connect("toggled", self.dumpmode_cb, mode) ! 425: button.unset_flags(gtk.CAN_FOCUS) ! 426: box.add(button) ! 427: group.set_active(True) ! 428: ! 429: dialogs = ( ! 430: ("Memload...", self.memload_cb), ! 431: ("Memsave...", self.memsave_cb), ! 432: ("Options...", self.options_cb) ! 433: ) ! 434: for label, cb in dialogs: ! 435: button = create_button(label, cb) ! 436: box.add(button) ! 437: ! 438: def stop_cb(self, widget): ! 439: if widget.get_active(): ! 440: self.hatari.pause() ! 441: self.address.clear() ! 442: self.address.dump() ! 443: else: ! 444: self.hatari.unpause() ! 445: ! 446: def dumpmode_cb(self, widget, mode): ! 447: if widget.get_active(): ! 448: self.address.set_dumpmode(mode) ! 449: ! 450: def key_event_cb(self, widget, event): ! 451: if event.keyval in self.keys: ! 452: self.address.dump(None, self.keys[event.keyval]) ! 453: ! 454: def set_address_offset(self, widget, move_idx): ! 455: self.address.dump(None, move_idx) ! 456: ! 457: def monitor_cb(self, widget): ! 458: TodoDialog(self.window).run("add register / memory address range monitor window.") ! 459: ! 460: def memload_cb(self, widget): ! 461: if not self.dialog_load: ! 462: self.dialog_load = LoadDialog(self.window) ! 463: (filename, address) = self.dialog_load.run(self.address.get()) ! 464: if filename and address: ! 465: self.hatari.debug_command("l %s %06x" % (filename, address)) ! 466: ! 467: def memsave_cb(self, widget): ! 468: if not self.dialog_save: ! 469: self.dialog_save = SaveDialog(self.window) ! 470: (filename, address, length) = self.dialog_save.run(self.address.get()) ! 471: if filename and address and length: ! 472: self.hatari.debug_command("s %s %06x %06x" % (filename, address, length)) ! 473: ! 474: def options_cb(self, widget): ! 475: if not self.dialog_options: ! 476: self.dialog_options = OptionsDialog(self.window) ! 477: lines = self.dialog_options.run(self.config.get("[General]", "nLines")) ! 478: if lines: ! 479: self.config.set("[General]", "nLines", lines) ! 480: self.address.set_lines(lines) ! 481: ! 482: def load_options(self): ! 483: # TODO: move config to MemoryAddress class? ! 484: # (depends on how monitoring of addresses should work) ! 485: lines = self.address.get_lines() ! 486: miss_is_error = False # needed for adding windows ! 487: defaults = { "[General]": {"nLines": lines} } ! 488: self.config = ConfigStore("debugui.cfg", defaults, miss_is_error) ! 489: self.address.set_lines(self.config.get("[General]", "nLines")) ! 490: ! 491: def save_options(self): ! 492: self.config.save() ! 493: ! 494: def show(self): ! 495: self.stop_button.set_active(True) ! 496: self.window.show_all() ! 497: self.window.deiconify() ! 498: ! 499: def hide(self, widget, arg): ! 500: self.window.hide() ! 501: self.stop_button.set_active(False) ! 502: self.save_options() ! 503: return True ! 504: ! 505: def quit(self, widget, arg): ! 506: KillDialog(self.window).run(self.hatari) ! 507: gtk.main_quit() ! 508: ! 509: ! 510: def main(): ! 511: import sys ! 512: from hatari import Hatari ! 513: hatariobj = Hatari() ! 514: if len(sys.argv) > 1: ! 515: if sys.argv[1] in ("-h", "--help"): ! 516: print "usage: %s [hatari options]" % os.path.basename(sys.argv[0]) ! 517: return ! 518: args = sys.argv[1:] ! 519: else: ! 520: args = None ! 521: hatariobj.run(args) ! 522: ! 523: info = UInfo() ! 524: debugui = HatariDebugUI(hatariobj, True) ! 525: debugui.window.show_all() ! 526: gtk.main() ! 527: debugui.save_options() ! 528: ! 529: ! 530: if __name__ == "__main__": ! 531: main()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.