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