metasm 1.0.0

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