metasm 1.0.0

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