PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/metasm/metasm/gui/debug.rb

https://github.com/Jonono2/metasploit-framework
Ruby | 1294 lines | 1133 code | 132 blank | 29 comment | 198 complexity | 3000e38849fed30907aa965ff850c545 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, GPL-3.0, LGPL-2.1, GPL-2.0
  1. # This file is part of Metasm, the Ruby assembly manipulation suite
  2. # Copyright (C) 2006-2009 Yoann GUILLOT
  3. #
  4. # Licence is LGPL, see LICENCE in the top-level directory
  5. require 'metasm/gui/dasm_main'
  6. module Metasm
  7. module Gui
  8. # TODO invalidate dbg.disassembler on selfmodifying code
  9. class DbgWidget < ContainerVBoxWidget
  10. attr_accessor :dbg, :console, :regs, :code, :mem, :win
  11. attr_accessor :watchpoint
  12. attr_accessor :parent_widget, :keyboard_callback, :keyboard_callback_ctrl
  13. def initialize_widget(dbg)
  14. @dbg = dbg
  15. @keyboard_callback = {}
  16. @keyboard_callback_ctrl = {}
  17. @parent_widget = nil
  18. @regs = DbgRegWidget.new(dbg, self)
  19. @mem = DisasmWidget.new(dbg.disassembler)
  20. @code = DisasmWidget.new(dbg.disassembler) # after mem so that dasm.gui == @code
  21. @console = DbgConsoleWidget.new(dbg, self)
  22. @code.parent_widget = self
  23. @mem.parent_widget = self
  24. @dbg.disassembler.disassemble_fast(@dbg.pc)
  25. oldcb = @code.bg_color_callback
  26. @code.bg_color_callback = lambda { |a|
  27. if a == @dbg.pc
  28. :red_bg
  29. # TODO breakpoints & stuff
  30. elsif oldcb; oldcb[a]
  31. end
  32. }
  33. # TODO popup menu, set bp, goto here, show arg in memdump..
  34. @children = [@code, @mem, @regs]
  35. add @regs, 'expand' => false # XXX
  36. add @mem
  37. add @code
  38. add @console
  39. @watchpoint = { @code => @dbg.register_pc }
  40. pc = @dbg.resolve_expr(@watchpoint[@code])
  41. graph = :graph if @dbg.disassembler.function_blocks(pc).to_a.length < 100
  42. @code.focus_addr(pc, graph, true)
  43. @mem.focus_addr(0, :hex, true)
  44. end
  45. def swapin_tid
  46. @regs.swapin_tid
  47. @dbg.disassembler.disassemble_fast(@dbg.pc)
  48. @children.each { |c|
  49. if wp = @watchpoint[c]
  50. c.focus_addr @dbg.resolve_expr(wp), nil, true
  51. end
  52. }
  53. redraw
  54. end
  55. def swapin_pid
  56. @mem.dasm = @dbg.disassembler
  57. @code.dasm = @dbg.disassembler
  58. swapin_tid
  59. gui_update
  60. end
  61. def keypress(key)
  62. return true if @keyboard_callback[key] and @keyboard_callback[key][key]
  63. case key
  64. when :f5; protect { dbg_continue }
  65. when :f10; protect { dbg_stepover }
  66. when :f11; protect { dbg_singlestep }
  67. when :f12; protect { dbg_stepout }
  68. when ?.; @console.grab_focus
  69. else return @parent_widget ? @parent_widget.keypress(key) : false
  70. end
  71. true
  72. end
  73. def keypress_ctrl(key)
  74. return true if @keyboard_callback_ctrl[key] and @keyboard_callback_ctrl[key][key]
  75. case key
  76. when :f5; protect { @dbg.pass_current_exception ; dbg.continue }
  77. else return @parent_widget ? @parent_widget.keypress_ctrl(key) : false
  78. end
  79. true
  80. end
  81. def pre_dbg_run
  82. @regs.pre_dbg_run
  83. end
  84. # TODO check_target always, incl when :stopped
  85. def post_dbg_run
  86. # focus currently stopped threads
  87. if @dbg.state == :running and tt = @dbg.tid_stuff.find { |tid, tstuff| tstuff[:state] != :running }
  88. @dbg.tid = tt[0]
  89. end
  90. want_redraw = true
  91. return if @idle_checking ||= nil # load only one bg proc
  92. @idle_checking = true
  93. Gui.idle_add {
  94. protect {
  95. @dbg.check_target
  96. if @dbg.state == :running
  97. redraw if want_redraw # redraw once if the target is running (less flicker with singlestep)
  98. want_redraw = false
  99. sleep 0.01
  100. next true
  101. end
  102. @idle_checking = false
  103. @dbg.dasm_invalidate
  104. @mem.gui_update
  105. @dbg.disassembler.sections.clear if @dbg.state == :dead
  106. @dbg.disassembler.disassemble_fast(@dbg.pc)
  107. @children.each { |c|
  108. if wp = @watchpoint[c]
  109. c.focus_addr @dbg.resolve_expr(wp), nil, true
  110. end
  111. }
  112. redraw
  113. false
  114. }
  115. }
  116. end
  117. def wrap_run
  118. pre_dbg_run
  119. yield
  120. post_dbg_run
  121. end
  122. def dbg_continue(*a) wrap_run { @dbg.continue(*a) } end
  123. def dbg_singlestep(*a) wrap_run { @dbg.singlestep(*a) } end
  124. def dbg_stepover(*a) wrap_run { @dbg.stepover(*a) } end
  125. def dbg_stepout(*a) wrap_run { @dbg.stepout(*a) } end
  126. def redraw
  127. super
  128. @console.redraw
  129. @regs.gui_update
  130. @children.each { |c| c.redraw }
  131. end
  132. def gui_update
  133. @console.redraw
  134. @children.each { |c| c.gui_update }
  135. end
  136. def prompt_attach(caption='chose target')
  137. l = nil
  138. i = inputbox(caption) { |name|
  139. i = nil ; l.destroy if l and not l.destroyed?
  140. @dbg.attach(name)
  141. }
  142. # build process list in bg (exe name resolution takes a few seconds)
  143. list = [['pid', 'name']]
  144. list_pr = OS.current.list_processes
  145. Gui.idle_add {
  146. if pr = list_pr.shift
  147. list << [pr.pid, pr.path] if pr.path
  148. true
  149. elsif i
  150. me = ::Process.pid.to_s
  151. l = listwindow('running processes', list,
  152. :noshow => true,
  153. :color_callback => lambda { |le| [:grey, :palegrey] if le[0] == me }
  154. ) { |e|
  155. i.text = e[0]
  156. i.keypress(:enter) if l.destroyed?
  157. }
  158. l.x += l.width
  159. l.show
  160. false
  161. end
  162. } if not list_pr.empty?
  163. end
  164. def prompt_createprocess(caption='chose path')
  165. openfile(caption) { |path|
  166. path = '"' + path + '"' if @dbg.shortname == 'windbg' and path =~ /\s/
  167. inputbox('target args?', :text => path) { |pa|
  168. @dbg.create_process(pa)
  169. }
  170. }
  171. end
  172. def prompt_datawatch
  173. inputbox('data watch', :text => @watchpoint[@mem].to_s) { |e|
  174. case e
  175. when '', 'nil', 'none', 'delete'
  176. @watchpoint.delete @mem
  177. else
  178. @watchpoint[@mem] = @console.parse_expr(e)
  179. end
  180. }
  181. end
  182. def dragdropfile(f)
  183. case f
  184. when /\.(c|h|cpp)$/; @dbg.disassembler.parse_c_file(f)
  185. when /\.map$/; @dbg.load_map(f)
  186. when /\.rb$/; @dbg.load_plugin(f) ; @console.add_log "loaded plugin #{File.basename(f, '.rb')}"
  187. else messagebox("unsupported file extension #{f}")
  188. end
  189. end
  190. def extend_contextmenu(tg, menu, addr=nil)
  191. if addr
  192. bm = tg.new_menu
  193. bl = @dbg.all_breakpoints(addr)
  194. if not bl.empty?
  195. tg.addsubmenu(bm, '_clear breakpoint') { bl.each { |b| @dbg.del_bp(b) } }
  196. end
  197. tg.addsubmenu(bm, '_go here') { @dbg.bpx(addr, true) ; dbg_continue }
  198. tg.addsubmenu(bm, '_bpx') { @dbg.bpx(addr) }
  199. tg.addsubmenu(bm, 'bpm _read') { @dbg.hwbp(addr, :r, 1) }
  200. tg.addsubmenu(bm, 'bpm _write') { @dbg.hwbp(addr, :w, 1) }
  201. tg.addsubmenu(menu, '_bp', bm)
  202. end
  203. if @parent_widget.respond_to?(:extend_contextmenu)
  204. @parent_widget.extend_contextmenu(tg, menu, addr)
  205. end
  206. end
  207. end
  208. # a widget that displays values of registers of a Debugger
  209. # also controls the Debugger and commands slave windows (showing listing & memory)
  210. class DbgRegWidget < DrawableWidget
  211. attr_accessor :dbg
  212. def initialize_widget(dbg, parent_widget)
  213. @dbg = dbg
  214. @parent_widget = parent_widget
  215. @caret_x = @caret_reg = 0
  216. @oldcaret_x = @oldcaret_reg = 42
  217. @tid_stuff = {}
  218. swapin_tid
  219. @reg_pos = [] # list of x y w h vx of the reg drawing on widget, vx is x of value
  220. @default_color_association = ColorTheme.merge :label => :text, :data => :blue, :write_pending => :darkred,
  221. :changed => :green, :caret => :text, :inactive => :palegrey
  222. end
  223. def swapin_tid
  224. stf = @tid_stuff[[@dbg.pid, @dbg.tid]] ||= {}
  225. return if not @dbg.cpu
  226. @write_pending = stf[:write_pending] ||= {} # addr -> newvalue (bytewise)
  227. @registers = stf[:registers] ||= @dbg.register_list
  228. @flags = stf[:flags] ||= @dbg.flag_list
  229. @register_size = stf[:reg_sz] ||= @registers.inject(Hash.new(1)) { |h, r| h.update r => @dbg.register_size[r]/4 }
  230. @reg_cache = stf[:reg_cache] ||= Hash.new(0)
  231. @reg_cache_old = stf[:reg_cache_old] ||= {}
  232. end
  233. def initialize_visible
  234. gui_update
  235. end
  236. def click(ex, ey)
  237. if p = @reg_pos.find { |x, y, w, h, vx| x <= ex and x+w >= ex and y <= ey and y+h >= ey }
  238. @caret_reg = @reg_pos.index(p)
  239. @caret_x = ((ex - p[4]) / @font_width).to_i
  240. rs = @register_size[@registers[@caret_reg]]
  241. @caret_x = rs-1 if @caret_x > rs-1
  242. @caret_x = 0 if @caret_x < 0
  243. update_caret
  244. end
  245. end
  246. def rightclick(x, y)
  247. doubleclick(x, y) # XXX
  248. end
  249. def doubleclick(x, y)
  250. gui_update # XXX
  251. end
  252. def paint
  253. x = 1
  254. y = 0
  255. w_w = width
  256. render = lambda { |str, color|
  257. draw_string_color(color, x, y, str)
  258. x += str.length * @font_width
  259. }
  260. @reg_pos = []
  261. running = (@dbg.state != :stopped)
  262. regstrlen = @registers.map { |reg| reg.to_s.length + 1 }.max
  263. @registers.each { |reg|
  264. strlen = regstrlen + @register_size[reg]
  265. if x + strlen*@font_width >= w_w
  266. x = 1
  267. y += @font_height
  268. end
  269. @reg_pos << [x, y, (strlen+1)*@font_width, @font_height, x+regstrlen*@font_width]
  270. render["#{reg}=".ljust(regstrlen), :label]
  271. v = @write_pending[reg] || @reg_cache[reg]
  272. col = running ? :inactive : @write_pending[reg] ? :write_pending : @reg_cache_old.fetch(reg, v) != v ? :changed : :data
  273. render["%0#{@register_size[reg]}x " % v, col]
  274. x += @font_width # space
  275. }
  276. @flags.each { |reg|
  277. if x + @font_width >= w_w # XXX nowrap flags ?
  278. x = 1
  279. y += @font_height
  280. end
  281. @reg_pos << [x, y, @font_width, @font_height, x]
  282. v = @write_pending[reg] || @reg_cache[reg]
  283. col = running ? :inactive : @write_pending[reg] ? :write_pending : @reg_cache_old.fetch(reg, v) != v ? :changed : :data
  284. v = v == 0 ? reg.to_s.downcase : reg.to_s.upcase
  285. render[v, col]
  286. x += @font_width # space
  287. }
  288. if focus?
  289. # draw caret
  290. cx = @reg_pos[@caret_reg][4] + @caret_x*@font_width
  291. cy = @reg_pos[@caret_reg][1]
  292. draw_line_color(:caret, cx, cy, cx, cy+@font_height-1)
  293. end
  294. @oldcaret_x, @oldcaret_reg = @caret_x, @caret_reg
  295. @parent_widget.resize_child(self, width, y+@font_height)
  296. end
  297. # keyboard binding
  298. # basic navigation (arrows, pgup etc)
  299. def keypress(key)
  300. case key
  301. when :left
  302. if @caret_x > 0
  303. @caret_x -= 1
  304. update_caret
  305. end
  306. when :right
  307. if @caret_x < @register_size[@registers[@caret_reg]]-1
  308. @caret_x += 1
  309. update_caret
  310. end
  311. when :up
  312. if @caret_reg > 0
  313. @caret_reg -= 1
  314. else
  315. @caret_reg = @registers.length+@flags.length-1
  316. end
  317. @caret_x = 0
  318. update_caret
  319. when :down
  320. if @caret_reg < @registers.length+@flags.length-1
  321. @caret_reg += 1
  322. else
  323. @caret_reg = 0
  324. end
  325. @caret_x = 0
  326. update_caret
  327. when :home
  328. @caret_x = 0
  329. update_caret
  330. when :end
  331. @caret_x = @register_size[@registers[@caret_reg]]-1
  332. update_caret
  333. when :tab
  334. if @caret_reg < @registers.length+@flags.length-1
  335. @caret_reg += 1
  336. else
  337. @caret_reg = 0
  338. end
  339. @caret_x = 0
  340. update_caret
  341. when :backspace
  342. # TODO
  343. when :enter
  344. commit_writes
  345. redraw
  346. when :esc
  347. @write_pending.clear
  348. redraw
  349. when ?\x20..?\x7e
  350. if ?a.kind_of?(String)
  351. v = key.ord
  352. case key
  353. when ?\x20; v = nil # keep current value
  354. when ?0..?9; v -= ?0.ord
  355. when ?a..?f; v -= ?a.ord-10
  356. when ?A..?F; v -= ?A.ord-10
  357. else return false
  358. end
  359. else
  360. case v = key
  361. when ?\x20; v = nil
  362. when ?0..?9; v -= ?0
  363. when ?a..?f; v -= ?a-10
  364. when ?A..?F; v -= ?A-10
  365. else return false
  366. end
  367. end
  368. reg = @registers[@caret_reg] || @flags[@caret_reg-@registers.length]
  369. rsz = @register_size[reg]
  370. if v and rsz != 1
  371. oo = 4*(rsz-@caret_x-1)
  372. ov = @write_pending[reg] || @reg_cache[reg]
  373. ov &= ~(0xf << oo)
  374. ov |= v << oo
  375. @write_pending[reg] = ov
  376. elsif v and (v == 0 or v == 1) # TODO change z flag by typing 'z' or 'Z'
  377. @write_pending[reg] = v
  378. rsz = 1
  379. end
  380. if rsz == 1
  381. @caret_reg += 1
  382. @caret_reg = @registers.length if @caret_reg >= @registers.length + @flags.length
  383. elsif @caret_x < rsz-1
  384. @caret_x += 1
  385. else
  386. @caret_x = 0
  387. end
  388. redraw
  389. else return false
  390. end
  391. true
  392. end
  393. def pre_dbg_run
  394. @reg_cache_old.replace @reg_cache if @reg_cache
  395. end
  396. def commit_writes
  397. @write_pending.each { |k, v|
  398. if @registers.index(k)
  399. @dbg.set_reg_value(k, v)
  400. else
  401. @dbg.set_flag_value(k, v)
  402. end
  403. @reg_cache[k] = v
  404. }
  405. @write_pending.clear
  406. end
  407. def gui_update
  408. @reg_cache.replace @registers.inject({}) { |h, r| h.update r => @dbg.get_reg_value(r) }
  409. @flags.each { |f| @reg_cache[f] = @dbg.get_flag_value(f) }
  410. redraw
  411. end
  412. # hint that the caret moved
  413. def update_caret
  414. return if @oldcaret_x == @caret_x and @oldcaret_reg == @caret_reg
  415. invalidate_caret(@oldcaret_x, 0, *@reg_pos[@oldcaret_reg].values_at(4, 1))
  416. invalidate_caret(@caret_x, 0, *@reg_pos[@caret_reg].values_at(4, 1))
  417. @oldcaret_x, @oldcaret_reg = @caret_x, @caret_reg
  418. end
  419. end
  420. # a widget that displays logs of the debugger, and a cli interface to the dbg
  421. class DbgConsoleWidget < DrawableWidget
  422. attr_accessor :dbg, :cmd_history, :log, :statusline, :commands, :cmd_help
  423. def initialize_widget(dbg, parent_widget)
  424. @dbg = dbg
  425. @parent_widget = parent_widget
  426. @dbg.gui = self
  427. @log = []
  428. @log_length = 4000
  429. @log_offset = 0
  430. @curline = ''
  431. @statusline = 'type \'help\' for help'
  432. @cmd_history = ['']
  433. @cmd_history_length = 200 # number of past commands to remember
  434. @cmd_histptr = nil
  435. @dbg.set_log_proc { |l| add_log l }
  436. @default_color_association = ColorTheme.merge :log => :palegrey, :curline => :white, :caret => :yellow,
  437. :background => :black, :status => :black, :status_bg => '088'
  438. init_commands
  439. end
  440. def initialize_visible
  441. grab_focus
  442. gui_update
  443. end
  444. def swapin_tid
  445. @parent_widget.swapin_tid
  446. end
  447. def swapin_pid
  448. @parent_widget.swapin_pid
  449. end
  450. def click(x, y)
  451. @caret_x = (x-1).to_i / @font_width - 1
  452. @caret_x = [[@caret_x, 0].max, @curline.length].min
  453. update_caret
  454. end
  455. def doubleclick(x, y)
  456. # TODO real copy/paste
  457. # for now, copy the line under the dblclick
  458. y -= height % @font_height
  459. y = y.to_i / @font_height
  460. hc = height / @font_height
  461. if y == hc - 1
  462. txt = @statusline
  463. elsif y == hc - 2
  464. txt = @curline
  465. else
  466. txt = @log.reverse[@log_offset + hc - y - 3].to_s
  467. end
  468. clipboard_copy(txt)
  469. end
  470. # copy/paste word under cursor (paste when on last line)
  471. def rightclick(x, y)
  472. y -= height % @font_height
  473. y = y.to_i / @font_height
  474. hc = height / @font_height
  475. x /= @font_width
  476. if y >= hc - 2
  477. keypress_ctrl ?v
  478. else
  479. txt = @log.reverse[@log_offset + hc - y - 3].to_s
  480. word = txt[0...x].to_s[/\w*$/] << txt[x..-1].to_s[/^\w*/]
  481. clipboard_copy(word)
  482. end
  483. end
  484. def mouse_wheel(dir, x, y)
  485. case dir
  486. when :up; @log_offset += 3
  487. when :down; @log_offset -= 3
  488. end
  489. redraw
  490. end
  491. def paint
  492. y = height
  493. render = lambda { |str, color|
  494. draw_string_color(color, 1, y, str)
  495. y -= @font_height
  496. }
  497. w_w = width
  498. y -= @font_height
  499. draw_rectangle_color(:status_bg, 0, y, w_w, @font_height)
  500. str = "#{@dbg.pid}:#{@dbg.tid} #{@dbg.state} #{@dbg.info}"
  501. draw_string_color(:status, w_w-str.length*@font_width-1, y, str)
  502. draw_string_color(:status, 1+@font_width, y, @statusline)
  503. y -= @font_height
  504. w_w_c = w_w/@font_width
  505. @caret_y = y
  506. if @caret_x < w_w_c-1
  507. render[':' + @curline, :curline]
  508. else
  509. render['~' + @curline[@caret_x-w_w_c+2, w_w_c], :curline]
  510. end
  511. l_nr = -1
  512. lastline = nil
  513. @log_offset = 0 if @log_offset < 0
  514. @log.reverse.each { |l|
  515. l.scan(/.{1,#{w_w/@font_width}}/).reverse_each { |l_|
  516. lastline = l_
  517. l_nr += 1
  518. next if l_nr < @log_offset
  519. render[l_, :log]
  520. }
  521. break if y < 0
  522. }
  523. if lastline and l_nr < @log_offset
  524. render[lastline, :log]
  525. @log_offset = l_nr-1
  526. end
  527. if focus?
  528. cx = [@caret_x+1, w_w_c-1].min*@font_width+1
  529. cy = @caret_y
  530. draw_line_color(:caret, cx, cy, cx, cy+@font_height-1)
  531. end
  532. @oldcaret_x = @caret_x
  533. end
  534. def keypress(key)
  535. case key
  536. when :left
  537. if @caret_x > 0
  538. @caret_x -= 1
  539. update_caret
  540. end
  541. when :right
  542. if @caret_x < @curline.length
  543. @caret_x += 1
  544. update_caret
  545. end
  546. when :up
  547. if not @cmd_histptr
  548. if @curline != ''
  549. @cmd_history << @curline
  550. @cmd_histptr = 2
  551. else
  552. @cmd_histptr = 1
  553. end
  554. else
  555. @cmd_histptr += 1
  556. @cmd_histptr = 1 if @cmd_histptr > @cmd_history.length
  557. end
  558. @curline = @cmd_history[-@cmd_histptr].dup
  559. @caret_x = @curline.length
  560. update_status_cmd
  561. redraw
  562. when :down
  563. if not @cmd_histptr
  564. @cmd_history << @curline if @curline != ''
  565. @cmd_histptr = @cmd_history.length
  566. else
  567. @cmd_histptr -= 1
  568. @cmd_histptr = @cmd_history.length if @cmd_histptr < 1
  569. end
  570. @curline = @cmd_history[-@cmd_histptr].dup
  571. @caret_x = @curline.length
  572. update_status_cmd
  573. redraw
  574. when :home
  575. @caret_x = 0
  576. update_caret
  577. when :end
  578. @caret_x = @curline.length
  579. update_caret
  580. when :pgup
  581. @log_offset += height/@font_height - 3
  582. redraw
  583. when :pgdown
  584. @log_offset -= height/@font_height - 3
  585. redraw
  586. when :tab
  587. # autocomplete
  588. if @caret_x > 0 and not @curline[0, @caret_x].index(?\ ) and st = @curline[0, @caret_x] and not @commands[st]
  589. keys = @commands.keys.find_all { |k| k[0, st.length] == st }
  590. while st.length < keys.first.to_s.length and keys.all? { |k| k[0, st.length+1] == keys.first[0, st.length+1] }
  591. st << keys.first[st.length]
  592. @curline[@caret_x, 0] = st[-1, 1]
  593. @caret_x += 1
  594. end
  595. update_status_cmd
  596. redraw
  597. end
  598. when :enter
  599. @cmd_histptr = nil
  600. handle_command
  601. update_status_cmd
  602. when :esc
  603. when :delete
  604. if @caret_x < @curline.length
  605. @curline[@caret_x, 1] = ''
  606. update_status_cmd
  607. redraw
  608. end
  609. when :backspace
  610. if @caret_x > 0
  611. @caret_x -= 1
  612. @curline[@caret_x, 1] = ''
  613. update_status_cmd
  614. redraw
  615. end
  616. when :insert
  617. if keyboard_state(:shift)
  618. txt = clipboard_paste.to_s
  619. @curline[@caret_x, 0] = txt
  620. @caret_x += txt.length
  621. update_status_cmd
  622. redraw
  623. end
  624. when Symbol; return false # avoid :shift cannot coerce to Int warning
  625. when ?\x20..?\x7e
  626. @curline[@caret_x, 0] = key.chr
  627. @caret_x += 1
  628. update_status_cmd
  629. redraw
  630. else return false
  631. end
  632. true
  633. end
  634. def keypress_ctrl(key)
  635. case key
  636. when ?v
  637. txt = clipboard_paste.to_s
  638. @curline[@caret_x, 0] = txt
  639. @caret_x += txt.length
  640. update_status_cmd
  641. redraw
  642. else return false
  643. end
  644. true
  645. end
  646. def update_status_cmd
  647. st = @curline.split.first
  648. if @commands[st]
  649. @statusline = "#{st}: #{@cmd_help[st]}"
  650. else
  651. keys = @commands.keys.find_all { |k| k[0, st.length] == st } if st
  652. if keys and not keys.empty?
  653. @statusline = keys.sort.join(' ')
  654. else
  655. @statusline = 'type \'help\' for help'
  656. end
  657. end
  658. end
  659. def new_command(*cmd, &b)
  660. hlp = cmd.pop if cmd.last.include? ' '
  661. cmd.each { |c|
  662. @cmd_help[c] = hlp || 'nodoc'
  663. @commands[c] = lambda { |*a| protect { b.call(*a) } }
  664. }
  665. end
  666. # arg str -> expr value, with special codeptr/dataptr = code/data.curaddr
  667. def parse_expr(arg)
  668. parse_expr!(arg.dup)
  669. end
  670. def parse_expr!(arg)
  671. @dbg.parse_expr!(arg) { |e|
  672. case e.downcase
  673. when 'code_addr', 'codeptr'; @parent_widget.code.curaddr
  674. when 'data_addr', 'dataptr'; @parent_widget.mem.curaddr
  675. end
  676. }
  677. end
  678. def solve_expr(arg)
  679. return arg if arg.kind_of? Integer
  680. solve_expr!(arg.dup)
  681. end
  682. def solve_expr!(arg)
  683. return if not e = parse_expr!(arg)
  684. @dbg.resolve_expr(e)
  685. end
  686. # update the data window, or dump data to console if len given
  687. def cmd_dd(addr, dlen=nil, len=nil)
  688. if addr.kind_of? String
  689. s = addr.strip
  690. addr = solve_expr!(s) || @parent_widget.mem.curaddr
  691. if not s.empty?
  692. s = s[1..-1] if s[0] == ?,
  693. len ||= solve_expr(s)
  694. end
  695. end
  696. if len
  697. while len > 0
  698. data = @dbg.memory[addr, [len, 16].min]
  699. le = (@dbg.cpu.endianness == :little)
  700. data = '' if @dbg.memory.page_invalid?(addr)
  701. case dlen
  702. when nil; add_log "#{Expression[addr]} #{data.unpack('C*').map { |c| '%02X' % c }.join(' ').ljust(2*16+15)} #{data.tr("^\x20-\x7e", '.')}"
  703. when 1; add_log "#{Expression[addr]} #{data.unpack('C*').map { |c| '%02X' % c }.join(' ')}"
  704. when 2; add_log "#{Expression[addr]} #{data.unpack(le ? 'v*' : 'n*').map { |c| '%04X' % c }.join(' ')}"
  705. when 4; add_log "#{Expression[addr]} #{data.unpack(le ? 'V*' : 'N*').map { |c| '%08X' % c }.join(' ')}"
  706. when 8; add_log "#{Expression[addr]} #{data.unpack('Q*').map { |c| '%016X' % c }.join(' ')}"
  707. end
  708. addr += 16
  709. len -= 16
  710. end
  711. else
  712. if dlen
  713. @parent_widget.mem.view(:hex).data_size = dlen
  714. @parent_widget.mem.view(:hex).resized
  715. @parent_widget.mem.showview(:hex)
  716. end
  717. @parent_widget.mem.focus_addr(solve_expr(addr))
  718. @parent_widget.mem.gui_update
  719. end
  720. end
  721. def init_commands
  722. @commands = {}
  723. @cmd_help = {}
  724. p = @parent_widget
  725. new_command('help') { add_log @commands.keys.sort.join(' ') } # TODO help <subject>
  726. new_command('d', 'focus data window on an address') { |arg| cmd_dd(arg) }
  727. new_command('db', 'dump/focus bytes in data window') { |arg| cmd_dd(arg, 1) }
  728. new_command('dw', 'dump/focus words in data window') { |arg| cmd_dd(arg, 2) }
  729. new_command('dd', 'dump/focus dwords in data window') { |arg| cmd_dd(arg, 4) }
  730. new_command('dq', 'dump/focus qwords in data window') { |arg| cmd_dd(arg, 8) }
  731. new_command('dc', 'focus C struct in data window: <name> <addr>') { |arg|
  732. name, addr = arg.strip.split(/\s+/, 2)
  733. addr = (addr ? solve_expr(addr) : @parent_widget.mem.curaddr)
  734. @parent_widget.mem.focus_addr(addr, :cstruct, false, name)
  735. }
  736. new_command('dC', 'dump C struct: dC <name> <addr>') { |arg|
  737. name, addr = arg.strip.split(/\s+/, 2)
  738. addr = (addr ? solve_expr(addr) : @parent_widget.mem.curaddr)
  739. if st = @dbg.disassembler.c_parser.decode_c_struct(name, @dbg.memory, addr)
  740. add_log st.to_s.gsub("\t", ' ')
  741. end
  742. }
  743. new_command('u', 'focus code window on an address') { |arg| p.code.focus_addr(solve_expr(arg)) }
  744. new_command('.', 'focus code window on current address') { p.code.focus_addr(solve_expr(@dbg.register_pc.to_s)) }
  745. new_command('wc', 'set code window height') { |arg|
  746. if arg == ''
  747. p.code.curview.grab_focus
  748. else
  749. p.resize_child(p.code, width, arg.to_i*@font_height)
  750. end
  751. }
  752. new_command('wd', 'set data window height') { |arg|
  753. if arg == ''
  754. p.mem.curview.grab_focus
  755. else
  756. p.resize_child(p.mem, width, arg.to_i*@font_height)
  757. end
  758. }
  759. new_command('wp', 'set console window height') { |arg|
  760. if arg == ''
  761. grab_focus
  762. else
  763. p.resize_child(self, width, arg.to_i*@font_height)
  764. end
  765. }
  766. new_command('width', 'set window width (chars)') { |arg|
  767. if a = solve_expr(arg); p.win.width = a*@font_width
  768. else add_log "width #{p.win.width/@font_width}"
  769. end
  770. }
  771. new_command('height', 'set window height (chars)') { |arg|
  772. if a = solve_expr(arg); p.win.height = a*@font_height
  773. else add_log "height #{p.win.height/@font_height}"
  774. end
  775. }
  776. new_command('continue', 'run', 'let the target run until something occurs') { p.dbg_continue }
  777. new_command('stepinto', 'singlestep', 'run a single instruction of the target') { p.dbg_singlestep }
  778. new_command('stepover', 'run a single instruction of the target, do not enter into subfunctions') { p.dbg_stepover }
  779. new_command('stepout', 'stepover until getting out of the current function') { p.dbg_stepout }
  780. new_command('bpx', 'set a breakpoint') { |arg|
  781. arg =~ /^(.*?)( once)?(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i
  782. e, o, c, a = $1, $2, ($3 || $5), $4
  783. o = o ? true : false
  784. cd = parse_expr(c) if c
  785. cb = lambda { a.split(';').each { |aaa| run_command(aaa) } } if a
  786. @dbg.bpx(solve_expr(e), o, cd, &cb)
  787. }
  788. new_command('hwbp', 'set a hardware breakpoint (hwbp 0x2345 w)') { |arg|
  789. arg =~ /^(.*?)( once)?( [rwx])?(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i
  790. e, o, t, c, a = $1, $2, $3, ($4 || $6), $5
  791. o = o ? true : false
  792. t = (t || 'x').strip.to_sym
  793. cd = parse_expr(c) if c
  794. cb = lambda { a.split(';').each { |aaa| run_command(aaa) } } if a
  795. @dbg.hwbp(solve_expr(e), t, 1, o, cd, &cb)
  796. }
  797. new_command('bpm', 'set a hardware memory breakpoint: bpm r 0x4800ff 16') { |arg|
  798. arg =~ /^(.*?)(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i
  799. e, c, a = $1, ($2 || $4), $3
  800. cd = parse_expr(c) if c
  801. cb = lambda { a.split(';').each { |aaa| run_command(aaa) } } if a
  802. raise 'bad syntax: bpm r|w|x addr [len]' unless e =~ /^([rwx]) (.*)/i
  803. mode = $1.downcase.to_sym
  804. e = $2
  805. exp = solve_expr!(e)
  806. len = solve_expr(e) if e != ''
  807. len ||= 1
  808. @dbg.bpm(exp, mode, len, false, cd, &cb)
  809. }
  810. new_command('g', 'wait until target reaches the specified address') { |arg|
  811. arg =~ /^(.*?)(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i
  812. e, c, a = $1, ($2 || $4), $3
  813. cd = parse_expr(c) if c
  814. cb = lambda { a.split(';').each { |aaa| run_command(aaa) } } if a
  815. @dbg.bpx(solve_expr(e), true, cd, &cb) if arg
  816. p.dbg_continue
  817. }
  818. new_command('refresh', 'redraw', 'update', 'update the target memory/register cache') {
  819. @dbg.invalidate
  820. @dbg.dasm_invalidate
  821. p.gui_update
  822. }
  823. new_command('bl', 'list breakpoints') {
  824. @bl = []
  825. @dbg.all_breakpoints.each { |b|
  826. add_log "#{@bl.length} #{@dbg.addrname!(b.address)} #{b.type} #{b.state}#{" if #{b.condition}" if b.condition}"
  827. @bl << b
  828. }
  829. }
  830. new_command('bc', 'clear breakpoints') { |arg|
  831. @bl ||= @dbg.all_breakpoints
  832. if arg == '*'
  833. @bl.each { |b| @dbg.del_bp(b) }
  834. else
  835. next if not i = solve_expr(arg)
  836. if b = @bl[i]
  837. @dbg.del_bp(b)
  838. end
  839. end
  840. }
  841. new_command('break', 'interrupt a running target') { |arg| @dbg.break ; p.post_dbg_run }
  842. new_command('kill', 'kill the target') { |arg| @dbg.kill(arg) ; p.post_dbg_run }
  843. new_command('detach', 'detach from the target') { @dbg.detach ; p.post_dbg_run }
  844. new_command('r', 'read/write the content of a register') { |arg|
  845. reg, val = arg.split(/\s+|\s*=\s*/, 2)
  846. if reg == 'fl'
  847. @dbg.toggle_flag(val.to_sym)
  848. elsif not reg
  849. @dbg.register_list.each { |r|
  850. add_log "#{r} = #{Expression[@dbg.get_reg_value(r)]}"
  851. }
  852. elsif not val
  853. add_log "#{reg} = #{Expression[@dbg.get_reg_value(reg.to_sym)]}"
  854. else
  855. @dbg.set_reg_value(reg.to_sym, solve_expr(val))
  856. end
  857. p.regs.gui_update
  858. }
  859. new_command('ma', 'memory_ascii', 'write memory (ascii) - ma <addr> foo bar') { |arg|
  860. next if not addr = solve_expr!(arg)
  861. data = arg.strip
  862. @dbg.memory[addr, data.length] = data
  863. @dbg.invalidate
  864. @dbg.dasm_invalidate
  865. p.gui_update
  866. }
  867. new_command('mx', 'memory_hex', 'write memory (hex) - mx <addr> 0011223344') { |arg|
  868. next if not addr = solve_expr!(arg)
  869. data = [arg.delete(' ')].pack('H*')
  870. @dbg.memory[addr, data.length] = data
  871. @dbg.invalidate
  872. @dbg.dasm_invalidate
  873. p.gui_update
  874. }
  875. new_command('?', 'display a value') { |arg|
  876. next if not v = solve_expr(arg)
  877. add_log "#{v} 0x#{v.to_s(16)} #{[v & 0xffff_ffff].pack('L').inspect} #{@dbg.addrname!(v)}"
  878. }
  879. new_command('exit', 'quit', 'quit the debugger interface') { p.win.destroy }
  880. new_command('ruby', 'execute arbitrary ruby code') { |arg|
  881. case ret = eval(arg)
  882. when nil, true, false, Symbol; add_log ret.inspect
  883. when String; add_log ret[0, 64].inspect
  884. when Integer, Expression; add_log Expression[ret].to_s
  885. else add_log "#<#{ret.class}>"
  886. end
  887. }
  888. new_command('loadsyms', 'load symbols from a mapped module') { |arg|
  889. if not arg.empty? and arg = (solve_expr(arg) rescue arg)
  890. @dbg.loadsyms(arg)
  891. else
  892. @dbg.loadallsyms { |a|
  893. @statusline = "loading symbols from #{Expression[a]}"
  894. redraw
  895. Gui.main_iter
  896. }
  897. end
  898. p.gui_update
  899. }
  900. new_command('scansyms', 'scan target memory for loaded modules') {
  901. if defined? @scan_addr and @scan_addr
  902. add_log 'scanning @%08x' % @scan_addr
  903. next
  904. end
  905. @scan_addr = 0
  906. Gui.idle_add {
  907. if @scan_addr <= 0xffff_f000 # cpu.size?
  908. protect { @dbg.loadsyms(@scan_addr) }
  909. @scan_addr += 0x1000
  910. true
  911. else
  912. add_log 'scansyms finished'
  913. @scan_addr = nil
  914. p.gui_update
  915. nil
  916. end
  917. }
  918. }
  919. new_command('symbol', 'display information on symbols') { |arg|
  920. arg = arg.to_s.downcase
  921. @dbg.symbols.map { |k, v| an = @dbg.addrname(k) ; [k, an] if an.downcase.include? arg }.compact.sort_by { |k, v| v.downcase }.each { |k, v|
  922. add_log "#{Expression[k]} #{@dbg.addrname(k)}"
  923. }
  924. }
  925. new_command('maps', 'show file mappings from parsed modules') { |arg|
  926. want = arg.to_s.downcase
  927. want = nil if want == ''
  928. @dbg.modulemap.map { |n, (a_b, a_e)|
  929. [a_b, "#{Expression[a_b]}-#{Expression[a_e]} #{n}"] if not want or n.downcase.include?(want)
  930. }.compact.sort.each { |s1, s2|
  931. add_log s2
  932. }
  933. }
  934. new_command('rawmaps', 'show OS file mappings') { |arg|
  935. # XXX listwindow
  936. @dbg.mappings.sort.each { |a, l, *i|
  937. foo = i*' '
  938. next if arg.to_s != '' and foo !~ /#{arg}/i
  939. add_log "%08x %06x %s" % [a, l, i*' ']
  940. }
  941. }
  942. new_command('add_symbol', 'add a symbol name') { |arg|
  943. name, val = arg.to_s.split(/\s+/, 2)
  944. val = solve_expr(val)
  945. if val.kind_of? Integer
  946. @dbg.symbols[val] = name
  947. @dbg.disassembler.set_label_at(val, name)
  948. p.gui_update
  949. end
  950. }
  951. new_command('bt', 'backtrace', 'stacktrace', 'bt [limit] - show a stack trace from current pc') { |arg|
  952. arg = solve_expr(arg) if arg
  953. arg = 500 if not arg.kind_of? ::Integer
  954. @dbg.stacktrace(arg) { |a, s| add_log "#{Expression[a]} #{s}" }
  955. }
  956. new_command('dasm', 'disassemble_fast', 'disassembles from an address') { |arg|
  957. addr = solve_expr(arg)
  958. dasm = @dbg.disassembler
  959. dasm.disassemble_fast(addr)
  960. dasm.function_blocks(addr).keys.sort.each { |a|
  961. next if not di = dasm.di_at(a)
  962. dasm.dump_block(di.block) { |l| add_log l }
  963. }
  964. p.gui_update
  965. }
  966. new_command('save_hist', 'save the command buffer to a file') { |arg|
  967. File.open(arg, 'w') { |fd| fd.puts @log }
  968. }
  969. new_command('watch', 'follow an expression in the data view (none to delete)') { |arg|
  970. if not arg
  971. add_log p.watchpoint[p.mem].to_s
  972. elsif arg == 'nil' or arg == 'none' or arg == 'delete'
  973. p.watchpoint.delete p.mem
  974. else
  975. p.watchpoint[p.mem] = parse_expr(arg)
  976. end
  977. }
  978. new_command('list_pid', 'list pids currently debugged') { |arg|
  979. add_log @dbg.list_debug_pids.sort.map { |pp| pp == @dbg.pid ? "*#{pp}" : pp }.join(' ')
  980. }
  981. new_command('list_tid', 'list tids currently debugged') { |arg|
  982. add_log @dbg.list_debug_tids.sort.map { |tt| tt == @dbg.tid ? "*#{tt}" : tt }.join(' ')
  983. }
  984. new_command('list_processes', 'list processes available for debugging') { |arg|
  985. @dbg.list_processes.each { |pp|
  986. add_log "#{pp.pid} #{pp.path}"
  987. }
  988. }
  989. new_command('list_threads', 'list thread ids of the current process') { |arg|
  990. @dbg.list_threads.each { |t|
  991. stf = { :state => @dbg.state, :info => @dbg.info } if t == @dbg.tid
  992. stf ||= @dbg.tid_stuff[t]
  993. stf ||= {}
  994. add_log "#{t} #{stf[:state]} #{stf[:info]}"
  995. }
  996. }
  997. new_command('pid', 'select a pid') { |arg|
  998. if pid = solve_expr(arg)
  999. @dbg.pid = pid
  1000. else
  1001. add_log "pid #{@dbg.pid}"
  1002. end
  1003. }
  1004. new_command('tid', 'select a tid') { |arg|
  1005. if tid = solve_expr(arg)
  1006. @dbg.tid = tid
  1007. else
  1008. add_log "tid #{@dbg.tid} #{@dbg.state} #{@dbg.info}"
  1009. end
  1010. }
  1011. new_command('exception_pass', 'pass the exception unhandled to the target on next continue') {
  1012. @dbg.pass_current_exception
  1013. }
  1014. new_command('exception_handle', 'handle the exception, hide it from the target on next continue') {
  1015. @dbg.pass_current_exception false
  1016. }
  1017. new_command('exception_pass_all', 'ignore all target exceptions') {
  1018. @dbg.pass_all_exceptions = true
  1019. }
  1020. new_command('exception_handle_all', 'break on target exceptions') {
  1021. @dbg.pass_all_exceptions = false
  1022. }
  1023. new_command('thread_events_break', 'break on thread creation/termination') {
  1024. @dbg.ignore_newthread = false
  1025. @dbg.ignore_endthread = false
  1026. }
  1027. new_command('thread_events_ignore', 'ignore thread creation/termination') {
  1028. @dbg.ignore_newthread = true
  1029. @dbg.ignore_endthread = true
  1030. }
  1031. new_command('trace_children', 'trace children of debuggee (0|1)') { |arg|
  1032. arg = case arg.to_s.strip.downcase
  1033. when '0', 'no', 'false'; false
  1034. else true
  1035. end
  1036. add_log "trace children #{arg ? 'active' : 'inactive'}"
  1037. # update the flag for future debugee
  1038. @dbg.trace_children = arg
  1039. # change current debugee setting if supported
  1040. @dbg.do_trace_children if @dbg.respond_to?(:do_trace_children)
  1041. }
  1042. new_command('attach', 'attach to a running process') { |arg|
  1043. if pr = @dbg.list_processes.find { |pp| pp.path.to_s.downcase.include?(arg.downcase) }
  1044. pid = pr.pid
  1045. else
  1046. pid = solve_expr(arg)
  1047. end
  1048. @dbg.attach(pid)
  1049. }
  1050. new_command('create_process', 'create a new process and debug it') { |arg|
  1051. @dbg.create_process(arg)
  1052. }
  1053. new_command('plugin', 'load', 'load a debugger plugin') { |arg|
  1054. @dbg.load_plugin arg
  1055. add_log "loaded plugin #{File.basename(arg, '.rb')}"
  1056. }
  1057. @dbg.ui_command_setup(self) if @dbg.respond_to? :ui_command_setup
  1058. end
  1059. def wrap_run(&b) @parent_widget.wrap_run(&b) end
  1060. def keyboard_callback; @parent_widget.keyboard_callback end
  1061. def keyboard_callback_ctrl; @parent_widget.keyboard_callback_ctrl end
  1062. def handle_command
  1063. add_log(":#@curline")
  1064. return if @curline == ''
  1065. @cmd_history << @curline
  1066. @cmd_history.shift if @cmd_history.length > @cmd_history_length
  1067. @log_offset = 0
  1068. cmd = @curline
  1069. @curline = ''
  1070. @caret_x = 0
  1071. run_command(cmd)
  1072. end
  1073. def run_command(cmd)
  1074. cmd = cmd.sub(/^\s+/, '')
  1075. cn = cmd.split.first
  1076. if not @commands[cn]
  1077. a = @commands.keys.find_all { |k| k[0, cn.length] == cn }
  1078. cn = a.first if a.length == 1
  1079. end
  1080. if pc = @commands[cn]
  1081. pc[cmd.split(/\s+/, 2)[1].to_s]
  1082. else
  1083. add_log 'unknown command'
  1084. end
  1085. end
  1086. def add_log(l)
  1087. @log << l.to_s
  1088. @log.shift if log.length > @log_length
  1089. redraw
  1090. end
  1091. def gui_update
  1092. redraw
  1093. end
  1094. # hint that the caret moved
  1095. def update_caret
  1096. return if @oldcaret_x == @caret_x
  1097. w_w = width - @font_width
  1098. x1 = (@oldcaret_x+1) * @font_width + 1
  1099. x2 = (@caret_x+1) * @font_width + 1
  1100. y = @caret_y
  1101. if x1 > w_w or x2 > w_w
  1102. invalidate(0, y, 100000, @font_height)
  1103. else
  1104. invalidate(x1-1, y, 2, @font_height)
  1105. invalidate(x2-1, y, 2, @font_height)
  1106. end
  1107. @oldcaret_x = @caret_x
  1108. end
  1109. end
  1110. class DbgWindow < Window
  1111. attr_accessor :dbg_widget
  1112. def initialize_window(dbg = nil, title='metasm debugger')
  1113. self.title = title
  1114. display(dbg) if dbg
  1115. end
  1116. # show a new DbgWidget
  1117. def display(dbg)
  1118. @dbg_widget = DbgWidget.new(dbg)
  1119. @dbg_widget.win = self
  1120. self.widget = @dbg_widget
  1121. @dbg_widget
  1122. end
  1123. def build_menu
  1124. filemenu = new_menu
  1125. addsubmenu(filemenu, '_attach process') { @dbg_widget.prompt_attach }
  1126. addsubmenu(filemenu, 'create _process') { @dbg_widget.prompt_createprocess }
  1127. addsubmenu(filemenu, 'open _dasm window') { DasmWindow.new }
  1128. addsubmenu(filemenu)
  1129. addsubmenu(filemenu, 'QUIT') { destroy }
  1130. addsubmenu(@menu, filemenu, '_File')
  1131. dbgmenu = new_menu
  1132. addsubmenu(dbgmenu, 'continue', '<f5>') { @dbg_widget.dbg_continue }
  1133. addsubmenu(dbgmenu, 'step over', '<f10>') { @dbg_widget.dbg_stepover }
  1134. addsubmenu(dbgmenu, 'step into', '<f11>') { @dbg_widget.dbg_singlestep }
  1135. addsubmenu(dbgmenu, '_kill target') { @dbg_widget.dbg.kill }
  1136. addsubmenu(dbgmenu, '_detach target') { @dbg_widget.dbg.detach }
  1137. addsubmenu(dbgmenu)
  1138. addsubmenu(dbgmenu, 'QUIT') { destroy }
  1139. addsubmenu(@menu, dbgmenu, '_Debug')
  1140. codeviewmenu = new_menu
  1141. addsubmenu(codeviewmenu, '_listing') { @dbg_widget.code.focus_addr(@dbg_widget.code.curaddr, :listing) }
  1142. addsubmenu(codeviewmenu, '_graph') { @dbg_widget.code.focus_addr(@dbg_widget.code.curaddr, :graph) }
  1143. addsubmenu(codeviewmenu, 'raw _opcodes') { @dbg_widget.code.focus_addr(@dbg_widget.code.curaddr, :opcodes) }
  1144. dataviewmenu = new_menu
  1145. addsubmenu(dataviewmenu, '_hexa') { @dbg_widget.mem.focus_addr(@dbg_widget.mem.curaddr, :hex) }
  1146. addsubmenu(dataviewmenu, 'raw _opcodes') { @dbg_widget.mem.focus_addr(@dbg_widget.mem.curaddr, :opcodes) }
  1147. addsubmenu(dataviewmenu, '_c struct') { @dbg_widget.mem.focus_addr(@dbg_widget.mem.curaddr, :cstruct) }
  1148. focusmenu = new_menu
  1149. addsubmenu(focusmenu, '_regs') { @dbg_widget.regs.grab_focus ; @dbg_widget.redraw }
  1150. addsubmenu(focusmenu, '_data') { @dbg_widget.mem.grab_focus ; @dbg_widget.redraw }
  1151. addsubmenu(focusmenu, '_code') { @dbg_widget.code.grab_focus ; @dbg_widget.redraw }
  1152. addsubmenu(focusmenu, 'conso_le', '.') { @dbg_widget.console.grab_focus ; @dbg_widget.redraw }
  1153. viewmenu = new_menu
  1154. addsubmenu(viewmenu, codeviewmenu, '_code display')
  1155. addsubmenu(viewmenu, dataviewmenu, '_data display')
  1156. addsubmenu(viewmenu, focusmenu, '_focus')
  1157. addsubmenu(viewmenu, 'data _watch') { @dbg_widget.prompt_datawatch }
  1158. addsubmenu(@menu, viewmenu, '_Views')
  1159. end
  1160. end
  1161. end
  1162. end