improve source file lookup
[rofl0r-gdbpimp.git] / gdb.py
blob8aac0b4ac9c28d5a54d5b72ab44dc13e0fb24691
1 from __future__ import unicode_literals
2 from proc import Proc
3 import tokenizer, py_console
4 import time, os, pty, codecs, re
6 def extract_filename(fn):
7 a = fn.split('/')
8 return a[-1]
10 class GDB():
11 def __init__(self, command, *args, **kwargs):
12 self.command = command
13 self.proc = Proc("LC_ALL=C gdb --args " + command, shell=True)
14 self.debugf = open('debug.log', 'w')
15 self.compilation_directory = ''
16 self.sourcefile = ''
17 self.sources = []
18 self.breakpoints = {}
19 self.lineno = -1
20 self.cached_stdout = ''
21 self.cached_stderr = ''
22 while 1:
23 time.sleep(0.01)
24 if self.proc.canread(self.proc.stdout()): break
25 if self.proc.canread(self.proc.stderr()): break
26 s, t = self.read()
27 # disable getting the output split into single pages and interacting
28 self.send('set pagination off')
29 self.read()
30 # disable printing of strings interrupted by <repeats x times>
31 self.send('set print repeats 0xfffffffe')
32 self.read()
33 master, slave = pty.openpty()
34 self.send('set inferior-tty ' + os.ttyname(slave))
35 self.read()
36 self.pty_master = master
37 self.pty_slave = slave
38 self._reload_sources()
39 self.cached_stdout = s
40 self.cached_stderr = t
41 def add_bp(self, file, line):
42 file = self.find_sourcefile(file)
43 if not file in self.breakpoints: self.breakpoints[file] = []
44 self.breakpoints[file].append(line)
45 def _set_exit_code(self, ec):
46 self.proc.exitcode = ec
47 def _reload_sources(self):
48 self.send('info sources')
49 s = self.raw_read('stdout')
50 lines = s.split('\n')
51 self.sources = []
52 for line in lines:
53 if line.startswith('Source files for'): continue
54 line = line.rstrip('\n')
55 if line == '': continue
56 items = line.split(', ')
57 for item in items: self.sources.append(item)
58 def set_sourcefile(self, file):
59 if len(file) and not file.startswith('/'):
60 file = self.compilation_directory + '/' + file
61 self.debug("sourcefile " + file)
62 self.sourcefile = file
63 def find_sourcefile_single(self, file):
64 full = None
65 if len(file) and not file.startswith('/'):
66 full = self.compilation_directory + '/' + file
67 for s in self.sources:
68 if s == file or (full and s == full): return s
69 if full:
70 tmp = '/' + file
71 for s in self.sources:
72 if s.endswith(tmp): return s
73 return None
74 def find_sourcefile(self, file):
75 if self.sourcefile == file:
76 return file
77 if not file.startswith('/') and self.compilation_directory + '/' + file == self.sourcefile:
78 return self.sourcefile
79 s = self.find_sourcefile_single(file)
80 if s: return s
81 self._reload_sources()
82 s = self.find_sourcefile_single(file)
83 return s
85 def _consume_cached(self, which):
86 if which == 'stdout':
87 res = self.cached_stdout
88 self.cached_stdout = ''
89 else:
90 res = self.cached_stderr
91 self.cached_stderr = ''
92 return res
94 def istdout(self):
95 return self.pty_master
96 def istdout_canread(self):
97 return self.proc.canread(self.pty_master)
98 def debug(self, text):
99 self.debugf.write(text + '\n')
100 self.debugf.flush()
101 def raw_read(self, filename):
102 if self.get_exitcode() is not None: return ''
103 s = self._consume_cached(filename)
104 if filename == 'stdout':
105 f = self.proc.stdout()
106 else:
107 f = self.proc.stderr()
108 while self.proc.canread(f):
109 c = f.read(1)
110 if not c: break
111 s += c
112 return s
113 def read(self):
114 s = self.raw_read('stdout')
115 #if '[Inferior 1 (process 19071) exited normally]
116 # [Inferior 1 (process 19224) exited with code 01]
117 lines = s.split('\n')
118 for l in lines:
119 if l.startswith('The program is not being run.'):
120 self._set_exit_code(-3)
121 if l.startswith('[Inferior 1 (process '):
122 if l.endswith('exited normally]'): self.proc.exitcode = 0
123 if ') exited with code ' in l and l.endswith(']'):
124 self._set_exit_code(int(l.split(' ')[-1].rstrip(']'), 10))
125 if not self.proc.exitcode and lines[-1] != '(gdb) ':
126 s += self.proc.read_until(self.proc.stdout(), '(gdb) ')
127 lines = s.split('\n')
128 for l in lines:
129 self.debug('L ' + l)
130 if l.startswith('Breakpoint ') or l.startswith('Temporary breakpoint '):
131 a = l.split(' ')
132 le = a[-1]
133 self.debug(repr(a))
134 file = None
135 if le.find(':') == -1:
136 # dont have a file:lineno tuple at the end, it's the confirmation a breakpoint has been set
137 if len(a) > 4 and a[-4] == u'file' and a[-2] == u'line':
138 file = a[-3][:-1]
139 lineno = a[-1][:-1]
140 if not l.startswith('Temporary'):
141 self.add_bp(file, int(lineno))
142 file = None
143 else:
144 file, lineno = le.split(':')
145 if file is not None:
146 self.set_sourcefile(self.find_sourcefile(file))
147 self.lineno = int(lineno)
149 t = self.raw_read('stderr')
150 lines = t.split('\n')
151 for l in lines:
152 if len(l): self.debug('E ' + l)
153 if l.startswith('During symbol reading, incomplete CFI data'):
154 self._set_exit_code(-1)
155 if l.startswith('Cannot find bounds of current function'):
156 self._set_exit_code(-2)
157 return s, t
158 def get_exitcode(self):
159 return self.proc.exitcode
160 def send(self, command):
161 if self.get_exitcode() is not None: return
162 self.proc.stdin().write(command + '\n')
163 time.sleep(0.0001)
164 def set_source_from_ouput(self, s):
165 a = s.split('\n')
166 for x in a:
167 if x.startswith('Located in '):
168 self.sourcefile = x[len('Located in '):]
169 elif x.startswith('Compilation directory is '):
170 self.compilation_directory = x[len('Compilation directory is '):]
171 return self.sourcefile
174 def concat_argv():
175 import sys
176 s = ''
177 for i in xrange(1, len(sys.argv)):
178 if len(s): s += ' '
179 s += sys.argv[i]
180 return s
182 def setup_gdb(gdb):
183 o = ''
184 s, t = gdb.read()
185 o += s
186 o += t
188 gdb.send('l')
189 s, t = gdb.read()
190 o += s
191 o += t
193 gdb.send('info source')
194 s, t = gdb.read()
195 source = gdb.set_source_from_ouput(s)
197 gdb.send('tbreak main')
198 s, t = gdb.read()
199 o += s
200 o += t
201 gdb.send('r')
202 s, t = gdb.read()
203 o += s
204 o += t
205 return o
208 from prompt_toolkit import Application
209 from prompt_toolkit.buffer import Buffer
210 from prompt_toolkit.widgets import TextArea, Label, MenuContainer, MenuItem
211 from prompt_toolkit.layout.containers import HSplit, VSplit, Window, ScrollOffsets, ConditionalContainer
212 from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
213 from prompt_toolkit.layout.layout import Layout
214 from prompt_toolkit.application import get_app
215 from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
216 from prompt_toolkit.layout.dimension import LayoutDimension, Dimension
217 from prompt_toolkit.lexers import PygmentsLexer
218 from prompt_toolkit.document import Document
219 from prompt_toolkit.selection import SelectionState
220 from prompt_toolkit.filters import Condition
221 from prompt_toolkit.history import InMemoryHistory
222 from pygments.lexers.c_cpp import CLexer
223 from pygments.token import Token
224 from prompt_toolkit.styles import Style, style_from_pygments_cls, merge_styles
225 from editor_style import CodeviewStyle
226 import six
228 class OrderedDict():
229 def __init__(self):
230 self.values = {}
231 self.order = None
232 self._changed = None
233 def append(self, key, value):
234 self.order.append(key)
235 self.values[key] = value
236 def update(self, values):
237 if self.order is None or len(self.order) == 0:
238 self.order = sorted(values.keys())
239 self.values = values
240 self._changed = self.order
241 return
242 i = 0
243 while i < len(self.order):
244 if not self.order[i] in values:
245 self.order.pop(i)
246 else: i += 1
247 new = []
248 changed = []
249 for key in values:
250 if not key in self.order: new.append(key)
251 elif not self.values[key] == values[key]: changed.append(key)
252 new = sorted(new)
253 self.order.extend(new)
254 self.values = values
255 changed.extend(new)
256 self._changed = changed
257 def was_changed(self, key):
258 return self._changed is not None and key in self._changed
259 def get_value_by_index(self, index):
260 if index < len(self):
261 return self[self.order[index]]
262 return None
263 def __len__(self):
264 if self.order is None: return 0
265 return len(self.order)
266 def __iter__(self):
267 if self.order is None: return
268 for x in xrange(len(self.order)):
269 yield self.order[x]
270 def __getitem__(self, index):
271 if index in self.values:
272 return self.values[index]
273 return None
275 from prompt_toolkit.mouse_events import MouseEventType
276 def if_mousedown(handler):
277 def handle_if_mouse_down(mouse_event):
278 if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
279 return handler(mouse_event)
280 else:
281 return NotImplemented
282 return handle_if_mouse_down
284 class SidebarControl(FormattedTextControl):
285 def move_cursor_down(self):
286 get_app().my.controls[name].selected_option_index += 1
287 def move_cursor_up(self):
288 get_app().my.controls[name].selected_option_index -= 1
289 def focus_on_click(self):
290 return True
292 def get_config_file_name():
293 import os
294 return os.getenv('HOME') + '/.gdbpimp.conf.json'
295 def write_config_file(s):
296 with open(get_config_file_name(), 'w') as h: h.write(s)
297 def get_config_file_contents():
298 try:
299 with open(get_config_file_name(), 'r') as h: s = h.read()
300 return s
301 except: return None
302 def json_load_config():
303 import json
304 try: return json.loads(get_config_file_contents())
305 except: return None
306 def load_config():
307 import json
308 get_app().my.saved_config = json_load_config() or {}
309 if get_app().my.gdb.command in get_app().my.saved_config:
310 if 'exprs' in get_app().my.saved_config[get_app().my.gdb.command]:
311 get_app().my.exprs_dict = get_app().my.saved_config[get_app().my.gdb.command]['exprs']
312 def save_config():
313 get_app().my.saved_config[get_app().my.gdb.command] = {
314 'exprs' : get_app().my.exprs_dict,
316 import json
317 write_config_file(json.dumps(get_app().my.saved_config))
319 def sidebar(name, kvdict):
320 # shamelessly stolen and adapted from ptpython/layout.py
321 _MAX_KEY_WIDTH = 8
322 _VAL_WIDTH = 14 # sufficient to print "0x" + 12hex chars for a 48bit memory address
323 _CTR_WIDTH = _MAX_KEY_WIDTH + _VAL_WIDTH
324 def center_str(s, w):
325 l = len(s)
326 e = w - l
327 t = ''
328 i = 0
329 while i < e/2:
330 t += ' '
331 i += 1
332 t += s
333 i = len(t)
334 while i < w:
335 t += ' '
336 i += 1
337 return t
339 def pad_or_cut(s, w):
340 if len(s) > w: s = s[:w]
341 while len(s) < w: s+= ' '
342 return s
344 def get_text_fragments():
345 tokens = []
346 def append_title(title):
347 @if_mousedown
348 def focus_from_title(mouse_event):
349 get_app().my.set_focus(name)
350 foc = ',focused' if get_app().my.focused_control == name else ''
351 tokens.extend([
352 ('class:sidebar', ' ', focus_from_title),
353 ('class:sidebar.title'+foc, center_str(title, _CTR_WIDTH), focus_from_title),
354 ('class:sidebar', '\n'),
356 def append(index, label, status, max_key_len):
357 key_len = min(_MAX_KEY_WIDTH, max_key_len)
358 val_len = _CTR_WIDTH - key_len
359 selected = get_app().my.controls[name].selected_option_index == index
361 @if_mousedown
362 def select_item(mouse_event):
363 get_app().my.set_focus(name)
364 get_app().my.controls[name].selected_option_index = index
366 @if_mousedown
367 def trigger_vardetail(mouse_event):
368 get_app().my.set_focus(name)
369 get_app().my.controls[name].selected_option_index = index
370 vardetails_toggle_on_off()
372 odd = 'odd' if index%2 != 0 else ''
373 sel = ',selected' if selected else ''
374 chg = ',changed' if kvdict().was_changed(label) else ''
375 tokens.append(('class:sidebar' + sel, '>' if selected else ' '))
376 tokens.append(('class:sidebar.label' + odd + sel, pad_or_cut(label, key_len), select_item))
377 tokens.append(('class:sidebar.status' + odd + sel + chg, pad_or_cut(status, val_len), trigger_vardetail))
378 if selected:
379 tokens.append(('[SetCursorPosition]', ''))
380 tokens.append(('class:sidebar', '<' if selected else ' '))
381 tokens.append(('class:sidebar', '\n'))
384 i = 0
385 append_title(name)
386 mydict = kvdict() if callable(kvdict) else kvdict
387 max_key_len = 0
388 for key in mydict: max_key_len = max(max_key_len, len(key))
389 for key in mydict:
390 values = mydict[key]
391 append(i, key, '%s' % values[0], max_key_len+1)
392 i += 1
393 tokens.pop() # Remove last newline.
394 i += 1 # title
395 get_app().my.controls[name].height = Dimension(min=2, max=i+1 if i>1 else 2)
396 return tokens
398 ctrl = Window(
399 SidebarControl(get_text_fragments),
400 style='class:sidebar',
401 width=Dimension.exact(_CTR_WIDTH+2),
402 height=Dimension(min=2),
403 scroll_offsets=ScrollOffsets(top=1, bottom=1))
404 ctrl.selected_option_index = 0
405 return ctrl
407 def vardetails_toggle_on_off():
408 app = get_app()
409 if app.my.controls['vardetails'].text == '':
410 app.my.controls['vardetails'].text = 'X'
411 app.my.controls['vardetails'].update()
412 else: app.my.controls['vardetails'].text = ''
414 def load_sidebar_bindings(name):
415 #sidebar_visible = Condition(lambda: config.show_sidebar)
416 sidebar_visible = Condition(lambda: True)
417 sidebar_focused = Condition(lambda: get_app().my.focused_control == name)
418 sidebar_handles_keys = sidebar_visible & sidebar_focused
419 bindings = KeyBindings()
420 handle = bindings.add
421 @handle('up', filter=sidebar_handles_keys)
422 def _(event):
423 event.app.my.controls[name].selected_option_index = (
424 (event.app.my.controls[name].selected_option_index - 1) % len(vars(event.app.my)[name]))
425 # the vars thing is so we can point to app.my.locals OrderedDict
426 # but when we repurpose the sidebar, to something else
427 @handle('down', filter=sidebar_handles_keys)
428 def _(event):
429 event.app.my.controls[name].selected_option_index = (
430 (event.app.my.controls[name].selected_option_index + 1) % len(vars(event.app.my)[name]))
431 if name == 'locals':
432 @handle('enter', filter=sidebar_handles_keys)
433 def _(event):
434 vardetails_toggle_on_off()
435 return bindings
437 def load_inputbar_bindings():
438 inputbar_focused = Condition(lambda: get_app().my.focused_control == 'input')
439 bindings = KeyBindings()
440 handle = bindings.add
441 @handle('up', filter=inputbar_focused)
442 def _(event):
443 event.app.my.controls['input'].content.buffer.auto_up()
444 @handle('down', filter=inputbar_focused)
445 def _(event):
446 event.app.my.controls['input'].content.buffer.auto_down()
447 return bindings
449 def setup_app(gdb):
451 def codeview_line_prefix(line_number, wrap_count):
452 try:
453 if False: pass
454 elif line_number +1 == get_app().my.gdb.lineno:
455 return [('class:text-area.pfx,selected', '>')]
456 elif get_app().my.gdb.sourcefile in get_app().my.gdb.breakpoints and line_number+1 in get_app().my.gdb.breakpoints[get_app().my.gdb.sourcefile]:
457 return [('class:text-area.pfx.bp', 'o')]
458 except: pass
459 return [('class:text-area.pfx', ' ')]
461 controls = {}
463 controls['header'] = Label(
464 text = u'',
465 style = u'class:header_label',
467 controls['codeview'] = TextArea(
468 text = u'',
469 read_only = True,
470 scrollbar = True,
471 line_numbers = True,
472 wrap_lines = True,
473 get_line_prefix = codeview_line_prefix,
474 lexer=PygmentsLexer(CLexer),
475 style = u'class:codeview',
476 focusable = True,
477 focus_on_click=True,
479 controls['gdbout'] = TextArea(
480 text = u'',
481 read_only = True,
482 scrollbar = True,
483 wrap_lines = True,
484 style = u'class:gdbout',
485 height = LayoutDimension(4, 16, preferred=8),
486 focusable = True,
487 focus_on_click=True,
489 controls['inferiorout'] = TextArea(
490 text = u'',
491 read_only = True,
492 scrollbar = True,
493 wrap_lines = False,
494 style = u'class:inferiorout',
495 height = LayoutDimension(1, 16, preferred=1),
496 focusable = True,
497 focus_on_click=True,
499 controls['locals'] = sidebar('locals', lambda : get_app().my.locals)
500 controls['exprs'] = sidebar('exprs', lambda : get_app().my.exprs)
501 controls['args'] = sidebar('args', lambda : get_app().my.args)
502 controls['input_label'] = Label(
503 text = u'(gdb) ',
504 style = u'class:input_label',
505 width = LayoutDimension.exact(6),
507 controls['input'] = Window(
508 content=BufferControl(
509 buffer=Buffer(
510 read_only = False,
511 multiline = False,
512 history = InMemoryHistory(),
514 focusable = True,
515 focus_on_click=True,
517 height=LayoutDimension.exact(1),
518 dont_extend_height=True,
519 style = u'class:input',
521 controls['vardetails'] = TextArea(
522 height=LayoutDimension(1, 4),
523 wrap_lines = True,
524 read_only = True,
525 style = u'class:vardetails',
527 def up_():
528 val = get_app().my.locals.get_value_by_index( \
529 get_app().my.controls['locals'].selected_option_index)
530 text = get_app().my.controls['vardetails'].text
531 if val is None and text != '':
532 get_app().my.controls['vardetails'].text = '<out of scope>'
533 elif text != '':
534 get_app().my.controls['vardetails'].text = val[1]
535 controls['vardetails'].update = up_
537 def need_vardetails():
538 return get_app().my.controls['vardetails'].text != ''
540 controls['sidebar'] = HSplit([
541 controls['exprs'],
542 controls['args'],
543 controls['locals'],
545 controls['sidebar']._remaining_space_window.style = 'class:sidebar'
547 controls['root_container'] = HSplit([
548 controls['header'],
549 ConditionalContainer(controls['vardetails'], Condition(need_vardetails)),
550 VSplit([
551 HSplit([
552 controls['codeview'],
553 controls['inferiorout'],
554 controls['gdbout'],
555 VSplit([
556 controls['input_label'],
557 controls['input'],
560 controls['sidebar'],
564 def do_exit():
565 get_app().exit(result=True)
566 def do_cont():
567 run_gdb_cmd(get_app(), 'c')
568 def do_step_into():
569 run_gdb_cmd(get_app(), 's')
570 def do_step_over():
571 run_gdb_cmd(get_app(), 'n')
572 def do_set_bp():
573 if get_app().my.focused_control == 'codeview':
574 c = get_app().my.controls['codeview']
575 line, col = c.document.translate_index_to_position(c.document.cursor_position)
576 line += 1
577 run_gdb_cmd(get_app(), 'b %s:%d'%(get_app().my.gdb.sourcefile, line))
579 def do_toggle_prompt():
580 get_app().my.input_gdb = not get_app().my.input_gdb
581 get_app().my.controls['input_label'].text = '(gdb) ' if get_app().my.input_gdb else '>>> '
582 def do_toggle_mouse():
583 # we need to have the ability to turn mouse off to use the X11
584 # clipboard (selection needs to be handled by X11, not the app)
585 get_app().my.mouse_enabled = not get_app().my.mouse_enabled
587 controls['root_container'] = MenuContainer(body=controls['root_container'], menu_items=[
588 MenuItem('File', children=[
589 MenuItem('Load config', handler=load_config),
590 MenuItem('Save config', handler=save_config),
591 MenuItem('-', disabled=True),
592 MenuItem('Exit', handler=do_exit),
594 MenuItem('Debug', children=[
595 MenuItem('Continue (F5)', handler=do_cont),
596 MenuItem('Step Into (F7)', handler=do_step_into),
597 MenuItem('Step Over (F8)', handler=do_step_over),
598 MenuItem('Set Breakpoint (CTRL-b)', handler=do_set_bp),
600 MenuItem('Extra', children=[
601 MenuItem('Toggle python prompt (F1)', handler=do_toggle_prompt),
602 MenuItem('Toggle mouse support (F2)', handler=do_toggle_mouse),
604 ], floats=[])
606 kb = KeyBindings()
607 @kb.add(u'escape', 'f')
608 def _focus_menu(event):
609 get_app().layout.focus(get_app().my.controls['root_container'].window)
610 @kb.add(u'c-q')
611 def exit_(event):
612 do_exit()
613 @kb.add(u'f1')
614 def eff_one_(event):
615 do_toggle_prompt()
617 @kb.add(u'enter')
618 def enter_(event):
619 def add_expr(name, expr):
620 get_app().my.exprs_dict[name] = expr
621 def del_expr(name):
622 if name in get_app().my.exprs_dict:
623 del get_app().my.exprs_dict[name]
624 if event.app.my.focused_control != 'input':
625 event.app.my.set_focus('input')
626 return
627 text = event.app.my.controls['input'].content.buffer.text
628 if len(text) and text[0] == ':':
629 # direct command
630 command, rest = text.split(' ', 1)
631 if command == ':expr':
632 command, rest = rest.split(' ', 1)
633 if command == 'add':
634 name, expr = rest.split(' ', 1)
635 add_expr(name, expr)
636 elif command == 'del':
637 del_expr(rest)
638 elif event.app.my.input_gdb:
639 cmd = text
640 if not len(cmd): cmd = event.app.my.last_gdb_cmd
641 else: event.app.my.last_gdb_cmd = cmd
642 run_gdb_cmd(event.app, cmd)
643 if text == 'q':
644 event.app.exit()
645 else:
646 try: app.my.console.runsource(text)
647 except Exception as e:
648 import traceback
649 add_gdbview_text(event.app, traceback.format_exc())
650 event.app.my.controls['input'].content.buffer.reset(append_to_history=True)
652 @kb.add(u'tab')
653 def enter_(event):
654 for i in xrange(len(event.app.my.focus_list)):
655 if event.app.my.focus_list[i] == event.app.my.focused_control:
656 next_focus = i+1
657 if next_focus >= len(event.app.my.focus_list):
658 next_focus = 0
659 event.app.my.set_focus(event.app.my.focus_list[next_focus])
660 break
661 @kb.add(u'c-b')
662 def cb_(event):
663 do_set_bp()
664 @kb.add(u'f5')
665 def eff_five_(event):
666 do_cont()
667 @kb.add(u'f7')
668 def _(event):
669 do_step_into()
670 @kb.add(u'f8')
671 def _(event):
672 do_step_over()
673 @kb.add(u'f2')
674 def _(event):
675 do_toggle_mouse()
677 styledict = {
678 'gdbout':'bg:#000000 #888888',
679 'inferiorout':'bg:#330000 #888888',
680 'input' : 'bg:#000000 #8888ff underline',
681 'input_label' : 'bg:#000000 #8888ff underline',
682 'header_label' : 'bg:#9999ff #000000 underline',
683 'vardetails' : 'bg:#000000 #8888ff',
684 'text-area.pfx' : 'bg:#aaaaaa #ff0000',
685 'text-area.pfx.selected' : 'bg:#ff0000 #ffffff',
686 'sidebar': 'bg:#bbbbbb #000000',
687 'sidebar.title': 'bg:#668866 #ffffff',
688 'sidebar.title focused': 'bg:#000000 #ffffff bold',
689 'sidebar.label': 'bg:#bbbbbb #222222',
690 'sidebar.status': 'bg:#dddddd #000011',
691 'sidebar.labelodd': 'bg:#bbbb00 #222222',
692 'sidebar.statusodd': 'bg:#dddd00 #000011',
693 'sidebar.label selected': 'bg:#222222 #eeeeee',
694 'sidebar.status selected': 'bg:#444444 #ffffff bold',
695 'sidebar.status changed': 'bg:#dddddd #ff0000 bold',
696 'sidebar.statusodd changed': 'bg:#dddd00 #ff0000 bold',
699 pyg_style = style_from_pygments_cls(CodeviewStyle)
701 style = merge_styles([
702 Style.from_dict(styledict),
703 pyg_style,
706 @Condition
707 def _is_mouse_active():
708 return get_app().my.mouse_enabled
709 app = Application(
710 layout = Layout(
711 controls['root_container'],
712 focused_element=controls['input'],
714 style=style,
715 full_screen=True,
716 key_bindings=merge_key_bindings([
718 load_sidebar_bindings('locals'),
719 load_inputbar_bindings(),
721 mouse_support = _is_mouse_active,
723 class My(): pass
724 app.my = My()
725 app.my.saved_config = {}
726 app.my.mouse_enabled = True
727 app.my.controls = controls
728 app.my.control_to_name_mapping = {}
729 for name in controls:
730 app.my.control_to_name_mapping[controls[name]] = name
731 if isinstance(controls[name], TextArea) or 'control' in vars(controls[name]):
732 app.my.control_to_name_mapping[controls[name].control] = name
733 elif 'content' in vars(controls[name]):
734 app.my.control_to_name_mapping[controls[name].content] = name
736 app.my.locals = OrderedDict()
737 app.my.args = OrderedDict()
738 app.my.exprs = OrderedDict()
739 app.my.exprs_dict = dict()
740 app.my.gdb = gdb
741 app.my.last_gdb_cmd = ''
742 app.my.input_gdb = True
743 app.my.focus_list = ['input', 'codeview', 'inferiorout', 'gdbout', 'args', 'locals', 'exprs']
744 app.my.focused_control = 'input'
745 def _set_focus(ctrl_or_name):
746 if isinstance(ctrl_or_name, six.text_type):
747 ctrl = get_app().my.controls[ctrl_or_name]
748 name = ctrl_or_name
749 else:
750 ctrl = ctrl_or_name
751 name = get_app().my.control_to_name_mapping[ctrl]
752 get_app().layout.focus(ctrl)
753 get_app().my.focused_control = name
754 app.my.set_focus = _set_focus
755 def _has_focus(ctrl_or_name):
756 ctrl = get_app().my.controls[ctrl_or_name] if isinstance(ctrl_or_name, str) else ctrl_or_name
757 return get_app().layout.has_focus(ctrl)
758 app.my.has_focus = _has_focus
759 app_console_writefunc = lambda x: add_gdbview_text(get_app(), x)
760 app.my.console = py_console.Shell(locals=globals(), writefunc=app_console_writefunc)
761 def my_mouse_handler(self, mouse_event):
762 # loosely based on prompt_toolkit/layout/controls.py:716
763 #if self.focus_on_click() and mouse_event.event_type == MouseEventType.MOUSE_DOWN:
764 if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
765 get_app().my.set_focus(self)
766 processed_line = self._last_get_processed_line(mouse_event.position.y)
767 xpos = processed_line.display_to_source(mouse_event.position.x)
768 index = self.buffer.document.translate_row_col_to_index(mouse_event.position.y, xpos)
769 self.buffer.cursor_position = index
770 else: return NotImplemented
771 for x in app.my.focus_list:
772 if isinstance(app.my.controls[x], Window) and isinstance(app.my.controls[x].content, SidebarControl):
773 continue #don't override custom mouse handler
774 if isinstance(app.my.controls[x], TextArea):
775 app.my.controls[x].control.mouse_handler = my_mouse_handler.__get__(app.my.controls[x].control)
776 else:
777 app.my.controls[x].content.mouse_handler = my_mouse_handler.__get__(app.my.controls[x].content)
779 return app
781 def interact():
782 import code
783 code.InteractiveConsole(locals=globals()).interact()
785 def prepare_text(s):
786 return s.replace('\t', ' ')
788 def isnumeric(s):
789 if s == '': return False
790 for c in s:
791 if not c in '0123456789': return False
792 return True
794 def debug(app, text):
795 app.my.controls['header'].text = prepare_text(text)
797 def add_gdbview_text(app, text):
798 pt = prepare_text(text)
799 ad = '\n' + pt.replace('\n(gdb) ', '')
800 while len(ad) and ad[-1] == '\n': ad = ad[:-1]
801 app.my.controls['gdbout'].text += ad
802 scroll_down(app.my.controls['gdbout'])
804 def codeview_set_line(ctrl, lineno):
805 fl = ctrl.window.render_info.first_visible_line()
806 height = ctrl.window.render_info.last_visible_line() - fl
807 direction = -1 if lineno < fl else 1
808 scroll_to(ctrl, lineno + (height/2)*direction)
810 def try_set_sourcepos(app, file, lineno):
811 newfile = app.my.gdb.find_sourcefile(file)
812 if newfile:
813 app.my.gdb.set_sourcefile(newfile)
814 debug(app, app.my.gdb.sourcefile)
815 if isnumeric(lineno): app.my.gdb.lineno = int(lineno)
816 return True
817 app.my.gdb.debug("couldnt find sourcefile " + file)
818 return False
820 _STEP_COMMANDS = ['n','s','c','r','next','step','continue','run']
821 def run_gdb_cmd(app, cmd, hide=False):
822 oldsrc = app.my.gdb.sourcefile
823 app.my.gdb.send(cmd)
824 s, t = app.my.gdb.read()
825 if not hide: add_gdbview_text(app, s + t)
826 cmd0 = cmd if not ' ' in cmd else cmd.split(' ')[0]
827 if cmd0 in ['finish']:
828 r = re.compile('(0x[0-9a-f]+) in (.+) at (.+):([0-9]+)')
829 for line in s.split('\n'):
830 rm = r.match(line)
831 if rm:
832 try_set_sourcepos(app, rm.groups(0)[2], rm.groups(0)[3])
833 break
834 run_gdb_cmd(app, 's')
835 elif cmd0 in _STEP_COMMANDS:
836 segv=False
837 for line in s.split('\n'):
838 if 'Program received signal SIGSEGV' in line: segv=True
839 a = line.replace('\t', ' ').split(' ')
840 if isnumeric(a[0]): app.my.gdb.lineno = int(a[0])
841 elif ':' in a[-1]:
842 file, lineno = a[-1].split(':')
843 if not try_set_sourcepos(app, file, lineno) and not segv:
844 # we're in a file we don't have sources for, step over
845 app.my.gdb.debug("run finish")
846 run_gdb_cmd(app, 'finish')
847 break
848 if app.my.gdb.istdout_canread():
849 app.my.controls['inferiorout'].text += prepare_text(os.read(app.my.gdb.istdout(), 1024*4))
850 scroll_down(app.my.controls['inferiorout'])
851 if not app.my.gdb.sourcefile == oldsrc:
852 load_source(app)
853 if not cmd.startswith('b ') and app.my.gdb.lineno != -1:
854 codeview_set_line(app.my.controls['codeview'], app.my.gdb.lineno)
855 if cmd0 in _STEP_COMMANDS:
856 get_locals(app)
857 get_exprs(app)
858 get_args(app)
859 app.my.controls['vardetails'].update()
860 return s,t
862 def oct_to_hex(s):
863 foo = int(s[1:], 8)
864 return "\\x" + chr(foo).encode('hex')
866 def hexify_string_literal(s):
867 escaped = 0
868 t = '"'
869 i = 1
870 while i < len(s) -1:
871 if not escaped and s[i] == '\\':
872 escaped = 1
873 elif escaped:
874 if isnumeric(s[i]):
875 h = oct_to_hex(s[i-1:i+3])
876 if h == "\\x00": break
877 t += h
878 i += 2
879 else: t += "\\" + s[i]
880 escaped = 0
881 else:
882 t += s[i]
883 i += 1
884 return t + '"'
886 def get_locals_or_args(app, do_locals=True):
887 if do_locals:
888 app.my.gdb.send('info locals')
889 else:
890 app.my.gdb.send('info args')
891 s = app.my.gdb.proc.read_until(app.my.gdb.proc.stdout(), '(gdb) ')
892 mylocals = dict()
893 for line in s.split('\n'):
894 if ' = ' in line:
895 k, v = line.split(' = ', 1)
896 b = tokenizer.split_tokens(v)
897 mv = b[-1]
898 if mv.startswith('times>'): mv = b[0]
899 if mv[0] == "'" and len(mv) > 3: # turn gdb's gay octal literals into hex
900 charval = mv[1:-1]
901 if charval[0] == '\\' and isnumeric(charval[1]):
902 foo = int(charval[1:], 8)
903 mv = "'\\x" + chr(foo).encode('hex') + "'"
904 elif mv[0] == '"':
905 mv = hexify_string_literal(mv)
906 elif isnumeric(mv) and int(mv) > 0xffff:
907 mv = hex(int(mv))
908 elif 'out of bounds>' in v:
909 mv= '<OOB>'
910 elif mv == '}':
911 mv = v
912 mylocals[k] = (mv, v)
913 if do_locals:
914 app.my.locals.update(mylocals)
915 else:
916 app.my.args.update(mylocals)
918 def get_locals(app):
919 return get_locals_or_args(app, do_locals=True)
921 def get_args(app):
922 return get_locals_or_args(app, do_locals=False)
924 def get_exprs(app):
925 mylocals = dict()
926 for k in app.my.exprs_dict:
927 app.my.gdb.send(app.my.exprs_dict[k])
928 s = app.my.gdb.proc.read_until(app.my.gdb.proc.stdout(), '(gdb) ')
929 s = s.replace('\n(gdb) ', '')
930 if len(s) > 5 and s[0] == '$' and isnumeric(s[1]):
931 i = 2
932 while i < len(s) and isnumeric(s[i]): i += 1
933 if i + 2 < len(s) and s[i] == ' ' and s[i+1] == '=' and s[i+2] == ' ':
934 s = s.split(' = ', 1)[1]
935 mylocals[k] = (s, s)
936 app.my.exprs.update(mylocals)
938 def scroll_down(control):
939 set_lineno(control, count_char(control.text, '\n')+1)
941 def scroll_to(control, n):
942 set_lineno(control, n)
944 def load_source(app):
945 with codecs.open(app.my.gdb.sourcefile, 'r', 'utf-8') as f:
946 app.my.controls['codeview'].text = prepare_text(f.read())
948 def set_lineno(control, lineno):
949 control.buffer.cursor_position = \
950 control.buffer.document.translate_row_col_to_index(lineno - 1, 0)
952 def count_char(text, ch):
953 cnt = 0
954 for x in text:
955 if x == ch: cnt += 1
956 return cnt
958 if __name__ == '__main__':
959 gdb = GDB(concat_argv())
960 gdb_output = setup_gdb(gdb)
961 app = setup_app(gdb)
962 app.my.controls['gdbout'].text = prepare_text(gdb_output)
963 scroll_down(app.my.controls['gdbout'])
964 load_source(app)
966 app.run()
968 import sys
969 sys.exit(gdb.get_exitcode())