metasm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/BUGS +11 -0
  2. data/CREDITS +17 -0
  3. data/README +270 -0
  4. data/TODO +114 -0
  5. data/doc/code_organisation.txt +146 -0
  6. data/doc/const_missing.txt +16 -0
  7. data/doc/core_classes.txt +75 -0
  8. data/doc/feature_list.txt +53 -0
  9. data/doc/index.txt +59 -0
  10. data/doc/install_notes.txt +170 -0
  11. data/doc/style.css +3 -0
  12. data/doc/use_cases.txt +18 -0
  13. data/lib/metasm.rb +80 -0
  14. data/lib/metasm/arm.rb +12 -0
  15. data/lib/metasm/arm/debug.rb +39 -0
  16. data/lib/metasm/arm/decode.rb +167 -0
  17. data/lib/metasm/arm/encode.rb +77 -0
  18. data/lib/metasm/arm/main.rb +75 -0
  19. data/lib/metasm/arm/opcodes.rb +177 -0
  20. data/lib/metasm/arm/parse.rb +130 -0
  21. data/lib/metasm/arm/render.rb +55 -0
  22. data/lib/metasm/compile_c.rb +1457 -0
  23. data/lib/metasm/dalvik.rb +8 -0
  24. data/lib/metasm/dalvik/decode.rb +196 -0
  25. data/lib/metasm/dalvik/main.rb +60 -0
  26. data/lib/metasm/dalvik/opcodes.rb +366 -0
  27. data/lib/metasm/decode.rb +213 -0
  28. data/lib/metasm/decompile.rb +2659 -0
  29. data/lib/metasm/disassemble.rb +2068 -0
  30. data/lib/metasm/disassemble_api.rb +1280 -0
  31. data/lib/metasm/dynldr.rb +1329 -0
  32. data/lib/metasm/encode.rb +333 -0
  33. data/lib/metasm/exe_format/a_out.rb +194 -0
  34. data/lib/metasm/exe_format/autoexe.rb +82 -0
  35. data/lib/metasm/exe_format/bflt.rb +189 -0
  36. data/lib/metasm/exe_format/coff.rb +455 -0
  37. data/lib/metasm/exe_format/coff_decode.rb +901 -0
  38. data/lib/metasm/exe_format/coff_encode.rb +1078 -0
  39. data/lib/metasm/exe_format/dex.rb +457 -0
  40. data/lib/metasm/exe_format/dol.rb +145 -0
  41. data/lib/metasm/exe_format/elf.rb +923 -0
  42. data/lib/metasm/exe_format/elf_decode.rb +979 -0
  43. data/lib/metasm/exe_format/elf_encode.rb +1375 -0
  44. data/lib/metasm/exe_format/macho.rb +827 -0
  45. data/lib/metasm/exe_format/main.rb +228 -0
  46. data/lib/metasm/exe_format/mz.rb +164 -0
  47. data/lib/metasm/exe_format/nds.rb +172 -0
  48. data/lib/metasm/exe_format/pe.rb +437 -0
  49. data/lib/metasm/exe_format/serialstruct.rb +246 -0
  50. data/lib/metasm/exe_format/shellcode.rb +114 -0
  51. data/lib/metasm/exe_format/xcoff.rb +167 -0
  52. data/lib/metasm/gui.rb +23 -0
  53. data/lib/metasm/gui/cstruct.rb +373 -0
  54. data/lib/metasm/gui/dasm_coverage.rb +199 -0
  55. data/lib/metasm/gui/dasm_decomp.rb +369 -0
  56. data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
  57. data/lib/metasm/gui/dasm_graph.rb +1354 -0
  58. data/lib/metasm/gui/dasm_hex.rb +543 -0
  59. data/lib/metasm/gui/dasm_listing.rb +599 -0
  60. data/lib/metasm/gui/dasm_main.rb +906 -0
  61. data/lib/metasm/gui/dasm_opcodes.rb +291 -0
  62. data/lib/metasm/gui/debug.rb +1228 -0
  63. data/lib/metasm/gui/gtk.rb +884 -0
  64. data/lib/metasm/gui/qt.rb +495 -0
  65. data/lib/metasm/gui/win32.rb +3004 -0
  66. data/lib/metasm/gui/x11.rb +621 -0
  67. data/lib/metasm/ia32.rb +14 -0
  68. data/lib/metasm/ia32/compile_c.rb +1523 -0
  69. data/lib/metasm/ia32/debug.rb +193 -0
  70. data/lib/metasm/ia32/decode.rb +1167 -0
  71. data/lib/metasm/ia32/decompile.rb +564 -0
  72. data/lib/metasm/ia32/encode.rb +314 -0
  73. data/lib/metasm/ia32/main.rb +233 -0
  74. data/lib/metasm/ia32/opcodes.rb +872 -0
  75. data/lib/metasm/ia32/parse.rb +327 -0
  76. data/lib/metasm/ia32/render.rb +91 -0
  77. data/lib/metasm/main.rb +1193 -0
  78. data/lib/metasm/mips.rb +11 -0
  79. data/lib/metasm/mips/compile_c.rb +7 -0
  80. data/lib/metasm/mips/decode.rb +253 -0
  81. data/lib/metasm/mips/encode.rb +51 -0
  82. data/lib/metasm/mips/main.rb +72 -0
  83. data/lib/metasm/mips/opcodes.rb +443 -0
  84. data/lib/metasm/mips/parse.rb +51 -0
  85. data/lib/metasm/mips/render.rb +43 -0
  86. data/lib/metasm/os/gnu_exports.rb +270 -0
  87. data/lib/metasm/os/linux.rb +1112 -0
  88. data/lib/metasm/os/main.rb +1686 -0
  89. data/lib/metasm/os/remote.rb +527 -0
  90. data/lib/metasm/os/windows.rb +2027 -0
  91. data/lib/metasm/os/windows_exports.rb +745 -0
  92. data/lib/metasm/parse.rb +876 -0
  93. data/lib/metasm/parse_c.rb +3938 -0
  94. data/lib/metasm/pic16c/decode.rb +42 -0
  95. data/lib/metasm/pic16c/main.rb +17 -0
  96. data/lib/metasm/pic16c/opcodes.rb +68 -0
  97. data/lib/metasm/ppc.rb +11 -0
  98. data/lib/metasm/ppc/decode.rb +264 -0
  99. data/lib/metasm/ppc/decompile.rb +251 -0
  100. data/lib/metasm/ppc/encode.rb +51 -0
  101. data/lib/metasm/ppc/main.rb +129 -0
  102. data/lib/metasm/ppc/opcodes.rb +410 -0
  103. data/lib/metasm/ppc/parse.rb +52 -0
  104. data/lib/metasm/preprocessor.rb +1277 -0
  105. data/lib/metasm/render.rb +130 -0
  106. data/lib/metasm/sh4.rb +8 -0
  107. data/lib/metasm/sh4/decode.rb +336 -0
  108. data/lib/metasm/sh4/main.rb +292 -0
  109. data/lib/metasm/sh4/opcodes.rb +381 -0
  110. data/lib/metasm/x86_64.rb +12 -0
  111. data/lib/metasm/x86_64/compile_c.rb +1025 -0
  112. data/lib/metasm/x86_64/debug.rb +59 -0
  113. data/lib/metasm/x86_64/decode.rb +268 -0
  114. data/lib/metasm/x86_64/encode.rb +264 -0
  115. data/lib/metasm/x86_64/main.rb +135 -0
  116. data/lib/metasm/x86_64/opcodes.rb +118 -0
  117. data/lib/metasm/x86_64/parse.rb +68 -0
  118. data/misc/bottleneck.rb +61 -0
  119. data/misc/cheader-findpppath.rb +58 -0
  120. data/misc/hexdiff.rb +74 -0
  121. data/misc/hexdump.rb +55 -0
  122. data/misc/metasm-all.rb +13 -0
  123. data/misc/objdiff.rb +47 -0
  124. data/misc/objscan.rb +40 -0
  125. data/misc/pdfparse.rb +661 -0
  126. data/misc/ppc_pdf2oplist.rb +192 -0
  127. data/misc/tcp_proxy_hex.rb +84 -0
  128. data/misc/txt2html.rb +440 -0
  129. data/samples/a.out.rb +31 -0
  130. data/samples/asmsyntax.rb +77 -0
  131. data/samples/bindiff.rb +555 -0
  132. data/samples/compilation-steps.rb +49 -0
  133. data/samples/cparser_makestackoffset.rb +55 -0
  134. data/samples/dasm-backtrack.rb +38 -0
  135. data/samples/dasmnavig.rb +318 -0
  136. data/samples/dbg-apihook.rb +228 -0
  137. data/samples/dbghelp.rb +143 -0
  138. data/samples/disassemble-gui.rb +102 -0
  139. data/samples/disassemble.rb +133 -0
  140. data/samples/dump_upx.rb +95 -0
  141. data/samples/dynamic_ruby.rb +1929 -0
  142. data/samples/elf_list_needed.rb +46 -0
  143. data/samples/elf_listexports.rb +33 -0
  144. data/samples/elfencode.rb +25 -0
  145. data/samples/exeencode.rb +128 -0
  146. data/samples/factorize-headers-elfimports.rb +77 -0
  147. data/samples/factorize-headers-peimports.rb +109 -0
  148. data/samples/factorize-headers.rb +43 -0
  149. data/samples/gdbclient.rb +583 -0
  150. data/samples/generate_libsigs.rb +102 -0
  151. data/samples/hotfix_gtk_dbg.rb +59 -0
  152. data/samples/install_win_env.rb +78 -0
  153. data/samples/lindebug.rb +924 -0
  154. data/samples/linux_injectsyscall.rb +95 -0
  155. data/samples/machoencode.rb +31 -0
  156. data/samples/metasm-shell.rb +91 -0
  157. data/samples/pe-hook.rb +69 -0
  158. data/samples/pe-ia32-cpuid.rb +203 -0
  159. data/samples/pe-mips.rb +35 -0
  160. data/samples/pe-shutdown.rb +78 -0
  161. data/samples/pe-testrelocs.rb +51 -0
  162. data/samples/pe-testrsrc.rb +24 -0
  163. data/samples/pe_listexports.rb +31 -0
  164. data/samples/peencode.rb +19 -0
  165. data/samples/peldr.rb +494 -0
  166. data/samples/preprocess-flatten.rb +19 -0
  167. data/samples/r0trace.rb +308 -0
  168. data/samples/rubstop.rb +399 -0
  169. data/samples/scan_pt_gnu_stack.rb +54 -0
  170. data/samples/scanpeexports.rb +62 -0
  171. data/samples/shellcode-c.rb +40 -0
  172. data/samples/shellcode-dynlink.rb +146 -0
  173. data/samples/source.asm +34 -0
  174. data/samples/struct_offset.rb +47 -0
  175. data/samples/testpe.rb +32 -0
  176. data/samples/testraw.rb +45 -0
  177. data/samples/win32genloader.rb +132 -0
  178. data/samples/win32hooker-advanced.rb +169 -0
  179. data/samples/win32hooker.rb +96 -0
  180. data/samples/win32livedasm.rb +33 -0
  181. data/samples/win32remotescan.rb +133 -0
  182. data/samples/wintrace.rb +92 -0
  183. data/tests/all.rb +8 -0
  184. data/tests/dasm.rb +39 -0
  185. data/tests/dynldr.rb +35 -0
  186. data/tests/encodeddata.rb +132 -0
  187. data/tests/ia32.rb +82 -0
  188. data/tests/mips.rb +116 -0
  189. data/tests/parse_c.rb +239 -0
  190. data/tests/preprocessor.rb +269 -0
  191. data/tests/x86_64.rb +62 -0
  192. metadata +255 -0
@@ -0,0 +1,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