/
debug.py
229 lines (202 loc) · 8.08 KB
/
debug.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""
PC-BASIC - debug.py
DEBUG statement and utilities
(c) 2013, 2014, 2015, 2016 Rob Hagemans
This file is released under the GNU GPL version 3 or later.
"""
from StringIO import StringIO
import sys
import traceback
import logging
import os
import platform
from . import vartypes
from . import representation
from . import error
class DebugException(Exception):
"""Test exception for debugging purposes"""
def __str__(self):
return self.__doc__
class BaseDebugger(object):
""" Only debug uncaught exceptions. """
debug_mode = False
def __init__(self, session):
""" Initialise debugger. """
self.session = session
def bluescreen(self, e):
""" Display a modal exception message. """
self.session.screen.screen(0, 0, 0, 0, new_width=80)
self.session.screen.clear()
self.session.screen.init_mode()
exc_type, exc_value, exc_traceback = sys.exc_info()
# log the standard python error
logging.error(''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
# format the error more readably on the screen
self.session.screen.set_border(4)
self.session.screen.set_attr(0x70)
self.session.screen.write_line('EXCEPTION')
self.session.screen.set_attr(15)
if self.session.parser.run_mode:
self.session.program.bytecode.seek(-1, 1)
self.session.program.edit(
self.session.console,
self.session.program.get_line_number(
self.session.program.bytecode.tell()),
self.session.program.bytecode.tell())
self.session.screen.write_line('\n')
else:
self.session.direct_line.seek(0)
self.session.screen.write_line(str(
self.session.tokeniser.detokenise_compound_statement(
self.session.direct_line)[0])+'\n')
stack = traceback.extract_tb(exc_traceback)
for s in stack[-4:]:
stack_line = '{0}:{1}, {2}'.format(
os.path.split(s[0])[-1], s[1], s[2])
stack_line_2 = ' {0}'.format(s[3])
self.session.screen.set_attr(15)
self.session.screen.write_line(stack_line)
self.session.screen.set_attr(7)
self.session.screen.write_line(stack_line_2)
exc_message = traceback.format_exception_only(exc_type, exc_value)[0]
self.session.screen.set_attr(15)
self.session.screen.write('{0}:'.format(exc_type.__name__))
self.session.screen.set_attr(7)
self.session.screen.write_line(' {0}'.format(str(exc_value)))
self.session.screen.set_attr(0x70)
self.session.screen.write_line(
'\nThis is a bug in PC-BASIC.')
self.session.screen.set_attr(7)
self.session.screen.write(
'Sorry about that. Please send the above messages to the bugs forum\nby e-mail to ')
self.session.screen.set_attr(15)
self.session.screen.write(
'bugs@discussion.pcbasic.p.re.sf.net')
self.session.screen.set_attr(7)
self.session.screen.write(
' or by filing a bug\nreport at ')
self.session.screen.set_attr(15)
self.session.screen.write(
'https://github.com/robhagemans/pcbasic/issues')
self.session.screen.set_attr(7)
self.session.screen.write_line(
'. Please include')
self.session.screen.write_line('as much information as you can about what you were doing and how this happened.')
self.session.screen.write_line('Thank you!')
self.session.screen.set_attr(7)
self.session.parser.set_pointer(False)
def debug_step(self, linum):
""" Dummy debug step. """
def debug_exec(self, debug_cmd):
""" Dummy debug exec. """
class Debugger(BaseDebugger):
""" Debugging helper. """
debug_mode = True
def __init__(self, session):
""" Initialise debugger. """
BaseDebugger.__init__(self, session)
self.debug_tron = False
self.watch_list = []
def debug_step(self, linum):
""" Execute traces and watches on a program step. """
outstr = ''
if self.debug_tron:
outstr += ('['+('%i' % linum) +']')
for (expr, outs) in self.watch_list:
outstr += (' ' + expr +' = ')
outs.seek(2)
try:
val = self.session.parser.parse_expression(outs, self.session)
if val[0] == '$':
outstr += '"' + self.session.strings.copy(val) + '"'
else:
outstr += representation.number_to_str(val, screen=False)
except Exception as e:
logging.debug(str(type(e))+' '+str(e))
if outstr:
logging.debug(outstr)
def debug_exec(self, debug_cmd):
""" Execute a debug command. """
global debugger, session
# make session available to debugging commands
debugger = self
session = self.session
buf = StringIO()
save_stdout = sys.stdout
sys.stdout = buf
try:
exec(debug_cmd)
except DebugException:
raise
except error.Reset:
raise
except Exception as e:
logging.debug(str(type(e))+' '+str(e))
traceback.print_tb(sys.exc_info()[2])
sys.stdout = save_stdout
logging.debug(buf.getvalue()[:-1]) # exclude \n
##############################################################################
# debugging commands
# module-globals for use by debugging commands
# these should be set (by debug_exec) before using any of the below
debugger = None
# convenient access to current session
session = None
def crash():
""" Simulate a crash. """
raise DebugException()
def reset():
""" Ctrl+Alt+Delete. """
raise error.Reset()
def trace(on=True):
""" Switch line number tracing on or off. """
debugger.debug_tron = on
def watch(expr):
""" Add an expression to the watch list. """
outs = session.tokeniser.tokenise_line('?'+expr)
debugger.watch_list.append((expr, outs))
def show_variables():
""" Dump all variables to the log. """
logging.debug(repr(debugger.session.scalars.variables))
logging.debug(repr(debugger.session.arrays.arrays))
logging.debug(repr(debugger.session.strings.strings))
def show_screen():
""" Copy the screen buffer to the log. """
logging.debug(' +' + '-'*session.screen.mode.width+'+')
i = 0
lastwrap = False
for row in session.screen.apage.row:
s = [ c[0] for c in row.buf ]
i += 1
outstr = '{0:2}'.format(i)
if lastwrap:
outstr += ('\\')
else:
outstr += ('|')
outstr += (''.join(s))
if row.wrap:
logging.debug(outstr + '\\ {0:2}'.format(row.end))
else:
logging.debug(outstr + '| {0:2}'.format(row.end))
lastwrap = row.wrap
logging.debug(' +' + '-'*session.screen.mode.width+'+')
def show_program():
""" Write a marked-up hex dump of the program to the log. """
prog = debugger.session.program
code = prog.bytecode.getvalue()
offset_val, p = 0, 0
for key in sorted(prog.line_numbers.keys())[1:]:
offset, linum = code[p+1:p+3], code[p+3:p+5]
last_offset = offset_val
offset_val = (vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(offset))
- (debugger.session.memory.code_start + 1))
linum_val = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(linum))
logging.debug((code[p:p+1].encode('hex') + ' ' +
offset.encode('hex') + ' (+%03d) ' +
code[p+3:p+5].encode('hex') + ' [%05d] ' +
code[p+5:prog.line_numbers[key]].encode('hex')),
offset_val - last_offset, linum_val )
p = prog.line_numbers[key]
logging.debug(code[p:p+1].encode('hex') + ' ' +
code[p+1:p+3].encode('hex') + ' (ENDS) ' +
code[p+3:p+5].encode('hex') + ' ' + code[p+5:].encode('hex'))