metasm/gui/dasm_decomp.rb in metasm-1.0.3 vs metasm/gui/dasm_decomp.rb in metasm-1.0.4

- old
+ new

@@ -4,29 +4,30 @@ # Licence is LGPL, see LICENCE in the top-level directory module Metasm module Gui class CdecompListingWidget < DrawableWidget - attr_accessor :dasm, :curaddr, :tabwidth + attr_accessor :dasm, :curfuncaddr, :tabwidth def initialize_widget(dasm, parent_widget) @dasm = dasm @parent_widget = parent_widget @view_x = @view_y = 0 # coord of corner of view in characters @cwidth = @cheight = 1 # widget size in chars @line_text = [] @line_text_col = [] # each line is [[:col, 'text'], [:col, 'text']] - @curaddr = nil + @line_addr = [] + @curfuncaddr = nil @tabwidth = 8 @default_color_association = ColorTheme.merge :keyword => :blue, :localvar => :darkred, :globalvar => :darkgreen, :intrinsic => :darkyellow end def curfunc - @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[@curaddr] or @dasm.c_parser.toplevel.struct[@curaddr]) + @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[@curfuncaddr] or @dasm.c_parser.toplevel.struct[@curfuncaddr]) end def click(x, y) @caret_x = (x-1).to_i / @font_width + @view_x @caret_y = y.to_i / @font_height + @view_y @@ -57,11 +58,10 @@ end when :down if @caret_y < @line_text.length - 1 @view_y += 4 @caret_y += 4 - redraw end end redraw end @@ -146,96 +146,131 @@ update_caret when :end @caret_x = @line_text[@caret_y].to_s.length update_caret when :pgup - @caret_y -= @cheight/2 - @caret_y = 0 if @caret_y < 0 - update_caret + if @caret_y > 0 + @view_y -= @cheight/2 + @caret_y -= @cheight/2 + @caret_y = 0 if @caret_y < 0 + redraw + end when :pgdown - @caret_y += @cheight/2 - @caret_y = @line_text.length if @caret_y > @line_text.length - update_caret + if @caret_y < @line_text.length + @view_y += @cheight/2 + @caret_y += @cheight/2 + @caret_y = @line_text.length if @caret_y > @line_text.length + redraw + end when ?n # rename local/global variable - f = curfunc.initializer if curfunc and curfunc.initializer.kind_of? C::Block - n = @hl_word - if (f and f.symbol[n]) or @dasm.c_parser.toplevel.symbol[n] - @parent_widget.inputbox("new name for #{n}", :text => n) { |v| - if v !~ /^[a-z_$][a-z_0-9$]*$/i - @parent_widget.messagebox("invalid name #{v.inspect} !") - next + prompt_rename + when ?r # redecompile + @parent_widget.decompile(@curfuncaddr) + when ?t, ?y # change variable type (you'll want to redecompile after that) + prompt_retype + when ?h + display_hex + else return false + end + true + end + + def prompt_rename + f = curfunc.initializer if curfunc and curfunc.initializer.kind_of?(C::Block) + n = @hl_word + if (f and f.symbol[n]) or @dasm.c_parser.toplevel.symbol[n] + @parent_widget.inputbox("new name for #{n}", :text => n) { |v| + if v !~ /^[a-z_$][a-z_0-9$]*$/i + @parent_widget.messagebox("invalid name #{v.inspect} !") + next + end + if f and f.symbol[n] + # TODO add/update comment to the asm instrs + s = f.symbol[v] = f.symbol.delete(n) + s.misc ||= {} + uan = s.misc[:unalias_name] ||= n + s.name = v + f.decompdata[:unalias_name][uan] = v + elsif @dasm.c_parser.toplevel.symbol[n] + @dasm.rename_label(n, v) + @curfuncaddr = v if @curfuncaddr == n + end + gui_update + } + end + end + + def prompt_retype + f = curfunc.initializer if curfunc.kind_of?(C::Variable) and curfunc.initializer.kind_of?(C::Block) + n = @hl_word + cp = @dasm.c_parser + if (f and s = f.symbol[n]) or s = cp.toplevel.symbol[n] or s = cp.toplevel.symbol[@curfuncaddr] + s_ = s.dup + s_.initializer = nil if s.kind_of?(C::Variable) # for static var, avoid dumping the initializer in the textbox + s_.attributes &= C::Attributes::DECLSPECS if s_.attributes + @parent_widget.inputbox("new type for #{s.name}", :text => s_.dump_def(cp.toplevel)[0].join(' ')) { |t| + if t == '' + if s.type.kind_of?(C::Function) and s.initializer and s.initializer.decompdata + s.initializer.decompdata.delete(:return_type) + elsif f.symbol[n] and s.kind_of?(C::Variable) + s.misc ||= {} + uan = s.misc[:unalias_name] ||= s.name + f.decompdata[:unalias_type].delete uan end - if f and f.symbol[n] - # TODO add/update comment to the asm instrs - s = f.symbol[v] = f.symbol.delete(n) - s.name = v - f.decompdata[:stackoff_name][s.stackoff] = v if s.stackoff - elsif @dasm.c_parser.toplevel.symbol[n] - @dasm.rename_label(n, v) - @curaddr = v if @curaddr == n + next + end + begin + cp.lexer.feed(t) + raise 'bad type' if not v = C::Variable.parse_type(cp, cp.toplevel, true) + v.parse_declarator(cp, cp.toplevel) + if s.type.kind_of?(C::Function) and s.initializer and s.initializer.decompdata + # updated type of a decompiled func: update stack + vt = v.type.untypedef + vt = vt.type.untypedef if vt.kind_of?(C::Pointer) + raise 'function forever !' if not vt.kind_of?(C::Function) + # TODO _declspec + vt.args.to_a.each_with_index { |a, idx| + oa = curfunc.type.args.to_a[idx] + oa.misc ||= {} + a.misc ||= {} + uan = a.misc[:unalias_name] = oa.misc[:unalias_name] ||= oa.name + s.initializer.decompdata[:unalias_name][uan] = a.name if a.name + s.initializer.decompdata[:unalias_type][uan] = a.type + } + s.initializer.decompdata[:return_type] = vt.type + s.type = v.type + elsif f and s.kind_of?(C::Variable) and f.symbol[s.name] + s.misc ||= {} + uan = s.misc[:unalias_name] ||= s.name + f.decompdata[:unalias_type][uan] = v.type + s.type = v.type end gui_update - } + rescue Object + @parent_widget.messagebox([$!.message, $!.backtrace].join("\n"), "error") + end + cp.readtok until cp.eos? + } + end + end + + # change the display of an integer from hex to decimal + def display_hex + ce = curobj + if ce.kind_of?(C::CExpression) and not ce.op and ce.rexpr.kind_of?(::Integer) + ce.misc ||= {} + if ce.misc[:custom_display] =~ /^0x/ + ce.misc[:custom_display] = ce.rexpr.to_s + else + ce.misc[:custom_display] = '0x%X' % ce.rexpr end - when ?r # redecompile - @parent_widget.decompile(@curaddr) - when ?t # change variable type (you'll want to redecompile after that) - f = curfunc.initializer if curfunc.kind_of? C::Variable and curfunc.initializer.kind_of? C::Block - n = @hl_word - cp = @dasm.c_parser - if (f and s = f.symbol[n]) or s = cp.toplevel.symbol[n] or s = cp.toplevel.symbol[@curaddr] - s_ = s.dup - s_.initializer = nil if s.kind_of? C::Variable # for static var, avoid dumping the initializer in the textbox - s_.attributes &= C::Attributes::DECLSPECS if s_.attributes - @parent_widget.inputbox("new type for #{s.name}", :text => s_.dump_def(cp.toplevel)[0].join(' ')) { |t| - if t == '' - if s.type.kind_of? C::Function and s.initializer and s.initializer.decompdata - s.initializer.decompdata[:stackoff_type].clear - s.initializer.decompdata.delete :return_type - elsif s.kind_of? C::Variable and s.stackoff - f.decompdata[:stackoff_type].delete s.stackoff - end - next - end - begin - cp.lexer.feed(t) - raise 'bad type' if not v = C::Variable.parse_type(cp, cp.toplevel, true) - v.parse_declarator(cp, cp.toplevel) - if s.type.kind_of? C::Function and s.initializer and s.initializer.decompdata - # updated type of a decompiled func: update stack - vt = v.type.untypedef - vt = vt.type.untypedef if vt.kind_of? C::Pointer - raise 'function forever !' if not vt.kind_of? C::Function - # TODO _declspec - ao = 1 - vt.args.to_a.each { |a| - next if a.has_attribute_var('register') - ao = (ao + [cp.sizeof(a), cp.typesize[:ptr]].max - 1) / cp.typesize[:ptr] * cp.typesize[:ptr] - s.initializer.decompdata[:stackoff_name][ao] = a.name if a.name - s.initializer.decompdata[:stackoff_type][ao] = a.type - ao += cp.sizeof(a) - } - s.initializer.decompdata[:return_type] = vt.type - s.type = v.type - else - f.decompdata[:stackoff_type][s.stackoff] = v.type if f and s.kind_of? C::Variable and s.stackoff - s.type = v.type - end - gui_update - rescue Object - @parent_widget.messagebox([$!.message, $!.backtrace].join("\n"), "error") - end - cp.readtok until cp.eos? - } - end - else return false + gui_update end - true end def get_cursor_pos - [@curaddr, @caret_x, @caret_y, @view_y] + [@curfuncaddr, @caret_x, @caret_y, @view_y] end def set_cursor_pos(p) focus_addr p[0] @caret_x, @caret_y, @view_y = p[1, 3] @@ -256,11 +291,11 @@ # focus on addr # returns true on success (address exists & decompiled) def focus_addr(addr) if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr].kind_of?(C::Union)) - @curaddr = addr + @curfuncaddr = addr @caret_x = @caret_y = 0 gui_update return true end @@ -268,38 +303,79 @@ # scan up to func start/entrypoint todo = [addr] done = [] ep = @dasm.entrypoints.to_a.inject({}) { |h, e| h.update @dasm.normalize(e) => true } - while addr = todo.pop - next if not di = @dasm.di_at(addr) - addr = di.block.address - next if done.include?(addr) or not @dasm.di_at(addr) - done << addr - break if @dasm.function[addr] or ep[addr] + while laddr = todo.pop + next if not di = @dasm.di_at(laddr) + laddr = di.block.address + next if done.include?(laddr) or not @dasm.di_at(laddr) + done << laddr + break if @dasm.function[laddr] or ep[laddr] empty = true - @dasm.decoded[addr].block.each_from_samefunc(@dasm) { |na| empty = false ; todo << na } + @dasm.decoded[laddr].block.each_from_samefunc(@dasm) { |na| empty = false ; todo << na } break if empty end - @dasm.auto_label_at(addr, 'loc') if @dasm.get_section_at(addr) and not @dasm.get_label_at(addr) - return if not l = @dasm.get_label_at(addr) - @curaddr = l + @dasm.auto_label_at(laddr, 'loc') if @dasm.get_section_at(laddr) and not @dasm.get_label_at(laddr) + return if not l = @dasm.get_label_at(laddr) + @curfuncaddr = l @caret_x = @caret_y = 0 + @want_addr = addr gui_update true end # returns the address of the data under the cursor def current_address - @curaddr + if @line_c[@caret_y] + lc = {} + @line_c[@caret_y].each { |k, v| + lc[k] = v if v.misc and v.misc[:di_addr] + } + o = lc.keys.sort.reverse.find { |oo| oo < @caret_x } || lc.keys.min + end + o ? lc[o].misc[:di_addr] : @curfuncaddr end + # return the C object under the cursor + def curobj + if lc = @line_c[@caret_y] + o = lc.keys.sort.reverse.find { |oo| oo < @caret_x } || lc.keys.min + end + o ? lc[o] : curfunc + end + + def update_line_text - @line_text = curfunc.dump_def(@dasm.c_parser.toplevel)[0].map { |l| l.gsub("\t", ' '*@tabwidth) } + text = curfunc.dump_def(@dasm.c_parser.toplevel)[0] + @line_text = text.map { |l| l.gsub("\t", ' '*@tabwidth) } + # y => { x => C } + @line_c = text.map { |l| + h = {} + l.c_at_offset.each { |o, c| + oo = l[0, o].gsub("\t", ' '*@tabwidth).length + h[oo] = c + } + h + } + + @want_addr ||= nil + # define @caret_y to match @want_addr from focus_addr() + @line_c.each_with_index { |lc, y| + next if not @want_addr + lc.each { |o, c| + if @want_addr and c.misc and c.misc[:di_addr] + @caret_x, @caret_y = o, y+1 if @want_addr > c.misc[:di_addr] + @want_addr = nil if @want_addr <= c.misc[:di_addr] + end + } + } + @want_addr = nil + @line_text_col = [] - if f = curfunc and f.kind_of? C::Variable and f.initializer.kind_of? C::Block + if f = curfunc and f.kind_of?(C::Variable) and f.initializer.kind_of?(C::Block) keyword_re = /\b(#{C::Keyword.keys.join('|')})\b/ intrinsic_re = /\b(intrinsic_\w+)\b/ lv = f.initializer.symbol.keys lv << '00' if lv.empty? localvar_re = /\b(#{lv.join('|')})\b/ @@ -340,10 +416,10 @@ if not curfunc and not @decompiling ||= false @line_text = ['please wait'] @line_text_col = [[[:text, 'please wait']]] redraw @decompiling = true - @dasm.decompile_func(@curaddr) + protect { @dasm.decompile_func(@curfuncaddr) } @decompiling = false end if curfunc update_line_text update_caret