metasm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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