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,906 @@
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
+
7
+ require 'metasm/gui/dasm_hex'
8
+ require 'metasm/gui/dasm_listing'
9
+ require 'metasm/gui/dasm_opcodes'
10
+ require 'metasm/gui/dasm_coverage'
11
+ require 'metasm/gui/dasm_graph'
12
+ require 'metasm/gui/dasm_decomp'
13
+ require 'metasm/gui/dasm_funcgraph'
14
+ require 'metasm/gui/cstruct'
15
+
16
+ module Metasm
17
+ module Gui
18
+ # the main disassembler widget: this is a container for all the lower-level widgets that actually render the dasm state
19
+ class DisasmWidget < ContainerChoiceWidget
20
+ attr_accessor :entrypoints, :gui_update_counter_max
21
+ attr_accessor :keyboard_callback, :keyboard_callback_ctrl # hash key => lambda { |key| true if handled }
22
+ attr_accessor :clones
23
+ attr_accessor :pos_history, :pos_history_redo
24
+ attr_accessor :bg_color_callback # proc { |address| "rgb" # "00f" -> blue }
25
+ attr_accessor :focus_changed_callback
26
+ attr_accessor :parent_widget
27
+
28
+ def initialize_widget(dasm, ep=[])
29
+ @dasm = dasm
30
+ @dasm.gui = self
31
+ ep = [ep] if not ep.kind_of? Array
32
+ @entrypoints = ep
33
+ @pos_history = []
34
+ @pos_history_redo = []
35
+ @keyboard_callback = {}
36
+ @keyboard_callback_ctrl = {}
37
+ @clones = [self]
38
+ @parent_widget = nil
39
+ @gui_update_counter_max = 100
40
+ @dasm.callback_prebacktrace ||= lambda { Gui.main_iter }
41
+ start_disassemble_bg
42
+
43
+ addview :listing, AsmListingWidget.new(@dasm, self)
44
+ addview :graph, GraphViewWidget.new(@dasm, self)
45
+ addview :decompile, CdecompListingWidget.new(@dasm, self)
46
+ addview :opcodes, AsmOpcodeWidget.new(@dasm, self)
47
+ addview :hex, HexWidget.new(@dasm, self)
48
+ addview :coverage, CoverageWidget.new(@dasm, self)
49
+ addview :funcgraph, FuncGraphViewWidget.new(@dasm, self)
50
+ addview :cstruct, CStructWidget.new(@dasm, self)
51
+
52
+ view(:listing).grab_focus
53
+ end
54
+
55
+ attr_reader :dasm
56
+ # when updating @dasm, also update dasm for all views
57
+ def dasm=(d)
58
+ @dasm = d
59
+ view_indexes.each { |v|
60
+ w = view(v)
61
+ w.dasm = d if w.respond_to?(:'dasm=')
62
+ }
63
+ end
64
+
65
+ # start an idle callback that will run one round of @dasm.disassemble_mainiter
66
+ def start_disassemble_bg
67
+ gui_update_counter = 0
68
+ run = false
69
+ Gui.idle_add {
70
+ # metasm disassembler loop
71
+ # update gui once in a while
72
+ if run or not @entrypoints.empty? or not @dasm.addrs_todo.empty?
73
+ protect { run = @dasm.disassemble_mainiter(@entrypoints) }
74
+ gui_update_counter += 1
75
+ if gui_update_counter > @gui_update_counter_max
76
+ gui_update_counter = 0
77
+ gui_update
78
+ end
79
+ true
80
+ else
81
+ gui_update
82
+ false
83
+ end
84
+ }
85
+ end
86
+
87
+ def terminate
88
+ @clones.delete self
89
+ end
90
+
91
+ # returns the address of the item under the cursor in current view
92
+ def curaddr
93
+ curview.current_address
94
+ end
95
+
96
+ # returns the object under the cursor in current view (@dasm.decoded[curaddr])
97
+ def curobj
98
+ @dasm.decoded[curaddr]
99
+ end
100
+
101
+ # returns the address of the label under the cursor or the address of the line of the cursor
102
+ def pointed_addr
103
+ hl = curview.hl_word
104
+ if hl =~ /^[0-9].*h$/ and a = hl.to_i(16) and @dasm.get_section_at(a)
105
+ return a
106
+ end
107
+ @dasm.prog_binding[hl] || curview.current_address
108
+ end
109
+
110
+ # parse an address and change it to a canonical address form
111
+ # supported formats: label names, or string with numerical value, incl hex (0x42 and 42h)
112
+ # if the string is full decimal, a check against mapped space is done to find if it is
113
+ # hexadecimal (eg 08048000)
114
+ def normalize(addr)
115
+ case addr
116
+ when ::String
117
+ if @dasm.prog_binding[addr]
118
+ addr = @dasm.prog_binding[addr]
119
+ elsif (?0..?9).include? addr[0] or (?a..?f).include? addr.downcase[0]
120
+ case addr
121
+ when /^0x/i
122
+ when /h$/i; addr = '0x' + addr[0...-1]
123
+ when /[a-f]/i; addr = '0x' + addr
124
+ when /^[0-9]+$/
125
+ addr = '0x' + addr if not @dasm.get_section_at(addr.to_i) and
126
+ @dasm.get_section_at(addr.to_i(16))
127
+ end
128
+ begin
129
+ addr = Integer(addr)
130
+ rescue ::ArgumentError
131
+ return
132
+ end
133
+ else
134
+ return
135
+ end
136
+ end
137
+ addr
138
+ end
139
+
140
+ # display the specified address
141
+ # the display first searches in the current view (graph, listing, etc),
142
+ # if it cannot display the address all other views are tried in order
143
+ # the current focus address is saved in @pos_history (see focus_addr_back/redo)
144
+ # a messagebox is popped if no view can display the address unless quiet is true
145
+ def focus_addr(addr, viewidx=nil, quiet=false, *a)
146
+ viewidx ||= curview_index || :listing
147
+ return if not addr
148
+ return if viewidx == curview_index and addr == curaddr
149
+ oldpos = [curview_index, (curview.get_cursor_pos if curview)]
150
+ views = [viewidx, oldpos[0]]
151
+ views += [:listing, :graph] & view_indexes
152
+ if views.compact.uniq.find { |i|
153
+ o_p = view(i).get_cursor_pos
154
+ if (view(i).focus_addr(addr, *a) rescue nil)
155
+ view(i).gui_update if i != oldpos[0]
156
+ showview(i)
157
+ true
158
+ else
159
+ view(i).set_cursor_pos o_p
160
+ false
161
+ end
162
+ }
163
+ @pos_history << oldpos if oldpos[0] # ignore start focus_addr
164
+ @pos_history_redo.clear
165
+ true
166
+ else
167
+ messagebox "Invalid address #{addr}" if not quiet
168
+ if oldpos[0]
169
+ showview oldpos[0]
170
+ curview.set_cursor_pos oldpos[1]
171
+ end
172
+ false
173
+ end
174
+ end
175
+
176
+ # focus on the last address seen before the last focus_addr
177
+ def focus_addr_back(val = @pos_history.pop)
178
+ return if not val
179
+ @pos_history_redo << [curview_index, curview.get_cursor_pos]
180
+ showview val[0]
181
+ curview.set_cursor_pos val[1]
182
+ true
183
+ end
184
+
185
+ # undo focus_addr_back
186
+ def focus_addr_redo
187
+ if val = @pos_history_redo.pop
188
+ @pos_history << [curview_index, curview.get_cursor_pos]
189
+ showview val[0]
190
+ curview.set_cursor_pos val[1]
191
+ end
192
+ end
193
+
194
+ # ask the current view to update itself and redraw (incl all cloned widgets)
195
+ def gui_update
196
+ @clones.each { |c| c.do_gui_update }
197
+ end
198
+
199
+ # ask the current view to update itself
200
+ def do_gui_update
201
+ curview.gui_update # invalidate all views ?
202
+ end
203
+
204
+ # redraw the window
205
+ def redraw
206
+ curview.redraw
207
+ end
208
+
209
+ # calls focus_addr(pre_yield_curaddr) after yield
210
+ def keep_focus_while
211
+ addr = curaddr
212
+ yield
213
+ focus_addr curaddr if addr
214
+ end
215
+
216
+ # calls listwindow with the same argument, but also creates a new bg_color_callback
217
+ # that will color lines whose address is to be found in list[0] in green
218
+ # the callback is put only for the duration of the listwindow, and is not reentrant.
219
+ def list_bghilight(title, list, a={}, &b)
220
+ prev_colorcb = bg_color_callback
221
+ hash = list[1..-1].inject({}) { |h, l| h.update Expression[l[0] || :unknown].reduce => true }
222
+ @bg_color_callback = lambda { |addr| hash[addr] ? '0f0' : prev_colorcb ? prev_colorcb[addr] : nil }
223
+ redraw
224
+ popupend = lambda { @bg_color_callback = prev_colorcb ; redraw }
225
+ listwindow(title, list, a.merge(:ondestroy => popupend), &b)
226
+ end
227
+
228
+ # add/change a comment @addr
229
+ def add_comment(addr)
230
+ cmt = @dasm.comment[addr].to_a.join(' ')
231
+ if di = @dasm.di_at(addr)
232
+ cmt += di.comment.to_a.join(' ')
233
+ end
234
+ inputbox("new comment for #{Expression[addr]}", :text => cmt) { |c|
235
+ c = c.split("\n")
236
+ c = nil if c == []
237
+ if di = @dasm.di_at(addr)
238
+ di.comment = c
239
+ else
240
+ @dasm.comment[addr] = c
241
+ end
242
+ gui_update
243
+ }
244
+ end
245
+
246
+ # disassemble from this point
247
+ # if points to a call, make it return
248
+ def disassemble(addr)
249
+ if di = @dasm.di_at(addr) and di.opcode.props[:saveip]
250
+ di.block.each_to_normal { |t|
251
+ t = @dasm.normalize t
252
+ next if not @dasm.decoded[t]
253
+ @dasm.function[t] ||= @dasm.function[:default] ? @dasm.function[:default].dup : DecodedFunction.new
254
+ }
255
+ di.block.add_to_subfuncret(di.next_addr)
256
+ @dasm.addrs_todo << [di.next_addr, addr, true]
257
+ elsif addr
258
+ @dasm.addrs_todo << [addr]
259
+ end
260
+ start_disassemble_bg
261
+ end
262
+
263
+ # disassemble fast from this point (don't dasm subfunctions, don't backtrace)
264
+ def disassemble_fast(addr)
265
+ @dasm.disassemble_fast(addr)
266
+ gui_update
267
+ end
268
+
269
+ # disassemble fast & deep from this point (don't backtrace, but still dasm subfuncs)
270
+ def disassemble_fast_deep(addr)
271
+ @dasm.disassemble_fast_deep(addr)
272
+ gui_update
273
+ end
274
+
275
+ # (re)decompile
276
+ def decompile(addr)
277
+ if @dasm.c_parser and var = @dasm.c_parser.toplevel.symbol[addr] and (var.type.kind_of? C::Function or @dasm.di_at(addr))
278
+ @dasm.decompiler.redecompile(addr)
279
+ view(:decompile).curaddr = nil
280
+ end
281
+ focus_addr(addr, :decompile)
282
+ end
283
+
284
+ # change the format of displayed data under addr (byte, word, dword, qword)
285
+ # currently this is done using a fake empty xref
286
+ def toggle_data(addr)
287
+ return if @dasm.decoded[addr] or not @dasm.get_section_at(addr)
288
+ @dasm.add_xref(addr, Xref.new(nil, nil, 1)) if not @dasm.xrefs[addr]
289
+ @dasm.each_xref(addr) { |x|
290
+ x.len = {1 => 2, 2 => 4, 4 => 8}[x.len] || 1
291
+ break
292
+ }
293
+ gui_update
294
+ end
295
+
296
+ def list_functions
297
+ list = [['name', 'addr']]
298
+ @dasm.function.keys.each { |f|
299
+ addr = @dasm.normalize(f)
300
+ next if not @dasm.di_at(addr)
301
+ list << [@dasm.get_label_at(addr), Expression[addr]]
302
+ }
303
+ title = "list of functions"
304
+ listwindow(title, list) { |i| focus_addr i[1] }
305
+ end
306
+
307
+ def list_labels
308
+ list = [['name', 'addr']]
309
+ @dasm.prog_binding.each { |k, v|
310
+ list << [k, Expression[@dasm.normalize(v)]]
311
+ }
312
+ listwindow("list of labels", list) { |i| focus_addr i[1] }
313
+ end
314
+
315
+ def list_sections
316
+ list = [['addr', 'length', 'name', 'info']]
317
+ @dasm.section_info.each { |n,a,l,i|
318
+ list << [Expression[a], Expression[l], n, i]
319
+ }
320
+ listwindow("list of sections", list) { |i| focus_addr i[0] if i[0] != '0' or @dasm.get_section_at(0) }
321
+ end
322
+
323
+ def list_strings
324
+ list = [['addr', 'string', 'length']]
325
+ @dasm.strings_scan { |o, str|
326
+ list << [Expression[o], str[0, 24].inspect, str.length]
327
+ }
328
+ listwindow("list of strings", list) { |i| focus_addr i[0] }
329
+ end
330
+
331
+ def list_xrefs(addr)
332
+ list = [['address', 'type', 'instr']]
333
+ @dasm.each_xref(addr) { |xr|
334
+ next if not xr.origin
335
+ list << [Expression[xr.origin], "#{xr.type}#{xr.len}"]
336
+ if di = @dasm.di_at(xr.origin)
337
+ list.last << di.instruction
338
+ end
339
+ }
340
+ if list.length == 1
341
+ messagebox "no xref to #{Expression[addr]}" if addr
342
+ else
343
+ listwindow("list of xrefs to #{Expression[addr]}", list) { |i| focus_addr(i[0], nil, true) }
344
+ end
345
+ end
346
+
347
+ # jump to address
348
+ def prompt_goto
349
+ inputbox('address to go', :text => Expression[curaddr]) { |v|
350
+ focus_addr_autocomplete(v)
351
+ }
352
+ end
353
+
354
+ def prompt_backtrace
355
+ addr = curaddr
356
+ inputbox('expression to backtrace', :text => curview.hl_word) { |e|
357
+ expr = IndExpression.parse_string(e)
358
+ bd = {}
359
+ registers = (@dasm.cpu.dbg_register_list.map { |r| r.to_s } rescue [])
360
+ expr.externals.grep(String).each { |w|
361
+ if registers.include? w.downcase
362
+ bd[w] = w.downcase.to_sym
363
+ end
364
+ }
365
+ expr = expr.bind(bd).reduce { |e_| e_.len ||= @dasm.cpu.size/8 if e_.kind_of? Indirection ; nil }
366
+
367
+ log = []
368
+ dasm.backtrace(expr, addr, :log => log)
369
+ list = [['address', 'type', 'old value', 'value']]
370
+ log.each { |t, *a|
371
+ list << [Expression[a[-1]], t]
372
+ case t
373
+ when :start
374
+ list.last << a[0]
375
+ when :up
376
+ list.pop
377
+ when :di
378
+ list.last << a[1] << a[0]
379
+ when :func
380
+ list.last << a[1] << a[0]
381
+ when :found
382
+ list.pop
383
+ a[0].each { |e_| list << [nil, :found, Expression[e_]] }
384
+ else
385
+ list.last << a[0] << a[1..-1].inspect
386
+ end
387
+ }
388
+ list_bghilight("backtrace #{expr} from #{Expression[addr]}", list) { |i|
389
+ a = i[0].empty? ? i[2] : i[0]
390
+ focus_addr(a, nil, true)
391
+ }
392
+ }
393
+ end
394
+
395
+ # same as focus_addr, also understands partial label names
396
+ # if the partial part is ambiguous, show a listwindow with all matches (if show_alt)
397
+ def focus_addr_autocomplete(v, show_alt=true)
398
+ if not focus_addr(v, nil, true)
399
+ labels = @dasm.prog_binding.map { |k, vv|
400
+ [k, Expression[@dasm.normalize(vv)]] if k.downcase.include? v.downcase
401
+ }.compact
402
+ case labels.length
403
+ when 0; focus_addr(v)
404
+ when 1; focus_addr(labels[0][0])
405
+ else
406
+ if labels.all? { |k, vv| vv == labels[0][1] }
407
+ focus_addr(labels[0][0])
408
+ elsif show_alt
409
+ labels.unshift ['name', 'addr']
410
+ listwindow("list of labels", labels) { |i| focus_addr i[1] }
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ # parses a C header
417
+ def prompt_parse_c_file
418
+ openfile('open C header') { |f|
419
+ @dasm.parse_c_file(f) rescue messagebox("#{$!}\n#{$!.backtrace}")
420
+ }
421
+ end
422
+
423
+ # run arbitrary ruby
424
+ def prompt_run_ruby
425
+ inputbox('ruby code to eval()') { |c| messagebox eval(c).inspect[0, 512], 'eval' }
426
+ end
427
+
428
+ # run ruby plugin
429
+ def prompt_run_ruby_plugin
430
+ openfile('ruby plugin') { |f| @dasm.load_plugin(f) }
431
+ end
432
+
433
+ # search for a regexp in #dasm.decoded.to_s
434
+ def prompt_search_decoded
435
+ inputbox('text to search in instrs (regex)', :text => curview.hl_word) { |pat|
436
+ re = /#{pat}/i
437
+ found = []
438
+ @dasm.decoded.each { |k, v|
439
+ found << k if v.to_s =~ re
440
+ }
441
+ list = [['addr', 'str']] + found.map { |a| [Expression[a], @dasm.decoded[a].to_s] }
442
+ list_bghilight("search result for /#{pat}/i", list) { |i| focus_addr i[0] }
443
+ }
444
+ end
445
+
446
+ # calls the @dasm.rebase method to change the load base address of the current program
447
+ def rebase(addr=nil)
448
+ if not addr
449
+ inputbox('rebase address') { |a| rebase(Integer(a)) }
450
+ else
451
+ na = curaddr + dasm.rebase(addr)
452
+ gui_update
453
+ focus_addr na
454
+ end
455
+ end
456
+
457
+ # prompts for a new name for addr
458
+ def rename_label(addr)
459
+ old = addr
460
+ if @dasm.prog_binding[old] or old = @dasm.get_label_at(addr)
461
+ inputbox("new name for #{old}", :text => old) { |v|
462
+ if v == ''
463
+ @dasm.del_label_at(addr)
464
+ else
465
+ @dasm.rename_label(old, v)
466
+ end
467
+ gui_update
468
+ }
469
+ else
470
+ inputbox("label name for #{Expression[addr]}", :text => Expression[addr]) { |v|
471
+ next if v == ''
472
+ @dasm.set_label_at(addr, v)
473
+ if di = @dasm.di_at(addr)
474
+ @dasm.split_block(di.block, di.address)
475
+ end
476
+ gui_update
477
+ }
478
+ end
479
+ end
480
+
481
+ # pause/play disassembler
482
+ # returns true if playing
483
+ # this empties @dasm.addrs_todo, the dasm may still continue to work if this msg is
484
+ # handled during an instr decoding/backtrace (the backtrace may generate new addrs_todo)
485
+ # addresses in addrs_todo pointing to existing decoded instructions are left to create a prettier graph
486
+ def playpause_dasm
487
+ @dasm_pause ||= []
488
+ if @dasm_pause.empty? and @dasm.addrs_todo.empty?
489
+ true
490
+ elsif @dasm_pause.empty?
491
+ @dasm_pause = @dasm.addrs_todo.dup
492
+ @dasm.addrs_todo.replace @dasm_pause.find_all { |a, *b| @dasm.decoded[@dasm.normalize(a)] }
493
+ @dasm_pause -= @dasm.addrs_todo
494
+ puts "dasm paused (#{@dasm_pause.length})"
495
+ else
496
+ @dasm.addrs_todo.concat @dasm_pause
497
+ @dasm_pause.clear
498
+ puts "dasm restarted (#{@dasm.addrs_todo.length})"
499
+ start_disassemble_bg
500
+ true
501
+ end
502
+ end
503
+
504
+ # toggles <41h> vs <'A'> display
505
+ def toggle_expr_char(o)
506
+ @dasm.toggle_expr_char(o)
507
+ gui_update
508
+ end
509
+
510
+ # toggle <401000h> vs <'sub_fancyname'> in the current instr display
511
+ def toggle_expr_offset(o)
512
+ @dasm.toggle_expr_offset(o)
513
+ gui_update
514
+ end
515
+
516
+ def toggle_view(idx)
517
+ default = (idx == :graph ? :listing : :graph)
518
+ # switch to idx ; if already in idx, use default
519
+ focus_addr(curaddr, ((curview_index == idx) ? default : idx))
520
+ end
521
+
522
+ # undefines the whole function body
523
+ def undefine_function(addr, incl_subfuncs = false)
524
+ list = []
525
+ @dasm.each_function_block(addr, incl_subfuncs) { |b| list << b }
526
+ list.each { |b| @dasm.undefine_from(b) }
527
+ gui_update
528
+ end
529
+
530
+ def keypress_ctrl(key)
531
+ return true if @keyboard_callback_ctrl[key] and @keyboard_callback_ctrl[key][key]
532
+ case key
533
+ when :enter; focus_addr_redo
534
+ when ?o; w = toplevel ; w.promptopen if w.respond_to? :promptopen
535
+ when ?s; w = toplevel ; w.promptsave if w.respond_to? :promptsave
536
+ when ?r; prompt_run_ruby
537
+ when ?C; disassemble_fast_deep(curaddr)
538
+ when ?f; prompt_search_decoded
539
+ else return @parent_widget ? @parent_widget.keypress_ctrl(key) : false
540
+ end
541
+ true
542
+ end
543
+
544
+ def keypress(key)
545
+ return true if @keyboard_callback[key] and @keyboard_callback[key][key]
546
+ case key
547
+ when :enter; focus_addr curview.hl_word
548
+ when :esc; focus_addr_back
549
+ when ?/; inputbox('search word') { |w|
550
+ next unless curview.respond_to? :hl_word
551
+ next if w == ''
552
+ curview.hl_word = w
553
+ curview.redraw
554
+ }
555
+ when ?b; prompt_backtrace
556
+ when ?c; disassemble(curview.current_address)
557
+ when ?C; disassemble_fast(curview.current_address)
558
+ when ?d; toggle_data(curview.current_address)
559
+ when ?f; list_functions
560
+ when ?g; prompt_goto
561
+ when ?l; list_labels
562
+ when ?n; rename_label(pointed_addr)
563
+ when ?o; toggle_expr_offset(curobj)
564
+ when ?p; playpause_dasm
565
+ when ?r; toggle_expr_char(curobj)
566
+ when ?v; $VERBOSE = ! $VERBOSE ; puts "#{'not ' if not $VERBOSE}verbose" # toggle verbose flag
567
+ when ?x; list_xrefs(pointed_addr)
568
+ when ?;; add_comment(curview.current_address)
569
+
570
+ when ?\ ; toggle_view(:listing)
571
+ when :tab; toggle_view(:decompile)
572
+ when ?j; curview.keypress(:down)
573
+ when ?k; curview.keypress(:up)
574
+ else
575
+ p key if $DEBUG
576
+ return @parent_widget ? @parent_widget.keypress(key) : false
577
+ end
578
+ true
579
+ end
580
+
581
+ # creates a new dasm window with the same disassembler object, focus it on addr#win
582
+ def clone_window(*focus)
583
+ return if not popup = DasmWindow.new
584
+ popup.display(@dasm, @entrypoints)
585
+ w = popup.dasm_widget
586
+ w.bg_color_callback = @bg_color_callback if bg_color_callback
587
+ w.keyboard_callback = @keyboard_callback
588
+ w.keyboard_callback_ctrl = @keyboard_callback_ctrl
589
+ w.clones = @clones.concat w.clones
590
+ w.focus_addr(*focus)
591
+ popup
592
+ end
593
+
594
+ def dragdropfile(f)
595
+ case f
596
+ when /\.(c|h|cpp)$/; @dasm.parse_c_file(f)
597
+ when /\.map$/; @dasm.load_map(f)
598
+ when /\.rb$/; @dasm.load_plugin(f)
599
+ else messagebox("unsupported file extension #{f}")
600
+ end
601
+ end
602
+ end
603
+
604
+ # this widget is loaded in an empty DasmWindow to handle shortcuts (open file, etc)
605
+ class NoDasmWidget < DrawableWidget
606
+ def initialize_widget(window)
607
+ @window = window
608
+ end
609
+
610
+ def paint
611
+ end
612
+
613
+ def keypress(key)
614
+ case key
615
+ when ?v; $VERBOSE = !$VERBOSE
616
+ when ?d; $DEBUG = !$DEBUG
617
+ end
618
+ end
619
+
620
+ def keypress_ctrl(key)
621
+ case key
622
+ when ?o; @window.promptopen
623
+ when ?r; @window.promptruby
624
+ end
625
+ end
626
+
627
+ def dragdropfile(f)
628
+ case f
629
+ when /\.(c|h|cpp)$/; messagebox('load a binary first')
630
+ else @window.loadfile(f) # TODO prompt to start debugger instead of dasm
631
+ end
632
+ end
633
+ end
634
+
635
+ class DasmWindow < Window
636
+ attr_accessor :dasm_widget, :menu
637
+ def initialize_window(title = 'metasm disassembler', dasm=nil, *ep)
638
+ self.title = title
639
+ @dasm_widget = nil
640
+ if dasm
641
+ ep = ep.first if ep.length == 1 and ep.first.kind_of? Array
642
+ display(dasm, ep)
643
+ else
644
+ self.widget = NoDasmWidget.new(self)
645
+ end
646
+ end
647
+
648
+ def widget=(w)
649
+ super(w || NoDasmWidget.new(self))
650
+ end
651
+
652
+ def destroy_window
653
+ @dasm_widget.terminate if @dasm_widget
654
+ super()
655
+ end
656
+
657
+ # sets up a DisasmWidget as main widget of the window, replaces the current if it exists
658
+ # returns the widget
659
+ def display(dasm, ep=[])
660
+ @dasm_widget.terminate if @dasm_widget
661
+ ep = [ep] if not ep.kind_of? Array
662
+ @dasm_widget = DisasmWidget.new(dasm, ep)
663
+ self.widget = @dasm_widget
664
+ @dasm_widget.focus_addr(ep.first) if ep.first
665
+ @dasm_widget
666
+ end
667
+
668
+ # returns the specified widget from the @dasm_widget (idx in :hex, :listing, :graph etc)
669
+ def widget(idx=nil)
670
+ idx && @dasm_widget ? @dasm_widget.view(idx) : @dasm_widget
671
+ end
672
+
673
+ def loadfile(path, cpu='Ia32', exefmt=nil)
674
+ if exefmt
675
+ exefmt = Metasm.const_get(exefmt) if exefmt.kind_of? String
676
+ else
677
+ exefmt = AutoExe.orshellcode { cpu = Metasm.const_get(cpu) if cpu.kind_of? String ; cpu.new }
678
+ end
679
+
680
+ exe = exefmt.decode_file(path) { |type, str|
681
+ # Disassembler save file will use this callback with unhandled sections / invalid binary file path
682
+ case type
683
+ when 'binarypath'
684
+ ret = nil
685
+ openfile("please locate #{str}", :blocking => true) { |f| ret = f }
686
+ return if not ret
687
+ ret
688
+ end
689
+ }
690
+ (@dasm_widget ? DasmWindow.new : self).display(exe.disassembler)
691
+ exe
692
+ end
693
+
694
+ def promptopen(caption='chose target binary')
695
+ openfile(caption) { |exename| loadfile(exename) ; yield self if block_given? }
696
+ end
697
+
698
+ def promptdebug(caption='chose target')
699
+ l = nil
700
+ i = inputbox(caption) { |name|
701
+ i = nil ; l.destroy if l and not l.destroyed?
702
+ if pr = OS.current.find_process(name)
703
+ target = pr.debugger
704
+ elsif name =~ /^(udp|tcp|.*\d+.*):/i # don't match c:\kikoo, but allow 127.0.0.1 / [1:2::3]
705
+ target = GdbRemoteDebugger.new(name)
706
+ elsif pr = OS.current.create_process(name)
707
+ target = pr.debugger
708
+ else
709
+ messagebox('no such target')
710
+ next
711
+ end
712
+ DbgWindow.new(target)
713
+ destroy if not @dasm_widget
714
+ yield self if block_given?
715
+ }
716
+
717
+ # build process list in bg (exe name resolution takes a few seconds)
718
+ list = [['pid', 'name']]
719
+ list_pr = OS.current.list_processes
720
+ Gui.idle_add {
721
+ if pr = list_pr.shift
722
+ list << [pr.pid, pr.path] if pr.path
723
+ true
724
+ elsif i
725
+ me = ::Process.pid.to_s
726
+ l = listwindow('running processes', list,
727
+ :noshow => true,
728
+ :color_callback => lambda { |le| [:grey, :palegrey] if le[0] == me }
729
+ ) { |e| i.text = e[0] }
730
+ l.x += l.width
731
+ l.show
732
+ false
733
+ end
734
+ } if not list_pr.empty?
735
+ end
736
+
737
+ # reuse last @savefile to save dasm, prompt for file if undefined
738
+ def promptsave
739
+ return if not @dasm_widget
740
+ if @savefile ||= nil
741
+ @dasm_widget.dasm.save_file @savefile
742
+ return
743
+ end
744
+ openfile('chose save file') { |file|
745
+ @savefile = file
746
+ @dasm_widget.dasm.save_file(file)
747
+ }
748
+ end
749
+
750
+ # same as promptsave, but always prompt
751
+ def promptsaveas
752
+ @savefile = nil
753
+ promptsave
754
+ end
755
+
756
+ def promptruby
757
+ if @dasm_widget
758
+ @dasm_widget.prompt_run_ruby
759
+ else
760
+ inputbox('code to eval') { |c| messagebox eval(c).inspect[0, 512], 'eval' }
761
+ end
762
+ end
763
+
764
+ def build_menu
765
+ # TODO dynamic checkboxes (check $VERBOSE when opening the options menu to (un)set the mark)
766
+ filemenu = new_menu
767
+
768
+ # a fake unreferenced accel group, so that the shortcut keys appear in the menu, but the widget keypress is responsible
769
+ # of handling them (otherwise this would take precedence and :hex couldn't get 'c' etc)
770
+ # but ^o still works (must work even without DasmWidget loaded)
771
+
772
+ addsubmenu(filemenu, 'OPEN', '^o') { promptopen }
773
+ addsubmenu(filemenu, '_Debug') { promptdebug }
774
+ addsubmenu(filemenu, 'SAVE', '^s') { promptsave }
775
+ addsubmenu(filemenu, 'Save _as...') { promptsaveas }
776
+ addsubmenu(filemenu, 'CLOSE') {
777
+ if @dasm_widget
778
+ @dasm_widget.terminate
779
+ @dasm_widget = nil
780
+ self.widget = nil
781
+ end
782
+ }
783
+ addsubmenu(filemenu)
784
+
785
+ importmenu = new_menu
786
+ addsubmenu(importmenu, 'Load _map') {
787
+ openfile('chose map file') { |file|
788
+ @dasm_widget.dasm.load_map(File.read(file)) if @dasm_widget
789
+ } if @dasm_widget
790
+ }
791
+ addsubmenu(importmenu, 'Load _C') {
792
+ openfile('chose C file') { |file|
793
+ @dasm_widget.dasm.parse_c(File.read(file)) if @dasm_widget
794
+ } if @dasm_widget
795
+ }
796
+ addsubmenu(filemenu, '_Import', importmenu)
797
+
798
+ exportmenu = new_menu
799
+ addsubmenu(exportmenu, 'Save _map') {
800
+ savefile('chose map file') { |file|
801
+ File.open(file, 'w') { |fd|
802
+ fd.puts @dasm_widget.dasm.save_map
803
+ } if @dasm_widget
804
+ } if @dasm_widget
805
+ }
806
+ addsubmenu(exportmenu, 'Save _asm') {
807
+ savefile('chose asm file') { |file|
808
+ File.open(file, 'w') { |fd|
809
+ fd.puts @dasm_widget.dasm
810
+ } if @dasm_widget
811
+ } if @dasm_widget
812
+ }
813
+ addsubmenu(exportmenu, 'Save _C') {
814
+ savefile('chose C file') { |file|
815
+ File.open(file, 'w') { |fd|
816
+ fd.puts @dasm_widget.dasm.c_parser
817
+ } if @dasm_widget
818
+ } if @dasm_widget
819
+ }
820
+ addsubmenu(filemenu, '_Export', exportmenu)
821
+ addsubmenu(filemenu)
822
+ addsubmenu(filemenu, 'QUIT') { destroy } # post_quit_message ?
823
+
824
+ addsubmenu(@menu, filemenu, '_File')
825
+
826
+ actions = new_menu
827
+ dasm = new_menu
828
+ addsubmenu(dasm, '_Disassemble from here', 'c') { @dasm_widget.disassemble(@dasm_widget.curview.current_address) }
829
+ addsubmenu(dasm, 'Disassemble _fast from here', 'C') { @dasm_widget.disassemble_fast(@dasm_widget.curview.current_address) }
830
+ addsubmenu(dasm, 'Disassemble fast & dee_p from here', '^C') { @dasm_widget.disassemble_fast_deep(@dasm_widget.curview.current_address) }
831
+ addsubmenu(actions, dasm, '_Disassemble')
832
+ navigate = new_menu
833
+ addsubmenu(navigate, 'Follow', '<enter>') { @dasm_widget.focus_addr @dasm_widget.curview.hl_word } # XXX
834
+ addsubmenu(navigate, 'Jmp back', '<esc>') { @dasm_widget.focus_addr_back }
835
+ addsubmenu(navigate, 'Undo jmp back', '^<enter>') { @dasm_widget.focus_addr_redo }
836
+ addsubmenu(navigate, 'Goto', 'g') { @dasm_widget.prompt_goto }
837
+ addsubmenu(actions, navigate, 'Navigate')
838
+ addsubmenu(actions, '_Backtrace', 'b') { @dasm_widget.prompt_backtrace }
839
+ addsubmenu(actions, 'List functions', 'f') { @dasm_widget.list_functions }
840
+ addsubmenu(actions, 'List labels', 'l') { @dasm_widget.list_labels }
841
+ addsubmenu(actions, 'List xrefs', 'x') { @dasm_widget.list_xrefs(@dasm_widget.pointed_addr) }
842
+ addsubmenu(actions, 'Rebase') { @dasm_widget.rebase }
843
+ addsubmenu(actions, 'Rename label', 'n') { @dasm_widget.rename_label(@dasm_widget.pointed_addr) }
844
+ addsubmenu(actions, 'Decompile', '<tab>') { @dasm_widget.decompile(@dasm_widget.curview.current_address) }
845
+ addsubmenu(actions, 'Decompile finali_ze') { @dasm_widget.dasm.decompiler.finalize ; @dasm_widget.gui_update }
846
+ addsubmenu(actions, 'Comment', ';') { @dasm_widget.decompile(@dasm_widget.curview.current_address) }
847
+ addsubmenu(actions, '_Undefine') { @dasm_widget.dasm.undefine_from(@dasm_widget.curview.current_address) ; @dasm_widget.gui_update }
848
+ addsubmenu(actions, 'Unde_fine function') { @dasm_widget.undefine_function(@dasm_widget.curview.current_address) }
849
+ addsubmenu(actions, 'Undefine function & _subfuncs') { @dasm_widget.undefine_function(@dasm_widget.curview.current_address, true) }
850
+ addsubmenu(actions, 'Data', 'd') { @dasm_widget.toggle_data(@dasm_widget.curview.current_address) }
851
+ addsubmenu(actions, 'Pause dasm', 'p', :check) { |ck| !@dasm_widget.playpause_dasm }
852
+ addsubmenu(actions, 'Run ruby snippet', '^r') { promptruby }
853
+ addsubmenu(actions, 'Run _ruby plugin') { @dasm_widget.prompt_run_ruby_plugin }
854
+
855
+ addsubmenu(@menu, actions, '_Actions')
856
+
857
+ options = new_menu
858
+ addsubmenu(options, '_Verbose', :check, $VERBOSE, 'v') { |ck| $VERBOSE = ck }
859
+ addsubmenu(options, 'Debu_g', :check, $DEBUG) { |ck| $DEBUG = ck }
860
+ addsubmenu(options, 'Debug _backtrace', :check) { |ck| @dasm_widget.dasm.debug_backtrace = ck if @dasm_widget }
861
+ addsubmenu(options, 'Backtrace li_mit') {
862
+ inputbox('max blocks to backtrace', :text => @dasm_widget.dasm.backtrace_maxblocks) { |target|
863
+ @dasm_widget.dasm.backtrace_maxblocks = Integer(target) if not target.empty?
864
+ } if @dasm_widget
865
+ }
866
+ addsubmenu(options, 'Backtrace _limit (data)') {
867
+ inputbox('max blocks to backtrace data (-1 to never start)',
868
+ :text => @dasm_widget.dasm.backtrace_maxblocks_data) { |target|
869
+ @dasm_widget.dasm.backtrace_maxblocks_data = Integer(target) if not target.empty?
870
+ } if @dasm_widget
871
+ }
872
+ addsubmenu(options)
873
+ addsubmenu(options, 'Forbid decompile _types', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_decompile_types = ck }
874
+ addsubmenu(options, 'Forbid decompile _if/while', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_decompile_ifwhile = ck }
875
+ addsubmenu(options, 'Forbid decomp _optimize', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_optimize_code = ck }
876
+ addsubmenu(options, 'Forbid decomp optim_data', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_optimize_dataflow = ck }
877
+ addsubmenu(options, 'Forbid decomp optimlab_els', :check) { |ck| @dasm_widget.dasm.decompiler.forbid_optimize_labels = ck }
878
+ addsubmenu(options, 'Decompiler _recurse', :check, true) { |ck| @dasm_widget.dasm.decompiler.recurse = (ck ? 1/0.0 : 1) ; ck } # XXX race if changed while decompiling
879
+ # TODO CPU type, size, endian...
880
+ # factorize headers
881
+
882
+ addsubmenu(@menu, options, '_Options')
883
+
884
+ views = new_menu
885
+ addsubmenu(views, 'Dis_assembly') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :listing) }
886
+ addsubmenu(views, '_Graph') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :graph) }
887
+ addsubmenu(views, 'De_compiled') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :decompile) }
888
+ addsubmenu(views, 'Raw _opcodes') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :opcodes) }
889
+ addsubmenu(views, '_Hex') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :hex) }
890
+ addsubmenu(views, 'C S_truct') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :cstruct) }
891
+ addsubmenu(views, 'Co_verage') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :coverage) }
892
+ addsubmenu(views, '_Sections') { @dasm_widget.list_sections }
893
+ addsubmenu(views, 'St_rings') { @dasm_widget.list_strings }
894
+
895
+ funcgraph = new_menu
896
+ addsubmenu(funcgraph, 'Fu_ll') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :funcgraph, false, :full) }
897
+ addsubmenu(funcgraph, '_From there') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :funcgraph, false, :from) }
898
+ addsubmenu(funcgraph, '_To there') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :funcgraph, false, :to) }
899
+ addsubmenu(funcgraph, '_Butterfly') { @dasm_widget.focus_addr(@dasm_widget.curaddr, :funcgraph, false, :both) }
900
+ addsubmenu(views, '_Func graph', funcgraph)
901
+
902
+ addsubmenu(@menu, views, '_Views')
903
+ end
904
+ end
905
+ end
906
+ end