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.
- data/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- metadata +255 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
require 'metasm/gui/dasm_graph'
|
|
7
|
+
|
|
8
|
+
module Metasm
|
|
9
|
+
module Gui
|
|
10
|
+
class FuncGraphViewWidget < GraphViewWidget
|
|
11
|
+
# :full / :from / :to / :both
|
|
12
|
+
# :from = graph of functions called by addr
|
|
13
|
+
# :to = graph of functions calling addr
|
|
14
|
+
attr_accessor :graph_mode
|
|
15
|
+
def initialize(*a)
|
|
16
|
+
super(*a)
|
|
17
|
+
@graph_mode = :full
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_ctx(ctx)
|
|
21
|
+
addr = @curcontext.root_addrs[0]
|
|
22
|
+
case @graph_mode
|
|
23
|
+
when :full
|
|
24
|
+
g = @dasm.function_graph
|
|
25
|
+
when :from
|
|
26
|
+
g = @dasm.function_graph_from(addr)
|
|
27
|
+
when :to
|
|
28
|
+
g = @dasm.function_graph_to(addr)
|
|
29
|
+
when :both
|
|
30
|
+
# merge from+to
|
|
31
|
+
g = @dasm.function_graph_to(addr)
|
|
32
|
+
@dasm.function_graph_from(addr).each { |k, v|
|
|
33
|
+
g[k] ||= v
|
|
34
|
+
g[k] |= v
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
g = {addr => []} if not g or g.empty?
|
|
38
|
+
|
|
39
|
+
# create boxes
|
|
40
|
+
(g.keys + g.values).flatten.uniq.each { |a|
|
|
41
|
+
# box text
|
|
42
|
+
txt = @dasm.get_label_at(a)
|
|
43
|
+
txt ||= Expression[a].to_s
|
|
44
|
+
b = ctx.new_box a, :addresses => [a], :line_text_col => [], :line_address => [a]
|
|
45
|
+
b[:line_text_col] << [[txt, :label]]
|
|
46
|
+
b.w = txt.length * @font_width + 2
|
|
47
|
+
b.h = @font_height
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# link boxes
|
|
51
|
+
g.each { |f, tl| tl.each { |t| ctx.link_boxes(f, t) } }
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def doubleclick(x, y)
|
|
56
|
+
if find_box_xy(x, y) and @hl_word and @zoom >= 0.90 and @zoom <= 1.1
|
|
57
|
+
@mousemove_origin = nil
|
|
58
|
+
@parent_widget.focus_addr(@hl_word, :graph)
|
|
59
|
+
else
|
|
60
|
+
super(x, y)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_cursor_pos
|
|
65
|
+
[@curcontext.root_addrs[0], @graph_mode]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def set_cursor_pos(p)
|
|
69
|
+
addr, m = p
|
|
70
|
+
focus_addr(addr, m)
|
|
71
|
+
@caret_x = 0
|
|
72
|
+
update_caret
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def focus_addr(addr, mode=@graph_mode)
|
|
76
|
+
if mode == false
|
|
77
|
+
# simply center the view on addr in the current graph
|
|
78
|
+
raise 'fu' if not b = @curcontext.box.find { |b_| b_[:line_address][0] == addr }
|
|
79
|
+
@caret_box, @caret_x, @caret_y = b, 0, 0
|
|
80
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
|
81
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
|
82
|
+
@zoom = 1.0
|
|
83
|
+
|
|
84
|
+
focus_xy(b.x, b.y)
|
|
85
|
+
update_caret
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
return if not addr = @dasm.normalize(addr)
|
|
90
|
+
if not @dasm.function[addr]
|
|
91
|
+
return if not addr = @dasm.find_function_start(addr)
|
|
92
|
+
end
|
|
93
|
+
return true if @curcontext.root_addrs == [addr] and @graph_mode == mode
|
|
94
|
+
@graph_mode = mode
|
|
95
|
+
@curcontext = Graph.new('fu')
|
|
96
|
+
@curcontext.root_addrs = [addr]
|
|
97
|
+
@want_focus_addr = addr
|
|
98
|
+
gui_update
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,1354 @@
|
|
|
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
|
+
# TODO
|
|
26
|
+
class MergedBox
|
|
27
|
+
attr_accessor :id, :text, :x, :y, :w, :h
|
|
28
|
+
attr_accessor :to, :from
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_accessor :id, :box, :root_addrs, :view_x, :view_y, :keep_split
|
|
32
|
+
def initialize(id)
|
|
33
|
+
@id = id
|
|
34
|
+
@root_addrs = []
|
|
35
|
+
@view_x = @view_y = -0xfff_ffff
|
|
36
|
+
clear
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# empty @box
|
|
40
|
+
def clear
|
|
41
|
+
@box = []
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# link the two boxes (by id)
|
|
45
|
+
def link_boxes(id1, id2)
|
|
46
|
+
raise "unknown index 1 #{id1}" if not b1 = @box.find { |b| b.id == id1 }
|
|
47
|
+
raise "unknown index 2 #{id2}" if not b2 = @box.find { |b| b.id == id2 }
|
|
48
|
+
b1.to |= [b2]
|
|
49
|
+
b2.from |= [b1]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# creates a new box, ensures id is not already taken
|
|
53
|
+
def new_box(id, content=nil)
|
|
54
|
+
raise "duplicate id #{id}" if @box.find { |b| b.id == id }
|
|
55
|
+
b = Box.new(id, content)
|
|
56
|
+
@box << b
|
|
57
|
+
b
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# place boxes in a good-looking layout
|
|
61
|
+
def auto_arrange_init(list=@box)
|
|
62
|
+
# groups is an array of box groups
|
|
63
|
+
# all groups are centered on the origin
|
|
64
|
+
@groups = list.map { |b|
|
|
65
|
+
b.x = -b.w/2
|
|
66
|
+
b.y = -b.h/2
|
|
67
|
+
g = Box.new(nil, [b])
|
|
68
|
+
g.x = b.x - 8
|
|
69
|
+
g.y = b.y - 9
|
|
70
|
+
g.w = b.w + 16
|
|
71
|
+
g.h = b.h + 18
|
|
72
|
+
g
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# init group.to/from
|
|
76
|
+
# must always point to something that is in the 'groups' array
|
|
77
|
+
# no self references
|
|
78
|
+
# a box is in one and only one group in 'groups'
|
|
79
|
+
@groups.each { |g|
|
|
80
|
+
g.to = g.content.first.to.map { |t| next if not t = list.index(t) ; @groups[t] }.compact - [g]
|
|
81
|
+
g.from = g.content.first.from.map { |f| next if not f = list.index(f) ; @groups[f] }.compact - [g]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# walk from a box, fork at each multiple to, chop links to a previous box (loops etc)
|
|
85
|
+
@madetree = false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# gives a text representation of the current graph state
|
|
89
|
+
def dump_layout(groups=@groups)
|
|
90
|
+
groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.sort.inspect}" }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def auto_arrange_step
|
|
94
|
+
# TODO fix
|
|
95
|
+
# 0->[1, 2] 1->[3] 2->[3, 4] 3->[] 4->[1]
|
|
96
|
+
# push 0 jz l3 push 1 jz l4 push 2 l3: push 3 l4: hlt
|
|
97
|
+
# and more generally all non-looping graphs where this algo creates backward links
|
|
98
|
+
|
|
99
|
+
groups = @groups
|
|
100
|
+
return if groups.length <= 1
|
|
101
|
+
|
|
102
|
+
maketree = lambda { |roots|
|
|
103
|
+
next if @madetree
|
|
104
|
+
@madetree = true
|
|
105
|
+
|
|
106
|
+
maxdepth = {} # max arc count to reach this box from graph start (excl loop)
|
|
107
|
+
|
|
108
|
+
trim = lambda { |g, from|
|
|
109
|
+
# unlink g from (part of) its from
|
|
110
|
+
from.each { |gg| gg.to.delete g }
|
|
111
|
+
g.from -= from
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
walk = lambda { |g|
|
|
115
|
+
# score
|
|
116
|
+
parentdepth = g.from.map { |gg| maxdepth[gg] }
|
|
117
|
+
if parentdepth.empty?
|
|
118
|
+
# root
|
|
119
|
+
maxdepth[g] = 0
|
|
120
|
+
elsif parentdepth.include? nil
|
|
121
|
+
# not farthest parent found / loop
|
|
122
|
+
next
|
|
123
|
+
# elsif maxdepth[g] => ?
|
|
124
|
+
else
|
|
125
|
+
maxdepth[g] = parentdepth.max + 1
|
|
126
|
+
end
|
|
127
|
+
g.to.each { |gg| walk[gg] }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
roots.each { |g| trim[g, g.from] unless g.from.empty? }
|
|
131
|
+
roots.each { |g| walk[g] }
|
|
132
|
+
|
|
133
|
+
# handle loops now (unmarked nodes)
|
|
134
|
+
while unmarked = groups - maxdepth.keys and not unmarked.empty?
|
|
135
|
+
if g = unmarked.find { |g_| g_.from.find { |gg| maxdepth[gg] } }
|
|
136
|
+
# loop head
|
|
137
|
+
trim[g, g.from.find_all { |gg| not maxdepth[gg] }] # XXX not quite sure for this
|
|
138
|
+
walk[g]
|
|
139
|
+
else
|
|
140
|
+
# disconnected subgraph
|
|
141
|
+
g = unmarked.find { |g_| g_.from.empty? } || unmarked.first
|
|
142
|
+
trim[g, g.from]
|
|
143
|
+
maxdepth[g] = 0
|
|
144
|
+
walk[g]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# concat all ary boxes into its 1st element, remove trailing groups from 'groups'
|
|
150
|
+
# updates from/to
|
|
151
|
+
merge_groups = lambda { |ary|
|
|
152
|
+
bg = Box.new(nil, [])
|
|
153
|
+
bg.x, bg.y = ary.map { |g| g.x }.min, ary.map { |g| g.y }.min
|
|
154
|
+
bg.w, bg.h = ary.map { |g| g.x+g.w }.max - bg.x, ary.map { |g| g.y+g.h }.max - bg.y
|
|
155
|
+
ary.each { |g|
|
|
156
|
+
bg.content.concat g.content
|
|
157
|
+
bg.to |= g.to
|
|
158
|
+
bg.from |= g.from
|
|
159
|
+
}
|
|
160
|
+
bg.to -= ary
|
|
161
|
+
bg.to.each { |t| t.from = t.from - ary + [bg] }
|
|
162
|
+
bg.from -= ary
|
|
163
|
+
bg.from.each { |f| f.to = f.to - ary + [bg] }
|
|
164
|
+
idx = ary.map { |g| groups.index(g) }.min
|
|
165
|
+
groups = @groups = groups - ary
|
|
166
|
+
groups.insert(idx, bg)
|
|
167
|
+
bg
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# move all boxes within group of dx, dy
|
|
171
|
+
move_group = lambda { |g, dx, dy|
|
|
172
|
+
g.content.each { |b| b.x += dx ; b.y += dy }
|
|
173
|
+
g.x += dx ; g.y += dy
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
align_hz = lambda { |ary|
|
|
177
|
+
# if we have one of the block much bigger than the others, put it on the far right
|
|
178
|
+
big = ary.sort_by { |g| g.h }.last
|
|
179
|
+
if (ary-[big]).all? { |g| g.h < big.h/3 }
|
|
180
|
+
ary -= [big]
|
|
181
|
+
else
|
|
182
|
+
big = nil
|
|
183
|
+
end
|
|
184
|
+
nx = ary.map { |g| g.w }.inject(0) { |a, b| a+b } / -2
|
|
185
|
+
nx *= 2 if big and ary.length == 1 # just put the parent on the separation of the 2 child
|
|
186
|
+
ary.each { |g|
|
|
187
|
+
move_group[g, nx-g.x, 0]
|
|
188
|
+
nx += g.w
|
|
189
|
+
}
|
|
190
|
+
move_group[big, nx-big.x, 0] if big
|
|
191
|
+
}
|
|
192
|
+
align_vt = lambda { |ary|
|
|
193
|
+
ny = ary.map { |g| g.h }.inject(0) { |a, b| a+b } / -2
|
|
194
|
+
ary.each { |g|
|
|
195
|
+
move_group[g, 0, ny-g.y]
|
|
196
|
+
ny += g.h
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# scan groups for a column pattern (head has 1 'to' which from == [head])
|
|
201
|
+
group_columns = lambda {
|
|
202
|
+
groups.find { |g|
|
|
203
|
+
next if g.from.length == 1 and g.from.first.to.length == 1
|
|
204
|
+
ary = [g]
|
|
205
|
+
ary << (g = g.to.first) while g.to.length == 1 and g.to.first.from.length == 1
|
|
206
|
+
next if ary.length <= 1
|
|
207
|
+
align_vt[ary]
|
|
208
|
+
merge_groups[ary]
|
|
209
|
+
true
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# scan groups for a line pattern (multiple groups with same to & same from)
|
|
214
|
+
group_lines = lambda { |strict|
|
|
215
|
+
if groups.all? { |g1| g1.from.empty? and g1.to.empty? }
|
|
216
|
+
# disjoint subgraphs
|
|
217
|
+
align_hz[groups]
|
|
218
|
+
merge_groups[groups]
|
|
219
|
+
next true
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
groups.find { |g1|
|
|
223
|
+
ary = g1.from.map { |gg| gg.to }.flatten.uniq.find_all { |gg|
|
|
224
|
+
gg != g1 and
|
|
225
|
+
(gg.from - g1.from).empty? and (g1.from - gg.from).empty? and
|
|
226
|
+
(strict ? ((gg.to - g1.to).empty? and (g1.to - gg.to).empty?) : (g1.to & gg.to).first)
|
|
227
|
+
}
|
|
228
|
+
ary = g1.to.map { |gg| gg.from }.flatten.uniq.find_all { |gg|
|
|
229
|
+
gg != g1 and
|
|
230
|
+
(gg.to - g1.to).empty? and (g1.to - gg.to).empty? and
|
|
231
|
+
(strict ? ((gg.from - g1.from).empty? and (g1.from - gg.from).empty?) : (g1.from & gg.from).first)
|
|
232
|
+
} if ary.empty?
|
|
233
|
+
next if ary.empty?
|
|
234
|
+
ary << g1
|
|
235
|
+
dy = 16*ary.map { |g| g.to.length + g.from.length }.inject { |a, b| a+b }
|
|
236
|
+
ary.each { |g| g.h += dy ; g.y -= dy/2 }
|
|
237
|
+
align_hz[ary]
|
|
238
|
+
if ary.first.to.empty? # shrink graph if highly dissymetric and to.empty?
|
|
239
|
+
ah = ary.map { |g| g.h }.max
|
|
240
|
+
ary.each { |g|
|
|
241
|
+
move_group[g, 0, (g.h-ah)/2] # move up
|
|
242
|
+
next if not p = ary[ary.index(g)-1]
|
|
243
|
+
y = [g.y, p.y].min # shrink width
|
|
244
|
+
h = [g.h, p.h].min
|
|
245
|
+
xp = p.content.map { |b| b.x+b.w if b.y+b.h+8 >= y and b.y-8 <= y+h }.compact.max || p.x+p.w/2
|
|
246
|
+
xg = g.content.map { |b| b.x if b.y+b.h+8 >= y and b.y-8 <= y+h }.compact.min || g.x+g.w/2
|
|
247
|
+
dx = xg-xp-24
|
|
248
|
+
next if dx <= 0
|
|
249
|
+
ary.each { |gg|
|
|
250
|
+
dx = -dx if gg == g
|
|
251
|
+
move_group[gg, dx/2, 0]
|
|
252
|
+
}
|
|
253
|
+
if p.x+p.w > ary.last.x+ary.last.w or ary.first.x > g.x # fix broken centerism
|
|
254
|
+
x = [g.x, ary.first.x].min
|
|
255
|
+
xm = [p.x+p.w, ary.last.x+ary.last.w].max
|
|
256
|
+
ary.each { |gg| move_group[gg, (x+xm)/-2, 0] }
|
|
257
|
+
end
|
|
258
|
+
}
|
|
259
|
+
end
|
|
260
|
+
merge_groups[ary]
|
|
261
|
+
true
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
group_inv_if = {}
|
|
266
|
+
|
|
267
|
+
# scan groups for a if/then pattern (1 -> 2 -> 3 & 1 -> 3)
|
|
268
|
+
group_ifthen = lambda { |strict|
|
|
269
|
+
groups.reverse.find { |g|
|
|
270
|
+
next if not g2 = g.to.find { |g2_| (g2_.to.length == 1 and g.to.include?(g2_.to.first)) or
|
|
271
|
+
(not strict and g2_.to.empty?) }
|
|
272
|
+
next if strict and g2.from != [g] or g.to.length != 2
|
|
273
|
+
g2.h += 16 ; g2.y -= 8
|
|
274
|
+
align_vt[[g, g2]]
|
|
275
|
+
dx = -g2.x+8
|
|
276
|
+
dx -= g2.w+16 if group_inv_if[g]
|
|
277
|
+
move_group[g2, dx, 0]
|
|
278
|
+
merge_groups[[g, g2]]
|
|
279
|
+
true
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# if (a || b) c;
|
|
284
|
+
# the 'else' case handles '&& else', and && is two if/then nested
|
|
285
|
+
group_or = lambda { |strict|
|
|
286
|
+
groups.find { |g|
|
|
287
|
+
next if g.to.length != 2
|
|
288
|
+
g2 = g.to[0]
|
|
289
|
+
g2 = g.to[1] if not g2.to.include? g.to[1]
|
|
290
|
+
thn = (g.to & g2.to).first
|
|
291
|
+
next if g2.to.length != 2 or not thn or thn.to.length != 1
|
|
292
|
+
els = (g2.to - [thn]).first
|
|
293
|
+
if thn.to == [els]
|
|
294
|
+
els = nil
|
|
295
|
+
elsif els.to != thn.to
|
|
296
|
+
next if strict
|
|
297
|
+
align_vt[[g, g2]]
|
|
298
|
+
merge_groups[[g, g2]]
|
|
299
|
+
break true
|
|
300
|
+
else
|
|
301
|
+
align_hz[[thn, els]]
|
|
302
|
+
thn = merge_groups[[thn, els]]
|
|
303
|
+
end
|
|
304
|
+
thn.h += 16 ; thn.y -= 8
|
|
305
|
+
align_vt[[g, g2, thn]]
|
|
306
|
+
move_group[g2, -g2.x, 0]
|
|
307
|
+
move_group[thn, thn.x-8, 0] if not els
|
|
308
|
+
merge_groups[[g, g2, thn]]
|
|
309
|
+
true
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# loop with exit 1 -> 2, 3 & 2 -> 1
|
|
315
|
+
group_loop = lambda {
|
|
316
|
+
groups.find { |g|
|
|
317
|
+
next if not g2 = g.to.sort_by { |g2_| g2_.h }.find { |g2_| g2_.to == [g] or (g2_.to.empty? and g2_.from == [g]) }
|
|
318
|
+
g2.h += 16
|
|
319
|
+
align_vt[[g, g2]]
|
|
320
|
+
move_group[g2, g2.x-8, 0]
|
|
321
|
+
merge_groups[[g, g2]]
|
|
322
|
+
true
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# same single from or to
|
|
327
|
+
group_halflines = lambda {
|
|
328
|
+
ary = nil
|
|
329
|
+
if groups.find { |g| ary = g.from.find_all { |gg| gg.to == [g] } and ary.length > 1 } or
|
|
330
|
+
groups.find { |g| ary = g.to.find_all { |gg| gg.from == [g] } and ary.length > 1 }
|
|
331
|
+
align_hz[ary]
|
|
332
|
+
merge_groups[ary]
|
|
333
|
+
true
|
|
334
|
+
end
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# unknown pattern, group as we can..
|
|
339
|
+
group_other = lambda {
|
|
340
|
+
puts 'graph arrange: unknown configuration', dump_layout
|
|
341
|
+
g1 = groups.find_all { |g| g.from.empty? }
|
|
342
|
+
g1 << groups[rand(groups.length)] if g1.empty?
|
|
343
|
+
g2 = g1.map { |g| g.to }.flatten.uniq - g1
|
|
344
|
+
align_vt[g1]
|
|
345
|
+
g1 = merge_groups[g1]
|
|
346
|
+
g1.w += 128 ; g1.x -= 64
|
|
347
|
+
next if g2.empty?
|
|
348
|
+
align_vt[g2]
|
|
349
|
+
g2 = merge_groups[g2]
|
|
350
|
+
g2.w += 128 ; g2.x -= 64
|
|
351
|
+
|
|
352
|
+
align_hz[[g1, g2]]
|
|
353
|
+
merge_groups[[g1, g2]]
|
|
354
|
+
true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# check constructs with multiple blocks with to to end block (a la break;)
|
|
358
|
+
ign_break = lambda {
|
|
359
|
+
can_reach = lambda { |b1, b2, term|
|
|
360
|
+
next if b1 == term
|
|
361
|
+
done = [term]
|
|
362
|
+
todo = b1.to.dup
|
|
363
|
+
while t = todo.pop
|
|
364
|
+
next if done.include? t
|
|
365
|
+
done << t
|
|
366
|
+
break true if t == b2
|
|
367
|
+
todo.concat t.to
|
|
368
|
+
end
|
|
369
|
+
}
|
|
370
|
+
can_reach_unidir = lambda { |b1, b2, term| can_reach[b1, b2, term] and not can_reach[b2, b1, term] }
|
|
371
|
+
groups.find { |g|
|
|
372
|
+
f2 = nil
|
|
373
|
+
if (g.from.length > 2 and f3 = g.from.find { |f| f.to == [g] } and f1 = g.from.find { |f|
|
|
374
|
+
f2 = g.from.find { |ff| can_reach_unidir[ff, f3, g] and can_reach_unidir[f, ff, g] }}) or
|
|
375
|
+
(g.to.length > 2 and f3 = g.to.find { |f| f.from == [g] } and f1 = g.to.find { |f|
|
|
376
|
+
f2 = g.to.find { |ff| can_reach_unidir[f3, ff, g] and can_reach_unidir[ff, f, g] }})
|
|
377
|
+
group_inv_if[f1] = true
|
|
378
|
+
if f3.to == [g]
|
|
379
|
+
g.from.delete f2
|
|
380
|
+
f2.to.delete g
|
|
381
|
+
else
|
|
382
|
+
g.to.delete f2
|
|
383
|
+
f2.from.delete g
|
|
384
|
+
end
|
|
385
|
+
true
|
|
386
|
+
end
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# walk graph from roots, cut backward links
|
|
391
|
+
trim_graph = lambda {
|
|
392
|
+
next true if ign_break[]
|
|
393
|
+
g1 = groups.find_all { |g| g.from.empty? }
|
|
394
|
+
g1 << groups.first if g1.empty?
|
|
395
|
+
cntpre = groups.inject(0) { |cntpre_, g| cntpre_ + g.to.length }
|
|
396
|
+
g1.each { |g| maketree[[g]] }
|
|
397
|
+
cntpost = groups.inject(0) { |cntpre_, g| cntpre_ + g.to.length }
|
|
398
|
+
true if cntpre != cntpost
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# known, clean patterns
|
|
402
|
+
group_clean = lambda {
|
|
403
|
+
group_columns[] or group_lines[true] or group_ifthen[true] or group_loop[] or group_or[true]
|
|
404
|
+
}
|
|
405
|
+
# approximations
|
|
406
|
+
group_unclean = lambda {
|
|
407
|
+
group_lines[false] or group_or[false] or group_halflines[] or group_ifthen[false] or group_other[]
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
group_clean[] or trim_graph[] or group_unclean[]
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# the boxes have been almost put in place, here we soften a little the result & arrange some qwirks
|
|
414
|
+
def auto_arrange_post
|
|
415
|
+
# entrypoint should be above other boxes, same for exitpoints
|
|
416
|
+
@box.each { |b|
|
|
417
|
+
if b.from == []
|
|
418
|
+
chld = b.to
|
|
419
|
+
chld = @box - [b] if not @box.find { |bb| bb != b and bb.from == [] }
|
|
420
|
+
chld.each { |t| b.y = t.y - b.h - 16 if t.y < b.y+b.h }
|
|
421
|
+
end
|
|
422
|
+
if b.to == []
|
|
423
|
+
chld = b.from
|
|
424
|
+
chld = @box - [b] if not @box.find { |bb| bb != b and bb.to == [] }
|
|
425
|
+
chld.each { |f| b.y = f.y + f.h + 16 if f.y+f.h > b.y }
|
|
426
|
+
end
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
boxxy = @box.sort_by { |bb| bb.y }
|
|
430
|
+
# fill gaps that we created
|
|
431
|
+
@box.each { |b|
|
|
432
|
+
bottom = b.y+b.h
|
|
433
|
+
next if not follower = boxxy.find { |bb| bb.y+bb.h > bottom }
|
|
434
|
+
|
|
435
|
+
# preserve line[] constructs margins
|
|
436
|
+
gap = follower.y-16*follower.from.length - (bottom+16*b.to.length)
|
|
437
|
+
next if gap <= 0
|
|
438
|
+
|
|
439
|
+
@box.each { |bb|
|
|
440
|
+
if bb.y+bb.h <= bottom
|
|
441
|
+
bb.y += gap/2
|
|
442
|
+
else
|
|
443
|
+
bb.y -= gap/2
|
|
444
|
+
end
|
|
445
|
+
}
|
|
446
|
+
boxxy = @box.sort_by { |bb| bb.y }
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@box[0,0].each { |b|
|
|
450
|
+
# TODO elastic positionning (ignore up arrows ?) & collision detection (box/box + box/arrow)
|
|
451
|
+
f = b.from[0]
|
|
452
|
+
t = b.to[0]
|
|
453
|
+
if b.to.length == 1 and b.from.length == 1 and b.y+b.h<t.y and b.y>f.y+f.h
|
|
454
|
+
wx = (t.x+t.w/2 + f.x+f.w/2)/2 - b.w/2
|
|
455
|
+
wy = (t.y + f.y+f.h)/2 - b.h/2
|
|
456
|
+
b.x += (wx-b.x)/5
|
|
457
|
+
b.y += (wy-b.y)/5
|
|
458
|
+
end
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def auto_arrange_boxes
|
|
464
|
+
auto_arrange_init
|
|
465
|
+
nil while @groups.length > 1 and auto_arrange_step
|
|
466
|
+
@groups = []
|
|
467
|
+
auto_arrange_post
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class GraphViewWidget < DrawableWidget
|
|
476
|
+
attr_accessor :dasm, :caret_box, :curcontext, :zoom, :margin
|
|
477
|
+
# bool, specifies if we should display addresses before instrs
|
|
478
|
+
attr_accessor :show_addresses
|
|
479
|
+
|
|
480
|
+
def initialize_widget(dasm, parent_widget)
|
|
481
|
+
@dasm = dasm
|
|
482
|
+
@parent_widget = parent_widget
|
|
483
|
+
|
|
484
|
+
@show_addresses = false
|
|
485
|
+
|
|
486
|
+
@caret_box = nil
|
|
487
|
+
@selected_boxes = []
|
|
488
|
+
@shown_boxes = []
|
|
489
|
+
@mousemove_origin = @mousemove_origin_ctrl = nil
|
|
490
|
+
@curcontext = Graph.new(nil)
|
|
491
|
+
@margin = 8
|
|
492
|
+
@zoom = 1.0
|
|
493
|
+
@default_color_association = { :background => :paleblue, :hlbox_bg => :palegrey, :box_bg => :white,
|
|
494
|
+
:text => :black, :arrow_hl => :red, :comment => :darkblue, :address => :darkblue,
|
|
495
|
+
:instruction => :black, :label => :darkgreen, :caret => :black, :hl_word => :palered,
|
|
496
|
+
:cursorline_bg => :paleyellow, :arrow_cond => :darkgreen, :arrow_uncond => :darkblue,
|
|
497
|
+
:arrow_direct => :darkred }
|
|
498
|
+
# @othergraphs = ? (to keep user-specified formatting)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def resized(w, h)
|
|
502
|
+
redraw
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def find_box_xy(x, y)
|
|
506
|
+
x = @curcontext.view_x+x/@zoom
|
|
507
|
+
y = @curcontext.view_y+y/@zoom
|
|
508
|
+
@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 }
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def mouse_wheel_ctrl(dir, x, y)
|
|
512
|
+
case dir
|
|
513
|
+
when :up
|
|
514
|
+
if @zoom < 100
|
|
515
|
+
oldzoom = @zoom
|
|
516
|
+
@zoom *= 1.1
|
|
517
|
+
@zoom = 1.0 if (@zoom-1.0).abs < 0.05
|
|
518
|
+
@curcontext.view_x += (x / oldzoom - x / @zoom)
|
|
519
|
+
@curcontext.view_y += (y / oldzoom - y / @zoom)
|
|
520
|
+
end
|
|
521
|
+
when :down
|
|
522
|
+
if @zoom > 1.0/100
|
|
523
|
+
oldzoom = @zoom
|
|
524
|
+
@zoom /= 1.1
|
|
525
|
+
@zoom = 1.0 if (@zoom-1.0).abs < 0.05
|
|
526
|
+
@curcontext.view_x += (x / oldzoom - x / @zoom)
|
|
527
|
+
@curcontext.view_y += (y / oldzoom - y / @zoom)
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
redraw
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def mouse_wheel(dir, x, y)
|
|
534
|
+
case dir
|
|
535
|
+
when :up; @curcontext.view_y -= height/4 / @zoom
|
|
536
|
+
when :down; @curcontext.view_y += height/4 / @zoom
|
|
537
|
+
end
|
|
538
|
+
redraw
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def mousemove(x, y)
|
|
542
|
+
return if not @mousemove_origin
|
|
543
|
+
|
|
544
|
+
dx = (x - @mousemove_origin[0])/@zoom
|
|
545
|
+
dy = (y - @mousemove_origin[1])/@zoom
|
|
546
|
+
@mousemove_origin = [x, y]
|
|
547
|
+
if @selected_boxes.empty?
|
|
548
|
+
@curcontext.view_x -= dx ; @curcontext.view_y -= dy
|
|
549
|
+
else
|
|
550
|
+
@selected_boxes.each { |b| b.x += dx ; b.y += dy }
|
|
551
|
+
end
|
|
552
|
+
redraw
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
def mouserelease(x, y)
|
|
556
|
+
mousemove(x, y)
|
|
557
|
+
@mousemove_origin = nil
|
|
558
|
+
|
|
559
|
+
if @mousemove_origin_ctrl
|
|
560
|
+
x1 = @curcontext.view_x + @mousemove_origin_ctrl[0]/@zoom
|
|
561
|
+
x2 = x1 + (x - @mousemove_origin_ctrl[0])/@zoom
|
|
562
|
+
x1, x2 = x2, x1 if x1 > x2
|
|
563
|
+
y1 = @curcontext.view_y + @mousemove_origin_ctrl[1]/@zoom
|
|
564
|
+
y2 = y1 + (y - @mousemove_origin_ctrl[1])/@zoom
|
|
565
|
+
y1, y2 = y2, y1 if y1 > y2
|
|
566
|
+
@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 }
|
|
567
|
+
redraw
|
|
568
|
+
@mousemove_origin_ctrl = nil
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def click_ctrl(x, y)
|
|
573
|
+
if b = find_box_xy(x, y)
|
|
574
|
+
if @selected_boxes.include? b
|
|
575
|
+
@selected_boxes.delete b
|
|
576
|
+
else
|
|
577
|
+
@selected_boxes << b
|
|
578
|
+
end
|
|
579
|
+
redraw
|
|
580
|
+
else
|
|
581
|
+
@mousemove_origin_ctrl = [x, y]
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def click(x, y)
|
|
586
|
+
@mousemove_origin = [x, y]
|
|
587
|
+
if b = find_box_xy(x, y)
|
|
588
|
+
@selected_boxes = [b] if not @selected_boxes.include? b
|
|
589
|
+
@caret_box = b
|
|
590
|
+
@caret_x = (@curcontext.view_x+x/@zoom-b.x-1).to_i / @font_width
|
|
591
|
+
@caret_y = (@curcontext.view_y+y/@zoom-b.y-1).to_i / @font_height
|
|
592
|
+
update_caret
|
|
593
|
+
else
|
|
594
|
+
@selected_boxes = []
|
|
595
|
+
@caret_box = nil
|
|
596
|
+
end
|
|
597
|
+
redraw
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# if the target is a call to a subfunction, open a new window with the graph of this function (popup)
|
|
601
|
+
def rightclick(x, y)
|
|
602
|
+
if b = find_box_xy(x, y) and @zoom >= 0.90 and @zoom <= 1.1
|
|
603
|
+
click(x, y)
|
|
604
|
+
@mousemove_origin = nil
|
|
605
|
+
@parent_widget.clone_window(@hl_word, :graph)
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def doubleclick(x, y)
|
|
610
|
+
if b = find_box_xy(x, y)
|
|
611
|
+
@mousemove_origin = nil
|
|
612
|
+
if @hl_word and @zoom >= 0.90 and @zoom <= 1.1
|
|
613
|
+
@parent_widget.focus_addr(@hl_word)
|
|
614
|
+
else
|
|
615
|
+
@parent_widget.focus_addr b[:addresses].first
|
|
616
|
+
end
|
|
617
|
+
elsif doubleclick_check_arrow(x, y)
|
|
618
|
+
elsif @zoom == 1.0
|
|
619
|
+
zoom_all
|
|
620
|
+
else
|
|
621
|
+
@curcontext.view_x += (x/@zoom - x)
|
|
622
|
+
@curcontext.view_y += (y/@zoom - y)
|
|
623
|
+
@zoom = 1.0
|
|
624
|
+
end
|
|
625
|
+
redraw
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# check if the user clicked on the beginning/end of an arrow, if so focus on the other end
|
|
629
|
+
def doubleclick_check_arrow(x, y)
|
|
630
|
+
return if @margin*@zoom < 2
|
|
631
|
+
x = @curcontext.view_x+x/@zoom
|
|
632
|
+
y = @curcontext.view_y+y/@zoom
|
|
633
|
+
sx = nil
|
|
634
|
+
if bt = @shown_boxes.to_a.reverse.find { |b|
|
|
635
|
+
y >= b.y+b.h-1 and y <= b.y+b.h-1+@margin+2 and
|
|
636
|
+
sx = b.x+b.w/2 - b.to.length/2 * @margin/2 and
|
|
637
|
+
x >= sx-@margin/2 and x <= sx+b.to.length*@margin/2 # should be margin/4, but add a little comfort margin
|
|
638
|
+
}
|
|
639
|
+
idx = (x-sx+@margin/4).to_i / (@margin/2)
|
|
640
|
+
idx = 0 if idx < 0
|
|
641
|
+
idx = bt.to.length-1 if idx >= bt.to.length
|
|
642
|
+
if bt.to[idx]
|
|
643
|
+
if @parent_widget
|
|
644
|
+
@parent_widget.focus_addr bt.to[idx][:line_address][0]
|
|
645
|
+
else
|
|
646
|
+
focus_xy(bt.to[idx].x, bt.to[idx].y)
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
true
|
|
650
|
+
elsif bf = @shown_boxes.to_a.reverse.find { |b|
|
|
651
|
+
y >= b.y-@margin-2 and y <= b.y and
|
|
652
|
+
sx = b.x+b.w/2 - b.from.length/2 * @margin/2 and
|
|
653
|
+
x >= sx-@margin/2 and x <= sx+b.from.length*@margin/2
|
|
654
|
+
}
|
|
655
|
+
idx = (x-sx+@margin/4).to_i / (@margin/2)
|
|
656
|
+
idx = 0 if idx < 0
|
|
657
|
+
idx = bf.from.length-1 if idx >= bf.from.length
|
|
658
|
+
if bf.from[idx]
|
|
659
|
+
if @parent_widget
|
|
660
|
+
@parent_widget.focus_addr bf.from[idx][:line_address][-1]
|
|
661
|
+
else
|
|
662
|
+
focus_xy(bt.from[idx].x, bt.from[idx].y)
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
true
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# update the zoom & view_xy to show the whole graph in the window
|
|
670
|
+
def zoom_all
|
|
671
|
+
minx = @curcontext.box.map { |b| b.x }.min.to_i - 10
|
|
672
|
+
miny = @curcontext.box.map { |b| b.y }.min.to_i - 10
|
|
673
|
+
maxx = @curcontext.box.map { |b| b.x + b.w }.max.to_i + 10
|
|
674
|
+
maxy = @curcontext.box.map { |b| b.y + b.h }.max.to_i + 10
|
|
675
|
+
@zoom = [width.to_f/(maxx-minx), height.to_f/(maxy-miny)].min
|
|
676
|
+
@zoom = 1.0 if @zoom > 1.0 or (@zoom-1.0).abs < 0.1
|
|
677
|
+
@curcontext.view_x = minx + (maxx-minx-width/@zoom)/2
|
|
678
|
+
@curcontext.view_y = miny + (maxy-miny-height/@zoom)/2
|
|
679
|
+
redraw
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def paint
|
|
683
|
+
update_graph if @want_update_graph
|
|
684
|
+
if @want_focus_addr and @curcontext.box.find { |b_| b_[:line_address].index(@want_focus_addr) }
|
|
685
|
+
focus_addr(@want_focus_addr, false)
|
|
686
|
+
@want_focus_addr = nil
|
|
687
|
+
#zoom_all
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
@curcontext.box.each { |b|
|
|
691
|
+
# reorder arrows so that endings do not overlap
|
|
692
|
+
b.to = b.to.sort_by { |bt| bt.x+bt.w/2 }
|
|
693
|
+
b.from = b.from.sort_by { |bt| bt.x+bt.w/2 }
|
|
694
|
+
}
|
|
695
|
+
# arrows drawn first to stay under the boxes
|
|
696
|
+
# XXX precalc ?
|
|
697
|
+
@curcontext.box.each { |b|
|
|
698
|
+
b.to.each { |bt| paint_arrow(b, bt) }
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
@shown_boxes = []
|
|
702
|
+
w_w, w_h = width, height
|
|
703
|
+
@curcontext.box.each { |b|
|
|
704
|
+
next if b.x >= @curcontext.view_x+w_w/@zoom or b.y >= @curcontext.view_y+w_h/@zoom or b.x+b.w <= @curcontext.view_x or b.y+b.h <= @curcontext.view_y
|
|
705
|
+
@shown_boxes << b
|
|
706
|
+
paint_box(b)
|
|
707
|
+
}
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def set_color_arrow(b1, b2)
|
|
711
|
+
if b1 == @caret_box or b2 == @caret_box
|
|
712
|
+
draw_color :arrow_hl
|
|
713
|
+
elsif b1.to.length == 1
|
|
714
|
+
draw_color :arrow_uncond
|
|
715
|
+
elsif b1.direct_to == b2.id
|
|
716
|
+
draw_color :arrow_direct
|
|
717
|
+
else
|
|
718
|
+
draw_color :arrow_cond
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
def paint_arrow(b1, b2)
|
|
723
|
+
x1, y1 = b1.x+b1.w/2-@curcontext.view_x, b1.y+b1.h-@curcontext.view_y
|
|
724
|
+
x2, y2 = b2.x+b2.w/2-@curcontext.view_x, b2.y-1-@curcontext.view_y
|
|
725
|
+
x1o, x2o = x1, x2
|
|
726
|
+
margin = @margin
|
|
727
|
+
x1 += (-(b1.to.length-1)/2 + b1.to.index(b2)) * margin/2
|
|
728
|
+
x2 += (-(b2.from.length-1)/2 + b2.from.index(b1)) * margin/2
|
|
729
|
+
return if (y1+margin < 0 and y2 < 0) or (y1 > height/@zoom and y2-margin > height/@zoom) # just clip on y
|
|
730
|
+
margin, x1, y1, x2, y2, b1w, b2w, x1o, x2o = [margin, x1, y1, x2, y2, b1.w, b2.w, x1o, x2o].map { |v| v*@zoom }
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
# XXX gtk wraps coords around 0x8000
|
|
734
|
+
if x1.abs > 0x7000 ; y1 /= x1.abs/0x7000 ; x1 /= x1.abs/0x7000 ; end
|
|
735
|
+
if y1.abs > 0x7000 ; x1 /= y1.abs/0x7000 ; y1 /= y1.abs/0x7000 ; end
|
|
736
|
+
if x2.abs > 0x7000 ; y2 /= x2.abs/0x7000 ; x2 /= x2.abs/0x7000 ; end
|
|
737
|
+
if y2.abs > 0x7000 ; x2 /= y2.abs/0x7000 ; y2 /= y2.abs/0x7000 ; end
|
|
738
|
+
|
|
739
|
+
# straighten vertical arrows if possible
|
|
740
|
+
if y2 > y1 and (x1-x2).abs <= margin
|
|
741
|
+
if b1.to.length == 1
|
|
742
|
+
x1 = x2
|
|
743
|
+
elsif b2.from.length == 1
|
|
744
|
+
x2 = x1
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
set_color_arrow(b1, b2)
|
|
749
|
+
if margin > 1
|
|
750
|
+
# draw arrow tip
|
|
751
|
+
draw_line(x1, y1, x1, y1+margin)
|
|
752
|
+
draw_line(x2, y2-margin+1, x2, y2)
|
|
753
|
+
draw_line(x2-margin/2, y2-margin/2, x2, y2)
|
|
754
|
+
draw_line(x2+margin/2, y2-margin/2, x2, y2)
|
|
755
|
+
y1 += margin
|
|
756
|
+
y2 -= margin-1
|
|
757
|
+
end
|
|
758
|
+
if y2+margin >= y1-margin-1
|
|
759
|
+
# straight vertical down arrow
|
|
760
|
+
draw_line(x1, y1, x2, y2) if x1 != y1 or x2 != y2
|
|
761
|
+
|
|
762
|
+
# else arrow up, need to sneak around boxes
|
|
763
|
+
elsif x1o-b1w/2-margin >= x2o+b2w/2+margin # z
|
|
764
|
+
draw_line(x1, y1, x1o-b1w/2-margin, y1)
|
|
765
|
+
draw_line(x1o-b1w/2-margin, y1, x2o+b2w/2+margin, y2)
|
|
766
|
+
draw_line(x2o+b2w/2+margin, y2, x2, y2)
|
|
767
|
+
draw_line(x1, y1+1, x1o-b1w/2-margin, y1+1) # double
|
|
768
|
+
draw_line(x1o-b1w/2-margin+1, y1, x2o+b2w/2+margin+1, y2)
|
|
769
|
+
draw_line(x2o+b2w/2+margin, y2+1, x2, y2+1)
|
|
770
|
+
elsif x1+b1w/2+margin <= x2-b2w/2-margin # invert z
|
|
771
|
+
draw_line(x1, y1, x1o+b1w/2+margin, y1)
|
|
772
|
+
draw_line(x1o+b1w/2+margin, y1, x2o-b2w/2-margin, y2)
|
|
773
|
+
draw_line(x2o-b2w/2-margin, y2, x2, y2)
|
|
774
|
+
draw_line(x1, y1+1, x1+b1w/2+margin, y1+1) # double
|
|
775
|
+
draw_line(x1o+b1w/2+margin+1, y1, x2o-b2w/2-margin+1, y2)
|
|
776
|
+
draw_line(x2o-b2w/2-margin, y2+1, x2, y2+1)
|
|
777
|
+
else # turn around
|
|
778
|
+
x = (x1 <= x2 ? [x1o-b1w/2-margin, x2o-b2w/2-margin].min : [x1o+b1w/2+margin, x2o+b2w/2+margin].max)
|
|
779
|
+
draw_line(x1, y1, x, y1)
|
|
780
|
+
draw_line(x, y1, x, y2)
|
|
781
|
+
draw_line(x, y2, x2, y2)
|
|
782
|
+
draw_line(x1, y1+1, x, y1+1) # double
|
|
783
|
+
draw_line(x+1, y1, x+1, y2)
|
|
784
|
+
draw_line(x, y2+1, x2, y2+1)
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
def set_color_boxshadow(b)
|
|
789
|
+
draw_color :black
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def set_color_box(b)
|
|
793
|
+
if @selected_boxes.include? b
|
|
794
|
+
draw_color :hlbox_bg
|
|
795
|
+
else
|
|
796
|
+
draw_color :box_bg
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def paint_box(b)
|
|
801
|
+
set_color_boxshadow(b)
|
|
802
|
+
draw_rectangle((b.x-@curcontext.view_x+3)*@zoom, (b.y-@curcontext.view_y+4)*@zoom, b.w*@zoom, b.h*@zoom)
|
|
803
|
+
set_color_box(b)
|
|
804
|
+
draw_rectangle((b.x-@curcontext.view_x)*@zoom, (b.y-@curcontext.view_y+1)*@zoom, b.w*@zoom, b.h*@zoom)
|
|
805
|
+
|
|
806
|
+
# current text position
|
|
807
|
+
x = (b.x - @curcontext.view_x + 1)*@zoom
|
|
808
|
+
y = (b.y - @curcontext.view_y + 1)*@zoom
|
|
809
|
+
w_w = (b.x - @curcontext.view_x + b.w - @font_width)*@zoom
|
|
810
|
+
w_h = (b.y - @curcontext.view_y + b.h - @font_height)*@zoom
|
|
811
|
+
|
|
812
|
+
if @parent_widget and @parent_widget.bg_color_callback
|
|
813
|
+
ly = 0
|
|
814
|
+
b[:line_address].each { |a|
|
|
815
|
+
if c = @parent_widget.bg_color_callback[a]
|
|
816
|
+
draw_rectangle_color(c, (b.x-@curcontext.view_x)*@zoom, (1+b.y-@curcontext.view_y+ly*@font_height)*@zoom, b.w*@zoom, (@font_height*@zoom).ceil)
|
|
817
|
+
end
|
|
818
|
+
ly += 1
|
|
819
|
+
}
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
if @caret_box == b
|
|
823
|
+
draw_rectangle_color(:cursorline_bg, (b.x-@curcontext.view_x)*@zoom, (1+b.y-@curcontext.view_y+@caret_y*@font_height)*@zoom, b.w*@zoom, @font_height*@zoom)
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
return if @zoom < 0.99 or @zoom > 1.1
|
|
827
|
+
# TODO dynamic font size ?
|
|
828
|
+
|
|
829
|
+
# renders a string at current cursor position with a color
|
|
830
|
+
# must not include newline
|
|
831
|
+
render = lambda { |str, color|
|
|
832
|
+
# function ends when we write under the bottom of the listing
|
|
833
|
+
next if y >= w_h+2 or x >= w_w
|
|
834
|
+
if @hl_word
|
|
835
|
+
stmp = str
|
|
836
|
+
pre_x = 0
|
|
837
|
+
while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/
|
|
838
|
+
s1, s2 = $1, $2
|
|
839
|
+
pre_x += s1.length * @font_width
|
|
840
|
+
hl_x = s2.length * @font_width
|
|
841
|
+
draw_rectangle_color(:hl_word, x+pre_x, y, hl_x, @font_height*@zoom)
|
|
842
|
+
pre_x += hl_x
|
|
843
|
+
stmp = stmp[s1.length+s2.length..-1]
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
draw_string_color(color, x, y, str)
|
|
847
|
+
x += str.length * @font_width
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
b[:line_text_col].each { |list|
|
|
851
|
+
list.each { |s, c| render[s, c] }
|
|
852
|
+
x = (b.x - @curcontext.view_x + 1)*@zoom
|
|
853
|
+
y += @font_height*@zoom
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if b == @caret_box and focus?
|
|
857
|
+
cx = (b.x - @curcontext.view_x + 1 + @caret_x*@font_width)*@zoom
|
|
858
|
+
cy = (b.y - @curcontext.view_y + 1 + @caret_y*@font_height)*@zoom
|
|
859
|
+
draw_line_color(:caret, cx, cy, cx, cy+(@font_height-1)*@zoom)
|
|
860
|
+
end
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
def gui_update
|
|
864
|
+
@want_update_graph = true
|
|
865
|
+
redraw
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
#
|
|
869
|
+
# rebuild the code flow graph from @curcontext.roots
|
|
870
|
+
# recalc the boxes w/h
|
|
871
|
+
#
|
|
872
|
+
def update_graph
|
|
873
|
+
@want_update_graph = false
|
|
874
|
+
|
|
875
|
+
ctx = @curcontext
|
|
876
|
+
|
|
877
|
+
boxcnt = ctx.box.length
|
|
878
|
+
arrcnt = ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
|
|
879
|
+
ctx.clear
|
|
880
|
+
|
|
881
|
+
build_ctx(ctx)
|
|
882
|
+
|
|
883
|
+
ctx.auto_arrange_boxes
|
|
884
|
+
|
|
885
|
+
return if ctx != @curcontext
|
|
886
|
+
|
|
887
|
+
if boxcnt != ctx.box.length or arrcnt != ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
|
|
888
|
+
zoom_all
|
|
889
|
+
elsif @caret_box # update @caret_box with a box at the same place
|
|
890
|
+
bx = @caret_box.x + @caret_box.w/2
|
|
891
|
+
by = @caret_box.y + @caret_box.h/2
|
|
892
|
+
@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 }
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
def load_dotfile(path)
|
|
897
|
+
@want_update_graph = false
|
|
898
|
+
@curcontext.clear
|
|
899
|
+
boxes = {}
|
|
900
|
+
new_box = lambda { |text|
|
|
901
|
+
b = @curcontext.new_box(text, :line_text_col => [[[text, :text]]])
|
|
902
|
+
b.w = text.length * @font_width
|
|
903
|
+
b.h = @font_height
|
|
904
|
+
b
|
|
905
|
+
}
|
|
906
|
+
max = File.size(path)
|
|
907
|
+
i = 0
|
|
908
|
+
File.open(path) { |fd|
|
|
909
|
+
while l = fd.gets
|
|
910
|
+
case l.strip
|
|
911
|
+
when /^"?(\w+)"?\s*->\s*"?(\w+)"?;?$/
|
|
912
|
+
b1 = boxes[$1] ||= new_box[$1]
|
|
913
|
+
b2 = boxes[$2] ||= new_box[$2]
|
|
914
|
+
b1.to |= [b2]
|
|
915
|
+
b2.from |= [b1]
|
|
916
|
+
end
|
|
917
|
+
$stderr.printf("%.02f\r" % (fd.pos*100.0/max)) if (i += 1) & 0xff == 0
|
|
918
|
+
end
|
|
919
|
+
}
|
|
920
|
+
p boxes.length
|
|
921
|
+
redraw
|
|
922
|
+
rescue Interrupt
|
|
923
|
+
p boxes.length
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
# create the graph objects in ctx
|
|
927
|
+
def build_ctx(ctx)
|
|
928
|
+
# graph : block -> following blocks in same function
|
|
929
|
+
block_rel = {}
|
|
930
|
+
|
|
931
|
+
todo = ctx.root_addrs.dup
|
|
932
|
+
done = [:default, Expression::Unknown]
|
|
933
|
+
while a = todo.shift
|
|
934
|
+
a = @dasm.normalize a
|
|
935
|
+
next if done.include? a
|
|
936
|
+
done << a
|
|
937
|
+
next if not di = @dasm.di_at(a)
|
|
938
|
+
if not di.block_head?
|
|
939
|
+
block_rel[di.block.address] = [a]
|
|
940
|
+
@dasm.split_block(a)
|
|
941
|
+
end
|
|
942
|
+
block_rel[a] = []
|
|
943
|
+
di.block.each_to_samefunc(@dasm) { |t|
|
|
944
|
+
t = @dasm.normalize t
|
|
945
|
+
next if not @dasm.di_at(t)
|
|
946
|
+
todo << t
|
|
947
|
+
block_rel[a] << t
|
|
948
|
+
}
|
|
949
|
+
block_rel[a].uniq!
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
# populate boxes
|
|
953
|
+
addr2box = {}
|
|
954
|
+
todo = ctx.root_addrs.dup
|
|
955
|
+
todo.delete_if { |t| not @dasm.di_at(t) } # undefined func start
|
|
956
|
+
done = []
|
|
957
|
+
while a = todo.shift
|
|
958
|
+
next if done.include? a
|
|
959
|
+
done << a
|
|
960
|
+
if not ctx.keep_split.to_a.include?(a) and from = block_rel.keys.find_all { |ba| block_rel[ba].include? a } and
|
|
961
|
+
from.length == 1 and block_rel[from.first].length == 1 and
|
|
962
|
+
addr2box[from.first] and lst = @dasm.decoded[from.first].block.list.last and
|
|
963
|
+
lst.next_addr == a and (not lst.opcode.props[:saveip] or lst.block.to_subfuncret)
|
|
964
|
+
box = addr2box[from.first]
|
|
965
|
+
else
|
|
966
|
+
box = ctx.new_box a, :addresses => [], :line_text_col => [], :line_address => []
|
|
967
|
+
end
|
|
968
|
+
@dasm.decoded[a].block.list.each { |di_|
|
|
969
|
+
box[:addresses] << di_.address
|
|
970
|
+
addr2box[di_.address] = box
|
|
971
|
+
}
|
|
972
|
+
todo.concat block_rel[a]
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
# link boxes
|
|
976
|
+
ctx.box.each { |b|
|
|
977
|
+
next if not di = @dasm.decoded[b[:addresses].last]
|
|
978
|
+
a = di.block.address
|
|
979
|
+
next if not block_rel[a]
|
|
980
|
+
block_rel[a].each { |t|
|
|
981
|
+
ctx.link_boxes(b.id, t)
|
|
982
|
+
b.direct_to = t if t == di.next_addr
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
# calc box dimensions/text
|
|
987
|
+
ctx.box.each { |b|
|
|
988
|
+
colstr = []
|
|
989
|
+
curaddr = nil
|
|
990
|
+
line = 0
|
|
991
|
+
render = lambda { |str, col| colstr << [str, col] }
|
|
992
|
+
nl = lambda {
|
|
993
|
+
b[:line_address][line] = curaddr
|
|
994
|
+
b[:line_text_col][line] = colstr
|
|
995
|
+
colstr = []
|
|
996
|
+
line += 1
|
|
997
|
+
}
|
|
998
|
+
b[:addresses].each { |addr|
|
|
999
|
+
curaddr = addr
|
|
1000
|
+
if di = @dasm.di_at(curaddr)
|
|
1001
|
+
if di.block_head?
|
|
1002
|
+
# render dump_block_header, add a few colors
|
|
1003
|
+
b_header = '' ; @dasm.dump_block_header(di.block) { |l| b_header << l ; b_header << ?\n if b_header[-1] != ?\n }
|
|
1004
|
+
b_header.strip.each_line { |l| l.chomp!
|
|
1005
|
+
col = :comment
|
|
1006
|
+
col = :label if l[0, 2] != '//' and l[-1] == ?:
|
|
1007
|
+
render[l, col]
|
|
1008
|
+
nl[]
|
|
1009
|
+
}
|
|
1010
|
+
end
|
|
1011
|
+
render["#{Expression[curaddr]} ", :address] if @show_addresses
|
|
1012
|
+
render[di.instruction.to_s.ljust(di.comment ? 24 : 0), :instruction]
|
|
1013
|
+
render[' ; ' + di.comment.join(' ')[0, 64], :comment] if di.comment
|
|
1014
|
+
nl[]
|
|
1015
|
+
else
|
|
1016
|
+
# TODO real data display (dwords, xrefs, strings..)
|
|
1017
|
+
if label = @dasm.get_label_at(curaddr)
|
|
1018
|
+
render[label + ' ', :label]
|
|
1019
|
+
end
|
|
1020
|
+
s = @dasm.get_section_at(curaddr)
|
|
1021
|
+
render['db '+((s and s[0].data.length > s[0].ptr) ? Expression[s[0].read(1)[0]].to_s : '?'), :text]
|
|
1022
|
+
nl[]
|
|
1023
|
+
end
|
|
1024
|
+
}
|
|
1025
|
+
b.w = b[:line_text_col].map { |strc| strc.map { |s, c| s }.join.length }.max.to_i * @font_width + 2
|
|
1026
|
+
b.w += 1 if b.w % 2 == 0 # ensure boxes have odd width -> vertical arrows are straight
|
|
1027
|
+
b.h = line * @font_height
|
|
1028
|
+
}
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
def keypress_ctrl(key)
|
|
1032
|
+
case key
|
|
1033
|
+
when ?F
|
|
1034
|
+
@parent_widget.inputbox('text to search in curview (regex)', :text => @hl_word) { |pat|
|
|
1035
|
+
re = /#{pat}/i
|
|
1036
|
+
list = [['addr', 'instr']]
|
|
1037
|
+
@curcontext.box.each { |b|
|
|
1038
|
+
b[:line_text_col].zip(b[:line_address]) { |l, a|
|
|
1039
|
+
str = l.map { |s, c| s }.join
|
|
1040
|
+
list << [Expression[a], str] if str =~ re
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
@parent_widget.list_bghilight("search result for /#{pat}/i", list) { |i| @parent_widget.focus_addr i[0] }
|
|
1044
|
+
}
|
|
1045
|
+
else return false
|
|
1046
|
+
end
|
|
1047
|
+
true
|
|
1048
|
+
end
|
|
1049
|
+
|
|
1050
|
+
def keypress(key)
|
|
1051
|
+
case key
|
|
1052
|
+
when :left
|
|
1053
|
+
if @caret_box
|
|
1054
|
+
if @caret_x > 0
|
|
1055
|
+
@caret_x -= 1
|
|
1056
|
+
update_caret
|
|
1057
|
+
elsif b = @curcontext.box.sort_by { |b_| -b_.x }.find { |b_| b_.x < @caret_box.x and
|
|
1058
|
+
b_.y < @caret_box.y+@caret_y*@font_height and
|
|
1059
|
+
b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
|
|
1060
|
+
@caret_x = (b.w/@font_width).to_i
|
|
1061
|
+
@caret_y += ((@caret_box.y-b.y)/@font_height).to_i
|
|
1062
|
+
@caret_box = b
|
|
1063
|
+
update_caret
|
|
1064
|
+
redraw
|
|
1065
|
+
else
|
|
1066
|
+
@curcontext.view_x -= 20/@zoom
|
|
1067
|
+
redraw
|
|
1068
|
+
end
|
|
1069
|
+
else
|
|
1070
|
+
@curcontext.view_x -= 20/@zoom
|
|
1071
|
+
redraw
|
|
1072
|
+
end
|
|
1073
|
+
when :up
|
|
1074
|
+
if @caret_box
|
|
1075
|
+
if @caret_y > 0
|
|
1076
|
+
@caret_y -= 1
|
|
1077
|
+
update_caret
|
|
1078
|
+
elsif b = @curcontext.box.sort_by { |b_| -b_.y }.find { |b_| b_.y < @caret_box.y and
|
|
1079
|
+
b_.x < @caret_box.x+@caret_x*@font_width and
|
|
1080
|
+
b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
|
|
1081
|
+
@caret_x += ((@caret_box.x-b.x)/@font_width).to_i
|
|
1082
|
+
@caret_y = b[:line_address].length-1
|
|
1083
|
+
@caret_box = b
|
|
1084
|
+
update_caret
|
|
1085
|
+
redraw
|
|
1086
|
+
else
|
|
1087
|
+
@curcontext.view_y -= 20/@zoom
|
|
1088
|
+
redraw
|
|
1089
|
+
end
|
|
1090
|
+
else
|
|
1091
|
+
@curcontext.view_y -= 20/@zoom
|
|
1092
|
+
redraw
|
|
1093
|
+
end
|
|
1094
|
+
when :right
|
|
1095
|
+
if @caret_box
|
|
1096
|
+
if @caret_x <= @caret_box[:line_text_col].map { |s| s.map { |ss, cc| ss }.join.length }.max
|
|
1097
|
+
@caret_x += 1
|
|
1098
|
+
update_caret
|
|
1099
|
+
elsif b = @curcontext.box.sort_by { |b_| b_.x }.find { |b_| b_.x > @caret_box.x and
|
|
1100
|
+
b_.y < @caret_box.y+@caret_y*@font_height and
|
|
1101
|
+
b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
|
|
1102
|
+
@caret_x = 0
|
|
1103
|
+
@caret_y += ((@caret_box.y-b.y)/@font_height).to_i
|
|
1104
|
+
@caret_box = b
|
|
1105
|
+
update_caret
|
|
1106
|
+
redraw
|
|
1107
|
+
else
|
|
1108
|
+
@curcontext.view_x += 20/@zoom
|
|
1109
|
+
redraw
|
|
1110
|
+
end
|
|
1111
|
+
else
|
|
1112
|
+
@curcontext.view_x += 20/@zoom
|
|
1113
|
+
redraw
|
|
1114
|
+
end
|
|
1115
|
+
when :down
|
|
1116
|
+
if @caret_box
|
|
1117
|
+
if @caret_y < @caret_box[:line_address].length-1
|
|
1118
|
+
@caret_y += 1
|
|
1119
|
+
update_caret
|
|
1120
|
+
elsif b = @curcontext.box.sort_by { |b_| b_.y }.find { |b_| b_.y > @caret_box.y and
|
|
1121
|
+
b_.x < @caret_box.x+@caret_x*@font_width and
|
|
1122
|
+
b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
|
|
1123
|
+
@caret_x += ((@caret_box.x-b.x)/@font_width).to_i
|
|
1124
|
+
@caret_y = 0
|
|
1125
|
+
@caret_box = b
|
|
1126
|
+
update_caret
|
|
1127
|
+
redraw
|
|
1128
|
+
else
|
|
1129
|
+
@curcontext.view_y += 20/@zoom
|
|
1130
|
+
redraw
|
|
1131
|
+
end
|
|
1132
|
+
else
|
|
1133
|
+
@curcontext.view_y += 20/@zoom
|
|
1134
|
+
redraw
|
|
1135
|
+
end
|
|
1136
|
+
when :pgup
|
|
1137
|
+
if @caret_box
|
|
1138
|
+
@caret_y = 0
|
|
1139
|
+
update_caret
|
|
1140
|
+
else
|
|
1141
|
+
@curcontext.view_y -= height/4/@zoom
|
|
1142
|
+
redraw
|
|
1143
|
+
end
|
|
1144
|
+
when :pgdown
|
|
1145
|
+
if @caret_box
|
|
1146
|
+
@caret_y = @caret_box[:line_address].length-1
|
|
1147
|
+
update_caret
|
|
1148
|
+
else
|
|
1149
|
+
@curcontext.view_y += height/4/@zoom
|
|
1150
|
+
redraw
|
|
1151
|
+
end
|
|
1152
|
+
when :home
|
|
1153
|
+
if @caret_box
|
|
1154
|
+
@caret_x = 0
|
|
1155
|
+
update_caret
|
|
1156
|
+
else
|
|
1157
|
+
@curcontext.view_x = @curcontext.box.map { |b_| b_.x }.min-10
|
|
1158
|
+
@curcontext.view_y = @curcontext.box.map { |b_| b_.y }.min-10
|
|
1159
|
+
redraw
|
|
1160
|
+
end
|
|
1161
|
+
when :end
|
|
1162
|
+
if @caret_box
|
|
1163
|
+
@caret_x = @caret_box[:line_text_col][@caret_y].to_a.map { |ss, cc| ss }.join.length
|
|
1164
|
+
update_caret
|
|
1165
|
+
else
|
|
1166
|
+
@curcontext.view_x = [@curcontext.box.map { |b_| b_.x+b_.w }.max-width/@zoom+10, @curcontext.box.map { |b_| b_.x }.min-10].max
|
|
1167
|
+
@curcontext.view_y = [@curcontext.box.map { |b_| b_.y+b_.h }.max-height/@zoom+10, @curcontext.box.map { |b_| b_.y }.min-10].max
|
|
1168
|
+
redraw
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
when :delete
|
|
1172
|
+
@selected_boxes.each { |b_|
|
|
1173
|
+
@curcontext.box.delete b_
|
|
1174
|
+
b_.from.each { |bb| bb.to.delete b_ }
|
|
1175
|
+
b_.to.each { |bb| bb.from.delete b_ }
|
|
1176
|
+
}
|
|
1177
|
+
redraw
|
|
1178
|
+
|
|
1179
|
+
when ?a
|
|
1180
|
+
puts 'autoarrange'
|
|
1181
|
+
@curcontext.auto_arrange_boxes
|
|
1182
|
+
redraw
|
|
1183
|
+
puts 'autoarrange done'
|
|
1184
|
+
when ?u
|
|
1185
|
+
gui_update
|
|
1186
|
+
|
|
1187
|
+
when ?R
|
|
1188
|
+
load __FILE__
|
|
1189
|
+
when ?S # reset
|
|
1190
|
+
@curcontext.auto_arrange_init(@selected_boxes.empty? ? @curcontext.box : @selected_boxes)
|
|
1191
|
+
puts 'reset', @curcontext.dump_layout, ''
|
|
1192
|
+
zoom_all
|
|
1193
|
+
redraw
|
|
1194
|
+
when ?T # step auto_arrange
|
|
1195
|
+
@curcontext.auto_arrange_step
|
|
1196
|
+
puts @curcontext.dump_layout, ''
|
|
1197
|
+
zoom_all
|
|
1198
|
+
redraw
|
|
1199
|
+
when ?L # post auto_arrange
|
|
1200
|
+
@curcontext.auto_arrange_post
|
|
1201
|
+
zoom_all
|
|
1202
|
+
redraw
|
|
1203
|
+
when ?V # shrink
|
|
1204
|
+
@selected_boxes.each { |b_|
|
|
1205
|
+
dx = (b_.from + b_.to).map { |bb| bb.x+bb.w/2 - b_.x-b_.w/2 }
|
|
1206
|
+
dx = dx.inject(0) { |s, xx| s+xx }/dx.length
|
|
1207
|
+
b_.x += dx
|
|
1208
|
+
}
|
|
1209
|
+
redraw
|
|
1210
|
+
when ?I # create arbitrary boxes/links
|
|
1211
|
+
if @selected_boxes.empty?
|
|
1212
|
+
@fakebox ||= 0
|
|
1213
|
+
b = @curcontext.new_box "id_#@fakebox",
|
|
1214
|
+
:addresses => [], :line_address => [],
|
|
1215
|
+
:line_text_col => [[[" blublu #@fakebox", :text]]]
|
|
1216
|
+
b.w = @font_width * 15
|
|
1217
|
+
b.h = @font_height * 2
|
|
1218
|
+
b.x = rand(200) - 100
|
|
1219
|
+
b.y = rand(200) - 100
|
|
1220
|
+
|
|
1221
|
+
@fakebox += 1
|
|
1222
|
+
else
|
|
1223
|
+
b1, *bl = @selected_boxes
|
|
1224
|
+
bl = [b1] if bl.empty? # loop
|
|
1225
|
+
bl.each { |b2|
|
|
1226
|
+
if b1.to.include? b2
|
|
1227
|
+
b1.to.delete b2
|
|
1228
|
+
b2.from.delete b1
|
|
1229
|
+
else
|
|
1230
|
+
b1.to << b2
|
|
1231
|
+
b2.from << b1
|
|
1232
|
+
end
|
|
1233
|
+
}
|
|
1234
|
+
end
|
|
1235
|
+
redraw
|
|
1236
|
+
|
|
1237
|
+
when ?1 # (numeric) zoom to 1:1
|
|
1238
|
+
if @zoom == 1.0
|
|
1239
|
+
zoom_all
|
|
1240
|
+
else
|
|
1241
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
|
1242
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
|
1243
|
+
@zoom = 1.0
|
|
1244
|
+
end
|
|
1245
|
+
redraw
|
|
1246
|
+
when :insert # split curbox at @caret_y
|
|
1247
|
+
if @caret_box and a = @caret_box[:line_address][@caret_y] and @dasm.decoded[a]
|
|
1248
|
+
@dasm.split_block(a)
|
|
1249
|
+
@curcontext.keep_split ||= []
|
|
1250
|
+
@curcontext.keep_split |= [a]
|
|
1251
|
+
gui_update
|
|
1252
|
+
focus_addr a
|
|
1253
|
+
end
|
|
1254
|
+
else return false
|
|
1255
|
+
end
|
|
1256
|
+
true
|
|
1257
|
+
end
|
|
1258
|
+
|
|
1259
|
+
# find a suitable array of graph roots, walking up from a block (function start/entrypoint)
|
|
1260
|
+
def dasm_find_roots(addr)
|
|
1261
|
+
todo = [addr]
|
|
1262
|
+
done = []
|
|
1263
|
+
roots = []
|
|
1264
|
+
default_root = nil
|
|
1265
|
+
while a = todo.shift
|
|
1266
|
+
next if not di = @dasm.di_at(a)
|
|
1267
|
+
b = di.block
|
|
1268
|
+
a = b.address
|
|
1269
|
+
if done.include? a
|
|
1270
|
+
default_root ||= a
|
|
1271
|
+
next
|
|
1272
|
+
end
|
|
1273
|
+
done << a
|
|
1274
|
+
newf = []
|
|
1275
|
+
b.each_from_samefunc(@dasm) { |f| newf << f }
|
|
1276
|
+
if newf.empty?
|
|
1277
|
+
roots << b.address
|
|
1278
|
+
else
|
|
1279
|
+
todo.concat newf
|
|
1280
|
+
end
|
|
1281
|
+
end
|
|
1282
|
+
roots << default_root if roots.empty? and default_root
|
|
1283
|
+
|
|
1284
|
+
roots
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
def set_cursor_pos(p)
|
|
1288
|
+
addr, x = p
|
|
1289
|
+
focus_addr(addr)
|
|
1290
|
+
@caret_x = x
|
|
1291
|
+
update_caret
|
|
1292
|
+
end
|
|
1293
|
+
|
|
1294
|
+
def get_cursor_pos
|
|
1295
|
+
[current_address, @caret_x]
|
|
1296
|
+
end
|
|
1297
|
+
|
|
1298
|
+
# focus on addr
|
|
1299
|
+
# addr may be a dasm label, dasm address, dasm address in string form (eg "0DEADBEEFh")
|
|
1300
|
+
# addr must point to a decodedinstruction
|
|
1301
|
+
# if the addr is not found in curcontext, the code flow is walked up until a function
|
|
1302
|
+
# start or an entrypoint is found, then the graph is created from there
|
|
1303
|
+
# will call gui_update then
|
|
1304
|
+
def focus_addr(addr, can_update_context=true)
|
|
1305
|
+
return if @parent_widget and not addr = @parent_widget.normalize(addr)
|
|
1306
|
+
return if not @dasm.di_at(addr)
|
|
1307
|
+
|
|
1308
|
+
# move window / change curcontext
|
|
1309
|
+
if b = @curcontext.box.find { |b_| b_[:line_address].index(addr) }
|
|
1310
|
+
@caret_box, @caret_x, @caret_y = b, 0, b[:line_address].rindex(addr)
|
|
1311
|
+
@curcontext.view_x += (width/2 / @zoom - width/2)
|
|
1312
|
+
@curcontext.view_y += (height/2 / @zoom - height/2)
|
|
1313
|
+
@zoom = 1.0
|
|
1314
|
+
|
|
1315
|
+
focus_xy(b.x, b.y + @caret_y*@font_height)
|
|
1316
|
+
update_caret
|
|
1317
|
+
elsif can_update_context
|
|
1318
|
+
@curcontext = Graph.new 'testic'
|
|
1319
|
+
@curcontext.root_addrs = dasm_find_roots(addr)
|
|
1320
|
+
@want_focus_addr = addr
|
|
1321
|
+
gui_update
|
|
1322
|
+
else
|
|
1323
|
+
return
|
|
1324
|
+
end
|
|
1325
|
+
true
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
def focus_xy(x, y)
|
|
1329
|
+
if not @curcontext.view_x or @curcontext.view_x*@zoom + width*3/4 < x or @curcontext.view_x*@zoom > x
|
|
1330
|
+
@curcontext.view_x = (x - width/5)/@zoom
|
|
1331
|
+
redraw
|
|
1332
|
+
end
|
|
1333
|
+
if not @curcontext.view_y or @curcontext.view_y*@zoom + height*3/4 < y or @curcontext.view_y*@zoom > y
|
|
1334
|
+
@curcontext.view_y = (y - height/5)/@zoom
|
|
1335
|
+
redraw
|
|
1336
|
+
end
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1339
|
+
# hint that the caret moved
|
|
1340
|
+
# redraw, change the hilighted word
|
|
1341
|
+
def update_caret
|
|
1342
|
+
return if not @caret_box or not @caret_x or not l = @caret_box[:line_text_col][@caret_y]
|
|
1343
|
+
l = l.map { |s, c| s }.join
|
|
1344
|
+
@parent_widget.focus_changed_callback[] if @parent_widget and @parent_widget.focus_changed_callback and @oldcaret_y != @caret_y
|
|
1345
|
+
update_hl_word(l, @caret_x)
|
|
1346
|
+
redraw
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
def current_address
|
|
1350
|
+
@caret_box ? @caret_box[:line_address][@caret_y] : @curcontext.root_addrs.first
|
|
1351
|
+
end
|
|
1352
|
+
end
|
|
1353
|
+
end
|
|
1354
|
+
end
|