metasm 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.hgtags +3 -0
- data/Gemfile +1 -0
- data/INSTALL +61 -0
- data/LICENCE +458 -0
- data/README +29 -21
- data/Rakefile +10 -0
- data/TODO +10 -12
- data/doc/code_organisation.txt +2 -0
- data/doc/core/DynLdr.txt +247 -0
- data/doc/core/ExeFormat.txt +43 -0
- data/doc/core/Expression.txt +220 -0
- data/doc/core/GNUExports.txt +27 -0
- data/doc/core/Ia32.txt +236 -0
- data/doc/core/SerialStruct.txt +108 -0
- data/doc/core/VirtualString.txt +145 -0
- data/doc/core/WindowsExports.txt +61 -0
- data/doc/core/index.txt +1 -0
- data/doc/style.css +6 -3
- data/doc/usage/debugger.txt +327 -0
- data/doc/usage/index.txt +1 -0
- data/doc/use_cases.txt +2 -2
- data/metasm.gemspec +22 -0
- data/{lib/metasm.rb → metasm.rb} +11 -3
- data/{lib/metasm → metasm}/compile_c.rb +13 -7
- data/metasm/cpu/arc.rb +8 -0
- data/metasm/cpu/arc/decode.rb +425 -0
- data/metasm/cpu/arc/main.rb +191 -0
- data/metasm/cpu/arc/opcodes.rb +588 -0
- data/{lib/metasm → metasm/cpu}/arm.rb +7 -5
- data/{lib/metasm → metasm/cpu}/arm/debug.rb +2 -2
- data/{lib/metasm → metasm/cpu}/arm/decode.rb +13 -12
- data/{lib/metasm → metasm/cpu}/arm/encode.rb +23 -8
- data/{lib/metasm → metasm/cpu}/arm/main.rb +0 -3
- data/metasm/cpu/arm/opcodes.rb +324 -0
- data/{lib/metasm → metasm/cpu}/arm/parse.rb +25 -13
- data/{lib/metasm → metasm/cpu}/arm/render.rb +2 -2
- data/metasm/cpu/arm64.rb +15 -0
- data/metasm/cpu/arm64/debug.rb +38 -0
- data/metasm/cpu/arm64/decode.rb +289 -0
- data/metasm/cpu/arm64/encode.rb +41 -0
- data/metasm/cpu/arm64/main.rb +105 -0
- data/metasm/cpu/arm64/opcodes.rb +232 -0
- data/metasm/cpu/arm64/parse.rb +20 -0
- data/metasm/cpu/arm64/render.rb +95 -0
- data/{lib/metasm/ppc.rb → metasm/cpu/bpf.rb} +2 -4
- data/metasm/cpu/bpf/decode.rb +142 -0
- data/metasm/cpu/bpf/main.rb +60 -0
- data/metasm/cpu/bpf/opcodes.rb +81 -0
- data/metasm/cpu/bpf/render.rb +41 -0
- data/metasm/cpu/cy16.rb +9 -0
- data/metasm/cpu/cy16/decode.rb +253 -0
- data/metasm/cpu/cy16/main.rb +63 -0
- data/metasm/cpu/cy16/opcodes.rb +78 -0
- data/metasm/cpu/cy16/render.rb +41 -0
- data/metasm/cpu/dalvik.rb +11 -0
- data/{lib/metasm → metasm/cpu}/dalvik/decode.rb +35 -13
- data/{lib/metasm → metasm/cpu}/dalvik/main.rb +51 -2
- data/{lib/metasm → metasm/cpu}/dalvik/opcodes.rb +19 -11
- data/metasm/cpu/ia32.rb +17 -0
- data/{lib/metasm → metasm/cpu}/ia32/compile_c.rb +5 -7
- data/{lib/metasm → metasm/cpu}/ia32/debug.rb +5 -5
- data/{lib/metasm → metasm/cpu}/ia32/decode.rb +246 -59
- data/{lib/metasm → metasm/cpu}/ia32/decompile.rb +7 -7
- data/{lib/metasm → metasm/cpu}/ia32/encode.rb +19 -13
- data/{lib/metasm → metasm/cpu}/ia32/main.rb +51 -8
- data/metasm/cpu/ia32/opcodes.rb +1424 -0
- data/{lib/metasm → metasm/cpu}/ia32/parse.rb +47 -16
- data/{lib/metasm → metasm/cpu}/ia32/render.rb +31 -4
- data/metasm/cpu/mips.rb +14 -0
- data/{lib/metasm → metasm/cpu}/mips/compile_c.rb +1 -1
- data/metasm/cpu/mips/debug.rb +42 -0
- data/{lib/metasm → metasm/cpu}/mips/decode.rb +46 -16
- data/{lib/metasm → metasm/cpu}/mips/encode.rb +4 -3
- data/{lib/metasm → metasm/cpu}/mips/main.rb +11 -4
- data/{lib/metasm → metasm/cpu}/mips/opcodes.rb +86 -17
- data/{lib/metasm → metasm/cpu}/mips/parse.rb +1 -1
- data/{lib/metasm → metasm/cpu}/mips/render.rb +1 -1
- data/{lib/metasm/dalvik.rb → metasm/cpu/msp430.rb} +1 -1
- data/metasm/cpu/msp430/decode.rb +247 -0
- data/metasm/cpu/msp430/main.rb +62 -0
- data/metasm/cpu/msp430/opcodes.rb +101 -0
- data/{lib/metasm → metasm/cpu}/pic16c/decode.rb +6 -7
- data/{lib/metasm → metasm/cpu}/pic16c/main.rb +0 -0
- data/{lib/metasm → metasm/cpu}/pic16c/opcodes.rb +1 -1
- data/{lib/metasm/mips.rb → metasm/cpu/ppc.rb} +4 -4
- data/{lib/metasm → metasm/cpu}/ppc/decode.rb +18 -12
- data/{lib/metasm → metasm/cpu}/ppc/decompile.rb +3 -3
- data/{lib/metasm → metasm/cpu}/ppc/encode.rb +2 -2
- data/{lib/metasm → metasm/cpu}/ppc/main.rb +17 -12
- data/{lib/metasm → metasm/cpu}/ppc/opcodes.rb +11 -5
- data/metasm/cpu/ppc/parse.rb +55 -0
- data/metasm/cpu/python.rb +8 -0
- data/metasm/cpu/python/decode.rb +136 -0
- data/metasm/cpu/python/main.rb +36 -0
- data/metasm/cpu/python/opcodes.rb +180 -0
- data/{lib/metasm → metasm/cpu}/sh4.rb +1 -1
- data/{lib/metasm → metasm/cpu}/sh4/decode.rb +48 -17
- data/{lib/metasm → metasm/cpu}/sh4/main.rb +13 -4
- data/{lib/metasm → metasm/cpu}/sh4/opcodes.rb +7 -8
- data/metasm/cpu/x86_64.rb +15 -0
- data/{lib/metasm → metasm/cpu}/x86_64/compile_c.rb +28 -17
- data/{lib/metasm → metasm/cpu}/x86_64/debug.rb +4 -4
- data/{lib/metasm → metasm/cpu}/x86_64/decode.rb +57 -15
- data/{lib/metasm → metasm/cpu}/x86_64/encode.rb +55 -26
- data/{lib/metasm → metasm/cpu}/x86_64/main.rb +14 -6
- data/metasm/cpu/x86_64/opcodes.rb +136 -0
- data/{lib/metasm → metasm/cpu}/x86_64/parse.rb +10 -2
- data/metasm/cpu/x86_64/render.rb +35 -0
- data/metasm/cpu/z80.rb +9 -0
- data/metasm/cpu/z80/decode.rb +313 -0
- data/metasm/cpu/z80/main.rb +67 -0
- data/metasm/cpu/z80/opcodes.rb +224 -0
- data/metasm/cpu/z80/render.rb +59 -0
- data/{lib/metasm/os/main.rb → metasm/debug.rb} +160 -401
- data/{lib/metasm → metasm}/decode.rb +35 -4
- data/{lib/metasm → metasm}/decompile.rb +15 -16
- data/{lib/metasm → metasm}/disassemble.rb +201 -45
- data/{lib/metasm → metasm}/disassemble_api.rb +651 -87
- data/{lib/metasm → metasm}/dynldr.rb +220 -133
- data/{lib/metasm → metasm}/encode.rb +10 -1
- data/{lib/metasm → metasm}/exe_format/a_out.rb +9 -6
- data/{lib/metasm → metasm}/exe_format/autoexe.rb +1 -0
- data/{lib/metasm → metasm}/exe_format/bflt.rb +57 -27
- data/{lib/metasm → metasm}/exe_format/coff.rb +11 -3
- data/{lib/metasm → metasm}/exe_format/coff_decode.rb +53 -20
- data/{lib/metasm → metasm}/exe_format/coff_encode.rb +11 -13
- data/{lib/metasm → metasm}/exe_format/dex.rb +13 -5
- data/{lib/metasm → metasm}/exe_format/dol.rb +1 -0
- data/{lib/metasm → metasm}/exe_format/elf.rb +93 -57
- data/{lib/metasm → metasm}/exe_format/elf_decode.rb +143 -34
- data/{lib/metasm → metasm}/exe_format/elf_encode.rb +122 -31
- data/metasm/exe_format/gb.rb +65 -0
- data/metasm/exe_format/javaclass.rb +424 -0
- data/{lib/metasm → metasm}/exe_format/macho.rb +204 -16
- data/{lib/metasm → metasm}/exe_format/main.rb +26 -3
- data/{lib/metasm → metasm}/exe_format/mz.rb +1 -0
- data/{lib/metasm → metasm}/exe_format/nds.rb +7 -4
- data/{lib/metasm → metasm}/exe_format/pe.rb +71 -8
- data/metasm/exe_format/pyc.rb +167 -0
- data/{lib/metasm → metasm}/exe_format/serialstruct.rb +67 -14
- data/{lib/metasm → metasm}/exe_format/shellcode.rb +7 -3
- data/metasm/exe_format/shellcode_rwx.rb +114 -0
- data/metasm/exe_format/swf.rb +205 -0
- data/{lib/metasm → metasm}/exe_format/xcoff.rb +7 -7
- data/metasm/exe_format/zip.rb +335 -0
- data/metasm/gui.rb +13 -0
- data/{lib/metasm → metasm}/gui/cstruct.rb +35 -41
- data/{lib/metasm → metasm}/gui/dasm_coverage.rb +11 -11
- data/{lib/metasm → metasm}/gui/dasm_decomp.rb +7 -20
- data/{lib/metasm → metasm}/gui/dasm_funcgraph.rb +0 -0
- data/metasm/gui/dasm_graph.rb +1695 -0
- data/{lib/metasm → metasm}/gui/dasm_hex.rb +12 -8
- data/{lib/metasm → metasm}/gui/dasm_listing.rb +43 -28
- data/{lib/metasm → metasm}/gui/dasm_main.rb +310 -53
- data/{lib/metasm → metasm}/gui/dasm_opcodes.rb +5 -19
- data/{lib/metasm → metasm}/gui/debug.rb +93 -27
- data/{lib/metasm → metasm}/gui/gtk.rb +162 -40
- data/{lib/metasm → metasm}/gui/qt.rb +12 -2
- data/{lib/metasm → metasm}/gui/win32.rb +179 -42
- data/{lib/metasm → metasm}/gui/x11.rb +59 -59
- data/{lib/metasm → metasm}/main.rb +389 -264
- data/{lib/metasm/os/remote.rb → metasm/os/gdbremote.rb} +146 -54
- data/{lib/metasm → metasm}/os/gnu_exports.rb +1 -1
- data/{lib/metasm → metasm}/os/linux.rb +628 -151
- data/metasm/os/main.rb +330 -0
- data/{lib/metasm → metasm}/os/windows.rb +132 -42
- data/{lib/metasm → metasm}/os/windows_exports.rb +141 -0
- data/{lib/metasm → metasm}/parse.rb +26 -24
- data/{lib/metasm → metasm}/parse_c.rb +221 -116
- data/{lib/metasm → metasm}/preprocessor.rb +55 -40
- data/{lib/metasm → metasm}/render.rb +14 -38
- data/misc/hexdump.rb +2 -1
- data/misc/lint.rb +58 -0
- data/misc/txt2html.rb +9 -7
- data/samples/bindiff.rb +3 -4
- data/samples/dasm-plugins/bindiff.rb +15 -0
- data/samples/dasm-plugins/bookmark.rb +133 -0
- data/samples/dasm-plugins/c_constants.rb +57 -0
- data/samples/dasm-plugins/colortheme_solarized.rb +125 -0
- data/samples/dasm-plugins/cppobj_funcall.rb +60 -0
- data/samples/dasm-plugins/dasm_all.rb +70 -0
- data/samples/dasm-plugins/demangle_cpp.rb +31 -0
- data/samples/dasm-plugins/deobfuscate.rb +251 -0
- data/samples/dasm-plugins/dump_text.rb +35 -0
- data/samples/dasm-plugins/export_graph_svg.rb +86 -0
- data/samples/dasm-plugins/findgadget.rb +75 -0
- data/samples/dasm-plugins/hl_opcode.rb +32 -0
- data/samples/dasm-plugins/hotfix_gtk_dbg.rb +19 -0
- data/samples/dasm-plugins/imm2off.rb +34 -0
- data/samples/dasm-plugins/match_libsigs.rb +93 -0
- data/samples/dasm-plugins/patch_file.rb +95 -0
- data/samples/dasm-plugins/scanfuncstart.rb +36 -0
- data/samples/dasm-plugins/scanxrefs.rb +26 -0
- data/samples/dasm-plugins/selfmodify.rb +197 -0
- data/samples/dasm-plugins/stringsxrefs.rb +28 -0
- data/samples/dasmnavig.rb +1 -1
- data/samples/dbg-apihook.rb +24 -9
- data/samples/dbg-plugins/heapscan.rb +283 -0
- data/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c +155 -0
- data/samples/dbg-plugins/heapscan/compiled_heapscan_win.c +128 -0
- data/samples/dbg-plugins/heapscan/graphheap.rb +616 -0
- data/samples/dbg-plugins/heapscan/heapscan.rb +709 -0
- data/samples/dbg-plugins/heapscan/winheap.h +174 -0
- data/samples/dbg-plugins/heapscan/winheap7.h +307 -0
- data/samples/dbg-plugins/trace_func.rb +214 -0
- data/samples/disassemble-gui.rb +35 -5
- data/samples/disassemble.rb +31 -6
- data/samples/dump_upx.rb +24 -12
- data/samples/dynamic_ruby.rb +12 -3
- data/samples/exeencode.rb +6 -5
- data/samples/factorize-headers-peimports.rb +1 -1
- data/samples/lindebug.rb +175 -381
- data/samples/metasm-shell.rb +1 -2
- data/samples/peldr.rb +2 -2
- data/tests/all.rb +1 -1
- data/tests/arc.rb +26 -0
- data/tests/dynldr.rb +22 -4
- data/tests/expression.rb +55 -0
- data/tests/graph_layout.rb +285 -0
- data/tests/ia32.rb +79 -26
- data/tests/mips.rb +9 -2
- data/tests/x86_64.rb +66 -18
- metadata +330 -218
- data/lib/metasm/arm/opcodes.rb +0 -177
- data/lib/metasm/gui.rb +0 -23
- data/lib/metasm/gui/dasm_graph.rb +0 -1354
- data/lib/metasm/ia32.rb +0 -14
- data/lib/metasm/ia32/opcodes.rb +0 -873
- data/lib/metasm/ppc/parse.rb +0 -52
- data/lib/metasm/x86_64.rb +0 -12
- data/lib/metasm/x86_64/opcodes.rb +0 -118
- data/samples/gdbclient.rb +0 -583
- data/samples/rubstop.rb +0 -399
data/metasm/gui.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
backend = ENV['METASM_GUI'] || (
|
2
|
+
if RUBY_PLATFORM =~ /(i.86|x(86_)?64)-(mswin|mingw|cygwin)/i
|
3
|
+
'win32'
|
4
|
+
else
|
5
|
+
begin
|
6
|
+
require 'gtk2'
|
7
|
+
'gtk'
|
8
|
+
rescue LoadError
|
9
|
+
raise LoadError, 'No GUI ruby binding installed - please install libgtk2-ruby'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
)
|
13
|
+
require "metasm/gui/#{backend}"
|
@@ -23,8 +23,7 @@ class CStructWidget < DrawableWidget
|
|
23
23
|
@cwidth = @cheight = 1 # widget size in chars
|
24
24
|
@structdepth = 2
|
25
25
|
|
26
|
-
@default_color_association =
|
27
|
-
:background => :white, :hl_word => :palered, :comment => :darkblue }
|
26
|
+
@default_color_association = ColorTheme.merge :keyword => :blue
|
28
27
|
end
|
29
28
|
|
30
29
|
def click(x, y)
|
@@ -90,19 +89,7 @@ class CStructWidget < DrawableWidget
|
|
90
89
|
elsif cx < @view_x
|
91
90
|
else
|
92
91
|
t = t[(@view_x - cx + t.length)..-1] if cx-t.length < @view_x
|
93
|
-
|
94
|
-
stmp = t
|
95
|
-
pre_x = 0
|
96
|
-
while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/
|
97
|
-
s1, s2 = $1, $2
|
98
|
-
pre_x += s1.length*@font_width
|
99
|
-
hl_w = s2.length*@font_width
|
100
|
-
draw_rectangle_color(:hl_word, x+pre_x, y, hl_w, @font_height)
|
101
|
-
pre_x += hl_w
|
102
|
-
stmp = stmp[s1.length+s2.length..-1]
|
103
|
-
end
|
104
|
-
end
|
105
|
-
draw_string_color(c, x, y, t)
|
92
|
+
draw_string_hl(c, x, y, t)
|
106
93
|
x += t.length * @font_width
|
107
94
|
end
|
108
95
|
}
|
@@ -116,7 +103,7 @@ class CStructWidget < DrawableWidget
|
|
116
103
|
cy = (@caret_y-@view_y)*@font_height
|
117
104
|
draw_line_color(:caret, cx, cy, cx, cy+@font_height-1)
|
118
105
|
end
|
119
|
-
|
106
|
+
|
120
107
|
@oldcaret_x, @oldcaret_y = @caret_x, @caret_y
|
121
108
|
end
|
122
109
|
|
@@ -179,28 +166,31 @@ class CStructWidget < DrawableWidget
|
|
179
166
|
when ?l
|
180
167
|
liststructs
|
181
168
|
when ?t
|
182
|
-
inputbox('new struct name to use', :text => (@curstruct.name rescue '')) { |n|
|
183
|
-
lst = @dasm.c_parser.toplevel.struct.keys.grep(String)
|
184
|
-
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
185
|
-
focus_addr(@curaddr, @dasm.c_parser.toplevel.struct[fn])
|
186
|
-
else
|
187
|
-
lst = @dasm.c_parser.toplevel.symbol.keys.grep(String).find_all { |ln|
|
188
|
-
s = @dasm.c_parser.toplevel.symbol[ln]
|
189
|
-
s.kind_of?(C::TypeDef) and s.untypedef.kind_of?(C::Union)
|
190
|
-
}
|
191
|
-
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
192
|
-
focus_addr(@curaddr, @dasm.c_parser.toplevel.symbol[fn].untypedef)
|
193
|
-
else
|
194
|
-
liststructs(n)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
}
|
169
|
+
inputbox('new struct name to use', :text => (@curstruct.name rescue '')) { |n| focus_struct_byname(n) }
|
198
170
|
else return false
|
199
171
|
end
|
200
172
|
true
|
201
173
|
end
|
202
174
|
|
203
|
-
|
175
|
+
# display the struct or pop a list of matching struct names if ambiguous
|
176
|
+
def focus_struct_byname(n, addr=@curaddr)
|
177
|
+
lst = @dasm.c_parser.toplevel.struct.keys.grep(String)
|
178
|
+
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
179
|
+
focus_addr(addr, @dasm.c_parser.toplevel.struct[fn])
|
180
|
+
else
|
181
|
+
lst = @dasm.c_parser.toplevel.symbol.keys.grep(String).find_all { |ln|
|
182
|
+
s = @dasm.c_parser.toplevel.symbol[ln]
|
183
|
+
s.kind_of?(C::TypeDef) and s.untypedef.kind_of?(C::Union)
|
184
|
+
}
|
185
|
+
if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase }
|
186
|
+
focus_addr(addr, @dasm.c_parser.toplevel.symbol[fn].untypedef)
|
187
|
+
else
|
188
|
+
liststructs(n, addr)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def liststructs(partname=nil, addr=@curaddr)
|
204
194
|
tl = @dasm.c_parser.toplevel
|
205
195
|
list = [['name', 'size']]
|
206
196
|
list += tl.struct.keys.grep(String).sort.map { |stn|
|
@@ -216,12 +206,12 @@ class CStructWidget < DrawableWidget
|
|
216
206
|
}.compact
|
217
207
|
|
218
208
|
if partname and list.length == 2
|
219
|
-
focus_addr(
|
209
|
+
focus_addr(addr, tl.struct[list[1][0]] || tl.symbol[list[1][0]].untypedef)
|
220
210
|
return
|
221
211
|
end
|
222
212
|
|
223
213
|
listwindow('structs', list) { |stn|
|
224
|
-
focus_addr(
|
214
|
+
focus_addr(addr, tl.struct[stn[0]] || tl.symbol[stn[0]].untypedef)
|
225
215
|
}
|
226
216
|
end
|
227
217
|
|
@@ -240,7 +230,7 @@ class CStructWidget < DrawableWidget
|
|
240
230
|
def update_caret
|
241
231
|
if @caret_x < @view_x or @caret_x >= @view_x + @cwidth or @caret_y < @view_y or @caret_y >= @view_y + @cheight
|
242
232
|
redraw
|
243
|
-
elsif update_hl_word(@line_text[@caret_y], @caret_x)
|
233
|
+
elsif update_hl_word(@line_text[@caret_y], @caret_x, :c)
|
244
234
|
redraw
|
245
235
|
else
|
246
236
|
invalidate_caret(@oldcaret_x-@view_x, @oldcaret_y-@view_y)
|
@@ -254,9 +244,14 @@ class CStructWidget < DrawableWidget
|
|
254
244
|
def focus_addr(addr, struct=@curstruct)
|
255
245
|
return if @parent_widget and not addr = @parent_widget.normalize(addr)
|
256
246
|
@curaddr = addr
|
257
|
-
@curstruct = struct
|
258
247
|
@caret_x = @caret_y = 0
|
259
|
-
|
248
|
+
if struct.kind_of? String
|
249
|
+
@curstruct = nil
|
250
|
+
focus_struct_byname(struct)
|
251
|
+
else
|
252
|
+
@curstruct = struct
|
253
|
+
gui_update
|
254
|
+
end
|
260
255
|
true
|
261
256
|
end
|
262
257
|
|
@@ -278,7 +273,7 @@ class CStructWidget < DrawableWidget
|
|
278
273
|
@line_text_col << []
|
279
274
|
render[indent * [@structdepth - maxdepth, 0].max, :text]
|
280
275
|
}
|
281
|
-
|
276
|
+
|
282
277
|
if not obj
|
283
278
|
@line_text_col = [[]]
|
284
279
|
@line_dereference = []
|
@@ -308,7 +303,6 @@ class CStructWidget < DrawableWidget
|
|
308
303
|
elsif struct.kind_of?(C::Struct)
|
309
304
|
render["struct #{struct.name || '_'} st_#{Expression[@curaddr]} = ", :text] if not off
|
310
305
|
fldoff = struct.fldoffset
|
311
|
-
fbo = struct.fldbitoffset || {}
|
312
306
|
else
|
313
307
|
render["union #{struct.name || '_'} un_#{Expression[@curaddr]} = ", :text] if not off
|
314
308
|
end
|
@@ -363,7 +357,7 @@ class CStructWidget < DrawableWidget
|
|
363
357
|
else
|
364
358
|
@line_text_col = [[[:text, '/* no struct selected (list with "l") */']]]
|
365
359
|
end
|
366
|
-
|
360
|
+
|
367
361
|
@line_text = @line_text_col.map { |l| l.map { |c, s| s }.join }
|
368
362
|
update_caret
|
369
363
|
redraw
|
@@ -19,15 +19,15 @@ class CoverageWidget < DrawableWidget
|
|
19
19
|
@section_x = []
|
20
20
|
@slave = nil # another dasmwidget whose curaddr is kept sync
|
21
21
|
|
22
|
-
@default_color_association =
|
23
|
-
:background => :palegrey, :code => :red, :data => :blue
|
22
|
+
@default_color_association = ColorTheme.merge :caret => :yellow, :caret_col => :darkyellow,
|
23
|
+
:background => :palegrey, :code => :red, :data => :blue
|
24
24
|
end
|
25
25
|
|
26
26
|
def click(x, y)
|
27
27
|
x, y = x.to_i - 1, y.to_i
|
28
|
-
@sections.zip(@section_x).each { |
|
29
|
-
if x >= sx and x <
|
30
|
-
@curaddr =
|
28
|
+
@sections.zip(@section_x).each { |s, sx|
|
29
|
+
if x >= sx[0] and x < sx[1]+@pixel_w
|
30
|
+
@curaddr = s[0] + (x-sx[0])/@pixel_w*@byte_per_col + (y/@pixel_h-@spacing)*@byte_per_col/@col_height
|
31
31
|
@slave.focus_addr(@curaddr) if @slave rescue @slave=nil
|
32
32
|
redraw
|
33
33
|
break
|
@@ -125,13 +125,13 @@ class CoverageWidget < DrawableWidget
|
|
125
125
|
x += @spacing*@pixel_w
|
126
126
|
}
|
127
127
|
|
128
|
-
@sections.zip(@section_x).each { |
|
129
|
-
next if @curaddr.kind_of? Integer and not
|
130
|
-
next if @curaddr.kind_of? Expression and not
|
131
|
-
co = @curaddr-
|
132
|
-
if co >= 0 and co <
|
128
|
+
@sections.zip(@section_x).each { |s, sx|
|
129
|
+
next if @curaddr.kind_of? Integer and not s[0].kind_of? Integer
|
130
|
+
next if @curaddr.kind_of? Expression and not s[0].kind_of? Expression
|
131
|
+
co = @curaddr-s[0]
|
132
|
+
if co >= 0 and co < s[1]
|
133
133
|
draw_color :caret_col
|
134
|
-
x = sx + (co/@byte_per_col)*@pixel_w
|
134
|
+
x = sx[0] + (co/@byte_per_col)*@pixel_w
|
135
135
|
draw_rect[-@spacing, -1, 1]
|
136
136
|
draw_rect[@col_height, @col_height+@spacing, 1]
|
137
137
|
draw_color :caret
|
@@ -19,9 +19,8 @@ class CdecompListingWidget < DrawableWidget
|
|
19
19
|
@curaddr = nil
|
20
20
|
@tabwidth = 8
|
21
21
|
|
22
|
-
@default_color_association =
|
23
|
-
|
24
|
-
:globalvar => :darkgreen, :intrinsic => :darkyellow }
|
22
|
+
@default_color_association = ColorTheme.merge :keyword => :blue, :localvar => :darkred,
|
23
|
+
:globalvar => :darkgreen, :intrinsic => :darkyellow
|
25
24
|
end
|
26
25
|
|
27
26
|
def curfunc
|
@@ -91,19 +90,7 @@ class CdecompListingWidget < DrawableWidget
|
|
91
90
|
# must not include newline
|
92
91
|
render = lambda { |str, color|
|
93
92
|
# function ends when we write under the bottom of the listing
|
94
|
-
|
95
|
-
stmp = str
|
96
|
-
pre_x = 0
|
97
|
-
while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/
|
98
|
-
s1, s2 = $1, $2
|
99
|
-
pre_x += s1.length*@font_width
|
100
|
-
hl_w = s2.length*@font_width
|
101
|
-
draw_rectangle_color(:hl_word, x+pre_x, y, hl_w, @font_height)
|
102
|
-
pre_x += hl_w
|
103
|
-
stmp = stmp[s1.length+s2.length..-1]
|
104
|
-
end
|
105
|
-
end
|
106
|
-
draw_string_color(color, x, y, str)
|
93
|
+
draw_string_hl(color, x, y, str)
|
107
94
|
x += str.length * @font_width
|
108
95
|
}
|
109
96
|
|
@@ -128,7 +115,7 @@ class CdecompListingWidget < DrawableWidget
|
|
128
115
|
cy = (@caret_y-@view_y)*@font_height
|
129
116
|
draw_line_color(:caret, cx, cy, cx, cy+@font_height-1)
|
130
117
|
end
|
131
|
-
|
118
|
+
|
132
119
|
@oldcaret_x, @oldcaret_y = @caret_x, @caret_y
|
133
120
|
end
|
134
121
|
|
@@ -184,7 +171,7 @@ class CdecompListingWidget < DrawableWidget
|
|
184
171
|
f.decompdata[:stackoff_name][s.stackoff] = v if s.stackoff
|
185
172
|
elsif @dasm.c_parser.toplevel.symbol[n]
|
186
173
|
@dasm.rename_label(n, v)
|
187
|
-
@curaddr = v if @curaddr == n
|
174
|
+
@curaddr = v if @curaddr == n
|
188
175
|
end
|
189
176
|
gui_update
|
190
177
|
}
|
@@ -264,13 +251,13 @@ class CdecompListingWidget < DrawableWidget
|
|
264
251
|
invalidate_caret(@caret_x-@view_x, @caret_y-@view_y)
|
265
252
|
@oldcaret_x, @oldcaret_y = @caret_x, @caret_y
|
266
253
|
|
267
|
-
redraw if update_hl_word(@line_text[@caret_y], @caret_x)
|
254
|
+
redraw if update_hl_word(@line_text[@caret_y], @caret_x, :c)
|
268
255
|
end
|
269
256
|
|
270
257
|
# focus on addr
|
271
258
|
# returns true on success (address exists & decompiled)
|
272
259
|
def focus_addr(addr)
|
273
|
-
if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr])
|
260
|
+
if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr].kind_of?(C::Union))
|
274
261
|
@curaddr = addr
|
275
262
|
@caret_x = @caret_y = 0
|
276
263
|
gui_update
|
File without changes
|
@@ -0,0 +1,1695 @@
|
|
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 Graph
|
9
|
+
# one box, has a text, an id, and a list of other boxes to/from
|
10
|
+
class Box
|
11
|
+
attr_accessor :id, :x, :y, :w, :h
|
12
|
+
attr_accessor :to, :from # other boxes linked (arrays)
|
13
|
+
attr_accessor :content
|
14
|
+
attr_accessor :direct_to
|
15
|
+
def initialize(id, content=nil)
|
16
|
+
@id = id
|
17
|
+
@x = @y = @w = @h = 0
|
18
|
+
@to, @from = [], []
|
19
|
+
@content = content
|
20
|
+
end
|
21
|
+
def [](a) @content[a] end
|
22
|
+
#def inspect ; puts caller ; "#{Expression[@id] rescue @id.inspect}" end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :id, :box, :box_id, :root_addrs, :view_x, :view_y, :keep_split
|
26
|
+
def initialize(id)
|
27
|
+
@id = id
|
28
|
+
@root_addrs = []
|
29
|
+
@view_x = @view_y = -0xfff_ffff
|
30
|
+
clear
|
31
|
+
end
|
32
|
+
|
33
|
+
# empty @box
|
34
|
+
def clear
|
35
|
+
@box = []
|
36
|
+
@box_id = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# link the two boxes (by id)
|
40
|
+
def link_boxes(id1, id2)
|
41
|
+
raise "unknown index 1 #{id1}" if not b1 = @box_id[id1]
|
42
|
+
raise "unknown index 2 #{id2}" if not b2 = @box_id[id2]
|
43
|
+
b1.to |= [b2]
|
44
|
+
b2.from |= [b1]
|
45
|
+
end
|
46
|
+
|
47
|
+
# creates a new box, ensures id is not already taken
|
48
|
+
def new_box(id, content=nil)
|
49
|
+
raise "duplicate id #{id}" if @box_id[id]
|
50
|
+
b = Box.new(id, content)
|
51
|
+
@box << b
|
52
|
+
@box_id[id] = b
|
53
|
+
b
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns the [x1, y1, x2, y2] of the rectangle encompassing all boxes
|
57
|
+
def boundingbox
|
58
|
+
minx = @box.map { |b| b.x }.min.to_i
|
59
|
+
miny = @box.map { |b| b.y }.min.to_i
|
60
|
+
maxx = @box.map { |b| b.x + b.w }.max.to_i
|
61
|
+
maxy = @box.map { |b| b.y + b.h }.max.to_i
|
62
|
+
[minx, miny, maxx, maxy]
|
63
|
+
end
|
64
|
+
|
65
|
+
# a -> b -> c -> d (no other in/outs)
|
66
|
+
def pattern_layout_col(groups)
|
67
|
+
# find head
|
68
|
+
return if not head = groups.find { |g|
|
69
|
+
g.to.length == 1 and
|
70
|
+
g.to[0].from.length == 1 and
|
71
|
+
(g.from.length != 1 or g.from[0].to.length != 1)
|
72
|
+
}
|
73
|
+
|
74
|
+
# find full sequence
|
75
|
+
ar = [head]
|
76
|
+
while head.to.length == 1 and head.to[0].from.length == 1
|
77
|
+
head = head.to[0]
|
78
|
+
ar << head
|
79
|
+
end
|
80
|
+
|
81
|
+
# move boxes inside this group
|
82
|
+
maxw = ar.map { |g| g.w }.max
|
83
|
+
fullh = ar.inject(0) { |h, g| h + g.h }
|
84
|
+
cury = -fullh/2
|
85
|
+
ar.each { |g|
|
86
|
+
dy = cury - g.y
|
87
|
+
g.content.each { |b| b.y += dy }
|
88
|
+
cury += g.h
|
89
|
+
}
|
90
|
+
|
91
|
+
# create remplacement group
|
92
|
+
newg = Box.new(nil, ar.map { |g| g.content }.flatten)
|
93
|
+
newg.w = maxw
|
94
|
+
newg.h = fullh
|
95
|
+
newg.x = -newg.w/2
|
96
|
+
newg.y = -newg.h/2
|
97
|
+
newg.from = ar.first.from - ar
|
98
|
+
newg.to = ar.last.to - ar
|
99
|
+
# fix xrefs
|
100
|
+
newg.from.each { |g| g.to -= ar ; g.to << newg }
|
101
|
+
newg.to.each { |g| g.from -= ar ; g.from << newg }
|
102
|
+
# fix groups
|
103
|
+
groups[groups.index(head)] = newg
|
104
|
+
ar.each { |g| groups.delete g }
|
105
|
+
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
# if a group has no content close to its x/x+w borders, shrink it
|
110
|
+
def group_remove_hz_margin(g, maxw=16)
|
111
|
+
if g.content.empty?
|
112
|
+
g.x = -maxw/2 if g.x < -maxw/2
|
113
|
+
g.w = maxw if g.w > maxw
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
margin_left = g.content.map { |b| b.x }.min - g.x
|
118
|
+
margin_right = g.x+g.w - g.content.map { |b| b.x+b.w }.max
|
119
|
+
if margin_left + margin_right > maxw
|
120
|
+
g.w -= margin_left + margin_right - maxw
|
121
|
+
dx = (maxw/2 + margin_right - margin_left)/2
|
122
|
+
g.content.each { |b| b.x += dx }
|
123
|
+
g.x = -g.w/2
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# a -> [b, c, d] -> e
|
128
|
+
def pattern_layout_line(groups)
|
129
|
+
# find head
|
130
|
+
ar = []
|
131
|
+
groups.each { |g|
|
132
|
+
if g.from.length == 1 and g.to.length <= 1 and g.from.first.to.length > 1
|
133
|
+
ar = g.from.first.to.find_all { |gg| gg.from == g.from and gg.to == g.to }
|
134
|
+
elsif g.from.empty? and g.to.length == 1 and g.to.first.from.length > 1
|
135
|
+
ar = g.to.first.from.find_all { |gg| gg.from == g.from and gg.to == g.to }
|
136
|
+
else ar = []
|
137
|
+
end
|
138
|
+
break if ar.length > 1
|
139
|
+
}
|
140
|
+
return if ar.length <= 1
|
141
|
+
|
142
|
+
ar.each { |g| group_remove_hz_margin(g) }
|
143
|
+
|
144
|
+
# move boxes inside this group
|
145
|
+
#ar = ar.sort_by { |g| -g.h }
|
146
|
+
maxh = ar.map { |g| g.h }.max
|
147
|
+
fullw = ar.inject(0) { |w, g| w + g.w }
|
148
|
+
curx = -fullw/2
|
149
|
+
ar.each { |g|
|
150
|
+
# if no to, put all boxes at bottom ; if no from, put them at top
|
151
|
+
case [g.from.length, g.to.length]
|
152
|
+
when [1, 0]; dy = (g.h - maxh)/2
|
153
|
+
when [0, 1]; dy = (maxh - g.h)/2
|
154
|
+
else dy = 0
|
155
|
+
end
|
156
|
+
|
157
|
+
dx = curx - g.x
|
158
|
+
g.content.each { |b| b.x += dx ; b.y += dy }
|
159
|
+
curx += g.w
|
160
|
+
}
|
161
|
+
# add a 'margin-top' proportionnal to the ar width
|
162
|
+
# this gap should be relative to the real boxes and not possible previous gaps when
|
163
|
+
# merging lines (eg long line + many if patterns -> dont duplicate gaps)
|
164
|
+
boxen = ar.map { |g| g.content }.flatten
|
165
|
+
realh = boxen.map { |g| g.y + g.h }.max - boxen.map { |g| g.y }.min
|
166
|
+
if maxh < realh + fullw/4
|
167
|
+
maxh = realh + fullw/4
|
168
|
+
end
|
169
|
+
|
170
|
+
# create remplacement group
|
171
|
+
newg = Box.new(nil, ar.map { |g| g.content }.flatten)
|
172
|
+
newg.w = fullw
|
173
|
+
newg.h = maxh
|
174
|
+
newg.x = -newg.w/2
|
175
|
+
newg.y = -newg.h/2
|
176
|
+
newg.from = ar.first.from
|
177
|
+
newg.to = ar.first.to
|
178
|
+
# fix xrefs
|
179
|
+
newg.from.each { |g| g.to -= ar ; g.to << newg }
|
180
|
+
newg.to.each { |g| g.from -= ar ; g.from << newg }
|
181
|
+
# fix groups
|
182
|
+
groups[groups.index(ar.first)] = newg
|
183
|
+
ar.each { |g| groups.delete g }
|
184
|
+
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
188
|
+
# a -> b -> c & a -> c
|
189
|
+
def pattern_layout_ifend(groups)
|
190
|
+
# find head
|
191
|
+
return if not head = groups.find { |g|
|
192
|
+
g.to.length == 2 and
|
193
|
+
((g.to[0].from.length == 1 and g.to[0].to.length == 1 and g.to[0].to[0] == g.to[1]) or
|
194
|
+
(g.to[1].from.length == 1 and g.to[1].to.length == 1 and g.to[1].to[0] == g.to[0]))
|
195
|
+
}
|
196
|
+
|
197
|
+
if head.to[0].to.include?(head.to[1])
|
198
|
+
ten = head.to[0]
|
199
|
+
else
|
200
|
+
ten = head.to[1]
|
201
|
+
end
|
202
|
+
|
203
|
+
# stuff 'then' inside the 'if'
|
204
|
+
# move 'if' up, 'then' down
|
205
|
+
head.content.each { |g| g.y -= ten.h/2 }
|
206
|
+
ten.content.each { |g| g.y += head.h/2 }
|
207
|
+
head.h += ten.h
|
208
|
+
head.y -= ten.h/2
|
209
|
+
|
210
|
+
# widen 'if'
|
211
|
+
# this adds a phantom left side
|
212
|
+
# drop existing margins first
|
213
|
+
group_remove_hz_margin(ten)
|
214
|
+
dw = ten.w - head.w/2
|
215
|
+
if dw > 0
|
216
|
+
# need to widen head to fit ten
|
217
|
+
head.w += 2*dw
|
218
|
+
head.x -= dw
|
219
|
+
end
|
220
|
+
|
221
|
+
# merge
|
222
|
+
ten.content.each { |g| g.x += -ten.x }
|
223
|
+
head.content.concat ten.content
|
224
|
+
|
225
|
+
head.to.delete ten
|
226
|
+
head.to[0].from.delete ten
|
227
|
+
|
228
|
+
groups.delete ten
|
229
|
+
|
230
|
+
true
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
def pattern_layout_complex(groups)
|
235
|
+
order = order_graph(groups)
|
236
|
+
uniq = nil
|
237
|
+
if groups.sort_by { |g| order[g] }.find { |g|
|
238
|
+
next if g.to.length <= 1
|
239
|
+
# list all nodes reachable for every 'to'
|
240
|
+
reach = g.to.map { |t| list_reachable(t) }
|
241
|
+
# list all nodes reachable only from a single 'to'
|
242
|
+
uniq = []
|
243
|
+
reach.each_with_index { |r, i|
|
244
|
+
# take all nodes reachable from there ...
|
245
|
+
u = uniq[i] = r.dup
|
246
|
+
u.delete_if { |k, v| k.content.empty? } # ignore previous layout_complex artifacts
|
247
|
+
reach.each_with_index { |rr, ii|
|
248
|
+
next if i == ii
|
249
|
+
# ... and delete nodes reachable from anywhere else
|
250
|
+
rr.each_key { |k| u.delete k }
|
251
|
+
}
|
252
|
+
}
|
253
|
+
uniq.delete_if { |u| u.length <= 1 }
|
254
|
+
!uniq.empty?
|
255
|
+
}
|
256
|
+
# now layout every uniq subgroup independently
|
257
|
+
uniq.each { |u|
|
258
|
+
subgroups = groups.find_all { |g| u[g] }
|
259
|
+
|
260
|
+
# isolate subgroup from external links
|
261
|
+
# change all external links into a single empty box
|
262
|
+
newtop = Box.new(nil, [])
|
263
|
+
newtop.x = -8 ; newtop.y = -9
|
264
|
+
newtop.w = 16 ; newtop.h = 18
|
265
|
+
newbot = Box.new(nil, [])
|
266
|
+
newbot.x = -8 ; newbot.y = -9
|
267
|
+
newbot.w = 16 ; newbot.h = 18
|
268
|
+
hadfrom = [] ; hadto = []
|
269
|
+
subgroups.each { |g|
|
270
|
+
g.to.dup.each { |t|
|
271
|
+
next if u[t]
|
272
|
+
newbot.from |= [g]
|
273
|
+
g.to.delete t
|
274
|
+
hadto << t
|
275
|
+
g.to |= [newbot]
|
276
|
+
}
|
277
|
+
g.from.dup.each { |f|
|
278
|
+
next if u[f]
|
279
|
+
newtop.to |= [g]
|
280
|
+
g.from.delete f
|
281
|
+
hadfrom << f
|
282
|
+
g.from |= [newtop]
|
283
|
+
}
|
284
|
+
}
|
285
|
+
subgroups << newtop << newbot
|
286
|
+
|
287
|
+
# subgroup layout
|
288
|
+
auto_arrange_step(subgroups) while subgroups.length > 1
|
289
|
+
newg = subgroups[0]
|
290
|
+
|
291
|
+
# patch 'groups'
|
292
|
+
idx = groups.index { |g| u[g] }
|
293
|
+
groups.delete_if { |g| u[g] }
|
294
|
+
groups[idx, 0] = [newg]
|
295
|
+
|
296
|
+
# restore external links & fix xrefs
|
297
|
+
hadfrom.uniq.each { |f|
|
298
|
+
f.to.delete_if { |t| u[t] }
|
299
|
+
f.to |= [newg]
|
300
|
+
newg.from |= [f]
|
301
|
+
}
|
302
|
+
hadto.uniq.each { |t|
|
303
|
+
t.from.delete_if { |f| u[f] }
|
304
|
+
t.from |= [newg]
|
305
|
+
newg.to |= [t]
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
true
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# find the minimal set of nodes from which we can reach all others
|
314
|
+
# this is done *before* removing cycles in the graph
|
315
|
+
# returns the order (Hash group => group_order)
|
316
|
+
# roots have an order of 0
|
317
|
+
def order_graph(groups)
|
318
|
+
roots = groups.find_all { |g| g.from.empty? }
|
319
|
+
o = {} # tentative order
|
320
|
+
todo = []
|
321
|
+
|
322
|
+
loop do
|
323
|
+
roots.each { |g|
|
324
|
+
o[g] ||= 0
|
325
|
+
todo |= g.to.find_all { |gg| not o[gg] }
|
326
|
+
}
|
327
|
+
|
328
|
+
# order nodes from the tentative roots
|
329
|
+
until todo.empty?
|
330
|
+
n = todo.find { |g| g.from.all? { |gg| o[gg] } } || order_solve_cycle(todo, o)
|
331
|
+
todo.delete n
|
332
|
+
o[n] = n.from.map { |g| o[g] }.compact.max + 1
|
333
|
+
todo |= n.to.find_all { |g| not o[g] }
|
334
|
+
end
|
335
|
+
break if o.length >= groups.length
|
336
|
+
|
337
|
+
# pathological cases
|
338
|
+
|
339
|
+
if noroot = groups.find_all { |g| o[g] and g.from.find { |gg| not o[gg] } }.sort_by { |g| o[g] }.first
|
340
|
+
# we picked a root in the middle of the graph, walk up
|
341
|
+
todo |= noroot.from.find_all { |g| not o[g] }
|
342
|
+
until todo.empty?
|
343
|
+
n = todo.find { |g| g.to.all? { |gg| o[gg] } } ||
|
344
|
+
todo.sort_by { |g| g.to.map { |gg| o[gg] }.compact.min }.first
|
345
|
+
todo.delete n
|
346
|
+
o[n] = n.to.map { |g| o[g] }.compact.min - 1
|
347
|
+
todo |= n.from.find_all { |g| not o[g] }
|
348
|
+
end
|
349
|
+
# setup todo for next fwd iteration
|
350
|
+
todo |= groups.find_all { |g| not o[g] and g.from.find { |gg| o[gg] } }
|
351
|
+
else
|
352
|
+
# disjoint graph, start over from one other random node
|
353
|
+
roots << groups.find { |g| not o[g] }
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
if o.values.find { |rank| rank < 0 }
|
358
|
+
# did hit a pathological case, restart with found real roots
|
359
|
+
roots = groups.find_all { |g| not g.from.find { |gg| o[gg] < o[g] } }
|
360
|
+
o = {}
|
361
|
+
todo = []
|
362
|
+
roots.each { |g|
|
363
|
+
o[g] ||= 0
|
364
|
+
todo |= g.to.find_all { |gg| not o[gg] }
|
365
|
+
}
|
366
|
+
until todo.empty?
|
367
|
+
n = todo.find { |g| g.from.all? { |gg| o[gg] } } || order_solve_cycle(todo, o)
|
368
|
+
todo.delete n
|
369
|
+
o[n] = n.from.map { |g| o[g] }.compact.max + 1
|
370
|
+
todo |= n.to.find_all { |g| not o[g] }
|
371
|
+
end
|
372
|
+
|
373
|
+
# there's something screwy around here !
|
374
|
+
raise "moo" if o.length < groups.length
|
375
|
+
end
|
376
|
+
|
377
|
+
o
|
378
|
+
end
|
379
|
+
|
380
|
+
def order_solve_cycle(todo, o)
|
381
|
+
# 'todo' has no trivial candidate
|
382
|
+
# pick one node from todo which no other todo can reach
|
383
|
+
# exclude pathing through already ordered nodes
|
384
|
+
todo.find { |t1|
|
385
|
+
not todo.find { |t2| t1 != t2 and can_find_path(t2, t1, o.dup) }
|
386
|
+
} ||
|
387
|
+
# some cycle heads are mutually recursive
|
388
|
+
todo.sort_by { |t1|
|
389
|
+
# find the one who can reach the most others
|
390
|
+
[todo.find_all { |t2| t1 != t2 and can_find_path(t1, t2, o.dup) }.length,
|
391
|
+
# and with the highest rank
|
392
|
+
t1.from.map { |gg| o[gg] }.compact.max]
|
393
|
+
}.last
|
394
|
+
end
|
395
|
+
|
396
|
+
# checks if there is a path from src to dst avoiding stuff in 'done'
|
397
|
+
def can_find_path(src, dst, done={})
|
398
|
+
todo = [src]
|
399
|
+
while g = todo.pop
|
400
|
+
next if done[g]
|
401
|
+
return true if g == dst
|
402
|
+
done[g] = true
|
403
|
+
todo.concat g.to
|
404
|
+
end
|
405
|
+
false
|
406
|
+
end
|
407
|
+
|
408
|
+
# returns a hash with true for every node reachable from src (included)
|
409
|
+
def list_reachable(src, done={})
|
410
|
+
todo = [src]
|
411
|
+
while g = todo.pop
|
412
|
+
next if done[g]
|
413
|
+
done[g] = true
|
414
|
+
todo.concat g.to
|
415
|
+
end
|
416
|
+
done
|
417
|
+
end
|
418
|
+
|
419
|
+
# revert looping edges in groups
|
420
|
+
def make_tree(groups, order)
|
421
|
+
# now we have the roots and node orders
|
422
|
+
# revert cycling edges - o(chld) < o(parent)
|
423
|
+
order.each_key { |g|
|
424
|
+
g.to.dup.each { |gg|
|
425
|
+
if order[gg] < order[g]
|
426
|
+
# cycling edge, revert
|
427
|
+
g.to.delete gg
|
428
|
+
gg.from.delete g
|
429
|
+
g.from |= [gg]
|
430
|
+
gg.to |= [g]
|
431
|
+
end
|
432
|
+
}
|
433
|
+
}
|
434
|
+
end
|
435
|
+
|
436
|
+
# group groups in layers of same order
|
437
|
+
# create dummy groups along long edges so that no path exists between non-contiguous layers
|
438
|
+
def create_layers(groups, order)
|
439
|
+
newemptybox = lambda {
|
440
|
+
b = Box.new(nil, [])
|
441
|
+
b.x = -8
|
442
|
+
b.y = -9
|
443
|
+
b.w = 16
|
444
|
+
b.h = 18
|
445
|
+
groups << b
|
446
|
+
b
|
447
|
+
}
|
448
|
+
|
449
|
+
newboxo = {}
|
450
|
+
|
451
|
+
order.each_key { |g|
|
452
|
+
og = order[g] || newboxo[g]
|
453
|
+
g.to.dup.each { |gg|
|
454
|
+
ogg = order[gg] || newboxo[gg]
|
455
|
+
if ogg > og+1
|
456
|
+
# long edge, expand
|
457
|
+
sq = [g]
|
458
|
+
(ogg - 1 - og).times { |i| sq << newemptybox[] }
|
459
|
+
sq << gg
|
460
|
+
gg.from.delete g
|
461
|
+
g.to.delete gg
|
462
|
+
newboxo[g] ||= order[g]
|
463
|
+
sq.inject { |g1, g2|
|
464
|
+
g1.to |= [g2]
|
465
|
+
g2.from |= [g1]
|
466
|
+
newboxo[g2] = newboxo[g1]+1
|
467
|
+
g2
|
468
|
+
}
|
469
|
+
raise if newboxo[gg] != ogg
|
470
|
+
end
|
471
|
+
}
|
472
|
+
}
|
473
|
+
|
474
|
+
order.update newboxo
|
475
|
+
|
476
|
+
# layers[o] = [list of nodes of order o]
|
477
|
+
layers = []
|
478
|
+
groups.each { |g|
|
479
|
+
(layers[order[g]] ||= []) << g
|
480
|
+
}
|
481
|
+
|
482
|
+
layers
|
483
|
+
end
|
484
|
+
|
485
|
+
# take all groups, order them by order, layout as layers
|
486
|
+
# always return a single group holding everything
|
487
|
+
def layout_layers(groups)
|
488
|
+
order = order_graph(groups)
|
489
|
+
# already a tree
|
490
|
+
layers = create_layers(groups, order)
|
491
|
+
return if layers.empty?
|
492
|
+
|
493
|
+
layers.each { |l| l.each { |g| group_remove_hz_margin(g) } }
|
494
|
+
|
495
|
+
# widest layer width
|
496
|
+
maxlw = layers.map { |l| l.inject(0) { |ll, g| ll + g.w } }.max
|
497
|
+
|
498
|
+
# center the 1st layer boxes on a segment that large
|
499
|
+
x0 = -maxlw/2.0
|
500
|
+
curlw = layers[0].inject(0) { |ll, g| ll + g.w }
|
501
|
+
dx0 = (maxlw - curlw) / (2.0*layers[0].length)
|
502
|
+
layers[0].each { |g|
|
503
|
+
x0 += dx0
|
504
|
+
g.x = x0
|
505
|
+
x0 += g.w + dx0
|
506
|
+
}
|
507
|
+
|
508
|
+
# at this point, the goal is to reorder the most populated layer the best we can, and
|
509
|
+
# move other layers' boxes accordingly
|
510
|
+
layers[1..-1].each { |l|
|
511
|
+
# for each subsequent layer, reorder boxes based on their ties with the previous layer
|
512
|
+
i = 0
|
513
|
+
l.replace l.sort_by { |g|
|
514
|
+
# we know g.from is not empty (g would be in @layer[0])
|
515
|
+
medfrom = g.from.inject(0.0) { |mx, gg| mx + (gg.x + gg.w/2.0) } / g.from.length
|
516
|
+
# on ties, keep original order
|
517
|
+
[medfrom, i]
|
518
|
+
}
|
519
|
+
# now they are reordered, update their #x accordingly
|
520
|
+
# evenly distribute them in the layer
|
521
|
+
x0 = -maxlw/2.0
|
522
|
+
curlw = l.inject(0) { |ll, g| ll + g.w }
|
523
|
+
dx0 = (maxlw - curlw) / (2.0*l.length)
|
524
|
+
l.each { |g|
|
525
|
+
x0 += dx0
|
526
|
+
g.x = x0
|
527
|
+
x0 += g.w + dx0
|
528
|
+
}
|
529
|
+
}
|
530
|
+
|
531
|
+
layers[0...-1].reverse_each { |l|
|
532
|
+
# for each subsequent layer, reorder boxes based on their ties with the previous layer
|
533
|
+
i = 0
|
534
|
+
l.replace l.sort_by { |g|
|
535
|
+
if g.to.empty?
|
536
|
+
# TODO floating end
|
537
|
+
medfrom = 0
|
538
|
+
else
|
539
|
+
medfrom = g.to.inject(0.0) { |mx, gg| mx + (gg.x + gg.w/2.0) } / g.to.length
|
540
|
+
end
|
541
|
+
# on ties, keep original order
|
542
|
+
[medfrom, i]
|
543
|
+
}
|
544
|
+
# now they are reordered, update their #x accordingly
|
545
|
+
x0 = -maxlw/2.0
|
546
|
+
curlw = l.inject(0) { |ll, g| ll + g.w }
|
547
|
+
dx0 = (maxlw - curlw) / (2.0*l.length)
|
548
|
+
l.each { |g|
|
549
|
+
x0 += dx0
|
550
|
+
g.x = x0
|
551
|
+
x0 += g.w + dx0
|
552
|
+
}
|
553
|
+
}
|
554
|
+
|
555
|
+
# now the boxes are (hopefully) sorted correctly
|
556
|
+
# position them according to their ties with prev/next layer
|
557
|
+
# from the maxw layer (positionning = packed), propagate adjacent layers positions
|
558
|
+
maxidx = (0..layers.length).find { |i| l = layers[i] ; l.inject(0) { |ll, g| ll + g.w } == maxlw }
|
559
|
+
# list of layer indexes to walk
|
560
|
+
ilist = [maxidx]
|
561
|
+
ilist.concat((maxidx+1...layers.length).to_a) if maxidx < layers.length-1
|
562
|
+
ilist.concat((0..maxidx-1).to_a.reverse) if maxidx > 0
|
563
|
+
layerbox = []
|
564
|
+
ilist.each { |i|
|
565
|
+
l = layers[i]
|
566
|
+
curlw = l.inject(0) { |ll, g| ll + g.w }
|
567
|
+
# left/rightmost acceptable position for the current box w/o overflowing on the right side
|
568
|
+
minx = -maxlw/2.0
|
569
|
+
maxx = minx + (maxlw-curlw)
|
570
|
+
|
571
|
+
# replace whole layer with a box
|
572
|
+
newg = layerbox[i] = Box.new(nil, l.map { |g| g.content }.flatten)
|
573
|
+
newg.w = maxlw
|
574
|
+
newg.h = l.map { |g| g.h }.max
|
575
|
+
newg.x = -newg.w/2
|
576
|
+
newg.y = -newg.h/2
|
577
|
+
# dont care for from/to, we'll return a single box anyway
|
578
|
+
|
579
|
+
l.each { |g|
|
580
|
+
ref = (i < maxidx) ? g.to : g.from
|
581
|
+
# TODO elastic positionning around the ideal position
|
582
|
+
# (g and g+1 may have the same med, then center both on it)
|
583
|
+
if i == maxidx
|
584
|
+
nx = minx
|
585
|
+
elsif ref.empty?
|
586
|
+
nx = (minx+maxx)/2
|
587
|
+
else
|
588
|
+
# center on the outline of rx
|
589
|
+
# may want to center on rx center's center ?
|
590
|
+
rx = ref.sort_by { |gg| gg.x }
|
591
|
+
med = (rx.first.x + rx.last.x + rx.last.w - g.w) / 2.0
|
592
|
+
nx = [[med, minx].max, maxx].min
|
593
|
+
end
|
594
|
+
dx = nx+g.w/2
|
595
|
+
g.content.each { |b| b.x += dx }
|
596
|
+
minx = nx+g.w
|
597
|
+
maxx += g.w
|
598
|
+
}
|
599
|
+
}
|
600
|
+
|
601
|
+
newg = Box.new(nil, layerbox.map { |g| g.content }.flatten)
|
602
|
+
newg.w = layerbox.map { |g| g.w }.max
|
603
|
+
newg.h = layerbox.inject(0) { |h, g| h + g.h }
|
604
|
+
newg.x = -newg.w/2
|
605
|
+
newg.y = -newg.h/2
|
606
|
+
|
607
|
+
# vertical: just center each box on its layer
|
608
|
+
y0 = newg.y
|
609
|
+
layerbox.each { |lg|
|
610
|
+
lg.content.each { |b|
|
611
|
+
b.y += y0-lg.y
|
612
|
+
}
|
613
|
+
y0 += lg.h
|
614
|
+
}
|
615
|
+
|
616
|
+
groups.replace [newg]
|
617
|
+
end
|
618
|
+
|
619
|
+
|
620
|
+
# place boxes in a good-looking layout
|
621
|
+
# create artificial 'group' container for boxes, that will later be merged in geometrical patterns
|
622
|
+
def auto_arrange_init
|
623
|
+
# 'group' is an array of boxes
|
624
|
+
# all groups are centered on the origin
|
625
|
+
h = {} # { box => group }
|
626
|
+
@groups = @box.map { |b|
|
627
|
+
b.x = -b.w/2
|
628
|
+
b.y = -b.h/2
|
629
|
+
g = Box.new(nil, [b])
|
630
|
+
g.x = b.x - 8
|
631
|
+
g.y = b.y - 9
|
632
|
+
g.w = b.w + 16
|
633
|
+
g.h = b.h + 18
|
634
|
+
h[b] = g
|
635
|
+
g
|
636
|
+
}
|
637
|
+
|
638
|
+
# init group.to/from
|
639
|
+
# must always point to something that is in the 'groups' array
|
640
|
+
# no self references
|
641
|
+
# a box is in one and only one group in 'groups'
|
642
|
+
@groups.each { |g|
|
643
|
+
g.to = g.content.first.to.map { |t| h[t] if t != g }.compact
|
644
|
+
g.from = g.content.first.from.map { |f| h[f] if f != g }.compact
|
645
|
+
}
|
646
|
+
|
647
|
+
# order boxes
|
648
|
+
order = order_graph(@groups)
|
649
|
+
|
650
|
+
# remove cycles from the graph
|
651
|
+
make_tree(@groups, order)
|
652
|
+
end
|
653
|
+
|
654
|
+
def auto_arrange_step(groups=@groups)
|
655
|
+
pattern_layout_col(groups) or pattern_layout_line(groups) or
|
656
|
+
pattern_layout_ifend(groups) or pattern_layout_complex(groups) or
|
657
|
+
layout_layers(groups)
|
658
|
+
end
|
659
|
+
|
660
|
+
def auto_arrange_post
|
661
|
+
auto_arrange_movebox
|
662
|
+
#auto_arrange_vertical_shrink
|
663
|
+
end
|
664
|
+
|
665
|
+
# actually move boxes inside the groups
|
666
|
+
def auto_arrange_movebox
|
667
|
+
@groups.each { |g|
|
668
|
+
dx = (g.x + g.w/2).to_i
|
669
|
+
dy = (g.y + g.h/2).to_i
|
670
|
+
g.content.each { |b|
|
671
|
+
b.x += dx
|
672
|
+
b.y += dy
|
673
|
+
}
|
674
|
+
}
|
675
|
+
end
|
676
|
+
|
677
|
+
def auto_arrange_vertical_shrink
|
678
|
+
# vertical shrink
|
679
|
+
# TODO stuff may shrink vertically more if we could move it slightly horizontally...
|
680
|
+
@box.sort_by { |b| b.y }.each { |b|
|
681
|
+
|
682
|
+
next if b.from.empty?
|
683
|
+
# move box up to its from, unless something blocks the way
|
684
|
+
|
685
|
+
min_y = b.from.map { |bb|
|
686
|
+
bb.y+bb.h
|
687
|
+
}.find_all { |by|
|
688
|
+
by <= b.y
|
689
|
+
}.max
|
690
|
+
|
691
|
+
moo = []
|
692
|
+
moo << 8*b.from.length
|
693
|
+
moo << 8*b.from[0].to.length
|
694
|
+
cx = b.x+b.w/2
|
695
|
+
moo << b.from.map { |bb| (cx - (bb.x+bb.w/2)).abs }.max / 10
|
696
|
+
cx = b.from[0].x+b.from[0].w/2
|
697
|
+
moo << b.from[0].to.map { |bb| (cx - (bb.x+bb.w/2)).abs }.max / 10
|
698
|
+
margin_y = 16 + moo.max
|
699
|
+
|
700
|
+
next if not min_y or b.y <= min_y + margin_y
|
701
|
+
|
702
|
+
blocking = @box.find_all { |bb|
|
703
|
+
next if bb == b
|
704
|
+
bb.y+bb.h > min_y and bb.y+bb.h < b.y and
|
705
|
+
bb.x-12 < b.x+b.w and bb.x+bb.w+12 > b.x
|
706
|
+
}
|
707
|
+
|
708
|
+
may_y = blocking.map { |bb| bb.y+bb.h } << min_y
|
709
|
+
|
710
|
+
do_y = may_y.sort.map { |by| by + margin_y }.find { |by|
|
711
|
+
# should not collision with b if moved to by+margin_y
|
712
|
+
not blocking.find { |bb|
|
713
|
+
bb.x-12 < b.x+b.w and bb.x+bb.w+12 > b.x and
|
714
|
+
bb.y-12 < by+b.h and bb.y+bb.h+12 > by
|
715
|
+
}
|
716
|
+
}
|
717
|
+
|
718
|
+
b.y = do_y if do_y < b.y
|
719
|
+
|
720
|
+
# no need to re-sort outer loop
|
721
|
+
}
|
722
|
+
|
723
|
+
# TODO
|
724
|
+
# energy-minimal positionning of boxes from this basic layout
|
725
|
+
# avoid arrow confusions
|
726
|
+
end
|
727
|
+
|
728
|
+
def auto_arrange_boxes
|
729
|
+
auto_arrange_init
|
730
|
+
nil while @groups.length > 1 and auto_arrange_step
|
731
|
+
auto_arrange_post
|
732
|
+
@groups = []
|
733
|
+
end
|
734
|
+
|
735
|
+
# gives a text representation of the current graph state
|
736
|
+
def dump_layout(groups=@groups)
|
737
|
+
groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.sort.inspect}" }
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
|
742
|
+
|
743
|
+
|
744
|
+
|
745
|
+
class GraphViewWidget < DrawableWidget
|
746
|
+
attr_accessor :dasm, :caret_box, :curcontext, :zoom, :margin
|
747
|
+
# bool, specifies if we should display addresses before instrs
|
748
|
+
attr_accessor :show_addresses
|
749
|
+
|
750
|
+
def initialize_widget(dasm, parent_widget)
|
751
|
+
@dasm = dasm
|
752
|
+
@parent_widget = parent_widget
|
753
|
+
|
754
|
+
@show_addresses = false
|
755
|
+
|
756
|
+
@caret_box = nil
|
757
|
+
@selected_boxes = []
|
758
|
+
@shown_boxes = []
|
759
|
+
@mousemove_origin = @mousemove_origin_ctrl = nil
|
760
|
+
@curcontext = Graph.new(nil)
|
761
|
+
@want_focus_addr = nil
|
762
|
+
@margin = 8
|
763
|
+
@zoom = 1.0
|
764
|
+
@default_color_association = ColorTheme.merge :hlbox_bg => :palegrey, :box_bg => :white,
|
765
|
+
:arrow_hl => :red, :arrow_cond => :darkgreen, :arrow_uncond => :darkblue,
|
766
|
+
:arrow_direct => :darkred, :box_bg_shadow => :black, :background => :paleblue
|
767
|
+
# @othergraphs = ? (to keep user-specified formatting)
|
768
|
+
end
|
769
|
+
|
770
|
+
def view_x; @curcontext.view_x; end
|
771
|
+
def view_x=(vx); @curcontext.view_x = vx; end
|
772
|
+
def view_y; @curcontext.view_y; end
|
773
|
+
def view_y=(vy); @curcontext.view_y = vy; end
|
774
|
+
|
775
|
+
def resized(w, h)
|
776
|
+
redraw
|
777
|
+
end
|
778
|
+
|
779
|
+
def find_box_xy(x, y)
|
780
|
+
x = view_x+x/@zoom
|
781
|
+
y = view_y+y/@zoom
|
782
|
+
@shown_boxes.to_a.reverse.find { |b| b.x <= x and b.x+b.w > x and b.y <= y-1 and b.y+b.h > y+1 }
|
783
|
+
end
|
784
|
+
|
785
|
+
def mouse_wheel_ctrl(dir, x, y)
|
786
|
+
case dir
|
787
|
+
when :up
|
788
|
+
if @zoom < 100
|
789
|
+
# zoom in
|
790
|
+
oldzoom = @zoom
|
791
|
+
@zoom *= 1.1
|
792
|
+
@zoom = 1.0 if (@zoom-1.0).abs < 0.05
|
793
|
+
@curcontext.view_x += (x / oldzoom - x / @zoom)
|
794
|
+
@curcontext.view_y += (y / oldzoom - y / @zoom)
|
795
|
+
end
|
796
|
+
when :down
|
797
|
+
if @zoom > 1.0/1000
|
798
|
+
# zoom out
|
799
|
+
oldzoom = @zoom
|
800
|
+
@zoom /= 1.1
|
801
|
+
@zoom = 1.0 if (@zoom-1.0).abs < 0.05
|
802
|
+
@curcontext.view_x += (x / oldzoom - x / @zoom)
|
803
|
+
@curcontext.view_y += (y / oldzoom - y / @zoom)
|
804
|
+
end
|
805
|
+
end
|
806
|
+
redraw
|
807
|
+
end
|
808
|
+
|
809
|
+
def mouse_wheel(dir, x, y)
|
810
|
+
case dir
|
811
|
+
when :up; @curcontext.view_y -= height/4 / @zoom
|
812
|
+
when :down; @curcontext.view_y += height/4 / @zoom
|
813
|
+
end
|
814
|
+
redraw
|
815
|
+
end
|
816
|
+
|
817
|
+
def mousemove(x, y)
|
818
|
+
return if not @mousemove_origin
|
819
|
+
|
820
|
+
dx = (x - @mousemove_origin[0])/@zoom
|
821
|
+
dy = (y - @mousemove_origin[1])/@zoom
|
822
|
+
@mousemove_origin = [x, y]
|
823
|
+
if @selected_boxes.empty?
|
824
|
+
@curcontext.view_x -= dx ; @curcontext.view_y -= dy
|
825
|
+
else
|
826
|
+
@selected_boxes.each { |b| b.x += dx ; b.y += dy }
|
827
|
+
end
|
828
|
+
redraw
|
829
|
+
end
|
830
|
+
|
831
|
+
def mouserelease(x, y)
|
832
|
+
mousemove(x, y)
|
833
|
+
@mousemove_origin = nil
|
834
|
+
|
835
|
+
if @mousemove_origin_ctrl
|
836
|
+
x1 = view_x + @mousemove_origin_ctrl[0]/@zoom
|
837
|
+
x2 = x1 + (x - @mousemove_origin_ctrl[0])/@zoom
|
838
|
+
x1, x2 = x2, x1 if x1 > x2
|
839
|
+
y1 = view_y + @mousemove_origin_ctrl[1]/@zoom
|
840
|
+
y2 = y1 + (y - @mousemove_origin_ctrl[1])/@zoom
|
841
|
+
y1, y2 = y2, y1 if y1 > y2
|
842
|
+
@selected_boxes |= @curcontext.box.find_all { |b| b.x >= x1 and b.x + b.w <= x2 and b.y >= y1 and b.y + b.h <= y2 }
|
843
|
+
redraw
|
844
|
+
@mousemove_origin_ctrl = nil
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
def click_ctrl(x, y)
|
849
|
+
if b = find_box_xy(x, y)
|
850
|
+
if @selected_boxes.include? b
|
851
|
+
@selected_boxes.delete b
|
852
|
+
else
|
853
|
+
@selected_boxes << b
|
854
|
+
end
|
855
|
+
redraw
|
856
|
+
else
|
857
|
+
@mousemove_origin_ctrl = [x, y]
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
def click(x, y)
|
862
|
+
@mousemove_origin = [x, y]
|
863
|
+
if b = find_box_xy(x, y)
|
864
|
+
@selected_boxes = [b] if not @selected_boxes.include? b
|
865
|
+
@caret_box = b
|
866
|
+
@caret_x = (view_x+x/@zoom-b.x-1).to_i / @font_width
|
867
|
+
@caret_y = (view_y+y/@zoom-b.y-1).to_i / @font_height
|
868
|
+
update_caret
|
869
|
+
else
|
870
|
+
@selected_boxes = []
|
871
|
+
@caret_box = nil
|
872
|
+
end
|
873
|
+
redraw
|
874
|
+
end
|
875
|
+
|
876
|
+
def setup_contextmenu(b, m)
|
877
|
+
cm = new_menu
|
878
|
+
addsubmenu(cm, 'copy _word') { clipboard_copy(@hl_word) if @hl_word }
|
879
|
+
addsubmenu(cm, 'copy _line') { clipboard_copy(@caret_box[:line_text_col][@caret_y].map { |ss, cc| ss }.join) }
|
880
|
+
addsubmenu(cm, 'copy _box') {
|
881
|
+
sb = @selected_boxes
|
882
|
+
sb = [@curbox] if sb.empty?
|
883
|
+
clipboard_copy(sb.map { |ob| ob[:line_text_col].map { |s| s.map { |ss, cc| ss }.join + "\r\n" }.join }.join("\r\n"))
|
884
|
+
} # XXX auto \r\n vs \n
|
885
|
+
addsubmenu(m, '_clipboard', cm)
|
886
|
+
addsubmenu(m, 'clone _window') { @parent_widget.clone_window(@hl_word, :graph) }
|
887
|
+
addsubmenu(m, 'show descendants only') { hide_non_descendants(@selected_boxes) }
|
888
|
+
addsubmenu(m, 'show ascendants only') { hide_non_ascendants(@selected_boxes) }
|
889
|
+
addsubmenu(m, 'restore graph') { gui_update }
|
890
|
+
end
|
891
|
+
|
892
|
+
# if the target is a call to a subfunction, open a new window with the graph of this function (popup)
|
893
|
+
def rightclick(x, y)
|
894
|
+
if b = find_box_xy(x, y) and @zoom >= 0.90 and @zoom <= 1.1
|
895
|
+
click(x, y)
|
896
|
+
@mousemove_origin = nil
|
897
|
+
m = new_menu
|
898
|
+
setup_contextmenu(b, m)
|
899
|
+
if @parent_widget.respond_to?(:extend_contextmenu)
|
900
|
+
@parent_widget.extend_contextmenu(self, m, @caret_box[:line_address][@caret_y])
|
901
|
+
end
|
902
|
+
popupmenu(m, x, y)
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
def doubleclick(x, y)
|
907
|
+
@mousemove_origin = nil
|
908
|
+
if b = find_box_xy(x, y)
|
909
|
+
if @hl_word and @zoom >= 0.90 and @zoom <= 1.1
|
910
|
+
@parent_widget.focus_addr(@hl_word)
|
911
|
+
else
|
912
|
+
@parent_widget.focus_addr((b[:addresses] || b[:line_address]).first)
|
913
|
+
end
|
914
|
+
elsif doubleclick_check_arrow(x, y)
|
915
|
+
elsif @zoom == 1.0
|
916
|
+
zoom_all
|
917
|
+
else
|
918
|
+
@curcontext.view_x += (x/@zoom - x)
|
919
|
+
@curcontext.view_y += (y/@zoom - y)
|
920
|
+
@zoom = 1.0
|
921
|
+
end
|
922
|
+
redraw
|
923
|
+
end
|
924
|
+
|
925
|
+
# check if the user clicked on the beginning/end of an arrow, if so focus on the other end
|
926
|
+
def doubleclick_check_arrow(x, y)
|
927
|
+
return if @margin*@zoom < 2
|
928
|
+
x = view_x+x/@zoom
|
929
|
+
y = view_y+y/@zoom
|
930
|
+
sx = nil
|
931
|
+
if bt = @shown_boxes.to_a.reverse.find { |b|
|
932
|
+
y >= b.y+b.h-1 and y <= b.y+b.h-1+@margin+2 and
|
933
|
+
sx = b.x+b.w/2 - b.to.length/2 * @margin/2 and
|
934
|
+
x >= sx-@margin/2 and x <= sx+b.to.length*@margin/2 # should be margin/4, but add a little comfort margin
|
935
|
+
}
|
936
|
+
idx = (x-sx+@margin/4).to_i / (@margin/2)
|
937
|
+
idx = 0 if idx < 0
|
938
|
+
idx = bt.to.length-1 if idx >= bt.to.length
|
939
|
+
if bt.to[idx]
|
940
|
+
if @parent_widget
|
941
|
+
@caret_box, @caret_y = bt, bt[:line_address].length-1
|
942
|
+
@parent_widget.focus_addr bt.to[idx][:line_address][0]
|
943
|
+
else
|
944
|
+
focus_xy(bt.to[idx].x, bt.to[idx].y)
|
945
|
+
end
|
946
|
+
end
|
947
|
+
true
|
948
|
+
elsif bf = @shown_boxes.to_a.reverse.find { |b|
|
949
|
+
y >= b.y-@margin-2 and y <= b.y and
|
950
|
+
sx = b.x+b.w/2 - b.from.length/2 * @margin/2 and
|
951
|
+
x >= sx-@margin/2 and x <= sx+b.from.length*@margin/2
|
952
|
+
}
|
953
|
+
idx = (x-sx+@margin/4).to_i / (@margin/2)
|
954
|
+
idx = 0 if idx < 0
|
955
|
+
idx = bf.from.length-1 if idx >= bf.from.length
|
956
|
+
if bf.from[idx]
|
957
|
+
if @parent_widget
|
958
|
+
@caret_box, @caret_y = bf, bf[:line_address].length-1
|
959
|
+
@parent_widget.focus_addr bf.from[idx][:line_address][-1]
|
960
|
+
else
|
961
|
+
focus_xy(bt.from[idx].x, bt.from[idx].y)
|
962
|
+
end
|
963
|
+
end
|
964
|
+
true
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
# update the zoom & view_xy to show the whole graph in the window
|
969
|
+
def zoom_all
|
970
|
+
minx, miny, maxx, maxy = @curcontext.boundingbox
|
971
|
+
minx -= @margin
|
972
|
+
miny -= @margin
|
973
|
+
maxx += @margin
|
974
|
+
maxy += @margin
|
975
|
+
|
976
|
+
@zoom = [width.to_f/(maxx-minx), height.to_f/(maxy-miny)].min
|
977
|
+
@zoom = 1.0 if @zoom > 1.0 or (@zoom-1.0).abs < 0.1
|
978
|
+
@curcontext.view_x = minx + (maxx-minx-width/@zoom)/2
|
979
|
+
@curcontext.view_y = miny + (maxy-miny-height/@zoom)/2
|
980
|
+
redraw
|
981
|
+
end
|
982
|
+
|
983
|
+
def paint
|
984
|
+
update_graph if @want_update_graph
|
985
|
+
if @want_focus_addr and @curcontext.box.find { |b_| b_[:line_address].index(@want_focus_addr) }
|
986
|
+
focus_addr(@want_focus_addr, false)
|
987
|
+
@want_focus_addr = nil
|
988
|
+
#zoom_all
|
989
|
+
end
|
990
|
+
|
991
|
+
@curcontext.box.each { |b|
|
992
|
+
# reorder arrows so that endings do not overlap
|
993
|
+
b.to = b.to.sort_by { |bt| bt.x+bt.w/2 }
|
994
|
+
b.from = b.from.sort_by { |bt| bt.x+bt.w/2 }
|
995
|
+
}
|
996
|
+
# arrows drawn first to stay under the boxes
|
997
|
+
# XXX precalc ?
|
998
|
+
@curcontext.box.each { |b|
|
999
|
+
b.to.each { |bt| paint_arrow(b, bt) }
|
1000
|
+
}
|
1001
|
+
|
1002
|
+
@shown_boxes = []
|
1003
|
+
w_w = width
|
1004
|
+
w_h = height
|
1005
|
+
@curcontext.box.each { |b|
|
1006
|
+
next if b.x >= view_x+w_w/@zoom or b.y >= view_y+w_h/@zoom or b.x+b.w <= view_x or b.y+b.h <= view_y
|
1007
|
+
@shown_boxes << b
|
1008
|
+
paint_box(b)
|
1009
|
+
}
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
def set_color_arrow(b1, b2)
|
1013
|
+
if b1 == @caret_box or b2 == @caret_box
|
1014
|
+
draw_color :arrow_hl
|
1015
|
+
elsif b1.to.length == 1
|
1016
|
+
draw_color :arrow_uncond
|
1017
|
+
elsif b1.direct_to == b2.id
|
1018
|
+
draw_color :arrow_direct
|
1019
|
+
else
|
1020
|
+
draw_color :arrow_cond
|
1021
|
+
end
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
def paint_arrow(b1, b2)
|
1025
|
+
x1 = x1o = b1.x+b1.w/2-view_x
|
1026
|
+
y1 = b1.y+b1.h-view_y
|
1027
|
+
x2 = x2o = b2.x+b2.w/2-view_x
|
1028
|
+
y2 = b2.y-1-view_y
|
1029
|
+
margin = @margin
|
1030
|
+
x1 += (-(b1.to.length-1)/2 + b1.to.index(b2)) * margin/2
|
1031
|
+
x2 += (-(b2.from.length-1)/2 + b2.from.index(b1)) * margin/2
|
1032
|
+
return if (y1+margin < 0 and y2 < 0) or (y1 > height/@zoom and y2-margin > height/@zoom) # just clip on y
|
1033
|
+
margin, x1, y1, x2, y2, b1w, b2w, x1o, x2o = [margin, x1, y1, x2, y2, b1.w, b2.w, x1o, x2o].map { |v| v*@zoom }
|
1034
|
+
|
1035
|
+
|
1036
|
+
# straighten vertical arrows if possible
|
1037
|
+
if y2 > y1 and (x1-x2).abs <= margin
|
1038
|
+
if b1.to.length == 1
|
1039
|
+
x1 = x2
|
1040
|
+
elsif b2.from.length == 1
|
1041
|
+
x2 = x1
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
set_color_arrow(b1, b2)
|
1046
|
+
if margin > 1
|
1047
|
+
# draw arrow tip
|
1048
|
+
draw_line(x1, y1, x1, y1+margin)
|
1049
|
+
draw_line(x2, y2-margin+1, x2, y2)
|
1050
|
+
draw_line(x2-margin/2, y2-margin/2, x2, y2)
|
1051
|
+
draw_line(x2+margin/2, y2-margin/2, x2, y2)
|
1052
|
+
y1 += margin
|
1053
|
+
y2 -= margin-1
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
if y2 > y1 - b1.h*@zoom - 2*margin+1
|
1057
|
+
# straight arrow
|
1058
|
+
draw_line(x1, y1, x2, y2) if x1 != y1 or x2 != y2
|
1059
|
+
|
1060
|
+
else
|
1061
|
+
# arrow goes up: navigate around b2
|
1062
|
+
x = (x1 <= x2 ? [x1o-b1w/2-margin, x2o-b2w/2-margin].min : [x1o+b1w/2+margin, x2o+b2w/2+margin].max)
|
1063
|
+
draw_line(x1, y1, x, y1)
|
1064
|
+
draw_line(x, y1, x, y2)
|
1065
|
+
draw_line(x, y2, x2, y2)
|
1066
|
+
draw_line(x1, y1+1, x, y1+1) # double
|
1067
|
+
draw_line(x+1, y1, x+1, y2)
|
1068
|
+
draw_line(x, y2+1, x2, y2+1)
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def set_color_boxshadow(b)
|
1073
|
+
draw_color :box_bg_shadow
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
def set_color_box(b)
|
1077
|
+
if @selected_boxes.include? b
|
1078
|
+
draw_color :hlbox_bg
|
1079
|
+
else
|
1080
|
+
draw_color :box_bg
|
1081
|
+
end
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def paint_box(b)
|
1085
|
+
set_color_boxshadow(b)
|
1086
|
+
draw_rectangle((b.x-view_x+3)*@zoom, (b.y-view_y+4)*@zoom, b.w*@zoom, b.h*@zoom)
|
1087
|
+
set_color_box(b)
|
1088
|
+
draw_rectangle((b.x-view_x)*@zoom, (b.y-view_y+1)*@zoom, b.w*@zoom, b.h*@zoom)
|
1089
|
+
|
1090
|
+
# current text position
|
1091
|
+
x = (b.x - view_x + 1)*@zoom
|
1092
|
+
y = (b.y - view_y + 1)*@zoom
|
1093
|
+
w_w = (b.x - view_x + b.w - @font_width)*@zoom
|
1094
|
+
w_h = (b.y - view_y + b.h - @font_height)*@zoom
|
1095
|
+
w_h = height if w_h > height
|
1096
|
+
|
1097
|
+
if @parent_widget and @parent_widget.bg_color_callback
|
1098
|
+
ly = 0
|
1099
|
+
b[:line_address].each { |a|
|
1100
|
+
if c = @parent_widget.bg_color_callback[a]
|
1101
|
+
draw_rectangle_color(c, (b.x-view_x)*@zoom, (1+b.y-view_y+ly*@font_height)*@zoom, b.w*@zoom, (@font_height*@zoom).ceil)
|
1102
|
+
end
|
1103
|
+
ly += 1
|
1104
|
+
}
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
if @caret_box == b
|
1108
|
+
draw_rectangle_color(:cursorline_bg, (b.x-view_x)*@zoom, (1+b.y-view_y+@caret_y*@font_height)*@zoom, b.w*@zoom, @font_height*@zoom)
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
return if @zoom < 0.99 or @zoom > 1.1
|
1112
|
+
# TODO dynamic font size ?
|
1113
|
+
|
1114
|
+
# renders a string at current cursor position with a color
|
1115
|
+
# must not include newline
|
1116
|
+
render = lambda { |str, color|
|
1117
|
+
next if y >= w_h+2 or x >= w_w
|
1118
|
+
draw_string_hl(color, x, y, str)
|
1119
|
+
x += str.length * @font_width
|
1120
|
+
}
|
1121
|
+
|
1122
|
+
yoff = @font_height * @zoom
|
1123
|
+
b[:line_text_col].each { |list|
|
1124
|
+
list.each { |s, c| render[s, c] } if y >= -yoff
|
1125
|
+
x = (b.x - view_x + 1)*@zoom
|
1126
|
+
y += yoff
|
1127
|
+
break if y > w_h+2
|
1128
|
+
}
|
1129
|
+
|
1130
|
+
if b == @caret_box and focus?
|
1131
|
+
cx = (b.x - view_x + 1 + @caret_x*@font_width)*@zoom
|
1132
|
+
cy = (b.y - view_y + 1 + @caret_y*@font_height)*@zoom
|
1133
|
+
draw_line_color(:caret, cx, cy, cx, cy+(@font_height-1)*@zoom)
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
def gui_update
|
1138
|
+
@want_update_graph = true
|
1139
|
+
redraw
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
#
|
1143
|
+
# rebuild the code flow graph from @curcontext.roots
|
1144
|
+
# recalc the boxes w/h
|
1145
|
+
#
|
1146
|
+
def update_graph
|
1147
|
+
@want_update_graph = false
|
1148
|
+
|
1149
|
+
ctx = @curcontext
|
1150
|
+
|
1151
|
+
boxcnt = ctx.box.length
|
1152
|
+
arrcnt = ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
|
1153
|
+
ctx.clear
|
1154
|
+
|
1155
|
+
build_ctx(ctx)
|
1156
|
+
|
1157
|
+
ctx.auto_arrange_boxes
|
1158
|
+
|
1159
|
+
return if ctx != @curcontext
|
1160
|
+
|
1161
|
+
if boxcnt != ctx.box.length or arrcnt != ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
|
1162
|
+
zoom_all
|
1163
|
+
elsif @caret_box # update @caret_box with a box at the same place
|
1164
|
+
bx = @caret_box.x + @caret_box.w/2
|
1165
|
+
by = @caret_box.y + @caret_box.h/2
|
1166
|
+
@caret_box = ctx.box.find { |cb| cb.x < bx and cb.x+cb.w > bx and cb.y < by and cb.y+cb.h > by }
|
1167
|
+
end
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def load_dotfile(path)
|
1171
|
+
load_dot(File.read(path))
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
def load_dot(dota)
|
1175
|
+
@want_update_graph = false
|
1176
|
+
@curcontext.clear
|
1177
|
+
boxes = {}
|
1178
|
+
new_box = lambda { |text|
|
1179
|
+
b = @curcontext.new_box(text, :line_text_col => [[[text, :text]]])
|
1180
|
+
b.w = (text.length+1) * @font_width
|
1181
|
+
b.h = @font_height
|
1182
|
+
b
|
1183
|
+
}
|
1184
|
+
dota.scan(/^.*$/) { |l|
|
1185
|
+
a = l.strip.chomp(';').split(/->/).map { |s| s.strip.delete '"' }
|
1186
|
+
next if not id = a.shift
|
1187
|
+
b0 = boxes[id] ||= new_box[id]
|
1188
|
+
while id = a.shift
|
1189
|
+
b1 = boxes[id] ||= new_box[id]
|
1190
|
+
b0.to |= [b1]
|
1191
|
+
b1.from |= [b0]
|
1192
|
+
b0 = b1
|
1193
|
+
end
|
1194
|
+
}
|
1195
|
+
redraw
|
1196
|
+
rescue Interrupt
|
1197
|
+
puts "dot_len #{boxes.length}"
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
# create the graph objects in ctx
|
1201
|
+
def build_ctx(ctx)
|
1202
|
+
# graph : block -> following blocks in same function
|
1203
|
+
block_rel = {}
|
1204
|
+
|
1205
|
+
todo = ctx.root_addrs.dup
|
1206
|
+
done = [:default, Expression::Unknown]
|
1207
|
+
while a = todo.shift
|
1208
|
+
a = @dasm.normalize a
|
1209
|
+
next if done.include? a
|
1210
|
+
done << a
|
1211
|
+
next if not di = @dasm.di_at(a)
|
1212
|
+
if not di.block_head?
|
1213
|
+
block_rel[di.block.address] = [a]
|
1214
|
+
@dasm.split_block(a)
|
1215
|
+
end
|
1216
|
+
block_rel[a] = []
|
1217
|
+
di.block.each_to_samefunc(@dasm) { |t|
|
1218
|
+
t = @dasm.normalize t
|
1219
|
+
next if not @dasm.di_at(t)
|
1220
|
+
todo << t
|
1221
|
+
block_rel[a] << t
|
1222
|
+
}
|
1223
|
+
block_rel[a].uniq!
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
# populate boxes
|
1227
|
+
addr2box = {}
|
1228
|
+
todo = ctx.root_addrs.dup
|
1229
|
+
todo.delete_if { |t| not @dasm.di_at(t) } # undefined func start
|
1230
|
+
done = []
|
1231
|
+
while a = todo.shift
|
1232
|
+
next if done.include? a
|
1233
|
+
done << a
|
1234
|
+
if not ctx.keep_split.to_a.include?(a) and from = block_rel.keys.find_all { |ba| block_rel[ba].include? a } and
|
1235
|
+
from.length == 1 and block_rel[from.first].length == 1 and
|
1236
|
+
addr2box[from.first] and lst = @dasm.decoded[from.first].block.list.last and
|
1237
|
+
lst.next_addr == a and (not lst.opcode.props[:saveip] or lst.block.to_subfuncret)
|
1238
|
+
box = addr2box[from.first]
|
1239
|
+
else
|
1240
|
+
box = ctx.new_box a, :addresses => [], :line_text_col => [], :line_address => []
|
1241
|
+
end
|
1242
|
+
@dasm.decoded[a].block.list.each { |di_|
|
1243
|
+
box[:addresses] << di_.address
|
1244
|
+
addr2box[di_.address] = box
|
1245
|
+
}
|
1246
|
+
todo.concat block_rel[a]
|
1247
|
+
end
|
1248
|
+
|
1249
|
+
# link boxes
|
1250
|
+
ctx.box.each { |b|
|
1251
|
+
next if not di = @dasm.decoded[b[:addresses].last]
|
1252
|
+
a = di.block.address
|
1253
|
+
next if not block_rel[a]
|
1254
|
+
block_rel[a].each { |t|
|
1255
|
+
ctx.link_boxes(b.id, t)
|
1256
|
+
b.direct_to = t if t == di.next_addr
|
1257
|
+
}
|
1258
|
+
}
|
1259
|
+
|
1260
|
+
# calc box dimensions/text
|
1261
|
+
ctx.box.each { |b|
|
1262
|
+
colstr = []
|
1263
|
+
curaddr = nil
|
1264
|
+
line = 0
|
1265
|
+
render = lambda { |str, col| colstr << [str, col] }
|
1266
|
+
nl = lambda {
|
1267
|
+
b[:line_address][line] = curaddr
|
1268
|
+
b[:line_text_col][line] = colstr
|
1269
|
+
colstr = []
|
1270
|
+
line += 1
|
1271
|
+
}
|
1272
|
+
b[:addresses].each { |addr|
|
1273
|
+
curaddr = addr
|
1274
|
+
if di = @dasm.di_at(curaddr)
|
1275
|
+
if di.block_head?
|
1276
|
+
# render dump_block_header, add a few colors
|
1277
|
+
b_header = '' ; @dasm.dump_block_header(di.block) { |l| b_header << l ; b_header << ?\n if b_header[-1] != ?\n }
|
1278
|
+
b_header.strip.each_line { |l| l.chomp!
|
1279
|
+
col = :comment
|
1280
|
+
col = :label if l[0, 2] != '//' and l[-1] == ?:
|
1281
|
+
render[l, col]
|
1282
|
+
nl[]
|
1283
|
+
}
|
1284
|
+
end
|
1285
|
+
render["#{Expression[curaddr]} ", :address] if @show_addresses
|
1286
|
+
render[di.instruction.to_s.ljust(di.comment ? 18 : 0), :instruction]
|
1287
|
+
render[' ; ' + di.comment.join(' ')[0, 64], :comment] if di.comment
|
1288
|
+
nl[]
|
1289
|
+
else
|
1290
|
+
# TODO real data display (dwords, xrefs, strings..)
|
1291
|
+
if label = @dasm.get_label_at(curaddr)
|
1292
|
+
render[label + ' ', :label]
|
1293
|
+
end
|
1294
|
+
s = @dasm.get_section_at(curaddr)
|
1295
|
+
render['db '+((s and s[0].data.length > s[0].ptr) ? Expression[s[0].read(1)[0]].to_s : '?'), :text]
|
1296
|
+
nl[]
|
1297
|
+
end
|
1298
|
+
}
|
1299
|
+
b.w = b[:line_text_col].map { |strc| strc.map { |s, c| s }.join.length }.max.to_i * @font_width + 2
|
1300
|
+
b.w += 1 if b.w % 2 == 0 # ensure boxes have odd width -> vertical arrows are straight
|
1301
|
+
b.h = line * @font_height
|
1302
|
+
}
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
def keypress_ctrl(key)
|
1306
|
+
case key
|
1307
|
+
when ?F
|
1308
|
+
@parent_widget.inputbox('text to search in curview (regex)', :text => @hl_word) { |pat|
|
1309
|
+
re = /#{pat}/i
|
1310
|
+
list = [['addr', 'instr']]
|
1311
|
+
@curcontext.box.each { |b|
|
1312
|
+
b[:line_text_col].zip(b[:line_address]) { |l, a|
|
1313
|
+
str = l.map { |s, c| s }.join
|
1314
|
+
list << [Expression[a], str] if str =~ re
|
1315
|
+
}
|
1316
|
+
}
|
1317
|
+
@parent_widget.list_bghilight("search result for /#{pat}/i", list) { |i| @parent_widget.focus_addr i[0] }
|
1318
|
+
}
|
1319
|
+
when ?+; mouse_wheel_ctrl(:up, width/2, height/2)
|
1320
|
+
when ?-; mouse_wheel_ctrl(:down, width/2, height/2)
|
1321
|
+
else return false
|
1322
|
+
end
|
1323
|
+
true
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
def keypress(key)
|
1327
|
+
case key
|
1328
|
+
when :left
|
1329
|
+
if @caret_box
|
1330
|
+
if @caret_x > 0
|
1331
|
+
@caret_x -= 1
|
1332
|
+
update_caret
|
1333
|
+
elsif b = @curcontext.box.sort_by { |b_| -b_.x }.find { |b_| b_.x < @caret_box.x and
|
1334
|
+
b_.y < @caret_box.y+@caret_y*@font_height and
|
1335
|
+
b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
|
1336
|
+
@caret_x = (b.w/@font_width).to_i
|
1337
|
+
@caret_y += ((@caret_box.y-b.y)/@font_height).to_i
|
1338
|
+
@caret_box = b
|
1339
|
+
update_caret
|
1340
|
+
redraw
|
1341
|
+
else
|
1342
|
+
@curcontext.view_x -= 20/@zoom
|
1343
|
+
redraw
|
1344
|
+
end
|
1345
|
+
else
|
1346
|
+
@curcontext.view_x -= 20/@zoom
|
1347
|
+
redraw
|
1348
|
+
end
|
1349
|
+
when :up
|
1350
|
+
if @caret_box
|
1351
|
+
if @caret_y > 0
|
1352
|
+
@caret_y -= 1
|
1353
|
+
update_caret
|
1354
|
+
elsif b = @curcontext.box.sort_by { |b_| -b_.y }.find { |b_| b_.y < @caret_box.y and
|
1355
|
+
b_.x < @caret_box.x+@caret_x*@font_width and
|
1356
|
+
b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
|
1357
|
+
@caret_x += ((@caret_box.x-b.x)/@font_width).to_i
|
1358
|
+
@caret_y = b[:line_address].length-1
|
1359
|
+
@caret_box = b
|
1360
|
+
update_caret
|
1361
|
+
redraw
|
1362
|
+
else
|
1363
|
+
@curcontext.view_y -= 20/@zoom
|
1364
|
+
redraw
|
1365
|
+
end
|
1366
|
+
else
|
1367
|
+
@curcontext.view_y -= 20/@zoom
|
1368
|
+
redraw
|
1369
|
+
end
|
1370
|
+
when :right
|
1371
|
+
if @caret_box
|
1372
|
+
if @caret_x <= @caret_box[:line_text_col].map { |s| s.map { |ss, cc| ss }.join.length }.max
|
1373
|
+
@caret_x += 1
|
1374
|
+
update_caret
|
1375
|
+
elsif b = @curcontext.box.sort_by { |b_| b_.x }.find { |b_| b_.x > @caret_box.x and
|
1376
|
+
b_.y < @caret_box.y+@caret_y*@font_height and
|
1377
|
+
b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
|
1378
|
+
@caret_x = 0
|
1379
|
+
@caret_y += ((@caret_box.y-b.y)/@font_height).to_i
|
1380
|
+
@caret_box = b
|
1381
|
+
update_caret
|
1382
|
+
redraw
|
1383
|
+
else
|
1384
|
+
@curcontext.view_x += 20/@zoom
|
1385
|
+
redraw
|
1386
|
+
end
|
1387
|
+
else
|
1388
|
+
@curcontext.view_x += 20/@zoom
|
1389
|
+
redraw
|
1390
|
+
end
|
1391
|
+
when :down
|
1392
|
+
if @caret_box
|
1393
|
+
if @caret_y < @caret_box[:line_address].length-1
|
1394
|
+
@caret_y += 1
|
1395
|
+
update_caret
|
1396
|
+
elsif b = @curcontext.box.sort_by { |b_| b_.y }.find { |b_| b_.y > @caret_box.y and
|
1397
|
+
b_.x < @caret_box.x+@caret_x*@font_width and
|
1398
|
+
b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
|
1399
|
+
@caret_x += ((@caret_box.x-b.x)/@font_width).to_i
|
1400
|
+
@caret_y = 0
|
1401
|
+
@caret_box = b
|
1402
|
+
update_caret
|
1403
|
+
redraw
|
1404
|
+
else
|
1405
|
+
@curcontext.view_y += 20/@zoom
|
1406
|
+
redraw
|
1407
|
+
end
|
1408
|
+
else
|
1409
|
+
@curcontext.view_y += 20/@zoom
|
1410
|
+
redraw
|
1411
|
+
end
|
1412
|
+
when :pgup
|
1413
|
+
if @caret_box
|
1414
|
+
@caret_y -= (height/4/@zoom/@font_height).to_i
|
1415
|
+
@caret_y = 0 if @caret_y < 0
|
1416
|
+
update_caret(false)
|
1417
|
+
else
|
1418
|
+
@curcontext.view_y -= height/4/@zoom
|
1419
|
+
redraw
|
1420
|
+
end
|
1421
|
+
when :pgdown
|
1422
|
+
if @caret_box
|
1423
|
+
@caret_y += (height/4/@zoom/@font_height).to_i
|
1424
|
+
@caret_y = [@caret_box[:line_address].length-1, @caret_y].min
|
1425
|
+
update_caret(false)
|
1426
|
+
else
|
1427
|
+
@curcontext.view_y += height/4/@zoom
|
1428
|
+
redraw
|
1429
|
+
end
|
1430
|
+
when :home
|
1431
|
+
if @caret_box
|
1432
|
+
@caret_x = 0
|
1433
|
+
update_caret(false)
|
1434
|
+
else
|
1435
|
+
@curcontext.view_x = @curcontext.box.map { |b_| b_.x }.min-10
|
1436
|
+
@curcontext.view_y = @curcontext.box.map { |b_| b_.y }.min-10
|
1437
|
+
redraw
|
1438
|
+
end
|
1439
|
+
when :end
|
1440
|
+
if @caret_box
|
1441
|
+
@caret_x = @caret_box[:line_text_col][@caret_y].to_a.map { |ss, cc| ss }.join.length
|
1442
|
+
update_caret(false)
|
1443
|
+
else
|
1444
|
+
@curcontext.view_x = [@curcontext.box.map { |b_| b_.x+b_.w }.max-width/@zoom+10, @curcontext.box.map { |b_| b_.x }.min-10].max
|
1445
|
+
@curcontext.view_y = [@curcontext.box.map { |b_| b_.y+b_.h }.max-height/@zoom+10, @curcontext.box.map { |b_| b_.y }.min-10].max
|
1446
|
+
redraw
|
1447
|
+
end
|
1448
|
+
|
1449
|
+
when :delete
|
1450
|
+
@selected_boxes.each { |b_|
|
1451
|
+
@curcontext.box.delete b_
|
1452
|
+
b_.from.each { |bb| bb.to.delete b_ }
|
1453
|
+
b_.to.each { |bb| bb.from.delete b_ }
|
1454
|
+
}
|
1455
|
+
redraw
|
1456
|
+
when :popupmenu
|
1457
|
+
if @caret_box
|
1458
|
+
cx = (@caret_box.x - view_x + 1 + @caret_x*@font_width)*@zoom
|
1459
|
+
cy = (@caret_box.y - view_y + 1 + @caret_y*@font_height)*@zoom
|
1460
|
+
rightclick(cx, cy)
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
when ?a
|
1464
|
+
t0 = Time.now
|
1465
|
+
puts 'autoarrange'
|
1466
|
+
@curcontext.auto_arrange_boxes
|
1467
|
+
redraw
|
1468
|
+
puts 'autoarrange done %.02f' % (Time.now - t0)
|
1469
|
+
when ?u
|
1470
|
+
gui_update
|
1471
|
+
|
1472
|
+
when ?R
|
1473
|
+
load __FILE__
|
1474
|
+
when ?I # create arbitrary boxes/links
|
1475
|
+
if @selected_boxes.empty?
|
1476
|
+
@fakebox ||= 0
|
1477
|
+
b = @curcontext.new_box "id_#@fakebox",
|
1478
|
+
:addresses => [], :line_address => [],
|
1479
|
+
:line_text_col => [[[" blublu #@fakebox", :text]]]
|
1480
|
+
b.w = @font_width * 15
|
1481
|
+
b.h = @font_height * 2
|
1482
|
+
b.x = rand(200) - 100
|
1483
|
+
b.y = rand(200) - 100
|
1484
|
+
|
1485
|
+
@fakebox += 1
|
1486
|
+
else
|
1487
|
+
b1, *bl = @selected_boxes
|
1488
|
+
bl = [b1] if bl.empty? # loop
|
1489
|
+
bl.each { |b2|
|
1490
|
+
if b1.to.include? b2
|
1491
|
+
b1.to.delete b2
|
1492
|
+
b2.from.delete b1
|
1493
|
+
else
|
1494
|
+
b1.to << b2
|
1495
|
+
b2.from << b1
|
1496
|
+
end
|
1497
|
+
}
|
1498
|
+
end
|
1499
|
+
redraw
|
1500
|
+
|
1501
|
+
when ?1 # (numeric) zoom to 1:1
|
1502
|
+
if @zoom == 1.0
|
1503
|
+
zoom_all
|
1504
|
+
else
|
1505
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
1506
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
1507
|
+
@zoom = 1.0
|
1508
|
+
end
|
1509
|
+
redraw
|
1510
|
+
when :insert # split curbox at @caret_y
|
1511
|
+
if @caret_box and a = @caret_box[:line_address][@caret_y] and @dasm.decoded[a]
|
1512
|
+
@dasm.split_block(a)
|
1513
|
+
@curcontext.keep_split ||= []
|
1514
|
+
@curcontext.keep_split |= [a]
|
1515
|
+
gui_update
|
1516
|
+
focus_addr a
|
1517
|
+
end
|
1518
|
+
else return false
|
1519
|
+
end
|
1520
|
+
true
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
def hide_non_descendants(list)
|
1524
|
+
reach = {}
|
1525
|
+
todo = list.dup
|
1526
|
+
while b = todo.pop
|
1527
|
+
next if reach[b]
|
1528
|
+
reach[b] = true
|
1529
|
+
b.to.each { |bb|
|
1530
|
+
todo << bb if bb.y+bb.h >= b.y
|
1531
|
+
}
|
1532
|
+
end
|
1533
|
+
|
1534
|
+
@curcontext.box.delete_if { |bb|
|
1535
|
+
!reach[bb]
|
1536
|
+
}
|
1537
|
+
@curcontext.box.each { |bb|
|
1538
|
+
bb.from.delete_if { |bbb| !reach[bbb] }
|
1539
|
+
bb.to.delete_if { |bbb| !reach[bbb] }
|
1540
|
+
}
|
1541
|
+
redraw
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
def hide_non_ascendants(list)
|
1545
|
+
reach = {}
|
1546
|
+
todo = list.dup
|
1547
|
+
while b = todo.pop
|
1548
|
+
next if reach[b]
|
1549
|
+
reach[b] = true
|
1550
|
+
b.from.each { |bb|
|
1551
|
+
todo << bb if bb.y <= b.h+b.y
|
1552
|
+
}
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
@curcontext.box.delete_if { |bb|
|
1556
|
+
!reach[bb]
|
1557
|
+
}
|
1558
|
+
@curcontext.box.each { |bb|
|
1559
|
+
bb.from.delete_if { |bbb| !reach[bbb] }
|
1560
|
+
bb.to.delete_if { |bbb| !reach[bbb] }
|
1561
|
+
}
|
1562
|
+
redraw
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
# find a suitable array of graph roots, walking up from a block (function start/entrypoint)
|
1566
|
+
def dasm_find_roots(addr)
|
1567
|
+
todo = [addr]
|
1568
|
+
done = []
|
1569
|
+
roots = []
|
1570
|
+
default_root = nil
|
1571
|
+
while a = todo.shift
|
1572
|
+
next if not di = @dasm.di_at(a)
|
1573
|
+
b = di.block
|
1574
|
+
a = b.address
|
1575
|
+
if done.include? a
|
1576
|
+
default_root ||= a
|
1577
|
+
next
|
1578
|
+
end
|
1579
|
+
done << a
|
1580
|
+
newf = []
|
1581
|
+
b.each_from_samefunc(@dasm) { |f| newf << f }
|
1582
|
+
if newf.empty?
|
1583
|
+
roots << b.address
|
1584
|
+
else
|
1585
|
+
todo.concat newf
|
1586
|
+
end
|
1587
|
+
end
|
1588
|
+
roots << default_root if roots.empty? and default_root
|
1589
|
+
|
1590
|
+
roots
|
1591
|
+
end
|
1592
|
+
|
1593
|
+
def set_cursor_pos(p)
|
1594
|
+
addr, x = p
|
1595
|
+
focus_addr(addr)
|
1596
|
+
@caret_x = x
|
1597
|
+
update_caret
|
1598
|
+
end
|
1599
|
+
|
1600
|
+
def get_cursor_pos
|
1601
|
+
[current_address, @caret_x]
|
1602
|
+
end
|
1603
|
+
|
1604
|
+
# focus on addr
|
1605
|
+
# addr may be a dasm label, dasm address, dasm address in string form (eg "0DEADBEEFh")
|
1606
|
+
# addr must point to a decodedinstruction
|
1607
|
+
# if the addr is not found in curcontext, the code flow is walked up until a function
|
1608
|
+
# start or an entrypoint is found, then the graph is created from there
|
1609
|
+
# will call gui_update then
|
1610
|
+
def focus_addr(addr, can_update_context=true)
|
1611
|
+
return if @parent_widget and not addr = @parent_widget.normalize(addr)
|
1612
|
+
return if not @dasm.di_at(addr)
|
1613
|
+
|
1614
|
+
# move window / change curcontext
|
1615
|
+
if b = @curcontext.box.find { |b_| b_[:line_address].index(addr) }
|
1616
|
+
@caret_box, @caret_x, @caret_y = b, 0, b[:line_address].rindex(addr)
|
1617
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
1618
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
1619
|
+
@zoom = 1.0
|
1620
|
+
|
1621
|
+
update_caret
|
1622
|
+
elsif can_update_context
|
1623
|
+
@curcontext = Graph.new 'testic'
|
1624
|
+
@curcontext.root_addrs = dasm_find_roots(addr)
|
1625
|
+
@want_focus_addr = addr
|
1626
|
+
gui_update
|
1627
|
+
else
|
1628
|
+
return
|
1629
|
+
end
|
1630
|
+
true
|
1631
|
+
end
|
1632
|
+
|
1633
|
+
def focus_xy(x, y)
|
1634
|
+
# dont move during a click
|
1635
|
+
return if @mousemove_origin
|
1636
|
+
|
1637
|
+
# ensure the caret stays onscreen
|
1638
|
+
if not view_x
|
1639
|
+
@curcontext.view_x = x - width/5/@zoom
|
1640
|
+
redraw
|
1641
|
+
elsif @caret_box and @caret_box.w < width*27/30/@zoom
|
1642
|
+
# keep @caret_box full if possible
|
1643
|
+
if view_x + width/20/@zoom > @caret_box.x
|
1644
|
+
@curcontext.view_x = @caret_box.x-width/20/@zoom
|
1645
|
+
elsif view_x + width*9/10/@zoom < @caret_box.x+@caret_box.w
|
1646
|
+
@curcontext.view_x = @caret_box.x+@caret_box.w-width*9/10/@zoom
|
1647
|
+
end
|
1648
|
+
elsif view_x + width/20/@zoom > x
|
1649
|
+
@curcontext.view_x = x-width/20/@zoom
|
1650
|
+
redraw
|
1651
|
+
elsif view_x + width*9/10/@zoom < x
|
1652
|
+
@curcontext.view_x = x-width*9/10/@zoom
|
1653
|
+
redraw
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
if not view_y
|
1657
|
+
@curcontext.view_y = y - height/5/@zoom
|
1658
|
+
redraw
|
1659
|
+
elsif @caret_box and @caret_box.h < height*27/30/@zoom
|
1660
|
+
if view_y + height/20/@zoom > @caret_box.y
|
1661
|
+
@curcontext.view_y = @caret_box.y-height/20/@zoom
|
1662
|
+
elsif view_y + height*9/10/@zoom < @caret_box.y+@caret_box.h
|
1663
|
+
@curcontext.view_y = @caret_box.y+@caret_box.h-height*9/10/@zoom
|
1664
|
+
end
|
1665
|
+
elsif view_y + height/20/@zoom > y
|
1666
|
+
@curcontext.view_y = y-height/20/@zoom
|
1667
|
+
redraw
|
1668
|
+
elsif view_y + height*9/10/@zoom < y
|
1669
|
+
@curcontext.view_y = y-height*9/10/@zoom
|
1670
|
+
redraw
|
1671
|
+
end
|
1672
|
+
end
|
1673
|
+
|
1674
|
+
# hint that the caret moved
|
1675
|
+
# redraw, change the hilighted word
|
1676
|
+
def update_caret(update_hlword = true)
|
1677
|
+
return if not b = @caret_box or not @caret_x or not l = @caret_box[:line_text_col][@caret_y]
|
1678
|
+
|
1679
|
+
if update_hlword
|
1680
|
+
l = l.map { |s, c| s }.join
|
1681
|
+
@parent_widget.focus_changed_callback[] if @parent_widget and @parent_widget.focus_changed_callback and @oldcaret_y != @caret_y
|
1682
|
+
update_hl_word(l, @caret_x)
|
1683
|
+
end
|
1684
|
+
|
1685
|
+
focus_xy(b.x + @caret_x*@font_width, b.y + @caret_y*@font_height)
|
1686
|
+
|
1687
|
+
redraw
|
1688
|
+
end
|
1689
|
+
|
1690
|
+
def current_address
|
1691
|
+
@caret_box ? @caret_box[:line_address][@caret_y] : @curcontext.root_addrs.first
|
1692
|
+
end
|
1693
|
+
end
|
1694
|
+
end
|
1695
|
+
end
|