metasm 1.0.1 → 1.0.2

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