class Metasm::Gui::GraphHeapWidget

Attributes

addr_struct[RW]
heap[RW]
snapped[RW]

Public Instance Methods

build_ctx(ctx) click to toggle source

create the graph objects in ctx

# File samples/dbg-plugins/heapscan/graphheap.rb, line 282
def build_ctx(ctx)
        # create boxes
        todo = ctx.root_addrs.dup & @addr_struct.keys
        todo << @addr_struct.keys.first if todo.empty?
        done = []
        while a = todo.shift
                next if done.include? a
                done << a
                ctx.new_box a, :line_text_col => [], :line_address => [], :line_struct => [], :line_member => []
                todo.concat @heap.xrchunksto[a].to_a & @addr_struct.keys
        end

        # link boxes
        if (@heap.xrchunksto[ctx.box.first.id].to_a & @addr_struct.keys).length == ctx.box.length - 1
                ot = ctx.box[0].id
                ctx.box[1..-1].each { |b_|
                        ctx.link_boxes(ot, b_.id)
                }
        else
                ctx.box.each { |b|
                        @heap.xrchunksto[b.id].to_a.each { |t|
                                ctx.link_boxes(b.id, t) if @addr_struct[t]
                        }
                }
        end

        if snapped
                @datadiff = {}
        end

        # calc box dimensions/text
        ctx.box.each { |b|
                colstr = []
                curaddr = b.id
                curst = @addr_struct[b.id]
                curmb = nil
                margin = ''
                start_addr = curaddr
                if snapped
                        ghosts = snapped[curaddr]
                end
                line = 0
                render = lambda { |str, col| colstr << [str, col] }
                nl = lambda {
                        b[:line_address][line] = curaddr
                        b[:line_text_col][line] = colstr
                        b[:line_struct][line] = curst
                        b[:line_member][line] = curmb
                        colstr = []
                        line += 1
                }
                render_val = lambda { |v|
                        if v.kind_of?(::Integer)
                                if v > 0x100
                                        render['0x%X' % v, :text]
                                elsif v < -0x100
                                        render['-0x%X' % -v, :text]
                                else
                                        render[v.to_s, :text]
                                end
                        elsif not v
                                render['NULL', :text]
                        else
                                render[v.to_s, :text]
                        end
                }
                render_st = nil
                render_st_ar = lambda { |ast, m|
                        elemt = m.type.untypedef.type.untypedef
                        if elemt.kind_of?(C::BaseType) and elemt.name == :char
                                render[margin, :text]
                                render["#{m.type.type.to_s[1...-1]} #{m.name}[#{m.type.length}] = #{ast[m].to_array.pack('C*').sub(/\0.*$/m, '').inspect}", :text]
                                nl[]
                                curaddr += ast.cp.sizeof(m)
                        else
                                t = m.type.type.to_s[1...-1]
                                tsz = ast.cp.sizeof(m.type.type)
                                fust = curst
                                fumb = curmb
                                curst = ast[m]
                                ast[m].to_array.each_with_index { |v, i|
                                        curmb = i
                                        render[margin, :text]
                                        if elemt.kind_of?(C::Union)
                                                if m.type.untypedef.type.kind_of?(C::Union)
                                                        render[elemt.kind_of?(C::Struct) ? 'struct ' : 'union ', :text]
                                                        render["#{elemt.name} ", :text] if elemt.name
                                                else # typedef
                                                        render["#{elemt.to_s[1...-1]} ", :text]
                                                end
                                                render_st[v]
                                                render[" #{m.name}[#{i}]", :text]
                                        else
                                                render["#{t} #{m.name}[#{i}] = ", :text]
                                                render_val[v]
                                                @datadiff[curaddr] = true if ghosts and ghosts.all? { |g| g[curaddr-start_addr, tsz] == ghosts[0][curaddr-start_addr, tsz] } and ghosts[0][curaddr-start_addr, tsz] != ast.str[curaddr, tsz].to_str
                                        end
                                        render[';', :text]
                                        nl[]
                                        curaddr += tsz
                                }
                                curst = fust
                                curmb = fumb
                        end
                }
                render_st = lambda { |ast|
                        st_addr = curaddr
                        oldst = curst
                        oldmb = curmb
                        oldmargin = margin
                        render['{', :text]
                        nl[]
                        margin += '    '
                        curst = ast
                        ast.struct.members.each { |m|
                                curmb = m
                                curaddr = st_addr + ast.struct.offsetof(@heap.cp, m)

                                if bo = ast.struct.bitoffsetof(@heap.cp, m)
                                        # float curaddr to make ghost hilight work on bitfields
                                        curaddr += (1+bo[0])/1000.0
                                end

                                if m.type.untypedef.kind_of?(C::Array)
                                        render_st_ar[ast, m]
                                elsif m.type.untypedef.kind_of?(C::Union)
                                        render[margin, :text]
                                        if m.type.kind_of?(C::Union)
                                                render[m.type.kind_of?(C::Struct) ? 'struct ' : 'union ', :text]
                                                render["#{m.type.name} ", :text] if m.type.name
                                        else # typedef
                                                render["#{m.type.to_s[1...-1]} ", :text]
                                        end
                                        oca = curaddr
                                        render_st[ast[m]]
                                        nca = curaddr
                                        curaddr = oca
                                        render[" #{m.name if m.name};", :text]
                                        nl[]
                                        curaddr = nca
                                else
                                        render[margin, :text]
                                        render["#{m.type.to_s[1...-1]} ", :text]
                                        render["#{m.name} = ", :text]
                                        render_val[ast[m]]
                                        tsz = ast.cp.sizeof(m)
                                        # TODO bit-level multighosting
                                        if ghosts and ghosts.all? { |g| g[curaddr.to_i-start_addr, tsz] == ghosts[0][curaddr.to_i-start_addr, tsz] } and ghosts[0][curaddr.to_i-start_addr, tsz] != ast.str[curaddr.to_i, tsz].to_str
                                                if bo
                                                        ft = C::BaseType.new((bo[0] + bo[1] > 32) ? :__int64 : :__int32)
                                                        v1 = @heap.cp.decode_c_value(ghosts[0][curaddr.to_i-start_addr, tsz], ft, 0)
                                                        v2 = @heap.cp.decode_c_value(ast.str[curaddr.to_i, tsz], ft, 0)
                                                        @datadiff[curaddr] = true if (v1 >> bo[0]) & ((1 << bo[1])-1) != (v2 >> bo[0]) & ((1 << bo[1])-1)
                                                else
                                                        @datadiff[curaddr] = true
                                                end
                                        end
                                        render[';', :text]

                                        if m.type.kind_of?(C::Pointer) and m.type.type.kind_of?(C::BaseType) and m.type.type.name == :char
                                                if s = @dasm.decode_strz(ast[m], 32)
                                                        render["    // #{s.inspect}", :comment]
                                                end
                                        end
                                        nl[]
                                        curaddr += tsz
                                        curaddr = curaddr.to_i if bo
                                end
                        }
                        margin = oldmargin
                        curst = oldst
                        curmb = oldmb
                        render[margin, :text]
                        render['}', :text]
                }
                ast = @addr_struct[curaddr]
                render["struct #{ast.struct.name} *#{'0x%X' % curaddr} = ", :text]
                render_st[ast]
                render[';', :text]
                nl[]

                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
                b.h = line * @font_height
        }
end
create_struct(addr) click to toggle source

create the struct chunk_<addr>, register it in @heap.chunk_struct

# File samples/dbg-plugins/heapscan/graphheap.rb, line 529
def create_struct(addr)
        raise "no chunk here" if not @heap.chunks[addr]

        ptsz = @dasm.cpu.size/8

        # check if this is a c++ object with RTTI info
        vptr = @dasm.decode_dword(addr)
        rtti = @dasm.decode_dword(vptr-ptsz)
        case OS.shortname
        when 'winos'
                typeinfo = @dasm.decode_dword(rtti+3*ptsz) if rtti
                if typeinfo and s = @dasm.decode_strz(typeinfo+3*ptsz)
                        rtti_name = s[/^(.*)@@$/, 1]        # remove trailing @@
                end
        when 'linos'
                typeinfo = @dasm.decode_dword(rtti+ptsz) if rtti
                if typeinfo and s = @dasm.decode_strz(typeinfo)
                        rtti_name = s[/^[0-9]+(.*)$/, 1]    # remove leading number
                end
        end

        if rtti_name and st = @heap.cp.toplevel.struct[rtti_name]
                return @heap.chunk_struct[addr] = st
        end

        st = C::Struct.new
        st.name = rtti_name || "chunk_#{'%x' % addr}"
        st.members = []
        li = 0
        (@heap.chunks[addr] / ptsz).times { |i|
                n = 'unk_%x' % (ptsz*i)
                v = @dasm.decode_dword(addr+ptsz*i)
                if i == 0 and rtti_name
                        t = C::Pointer.new(C::Pointer.new(C::BaseType.new(:void)))
                        n = 'vtable'
                elsif @heap.chunks[v]
                        t = C::Pointer.new(C::BaseType.new(:void))
                else
                        t = C::BaseType.new("__int#{ptsz*8}".to_sym, :unsigned)
                end
                st.members << C::Variable.new(n, t)
                li = i+1
        }
        (@heap.chunks[addr] % ptsz).times { |i|
                n = 'unk_%x' % (ptsz*li+i)
                t = C::BaseType.new(:char, :unsigned)
                st.members << C::Variable.new(n, t)
        }
        @heap.cp.toplevel.struct[st.name] = st
        @heap.chunk_struct[addr] = st
end
do_focus_addr(addr) click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 520
def do_focus_addr(addr)
        st = @heap.chunk_struct[addr] || create_struct(addr)

        ed = @dasm.get_edata_at(addr)
        @addr_struct = { addr => @heap.cp.decode_c_struct(st.name, ed.data, ed.ptr) }
        gui_update
end
focus_addr(addr, fu=nil) click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 494
def focus_addr(addr, fu=nil)
        return if @parent_widget and not addr = @parent_widget.normalize(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

                focus_xy(b.x, b.y + @caret_y*@font_height)
                update_caret
        elsif addr_struct and (@addr_struct[addr] or @addr_struct.find { |a, s| addr >= a and addr < a+s.sizeof })
                @curcontext = Graph.new 'testic'
                @curcontext.root_addrs = struct_find_roots(addr)
                @want_focus_addr = addr
                gui_update
        elsif @heap.chunks[addr]
                @want_focus_addr = addr
                do_focus_addr(addr)
        else
                return
        end
        true
end
get_cursor_pos() click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 606
def get_cursor_pos
        [super, addr_struct]
end
keypress(k) click to toggle source
Calls superclass method Metasm::Gui::GraphViewWidget#keypress
# File samples/dbg-plugins/heapscan/graphheap.rb, line 48
def keypress(k)
        case k
        when u
                # update display (refresh struct member values)
                @parent_widget.parent_widget.console.commands['refresh'][]
                gui_update
        when t
                # change struct field type
                if @selected_boxes.length > 1
                        # mass-retype chunks
                        st = @addr_struct[@selected_boxes[0].id].struct
                        inputbox("replacement struct for selected chunks", :text => st.name) { |n|
                                next if not nst = @heap.cp.toplevel.struct[n]
                                @selected_boxes.each { |sb|
                                        as = @addr_struct[sb.id]
                                        @heap.chunk_struct[sb.id] = nst
                                        @addr_struct[sb.id] = @heap.cp.decode_c_struct(n, as.str, as.stroff)
                                }
                                gui_update
                        }
                elsif b = @caret_box
                        if @caret_y == 0
                                as = @addr_struct[b.id]
                                st = as.struct
                                inputbox("replacement struct for #{st.name}", :text => st.name) { |n|
                                        next if not nst = @heap.cp.toplevel.struct[n]
                                        @heap.chunk_struct[b.id] = nst
                                        @addr_struct[b.id] = @heap.cp.decode_c_struct(n, as.str, as.stroff)
                                        gui_update
                                }
                        elsif m = b[:line_member][@caret_y]
                                if m.kind_of?(Integer)
                                        # XXX Array, need to find the outer struct
                                        mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1]
                                        ar = b[:line_struct][@caret_y]
                                        st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct }
                                        raise '?' if not st
                                        st = st.struct
                                        m = st.fldlist[mn]
                                else
                                        st = b[:line_struct][@caret_y].struct
                                end
                                inputbox("new type for #{m.name}", :text => m.dump_def(@heap.cp.toplevel)[0].join(' ')) { |nn|
                                        nil while @heap.cp.readtok
                                        @heap.cp.lexer.feed nn
                                        if not v = C::Variable.parse_type(@heap.cp, @heap.cp.toplevel, true)
                                                nil while @heap.cp.readtok
                                                raise 'bad type'
                                        end
                                        v.parse_declarator(@heap.cp, @heap.cp.toplevel)
                                        nt = v.type
                                        nsz = @heap.cp.sizeof(nt)
                                        osz = @heap.cp.sizeof(m)
                                        if nsz > osz and st.kind_of?(C::Struct)
                                                idx = st.members.index(m)
                                                # eat next members
                                                while nsz > osz
                                                        break if idx+1 >= st.members.length
                                                        sz = @heap.cp.sizeof(st.members.delete_at(idx+1))
                                                        osz += sz
                                                end
                                        end
                                        if nsz < osz and st.kind_of?(C::Struct)
                                                idx = st.members.index(m)
                                                pos = st.offsetof(@heap.cp, m)
                                                # fill gap with bytes
                                                idx += 1
                                                while nsz < osz
                                                        st.members[idx, 0] = [C::Variable.new(('unk_%x' % (pos+nsz)), C::BaseType.new(:__int8, :unsigned))]
                                                        idx += 1
                                                        nsz += 1
                                                end
                                        end
                                        m.type = nt
                                        st.update_member_cache(@heap.cp)
                                        gui_update
                                }

                        end
                end
        when n
                # rename struct field
                if b = @caret_box
                        if @caret_y == 0
                                st = @addr_struct[b.id].struct
                                inputbox("new name for #{st.name}", :text => st.name) { |nn|
                                        raise "struct #{nn} already exists (try 't')" if @heap.cp.toplevel.struct[nn]
                                        @heap.cp.toplevel.struct[nn] = @heap.cp.toplevel.struct.delete(st.name)
                                        st.name = nn
                                        gui_update
                                }
                        elsif m = b[:line_member][@caret_y]
                                if m.kind_of?(Integer)
                                        mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1]
                                        ar = b[:line_struct][@caret_y]
                                        st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct }
                                        raise '?' if not st
                                        st = st.struct
                                        m = st.fldlist[mn]
                                else
                                        st = b[:line_struct][@caret_y].struct
                                end
                                inputbox("new name for #{m.name}", :text => m.name) { |nn|
                                        m.name = nn
                                        st.update_member_cache(@heap.cp)
                                        gui_update
                                }
                        end
                end
        when e
                # edit struct field value under the cursor
                if b = @caret_box
                        # TODO b[:struct][line], b.[:member][line] (int for Arrays)
                        st = b[:line_struct][@caret_y]
                        mb = b[:line_member][@caret_y]
                        if st and mb
                                if mb.kind_of?(C::Variable) and mb.type.kind_of?(C::Array) and mb.type.type.kind_of?(C::BaseType) and mb.type.type.name == :char
                                        defval = st[mb].to_array.pack('C*').gsub(/\0*$/, '').gsub(/[^\x20-\x7e]/, '.')
                                        string = true
                                else
                                        defval = st[mb]
                                        string = false
                                end
                                inputbox("new value for #{mb.respond_to?(:name) ? mb.name : mb}", :text => defval.to_s) { |nn|
                                        if string
                                                am = st[mb]
                                                (nn.unpack('C*') + [0]).each_with_index { |b_, i| am[i] = b_ }
                                        else
                                                st[mb] = Expression.parse_string(nn).reduce
                                        end
                                        gui_update
                                }
                        end
                end
        when x
                # show heap xrefs to the hilighted chunk
                if b = @caret_box
                        list = [['address', 'size']]
                        @heap.xrchunksfrom[b.id].to_a.each { |a|
                                list << [Expression[a], Expression[@heap.chunks[a]]]
                        }
                        if list.length == 1
                                messagebox "no xref to #{Expression[b.id]}"
                        else
                                listwindow("heap xrefs to #{Expression[b.id]}", list) { |i| @parent_widget.focus_addr(i[0], nil, true) }
                        end
                end
        when I
                # insert new field in struct
                if b = @caret_box
                        if m = b[:line_member][@caret_y]
                                if m.kind_of?(Integer)
                                        # XXX Array, need to find the outer struct
                                        mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1]
                                        ar = b[:line_struct][@caret_y]
                                        st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct }
                                        raise '?' if not st
                                        st = st.struct
                                        m = st.fldlist[mn]
                                else
                                        st = b[:line_struct][@caret_y].struct
                                end
                                inputbox("new type to insert before #{m.name}", :text => m.dump_def(@heap.cp.toplevel)[0].join(' ')) { |nn|
                                        nil while @heap.cp.readtok
                                        @heap.cp.lexer.feed nn
                                        if not v = C::Variable.parse_type(@heap.cp, @heap.cp.toplevel, true)
                                                nil while @heap.cp.readtok
                                                raise 'bad type'
                                        end
                                        v.parse_declarator(@heap.cp, @heap.cp.toplevel)
                                        nt = v.type
                                        idx = st.members.index(m)
                                        pos = st.offsetof(@heap.cp, m)
                                        name = oname = v.name || ('unk_%x_new' % pos)
                                        cntr = 0
                                        while st.members.find { |m_| m_.name == name }
                                                name = oname + "_#{cntr+=1}"
                                        end
                                        st.members[idx, 0] = [C::Variable.new(name, nt)]
                                        st.update_member_cache(@heap.cp)
                                        gui_update
                                }

                        end
                end
        when S
                # delete structure field
                if b = @caret_box
                        if m = b[:line_member][@caret_y]
                                if m.kind_of?(Integer)
                                        # XXX Array, need to find the outer struct
                                        mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1]
                                        ar = b[:line_struct][@caret_y]
                                        st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct }
                                        raise '?' if not st
                                        st = st.struct
                                        m = st.fldlist[mn]
                                else
                                        st = b[:line_struct][@caret_y].struct
                                end
                                inputbox("delete #{m.name} ?") { |nn|
                                        idx = st.members.index(m)
                                        st.members.delete_at(idx)
                                        st.update_member_cache(@heap.cp)
                                        gui_update
                                }

                        end
                end
        when +
                # append blocks linked from the currently shown blocks to the display
                @addr_struct.keys.each { |ak|
                        @heap.xrchunksto[ak].to_a.each { |nt|
                                next if @addr_struct[nt]
                                # TODO check if the pointer is a some_struct*
                                st = @heap.chunk_struct[nt] || create_struct(nt)
                                ed = @dasm.get_edata_at(nt)
                                @addr_struct[nt] = @heap.cp.decode_c_struct(st.name, ed.data, ed.ptr)
                        }
                }
                gui_update
        when -
                # remove graph leaves in an attempt to undo ?+
                unk = @addr_struct.keys.find_all { |ak|
                        (@heap.xrchunksto[ak].to_a & @addr_struct.keys).empty?
                }
                unk.each { |ak| @addr_struct.delete ak if @addr_struct.length > 1 }
                gui_update
        else return super(k)
        end
        true
end
set_color_arrow(b1, b2) click to toggle source

#addr_struct = 0x234 => AllocCStruct

# File samples/dbg-plugins/heapscan/graphheap.rb, line 12
def set_color_arrow(b1, b2)
        if b1 == @caret_box or b2 == @caret_box
                draw_color :arrow_hl
        else
                draw_color :arrow_cond
        end
end
set_cursor_pos(p) click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 610
def set_cursor_pos(p)
        s, @addr_struct = p
        super(s)
end
setup_contextmenu(b, m) click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 20
def setup_contextmenu(b, m)
        addsubmenu(m, '_follow pointer') {
                next if not lm = b[:line_member][@caret_y]
                addr = b[:line_struct][@caret_y][lm]
                next if not @heap.chunks[addr]
                if lm.kind_of?(::Integer)
                        t = b[:line_struct][@caret_y].struct.type
                else
                        t = lm.type
                end
                if t.pointer? and t.pointed.untypedef.kind_of? C::Union
                        @heap.chunk_struct[addr] ||= t.pointed.untypedef
                end
                st = @heap.chunk_struct[addr] || create_struct(addr)
                ed = @dasm.get_edata_at(addr)
                @addr_struct[addr] = @heap.cp.decode_c_struct(st.name, ed.data, ed.ptr)
                gui_update
        }
        addsubmenu(m, '_hide box') {
                @selected_boxes.each { |sb|
                        @addr_struct.delete sb.id if @addr_struct.length > 1
                }
                @curcontext.root_addrs = struct_find_roots(@addr_struct.keys.first)
                gui_update
        }
        super(b, m)
end
snap() click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 581
def snap
        if not snapped
                @datadiff = {}
                ocb = @parent_widget.bg_color_callback
                @parent_widget.bg_color_callback = lambda { |a|
                        if @datadiff[a]
                                'f88'
                        elsif ocb
                                ocb[a]
                        end
                }
        end
        @snapped = {}
        @addr_struct.each { |a, ast|
                @snapped[a] = [ast.str[ast.stroff, ast.sizeof].to_str]
        }
end
snap_add() click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 599
def snap_add
        return snap if not snapped
        @addr_struct.each { |a, ast|
                (@snapped[a] ||= []) << ast.str[ast.stroff, ast.sizeof].to_str
        }
end
struct_find_roots(addr) click to toggle source
# File samples/dbg-plugins/heapscan/graphheap.rb, line 469
def struct_find_roots(addr)
        addr = @addr_struct.keys.find { |a| addr >= a and addr < a+@addr_struct[a].sizeof } if not @addr_struct[addr]

        todo = [addr]
        done = []
        roots = []
        default_root = nil
        while a = todo.shift
                if done.include?(a) # cycle
                        default_root ||= a
                        next
                end
                done << a
                newf = @heap.xrchunksfrom[a].to_a & @addr_struct.keys
                if newf.empty?
                        roots << a
                else
                        todo.concat newf
                end
        end
        roots << default_root if roots.empty? and default_root

        roots
end