class Metasm::Gui::GraphViewWidget

Attributes

caret_box[RW]
curcontext[RW]
dasm[RW]
margin[RW]
show_addresses[RW]

bool, specifies if we should display addresses before instrs

zoom[RW]

Public Instance Methods

build_ctx(ctx) click to toggle source

create the graph objects in ctx

# File metasm/gui/dasm_graph.rb, line 1260
def build_ctx(ctx)
        # graph : block -> following blocks in same function
        block_rel = {}

        todo = ctx.root_addrs.dup
        done = [:default, Expression::Unknown]
        while a = todo.shift
                a = @dasm.normalize a
                next if done.include? a
                done << a
                next if not di = @dasm.di_at(a)
                if not di.block_head?
                        block_rel[di.block.address] = [a]
                        @dasm.split_block(a)
                end
                block_rel[a] = []
                di.block.each_to_samefunc(@dasm) { |t|
                        t = @dasm.normalize t
                        next if not @dasm.di_at(t)
                        todo << t
                        block_rel[a] << t
                }
                block_rel[a].uniq!
        end

        # populate boxes
        addr2box = {}
        todo = ctx.root_addrs.dup
        todo.delete_if { |t| not @dasm.di_at(t) }     # undefined func start
        done = []
        while a = todo.shift
                next if done.include? a
                done << a
                if not ctx.keep_split.to_a.include?(a) and from = block_rel.keys.find_all { |ba| block_rel[ba].include? a } and
                                from.length == 1 and block_rel[from.first].length == 1 and
                                addr2box[from.first] and lst = @dasm.decoded[from.first].block.list.last and
                                lst.next_addr == a and (not lst.opcode.props[:saveip] or lst.block.to_subfuncret)
                        box = addr2box[from.first]
                else
                        box = ctx.new_box a, :addresses => [], :line_text_col => [], :line_address => []
                end
                @dasm.decoded[a].block.list.each { |di_|
                        box[:addresses] << di_.address
                        addr2box[di_.address] = box
                }
                todo.concat block_rel[a]
        end

        # link boxes
        ctx.box.each { |b|
                next if not di = @dasm.decoded[b[:addresses].last]
                a = di.block.address
                next if not block_rel[a]
                block_rel[a].each { |t|
                        ctx.link_boxes(b.id, t)
                        b.direct_to = t if t == di.next_addr
                }
        }

        # calc box dimensions/text
        ctx.box.each { |b|
                colstr = []
                curaddr = nil
                line = 0
                render = lambda { |str, col| colstr << [str, col] }
                nl = lambda {
                        b[:line_address][line] = curaddr
                        b[:line_text_col][line] = colstr
                        colstr = []
                        line += 1
                }
                b[:addresses].each { |addr|
                        curaddr = addr
                        if di = @dasm.di_at(curaddr)
                                if di.block_head?
                                        # render dump_block_header, add a few colors
                                        b_header = '' ; @dasm.dump_block_header(di.block) { |l| b_header << l ; b_header << ?\n if b_header[-1] != ?\n }
                                        b_header.strip.each_line { |l| l.chomp!
                                                col = :comment
                                                col = :label if l[0, 2] != '//' and l[-1] == ?:
                                                render[l, col]
                                                nl[]
                                        }
                                end
                                render["#{Expression[curaddr]}   ", :address] if @show_addresses
                                render[di.instruction.to_s.ljust(di.comment ? 18 : 0), :instruction]
                                render[' ; ' + di.comment.join(' ')[0, 64], :comment] if di.comment
                                nl[]
                        else
                                # TODO real data display (dwords, xrefs, strings..)
                                if label = @dasm.get_label_at(curaddr)
                                        render[label + ' ', :label]
                                end
                                s = @dasm.get_section_at(curaddr)
                                render['db '+((s and s[0].data.length > s[0].ptr) ? Expression[s[0].read(1)[0]].to_s : '?'), :text]
                                nl[]
                        end
                }
                b.w = b[:line_text_col].map { |strc| strc.map { |s, c| s }.join.length }.max.to_i * @font_width + 2
                b.w += 1 if b.w % 2 == 0     # ensure boxes have odd width -> vertical arrows are straight
                b.h = line * @font_height
        }
end
click(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 920
def click(x, y)
        @mousemove_origin = [x, y]
        if b = find_box_xy(x, y)
                @selected_boxes = [b] if not @selected_boxes.include? b
                @caret_box = b
                @caret_x = (view_x+x/@zoom-b.x-1).to_i / @font_width
                @caret_y = (view_y+y/@zoom-b.y-1).to_i / @font_height
                update_caret
        else
                @selected_boxes = []
                @caret_box = nil
        end
        redraw
end
click_ctrl(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 907
def click_ctrl(x, y)
        if b = find_box_xy(x, y)
                if @selected_boxes.include? b
                        @selected_boxes.delete b
                else
                        @selected_boxes << b
                end
                redraw
        else
                @mousemove_origin_ctrl = [x, y]
        end
end
current_address() click to toggle source
# File metasm/gui/dasm_graph.rb, line 1749
def current_address
        @caret_box ? @caret_box[:line_address][@caret_y] : @curcontext.root_addrs.first
end
dasm_find_roots(addr) click to toggle source

find a suitable array of graph roots, walking up from a block (function start/entrypoint)

# File metasm/gui/dasm_graph.rb, line 1625
def dasm_find_roots(addr)
        todo = [addr]
        done = []
        roots = []
        default_root = nil
        while a = todo.shift
                next if not di = @dasm.di_at(a)
                b = di.block
                a = b.address
                if done.include? a
                        default_root ||= a
                        next
                end
                done << a
                newf = []
                b.each_from_samefunc(@dasm) { |f| newf << f }
                if newf.empty?
                        roots << b.address
                else
                        todo.concat newf
                end
        end
        roots << default_root if roots.empty? and default_root

        roots
end
doubleclick(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 965
def doubleclick(x, y)
        @mousemove_origin = nil
        if b = find_box_xy(x, y)
                if @hl_word and @zoom >= 0.90 and @zoom <= 1.1
                        @parent_widget.focus_addr(@hl_word)
                else
                        @parent_widget.focus_addr((b[:addresses] || b[:line_address]).first)
                end
        elsif doubleclick_check_arrow(x, y)
        elsif @zoom == 1.0
                zoom_all
        else
                @curcontext.view_x += (x/@zoom - x)
                @curcontext.view_y += (y/@zoom - y)
                @zoom = 1.0
        end
        redraw
end
doubleclick_check_arrow(x, y) click to toggle source

check if the user clicked on the beginning/end of an arrow, if so focus on the other end

# File metasm/gui/dasm_graph.rb, line 985
def doubleclick_check_arrow(x, y)
        return if @margin*@zoom < 2
        x = view_x+x/@zoom
        y = view_y+y/@zoom
        sx = nil
        if bt = @shown_boxes.to_a.reverse.find { |b|
                y >= b.y+b.h-1 and y <= b.y+b.h-1+@margin+2 and
                sx = b.x+b.w/2 - b.to.length/2 * @margin/2 and
                x >= sx-@margin/2 and x <= sx+b.to.length*@margin/2  # should be margin/4, but add a little comfort margin
        }
                idx = (x-sx+@margin/4).to_i / (@margin/2)
                idx = 0 if idx < 0
                idx = bt.to.length-1 if idx >= bt.to.length
                if bt.to[idx]
                        if @parent_widget
                                @caret_box, @caret_y = bt, bt[:line_address].length-1
                                @parent_widget.focus_addr bt.to[idx][:line_address][0]
                        else
                                focus_xy(bt.to[idx].x, bt.to[idx].y)
                        end
                end
                true
        elsif bf = @shown_boxes.to_a.reverse.find { |b|
                y >= b.y-@margin-2 and y <= b.y and
                sx = b.x+b.w/2 - b.from.length/2 * @margin/2 and
                x >= sx-@margin/2 and x <= sx+b.from.length*@margin/2
        }
                idx = (x-sx+@margin/4).to_i / (@margin/2)
                idx = 0 if idx < 0
                idx = bf.from.length-1 if idx >= bf.from.length
                if bf.from[idx]
                        if @parent_widget
                                @caret_box, @caret_y = bf, bf[:line_address].length-1
                                @parent_widget.focus_addr bf.from[idx][:line_address][-1]
                        else
                                focus_xy(bt.from[idx].x, bt.from[idx].y)
                        end
                end
                true
        end
end
find_box_xy(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 838
def find_box_xy(x, y)
        x = view_x+x/@zoom
        y = view_y+y/@zoom
        @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 }
end
focus_addr(addr, can_update_context=true) click to toggle source

focus on addr addr may be a dasm label, dasm address, dasm address in string form (eg “0DEADBEEFh”) addr must point to a decodedinstruction if the addr is not found in curcontext, the code flow is walked up until a function start or an entrypoint is found, then the graph is created from there will call gui_update then

# File metasm/gui/dasm_graph.rb, line 1669
def focus_addr(addr, can_update_context=true)
        return if @parent_widget and not addr = @parent_widget.normalize(addr)
        return if not @dasm.di_at(addr)

        # move window / change curcontext
        if b = @curcontext.box.find { |b_| b_[:line_address].index(addr) }
                @caret_box, @caret_x, @caret_y = b, 0, b[:line_address].rindex(addr)
                @curcontext.view_x += (width/2 / @zoom - width/2)
                @curcontext.view_y += (height/2 / @zoom - height/2)
                @zoom = 1.0

                update_caret
        elsif can_update_context
                @curcontext = Graph.new 'testic'
                @curcontext.root_addrs = dasm_find_roots(addr)
                @want_focus_addr = addr
                gui_update
        else
                return
        end
        true
end
focus_xy(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1692
def focus_xy(x, y)
        # dont move during a click
        return if @mousemove_origin

        # ensure the caret stays onscreen
        if not view_x
                @curcontext.view_x = x - width/5/@zoom
                redraw
        elsif @caret_box and @caret_box.w < width*27/30/@zoom
                # keep @caret_box full if possible
                if view_x + width/20/@zoom > @caret_box.x
                        @curcontext.view_x = @caret_box.x-width/20/@zoom
                elsif view_x + width*9/10/@zoom < @caret_box.x+@caret_box.w
                        @curcontext.view_x = @caret_box.x+@caret_box.w-width*9/10/@zoom
                end
        elsif view_x + width/20/@zoom > x
                @curcontext.view_x = x-width/20/@zoom
                redraw
        elsif view_x + width*9/10/@zoom < x
                @curcontext.view_x = x-width*9/10/@zoom
                redraw
        end

        if not view_y
                @curcontext.view_y = y - height/5/@zoom
                redraw
        elsif @caret_box and @caret_box.h < height*27/30/@zoom
                if view_y + height/20/@zoom > @caret_box.y
                        @curcontext.view_y = @caret_box.y-height/20/@zoom
                elsif view_y + height*9/10/@zoom < @caret_box.y+@caret_box.h
                        @curcontext.view_y = @caret_box.y+@caret_box.h-height*9/10/@zoom
                end
        elsif view_y + height/20/@zoom > y
                @curcontext.view_y = y-height/20/@zoom
                redraw
        elsif view_y + height*9/10/@zoom < y
                @curcontext.view_y = y-height*9/10/@zoom
                redraw
        end
end
get_cursor_pos() click to toggle source
# File metasm/gui/dasm_graph.rb, line 1659
def get_cursor_pos
        [current_address, @caret_x]
end
gui_update() click to toggle source
# File metasm/gui/dasm_graph.rb, line 1196
def gui_update
        @want_update_graph = true
        redraw
end
hide_non_ascendants(list) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1603
def hide_non_ascendants(list)
        reach = {}
        todo = list.dup
        while b = todo.pop
                next if reach[b]
                reach[b] = true
                b.from.each { |bb|
                        todo << bb if bb.y <= b.h+b.y
                }
        end

        @curcontext.box.delete_if { |bb|
                !reach[bb]
        }
        @curcontext.box.each { |bb|
                bb.from.delete_if { |bbb| !reach[bbb] }
                bb.to.delete_if { |bbb| !reach[bbb] }
        }
        redraw
end
hide_non_descendants(list) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1582
def hide_non_descendants(list)
        reach = {}
        todo = list.dup
        while b = todo.pop
                next if reach[b]
                reach[b] = true
                b.to.each { |bb|
                        todo << bb if bb.y+bb.h >= b.y
                }
        end

        @curcontext.box.delete_if { |bb|
                !reach[bb]
        }
        @curcontext.box.each { |bb|
                bb.from.delete_if { |bbb| !reach[bbb] }
                bb.to.delete_if { |bbb| !reach[bbb] }
        }
        redraw
end
initialize_widget(dasm, parent_widget) click to toggle source
# File metasm/gui/dasm_graph.rb, line 809
def initialize_widget(dasm, parent_widget)
        @dasm = dasm
        @parent_widget = parent_widget

        @show_addresses = false

        @caret_box = nil
        @selected_boxes = []
        @shown_boxes = []
        @mousemove_origin = @mousemove_origin_ctrl = nil
        @curcontext = Graph.new(nil)
        @want_focus_addr = nil
        @margin = 8
        @zoom = 1.0
        @default_color_association = ColorTheme.merge :hlbox_bg => :palegrey, :box_bg => :white,
                        :arrow_hl => :red, :arrow_cond => :darkgreen, :arrow_uncond => :darkblue,
                        :arrow_direct => :darkred, :box_bg_shadow => :black, :background => :paleblue
        # @othergraphs = ?    (to keep user-specified formatting)
end
keypress(key) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1385
def keypress(key)
        case key
        when :left
                if @caret_box
                        if @caret_x > 0
                                @caret_x -= 1
                                update_caret
                        elsif b = @curcontext.box.sort_by { |b_| -b_.x }.find { |b_| b_.x < @caret_box.x and
                                        b_.y < @caret_box.y+@caret_y*@font_height and
                                        b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
                                @caret_x = (b.w/@font_width).to_i
                                @caret_y += ((@caret_box.y-b.y)/@font_height).to_i
                                @caret_box = b
                                update_caret
                                redraw
                        else
                                @curcontext.view_x -= 20/@zoom
                                redraw
                        end
                else
                        @curcontext.view_x -= 20/@zoom
                        redraw
                end
        when :up
                if @caret_box
                        if @caret_y > 0
                                @caret_y -= 1
                                update_caret
                        elsif b = @curcontext.box.sort_by { |b_| -b_.y }.find { |b_| b_.y < @caret_box.y and
                                        b_.x < @caret_box.x+@caret_x*@font_width and
                                        b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
                                @caret_x += ((@caret_box.x-b.x)/@font_width).to_i
                                @caret_y = b[:line_address].length-1
                                @caret_box = b
                                update_caret
                                redraw
                        else
                                @curcontext.view_y -= 20/@zoom
                                redraw
                        end
                else
                        @curcontext.view_y -= 20/@zoom
                        redraw
                end
        when :right
                if @caret_box
                        if @caret_x <= @caret_box[:line_text_col].map { |s| s.map { |ss, cc| ss }.join.length }.max
                                @caret_x += 1
                                update_caret
                        elsif b = @curcontext.box.sort_by { |b_| b_.x }.find { |b_| b_.x > @caret_box.x and
                                        b_.y < @caret_box.y+@caret_y*@font_height and
                                        b_.y+b_.h > @caret_box.y+(@caret_y+1)*@font_height }
                                @caret_x = 0
                                @caret_y += ((@caret_box.y-b.y)/@font_height).to_i
                                @caret_box = b
                                update_caret
                                redraw
                        else
                                @curcontext.view_x += 20/@zoom
                                redraw
                        end
                else
                        @curcontext.view_x += 20/@zoom
                        redraw
                end
        when :down
                if @caret_box
                        if @caret_y < @caret_box[:line_address].length-1
                                @caret_y += 1
                                update_caret
                        elsif b = @curcontext.box.sort_by { |b_| b_.y }.find { |b_| b_.y > @caret_box.y and
                                        b_.x < @caret_box.x+@caret_x*@font_width and
                                        b_.x+b_.w > @caret_box.x+(@caret_x+1)*@font_width }
                                @caret_x += ((@caret_box.x-b.x)/@font_width).to_i
                                @caret_y = 0
                                @caret_box = b
                                update_caret
                                redraw
                        else
                                @curcontext.view_y += 20/@zoom
                                redraw
                        end
                else
                        @curcontext.view_y += 20/@zoom
                        redraw
                end
        when :pgup
                if @caret_box
                        @caret_y -= (height/4/@zoom/@font_height).to_i
                        @caret_y = 0 if @caret_y < 0
                        update_caret(false)
                else
                        @curcontext.view_y -= height/4/@zoom
                        redraw
                end
        when :pgdown
                if @caret_box
                        @caret_y += (height/4/@zoom/@font_height).to_i
                        @caret_y = [@caret_box[:line_address].length-1, @caret_y].min
                        update_caret(false)
                else
                        @curcontext.view_y += height/4/@zoom
                        redraw
                end
        when :home
                if @caret_box
                        @caret_x = 0
                        update_caret(false)
                else
                        @curcontext.view_x = @curcontext.box.map { |b_| b_.x }.min-10
                        @curcontext.view_y = @curcontext.box.map { |b_| b_.y }.min-10
                        redraw
                end
        when :end
                if @caret_box
                        @caret_x = @caret_box[:line_text_col][@caret_y].to_a.map { |ss, cc| ss }.join.length
                        update_caret(false)
                else
                        @curcontext.view_x = [@curcontext.box.map { |b_| b_.x+b_.w }.max-width/@zoom+10, @curcontext.box.map { |b_| b_.x }.min-10].max
                        @curcontext.view_y = [@curcontext.box.map { |b_| b_.y+b_.h }.max-height/@zoom+10, @curcontext.box.map { |b_| b_.y }.min-10].max
                        redraw
                end

        when :delete
                @selected_boxes.each { |b_|
                        @curcontext.box.delete b_
                        b_.from.each { |bb| bb.to.delete b_ }
                        b_.to.each { |bb| bb.from.delete b_ }
                }
                redraw
        when :popupmenu
                if @caret_box
                        cx = (@caret_box.x - view_x + 1 + @caret_x*@font_width)*@zoom
                        cy = (@caret_box.y - view_y + 1 + @caret_y*@font_height)*@zoom
                        rightclick(cx, cy)
                end

        when ?a
                t0 = Time.now
                puts 'autoarrange'
                @curcontext.auto_arrange_boxes
                redraw
                puts 'autoarrange done %.02f' % (Time.now - t0)
        when ?u
                gui_update

        when ?R
                load __FILE__
        when ?I       # create arbitrary boxes/links
                if @selected_boxes.empty?
                        @fakebox ||= 0
                        b = @curcontext.new_box "id_#@fakebox",
                                :addresses => [], :line_address => [],
                                :line_text_col => [[["  blublu #@fakebox", :text]]]
                        b.w = @font_width * 15
                        b.h = @font_height * 2
                        b.x = rand(200) - 100
                        b.y = rand(200) - 100

                        @fakebox += 1
                else
                        b1, *bl = @selected_boxes
                        bl = [b1] if bl.empty?      # loop
                        bl.each { |b2|
                                if b1.to.include? b2
                                        b1.to.delete b2
                                        b2.from.delete b1
                                else
                                        b1.to << b2
                                        b2.from << b1
                                end
                        }
                end
                redraw

        when ?1       # (numeric) zoom to 1:1
                if @zoom == 1.0
                        zoom_all
                else
                        @curcontext.view_x += (width/2 / @zoom - width/2)
                        @curcontext.view_y += (height/2 / @zoom - height/2)
                        @zoom = 1.0
                end
                redraw
        when :insert          # split curbox at @caret_y
                if @caret_box and a = @caret_box[:line_address][@caret_y] and @dasm.decoded[a]
                        @dasm.split_block(a)
                        @curcontext.keep_split ||= []
                        @curcontext.keep_split |= [a]
                        gui_update
                        focus_addr a
                end
        else return false
        end
        true
end
keypress_ctrl(key) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1364
def keypress_ctrl(key)
        case key
        when ?F
                @parent_widget.inputbox('text to search in curview (regex)', :text => @hl_word) { |pat|
                        re = /#{pat}/i
                        list = [['addr', 'instr']]
                        @curcontext.box.each { |b|
                                b[:line_text_col].zip(b[:line_address]) { |l, a|
                                        str = l.map { |s, c| s }.join
                                        list << [Expression[a], str] if str =~ re
                                }
                        }
                        @parent_widget.list_bghilight("search result for /#{pat}/i", list) { |i| @parent_widget.focus_addr i[0] }
                }
        when ?+; mouse_wheel_ctrl(:up, width/2, height/2)
        when ?-; mouse_wheel_ctrl(:down, width/2, height/2)
        else return false
        end
        true
end
load_dot(dota) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1233
def load_dot(dota)
        @want_update_graph = false
        @curcontext.clear
        boxes = {}
        new_box = lambda { |text|
                b = @curcontext.new_box(text, :line_text_col => [[[text, :text]]])
                b.w = (text.length+1) * @font_width
                b.h = @font_height
                b
        }
        dota.scan(/^.*$/) { |l|
                a = l.strip.chomp(';').split(/->/).map { |s| s.strip.delete '"' }
                next if not id = a.shift
                b0 = boxes[id] ||= new_box[id]
                while id = a.shift
                        b1 = boxes[id] ||= new_box[id]
                        b0.to   |= [b1]
                        b1.from |= [b0]
                        b0 = b1
                end
        }
        redraw
rescue Interrupt
        puts "dot_len #{boxes.length}"
end
load_dotfile(path) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1229
def load_dotfile(path)
        load_dot(File.read(path))
end
mouse_wheel(dir, x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 868
def mouse_wheel(dir, x, y)
        case dir
        when :up; @curcontext.view_y -= height/4 / @zoom
        when :down; @curcontext.view_y += height/4 / @zoom
        end
        redraw
end
mouse_wheel_ctrl(dir, x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 844
def mouse_wheel_ctrl(dir, x, y)
        case dir
        when :up
                if @zoom < 100
                        # zoom in
                        oldzoom = @zoom
                        @zoom *= 1.1
                        @zoom = 1.0 if (@zoom-1.0).abs < 0.05
                        @curcontext.view_x += (x / oldzoom - x / @zoom)
                        @curcontext.view_y += (y / oldzoom - y / @zoom)
                end
        when :down
                if @zoom > 1.0/1000
                        # zoom out
                        oldzoom = @zoom
                        @zoom /= 1.1
                        @zoom = 1.0 if (@zoom-1.0).abs < 0.05
                        @curcontext.view_x += (x / oldzoom - x / @zoom)
                        @curcontext.view_y += (y / oldzoom - y / @zoom)
                end
        end
        redraw
end
mousemove(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 876
def mousemove(x, y)
        return if not @mousemove_origin

        dx = (x - @mousemove_origin[0])/@zoom
        dy = (y - @mousemove_origin[1])/@zoom
        @mousemove_origin = [x, y]
        if @selected_boxes.empty?
                @curcontext.view_x -= dx ; @curcontext.view_y -= dy
        else
                @selected_boxes.each { |b| b.x += dx ; b.y += dy }
        end
        redraw
end
mouserelease(x, y) click to toggle source
# File metasm/gui/dasm_graph.rb, line 890
def mouserelease(x, y)
        mousemove(x, y)
        @mousemove_origin = nil

        if @mousemove_origin_ctrl
                x1 = view_x + @mousemove_origin_ctrl[0]/@zoom
                x2 = x1 + (x - @mousemove_origin_ctrl[0])/@zoom
                x1, x2 = x2, x1 if x1 > x2
                y1 = view_y + @mousemove_origin_ctrl[1]/@zoom
                y2 = y1 + (y - @mousemove_origin_ctrl[1])/@zoom
                y1, y2 = y2, y1 if y1 > y2
                @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 }
                redraw
                @mousemove_origin_ctrl = nil
        end
end
paint() click to toggle source
# File metasm/gui/dasm_graph.rb, line 1042
def paint
        update_graph if @want_update_graph
        if @want_focus_addr and @curcontext.box.find { |b_| b_[:line_address].index(@want_focus_addr) }
                focus_addr(@want_focus_addr, false)
                @want_focus_addr = nil
                #zoom_all
        end

        @curcontext.box.each { |b|
                # reorder arrows so that endings do not overlap
                b.to = b.to.sort_by { |bt| bt.x+bt.w/2 }
                b.from = b.from.sort_by { |bt| bt.x+bt.w/2 }
        }
        # arrows drawn first to stay under the boxes
        # XXX precalc ?
        @curcontext.box.each { |b|
                b.to.each { |bt| paint_arrow(b, bt) }
        }

        @shown_boxes = []
        w_w = width
        w_h = height
        @curcontext.box.each { |b|
                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
                @shown_boxes << b
                paint_box(b)
        }
end
paint_arrow(b1, b2) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1083
def paint_arrow(b1, b2)
        x1 = x1o = b1.x+b1.w/2-view_x
        y1 = b1.y+b1.h-view_y
        x2 = x2o = b2.x+b2.w/2-view_x
        y2 = b2.y-1-view_y
        margin = @margin
        x1 += (-(b1.to.length-1)/2 + b1.to.index(b2)) * margin/2
        x2 += (-(b2.from.length-1)/2 + b2.from.index(b1)) * margin/2
        return if (y1+margin < 0 and y2 < 0) or (y1 > height/@zoom and y2-margin > height/@zoom)      # just clip on y
        margin, x1, y1, x2, y2, b1w, b2w, x1o, x2o = [margin, x1, y1, x2, y2, b1.w, b2.w, x1o, x2o].map { |v| v*@zoom }


        # straighten vertical arrows if possible
        if y2 > y1 and (x1-x2).abs <= margin
                if b1.to.length == 1
                        x1 = x2
                elsif b2.from.length == 1
                        x2 = x1
                end
        end

        set_color_arrow(b1, b2)
        if margin > 1
                # draw arrow tip
                draw_line(x1, y1, x1, y1+margin)
                draw_line(x2, y2-margin+1, x2, y2)
                draw_line(x2-margin/2, y2-margin/2, x2, y2)
                draw_line(x2+margin/2, y2-margin/2, x2, y2)
                y1 += margin
                y2 -= margin-1
        end

        if y2 > y1 - b1.h*@zoom - 2*margin+1
                # straight arrow
                draw_line(x1, y1, x2, y2) if x1 != y1 or x2 != y2

        else
                # arrow goes up: navigate around b2
                x = (x1 <= x2 ? [x1o-b1w/2-margin, x2o-b2w/2-margin].min : [x1o+b1w/2+margin, x2o+b2w/2+margin].max)
                draw_line(x1, y1, x, y1)
                draw_line(x, y1, x, y2)
                draw_line(x, y2, x2, y2)
                draw_line(x1, y1+1, x, y1+1) # double
                draw_line(x+1, y1, x+1, y2)
                draw_line(x, y2+1, x2, y2+1)
        end
end
paint_box(b) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1143
def paint_box(b)
        set_color_boxshadow(b)
        draw_rectangle((b.x-view_x+3)*@zoom, (b.y-view_y+4)*@zoom, b.w*@zoom, b.h*@zoom)
        set_color_box(b)
        draw_rectangle((b.x-view_x)*@zoom, (b.y-view_y+1)*@zoom, b.w*@zoom, b.h*@zoom)

        # current text position
        x = (b.x - view_x + 1)*@zoom
        y = (b.y - view_y + 1)*@zoom
        w_w = (b.x - view_x + b.w - @font_width)*@zoom
        w_h = (b.y - view_y + b.h - @font_height)*@zoom
        w_h = height if w_h > height

        if @parent_widget and @parent_widget.bg_color_callback
                ly = 0
                b[:line_address].each { |a|
                        if c = @parent_widget.bg_color_callback[a]
                                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)
                        end
                        ly += 1
                }
        end

        if @caret_box == b
                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)
        end

        return if @zoom < 0.99 or @zoom > 1.1
        # TODO dynamic font size ?

        # renders a string at current cursor position with a color
        # must not include newline
        render = lambda { |str, color|
                next if y >= w_h+2 or x >= w_w
                draw_string_hl(color, x, y, str)
                x += str.length * @font_width
        }

        yoff = @font_height * @zoom
        b[:line_text_col].each { |list|
                list.each { |s, c| render[s, c] } if y >= -yoff
                x = (b.x - view_x + 1)*@zoom
                y += yoff
                break if y > w_h+2
        }

        if b == @caret_box and focus?
                cx = (b.x - view_x + 1 + @caret_x*@font_width)*@zoom
                cy = (b.y - view_y + 1 + @caret_y*@font_height)*@zoom
                draw_line_color(:caret, cx, cy, cx, cy+(@font_height-1)*@zoom)
        end
end
resized(w, h) click to toggle source
# File metasm/gui/dasm_graph.rb, line 834
def resized(w, h)
        redraw
end
rightclick(x, y) click to toggle source

if the target is a call to a subfunction, open a new window with the graph of this function (popup)

# File metasm/gui/dasm_graph.rb, line 952
def rightclick(x, y)
        if b = find_box_xy(x, y) and @zoom >= 0.90 and @zoom <= 1.1
                click(x, y)
                @mousemove_origin = nil
                m = new_menu
                setup_contextmenu(b, m)
                if @parent_widget.respond_to?(:extend_contextmenu)
                        @parent_widget.extend_contextmenu(self, m, @caret_box[:line_address][@caret_y])
                end
                popupmenu(m, x, y)
        end
end
set_color_arrow(b1, b2) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1071
def set_color_arrow(b1, b2)
        if b1 == @caret_box or b2 == @caret_box
                draw_color :arrow_hl
        elsif b1.to.length == 1
                draw_color :arrow_uncond
        elsif b1.direct_to == b2.id
                draw_color :arrow_direct
        else
                draw_color :arrow_cond
        end
end
set_color_box(b) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1135
def set_color_box(b)
        if @selected_boxes.include? b
                draw_color :hlbox_bg
        else
                draw_color :box_bg
        end
end
set_color_boxshadow(b) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1131
def set_color_boxshadow(b)
        draw_color :box_bg_shadow
end
set_cursor_pos(p) click to toggle source
# File metasm/gui/dasm_graph.rb, line 1652
def set_cursor_pos(p)
        addr, x = p
        focus_addr(addr)
        @caret_x = x
        update_caret
end
setup_contextmenu(b, m) click to toggle source
# File metasm/gui/dasm_graph.rb, line 935
def setup_contextmenu(b, m)
        cm = new_menu
        addsubmenu(cm, 'copy _word') { clipboard_copy(@hl_word) if @hl_word }
        addsubmenu(cm, 'copy _line') { clipboard_copy(@caret_box[:line_text_col][@caret_y].map { |ss, cc| ss }.join) }
        addsubmenu(cm, 'copy _box')  {
                sb = @selected_boxes
                sb = [@curbox] if sb.empty?
                clipboard_copy(sb.map { |ob| ob[:line_text_col].map { |s| s.map { |ss, cc| ss }.join + "\r\n" }.join }.join("\r\n"))
        }     # XXX auto \r\n vs \n
        addsubmenu(m, '_clipboard', cm)
        addsubmenu(m, 'clone _window') { @parent_widget.clone_window(@hl_word, :graph) }
        addsubmenu(m, 'show descendants only') { hide_non_descendants(@selected_boxes) }
        addsubmenu(m, 'show ascendants only') { hide_non_ascendants(@selected_boxes) }
        addsubmenu(m, 'restore graph') { gui_update }
end
update_caret(update_hlword = true) click to toggle source

hint that the caret moved redraw, change the hilighted word

# File metasm/gui/dasm_graph.rb, line 1735
def update_caret(update_hlword = true)
        return if not b = @caret_box or not @caret_x or not l = @caret_box[:line_text_col][@caret_y]

        if update_hlword
                l = l.map { |s, c| s }.join
                @parent_widget.focus_changed_callback[] if @parent_widget and @parent_widget.focus_changed_callback and @oldcaret_y != @caret_y
                update_hl_word(l, @caret_x)
        end

        focus_xy(b.x + @caret_x*@font_width, b.y + @caret_y*@font_height)

        redraw
end
update_graph() click to toggle source

rebuild the code flow graph from @curcontext.roots recalc the boxes w/h

# File metasm/gui/dasm_graph.rb, line 1205
def update_graph
        @want_update_graph = false

        ctx = @curcontext

        boxcnt = ctx.box.length
        arrcnt = ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
        ctx.clear

        build_ctx(ctx)

        ctx.auto_arrange_boxes

        return if ctx != @curcontext

        if boxcnt != ctx.box.length or arrcnt != ctx.box.inject(0) { |s, b| s + b.to.length + b.from.length }
                zoom_all
        elsif @caret_box      # update @caret_box with a box at the same place
                bx = @caret_box.x + @caret_box.w/2
                by = @caret_box.y + @caret_box.h/2
                @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 }
        end
end
view_x() click to toggle source
# File metasm/gui/dasm_graph.rb, line 829
def view_x; @curcontext.view_x; end
view_x=(vx) click to toggle source
# File metasm/gui/dasm_graph.rb, line 830
def view_x=(vx); @curcontext.view_x = vx; end
view_y() click to toggle source
# File metasm/gui/dasm_graph.rb, line 831
def view_y; @curcontext.view_y; end
view_y=(vy) click to toggle source
# File metasm/gui/dasm_graph.rb, line 832
def view_y=(vy); @curcontext.view_y = vy; end
zoom_all() click to toggle source

update the zoom & view_xy to show the whole graph in the window

# File metasm/gui/dasm_graph.rb, line 1028
def zoom_all
        minx, miny, maxx, maxy = @curcontext.boundingbox
        minx -= @margin
        miny -= @margin
        maxx += @margin
        maxy += @margin

        @zoom = [width.to_f/(maxx-minx), height.to_f/(maxy-miny)].min
        @zoom = 1.0 if @zoom > 1.0 or (@zoom-1.0).abs < 0.1
        @curcontext.view_x = minx + (maxx-minx-width/@zoom)/2
        @curcontext.view_y = miny + (maxy-miny-height/@zoom)/2
        redraw
end