Annotation of hatari/python-ui/debugui.py, revision 1.1

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()

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.