metasm 1.0.1 → 1.0.2

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