class Metasm::WebAsm
Attributes
wasm_file[RW]
Public Class Methods
new(*args)
click to toggle source
Calls superclass method
# File metasm/cpu/webasm/main.rb, line 12 def initialize(*args) super() @size = args.grep(Integer).first || 64 @wasm_file = args.grep(ExeFormat).first @endianness = args.delete(:little) || args.delete(:big) || (@wasm_file ? @wasm_file.endianness : :little) end
Public Instance Methods
abi_funcall()
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 118 def abi_funcall @abi_funcall ||= { :changed => [] } end
addop(name, bin, *args)
click to toggle source
# File metasm/cpu/webasm/opcodes.rb, line 10 def addop(name, bin, *args) o = Opcode.new name, bin args.each { |a| if a == :mem o.args << :uleb << :memoff elsif @valid_props[a] o.props[a] = true else o.args << a end } @opcode_list << o end
backtrace_is_function_return(expr, di=nil)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 297 def backtrace_is_function_return(expr, di=nil) expr and Expression[expr] == Expression[Indirection[:callstack, 8]] end
backtrace_is_stack_address(expr)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 313 def backtrace_is_stack_address(expr) ([:local_base, :opstack] & Expression[expr].expr_externals).first end
backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 309 def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) f.backtrace_binding = { :callstack => Expression[:callstack, :-, 8] } end
build_bin_lookaside()
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 11 def build_bin_lookaside lookaside = (0..0xff).inject({}) { |h, i| h.update i => [] } opcode_list.each { |op| lookaside[op.bin] << op } lookaside end
dbg_end_stepout(dbg, addr, di)
click to toggle source
# File metasm/cpu/webasm/debug.rb, line 27 def dbg_end_stepout(dbg, addr, di) di and di.opcode.props[:stopexec] and (di.opcode.name == 'return' or di.opcode.name == 'end') end
dbg_register_list()
click to toggle source
# File metasm/cpu/webasm/debug.rb, line 11 def dbg_register_list @dbg_register_list ||= [:pc, :opstack, :mem, :local_base] end
dbg_resolve_pc(di, fbd, pc_reg, dbg_ctx)
click to toggle source
Calls superclass method
# File metasm/cpu/webasm/debug.rb, line 15 def dbg_resolve_pc(di, fbd, pc_reg, dbg_ctx) case di.opcode.name when 'br_if', 'if' if dbg_ctx.resolve(Indirection[:opstack, 8]) != 0 fbd[pc_reg] = (di.opcode.name == 'if' ? di.next_addr : di.misc[:x]) else fbd[pc_reg] = (di.opcode.name == 'if' ? di.misc[:x] : di.next_addr) end else return super(di, fbd, pc_reg, dbg_ctx) end end
decode_br_table(edata)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 150 def decode_br_table(edata) count = decode_uleb(edata) ary = [] count.times { ary << decode_uleb(edata) } default = decode_uleb(edata) BrTable.new(ary, default) end
decode_c_function_prototype(cp, sym, orig=nil)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 317 def decode_c_function_prototype(cp, sym, orig=nil) disassembler_default_func end
decode_findopcode(edata)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 102 def decode_findopcode(edata) di = DecodedInstruction.new(self) val = edata.decode_imm(:u8, @endianness) di if di.opcode = bin_lookaside[val].first end
decode_instr_interpret(di, addr)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 130 def decode_instr_interpret(di, addr) case di.opcode.name when 'call' fnr = di.instruction.args.first.reduce di.misc ||= {} di.misc[:tg_func_nr] = fnr if f = @wasm_file.get_function_nr(fnr) tg = f[:init_offset] ? f[:init_offset] : "#{f[:module]}_#{f[:field]}" di.instruction.args[0] = Expression[tg] di.misc[:x] = [tg] else di.misc[:x] = [:default] end when 'call_indirect' di.misc ||= {} di.misc[:x] = [:default] end di end
decode_instr_op(edata, di)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 108 def decode_instr_op(edata, di) before_ptr = edata.ptr op = di.opcode di.instruction.opname = op.name op.args.each { |a| di.instruction.args << case a when :f32; Expression[edata.decode_imm(:u32, @endianness)] when :f64; Expression[edata.decode_imm(:u64, @endianness)] when :memoff; Memref.new(decode_uleb(edata)) when :uleb; Expression[decode_uleb(edata)] when :sleb; Expression[decode_uleb(edata, true)] when :blocksig; BlockSignature.new(decode_uleb(edata, true)) when :br_table; decode_br_table(edata) else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" end } di.bin_length = 1 + edata.ptr - before_ptr di end
decode_instruction_context(dasm, edata, di_addr, ctx)
click to toggle source
reuse the instructions from the cache
# File metasm/cpu/webasm/decode.rb, line 93 def decode_instruction_context(dasm, edata, di_addr, ctx) ctx ||= disassemble_init_context(dasm, di_addr) if not ctx[:di_cache][di_addr] di_addr = dasm.normalize(di_addr) disassemble_init_context(dasm, di_addr) end ctx[:di_cache][di_addr] end
decode_uleb(ed, signed=false)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 19 def decode_uleb(ed, signed=false) v = s = 0 while s < 10*7 b = ed.read(1).unpack('C').first.to_i v |= (b & 0x7f) << s s += 7 break if (b&0x80) == 0 end v = Expression.make_signed(v, s) if signed v end
decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil)
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 150 def decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil) func_entry = myblocks.first[0] if w_func = @wasm_file.function_body.find { |fb| fb[:init_offset] == func_entry } elsif g = @wasm_file.global.find { |gg| gg[:init_offset] == func_entry } w_func = { :local_var => [], :type => { :params => [], :ret => [g[:type]] } } elsif (@wasm_file.element.to_a + @wasm_file.data.to_a).find { |gg| gg[:init_offset] == func_entry } w_func = { :local_var => [], :type => { :params => [], :ret => ['i32'] } } end scope = func.initializer func.type.args.each { |a| scope.symbol[a.name] = a } stmts = scope.statements local = [] w_func[:type][:params].each { |t| local << C::Variable.new("arg_#{local.length}", wasm_type_to_type(t)) scope.symbol[local.last.name] = local.last func.type.args << local.last } w_func[:local_var].each { |t| local << C::Variable.new("var_#{local.length}", wasm_type_to_type(t)) scope.symbol[local.last.name] = local.last local.last.initializer = C::CExpression[0] stmts << C::Declaration.new(local.last) } opstack = {} # *(_int32*)(local_base+16) => 16 ce_ptr_offset = lambda { |ee, base| if ee.kind_of?(C::CExpression) and ee.op == :* and not ee.lexpr and ee.rexpr.kind_of?(C::CExpression) and not ee.rexpr.op and ee.rexpr.rexpr.kind_of?(C::CExpression) if not ee.rexpr.rexpr.op and ee.rexpr.rexpr.rexpr.kind_of?(C::Variable) and ee.rexpr.rexpr.rexpr.name == base 0 elsif ee.rexpr.rexpr.lexpr.kind_of?(C::Variable) and ee.rexpr.rexpr.lexpr.name == base and ee.rexpr.rexpr.rexpr.kind_of?(C::CExpression) and not ee.rexpr.rexpr.rexpr.op and ee.rexpr.rexpr.rexpr.rexpr.kind_of?(::Integer) if ee.rexpr.rexpr.op == :+ ee.rexpr.rexpr.rexpr.rexpr elsif ee.rexpr.rexpr.op == :- -ee.rexpr.rexpr.rexpr.rexpr end end end } opstack_idx = -1 ce_local_offset = lambda { |ee| ce_ptr_offset[ee, 'local_base'] } ce_opstack_offset = lambda { |ee| ce_ptr_offset[ee, 'frameptr'] } di_addr = nil # Expr => CExpr ce = lambda { |*e| c_expr = dcmp.decompile_cexpr(Expression[Expression[*e].reduce], scope) dcmp.walk_ce(c_expr, true) { |ee| if ee.rexpr.kind_of?(::Array) # funcall arglist ee.rexpr.map! { |eee| if loff = ce_local_offset[eee] C::CExpression[local[loff/8]] elsif soff = ce_opstack_offset[eee] C::CExpression[opstack[-soff/8]] else eee end } end if loff = ce_local_offset[ee.lexpr] ee.lexpr = local[loff/8] end if loff = ce_local_offset[ee.rexpr] ee.rexpr = local[loff/8] ee.rexpr = C::CExpression[ee.rexpr] if not ee.op and ee.type.pointer? end if soff = ce_opstack_offset[ee.rexpr] # must do soff.rexpr before lexpr in case of reaffectation ! ee.rexpr = opstack[-soff/8] ee.rexpr = C::CExpression[ee.rexpr] if not ee.op and ee.type.pointer? end if soff = ce_opstack_offset[ee.lexpr] if ee.op == :'=' # affectation: create a new variable varname = "loc_#{opstack_idx += 1}" ne = C::Variable.new(varname, wasm_type_to_type("i#{8*dcmp.sizeof(ee.lexpr)}")) scope.symbol[varname] = ne stmts << C::Declaration.new(ne) opstack[-soff/8] = ne end ee.lexpr = opstack[-soff/8] end } ret = if loff = ce_local_offset[c_expr] C::CExpression[local[loff/8]] elsif soff = ce_opstack_offset[c_expr] C::CExpression[opstack[-soff/8]] else c_expr end dcmp.walk_ce(ret) { |ee| ee.with_misc :di_addr => di_addr if di_addr } ret } blocks_toclean = myblocks.dup until myblocks.empty? b, to = myblocks.shift if l = dcmp.dasm.get_label_at(b) stmts << C::Label.new(l) end # go ! di_list = dcmp.dasm.decoded[b].block.list.dup di_list.each { |di| di_addr = di.address if di.opcode.name == 'if' or di.opcode.name == 'br_if' n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) bd = get_fwdemu_binding(di) if di.opcode.name == 'if' cc = ce[:!, bd[:flag]] else cc = ce[bd[:flag]] end stmts << C::If.new(C::CExpression[cc], C::Goto.new(n).with_misc(:di_addr => di.address)).with_misc(:di_addr => di.address) to.delete dcmp.dasm.normalize(n) elsif (di.opcode.name == 'end' or di.opcode.name == 'return') and di.opcode.props[:stopexec] fsig = w_func[:type] rettype = wasm_type_to_type(fsig[:ret].first) if fsig[:ret] and fsig[:ret].first if not fsig[:ret].empty? off = di.misc[:dcmp_stackoff] || -8 ret = C::CExpression[ce[Indirection[[:frameptr, :+, off], dcmp.sizeof(rettype)]]] end stmts << C::Return.new(ret).with_misc(:di_addr => di.address) elsif (di.opcode.name == 'end' or di.opcode.name == 'else') and di.misc[:dcmp_stackoff] and di.misc[:end_of] # end of block returning a value: store the value in a real variable instead of the autogenerated local # so that if { } else {} both update the same var start = di.misc[:end_of] start_rettype = start.instruction.args.first.to_s if start_rettype != 'none' retsz = dcmp.sizeof(wasm_type_to_type(start_rettype)) off = di.misc[:dcmp_stackoff] if not start.misc[:dcmp_retval] or not scope.symbol[start.misc[:dcmp_retval]] stmts << C::CExpression[ce[Indirection[[:frameptr, :+, off], retsz], :'=', Indirection[[:frameptr, :+, off], retsz]]] start.misc[:dcmp_retval] = stmts.last.lexpr.name else stmts << C::CExpression[ce[scope.symbol[start.misc[:dcmp_retval]], :'=', Indirection[[:frameptr, :+, off], retsz]]] end end elsif di.opcode.name == 'call' tg = di.misc[:x].first raise "no call target for #{di}" if not tg tg = dcmp.dasm.auto_label_at(tg, 'sub') if dcmp.dasm.get_section_at(tg) f = dcmp.c_parser.toplevel.symbol[tg] raise "no global function #{tg} for #{di}" if not f args = [] bd = get_fwdemu_binding(di) i = 0 while bd_arg = bd["param_#{i}"] args << ce[bd_arg] i += 1 end e = C::CExpression[f, :funcall, args].with_misc(:di_addr => di.address) if bd_ret = bd.index(Expression["ret_0"]) e = ce[bd_ret, :'=', e] end stmts << e elsif di.opcode.name == 'call_indirect' args = [] bd = get_fwdemu_binding(di) wt = @wasm_file.type[di.instruction.args.first.reduce] fptr = C::CExpression[[dcmp.c_parser.toplevel.symbol['indirect_calltable'], :[], ce[bd['func_idx']]], wasm_type_to_type(wt)] i = 0 while bd_arg = bd["param_#{i}"] args << ce[bd_arg] i += 1 end e = C::CExpression[fptr, :funcall, args].with_misc(:di_addr => di.address) if bd_ret = bd.index(Expression["ret_0"]) e = ce[bd_ret, :'=', e] end stmts << e else bd = get_fwdemu_binding(di) if di.backtrace_binding[:incomplete_binding] stmts << C::Asm.new(di.instruction.to_s, nil, nil, nil, nil, nil).with_misc(:di_addr => di.address) else bd.each { |k, v| next if k == :opstack e = ce[k, :'=', v] stmts << e if not e.kind_of?(C::Variable) # [:eflag_s, :=, :unknown].reduce } end end di_addr = nil } case to.length when 0 if not myblocks.empty? and not stmts.last.kind_of?(C::Return) puts " block #{Expression[b]} has no to and don't end in ret" end when 1 if (myblocks.empty? ? nextaddr != to[0] : myblocks.first.first != to[0]) stmts << C::Goto.new(dcmp.dasm.auto_label_at(to[0], 'unknown_goto')) end else puts " block #{Expression[b]} with multiple to" end end # cleanup di.bt_binding (we set :frameptr etc in those, this may confuse the dasm) blocks_toclean.each { |b_, to_| dcmp.dasm.decoded[b_].block.list.each { |di| di.backtrace_binding = nil } } end
decompile_check_abi(dcmp, entry, func)
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 366 def decompile_check_abi(dcmp, entry, func) scope = func.initializer @wasm_file.function_body.to_a.each { |fb| next if fb[:init_offset] != entry w_type = wasm_type_to_type(fb[:type]) func.type.type = w_type.type if func.type.args.length > w_type.args.length # detected an argument that is actually a local variable, move into func scope while a = func.type.args.delete_at(w_type.args.length) if a.has_attribute('unused') scope.symbol.delete a.name else a.initializer = C::CExpression[0] scope.statements[0, 0] = [C::Declaration.new(a)] end end end } end
decompile_func_finddeps(dcmp, blocks, func)
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 146 def decompile_func_finddeps(dcmp, blocks, func) {} end
decompile_func_finddeps_di(dcmp, func, di, a, w)
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 143 def decompile_func_finddeps_di(dcmp, func, di, a, w) end
decompile_init(dcmp)
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 25 def decompile_init(dcmp) mem = dcmp.c_parser.toplevel.symbol['mem'] = C::Variable.new('mem', C::Pointer.new(C::BaseType.new(:char))) mem.storage = :static dcmp.c_parser.toplevel.statements << C::Declaration.new(mem) global_idx = 0 @wasm_file.import.to_a.each { |i| case i[:kind] when 'global' global_idx += 1 var = C::Variable.new var.name = '%s_%s' % [i[:module], i[:field]] var.type = C::Array.new(wasm_type_to_type(i[:type]), 1) var.storage = :extern dcmp.c_parser.toplevel.symbol[var.name] = var dcmp.c_parser.toplevel.statements << C::Declaration.new(var) when 'function' var = C::Variable.new var.name = '%s_%s' % [i[:module], i[:field]] var.type = wasm_type_to_type(i[:type]) var.storage = :extern dcmp.c_parser.toplevel.symbol[var.name] = var dcmp.c_parser.toplevel.statements << C::Declaration.new(var) end } @wasm_file.global.to_a.each_with_index { |g, idx| g_name = 'global_%d' % global_idx global_idx += 1 var = C::Variable.new var.name = g_name var.type = C::Array.new(wasm_type_to_type(g[:type]), 1) var.storage = :static dcmp.c_parser.toplevel.symbol[var.name] = var dcmp.c_parser.toplevel.statements << C::Declaration.new(var) # decompile initializers g_init_name = g_name + '_init' dcmp.dasm.disassemble(g_init_name) dcmp.decompile_func(g_init_name) if init = dcmp.c_parser.toplevel.symbol[g_init_name] and init.initializer.kind_of?(C::Block) and init.initializer.statements.first.kind_of?(C::Return) dcmp.c_parser.toplevel.symbol[g_name].initializer = [ init.initializer.statements.first.value ] dcmp.c_parser.toplevel.symbol.delete(g_init_name) dcmp.c_parser.toplevel.statements.delete_if { |st| st.kind_of?(C::Declaration) and st.var.name == g_init_name } end } @wasm_file.table.to_a.each_with_index { |t, idx| break if idx > 0 t_name = 'indirect_calltable' var = C::Variable.new var.name = t_name sz = t[:limits][:initial_size] var.type = C::Array.new(C::Pointer.new(wasm_type_to_type(t[:type])), sz) var.storage = :static dcmp.c_parser.toplevel.symbol[var.name] = var dcmp.c_parser.toplevel.statements << C::Declaration.new(var) var.initializer = [C::CExpression[0]] * sz # initializer @wasm_file.element.to_a.each_with_index { |e, eidx| next if e[:table_index] != idx # address of the code that evals the index at which to place the elements inside the table e_init_name = "element_#{eidx}_init_addr" dcmp.dasm.disassemble(e_init_name) dcmp.decompile_func(e_init_name) if init = dcmp.c_parser.toplevel.symbol[e_init_name] and init.initializer.kind_of?(C::Block) and init.initializer.statements.first.kind_of?(C::Return) eoff = init.initializer.statements.first.value.reduce(dcmp.c_parser) dcmp.c_parser.toplevel.symbol.delete(e_init_name) dcmp.c_parser.toplevel.statements.delete_if { |st| st.kind_of?(C::Declaration) and st.var.name == e_init_name } e[:elems].each_with_index { |ev, vidx| # table 0 is the only table in a wasm file and contains a list of function indexes used with the call_indirect asm instruction # e_init_name gives the index at which we should put e[:elems], and we convert the func indexes into C names vidx += eoff if vidx >= sz or vidx < 0 puts "W: initializing indirect_calltable, would put #{ev} beyond end of table (#{vidx} > #{sz})" next end if not tg_func = @wasm_file.get_function_nr(ev) puts "W: initializing indirect_calltable, bad func index #{ev}" next end funcname = dcmp.dasm.get_label_at(tg_func[:init_offset]) || "func_at_#{'%x' % tg_func[:init_offset]}" # XXX should decompile funcname now ? var.initializer[vidx] = C::CExpression[:&, C::Variable.new(funcname)] } end } } end
decompile_makestackvars(dasm, funcstart, blocks) { |block| ... }
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 122 def decompile_makestackvars(dasm, funcstart, blocks) @decomp_mkstackvars_terminals = [:frameptr, :local_base, :mem] oldbd = {} oldbd[funcstart] = dasm.address_binding[funcstart] dasm.address_binding[funcstart] = { :opstack => Expression[:frameptr] } blocks.each { |block| oldbd[block.address] = dasm.address_binding[block.address] stkoff = dasm.backtrace(:opstack, block.address, :snapshot_addr => funcstart) dasm.address_binding[block.address] = { :opstack => Expression[:frameptr, :+, stkoff[0]-:frameptr] } yield block # store frameptr offset at each 'end' 'return' or 'else' instruction if di = block.list.last and %w[end return else].include?(di.opcode.name) stkoff = dasm.backtrace(:opstack, di.address, :snapshot_addr => funcstart) if stkoff.length == 1 and (stkoff[0] - :frameptr).kind_of?(::Integer) di.misc[:dcmp_stackoff] = stkoff[0] - :frameptr end end } oldbd.each { |a, b| b ? dasm.address_binding[a] = b : dasm.address_binding.delete(a) } end
disassemble_init_context(dasm, addr)
click to toggle source
when starting disassembly, pre-decode all instructions until the final 'end' and fixup the xrefs (if/block/loop…)
# File metasm/cpu/webasm/decode.rb, line 32 def disassemble_init_context(dasm, addr) dasm.misc ||= {} dasm.misc[:cpu_context] ||= {} cache = dasm.misc[:cpu_context][:di_cache] ||= {} addr = dasm.normalize(addr) return dasm.misc[:cpu_context] if cache[addr] code_start = addr stack = [[]] set_misc_x = lambda { |di, tg| di.misc[:x] ||= [] ; di.misc[:x] |= [tg] } while di = dasm.disassemble_instruction(addr) cache[addr] = di di.misc ||= {} di.misc[:code_start] = code_start case di.opcode.name when 'if', 'loop', 'block' stack << [di] when 'else' raise "bad #{di} #{stack.last.inspect}" if stack.last.empty? or stack.last.last.opcode.name != 'if' stack.last.each { |ddi| set_misc_x[ddi, di.next_addr] } # 'if' points past here di.misc[:end_of] = stack.last[0] # store matching 'if' stack.last[0] = di # 'else' replace 'if' when 'br', 'br_if', 'br_table' if di.opcode.name == 'br_table' depths = di.instruction.args.first.ary.uniq | [di.instruction.args.first.default] else depths = [di.instruction.args.first.reduce] end depths.each { |depth| tg = stack[-depth-1] # XXX skip if/else in the stack ? raise "bad #{di} (#{stack.length})" if not tg if tg.first and tg.first.opcode.name == 'loop' set_misc_x[di, tg.first.address] else tg << di end } when 'end' dis = stack.pop dis.each { |ddi| set_misc_x[ddi, di.next_addr] if ddi.opcode.name != 'loop' and ddi.opcode.name != 'block' } if stack.empty? # stack empty: end of func di.opcode = @opcode_list.find { |op| op.name == 'end' and op.props[:stopexec] } break else if dis.first di.misc[:end_of] = dis.first # store matching loop/block/if if dis.first.opcode.name == 'else' di.misc[:end_of] = dis.first.misc[:end_of] # else patched stack.last, recover original 'if' end end di.opcode = @opcode_list.find { |op| op.name == 'end' and not op.props[:stopexec] } end end addr = di.next_addr end dasm.misc[:cpu_context] end
disassembler_default_func()
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 301 def disassembler_default_func df = DecodedFunction.new ra = Indirection[:callstack, 8] df.backtracked_for << BacktraceTrace.new(ra, :default, ra, :x, nil) df.backtrace_binding = { :callstack => Expression[:callstack, :-, 8] } df end
encode_instr_op(program, i, op)
click to toggle source
# File metasm/cpu/webasm/encode.rb, line 83 def encode_instr_op(program, i, op) ed = EncodedData.new([op.bin].pack('C*')) op.args.zip(i.args).each { |oa, ia| case oa when :f32; ed << ia.encode(:u32, @endianness) when :f64; ed << ia.encode(:u64, @endianness) when :memoff; ed << encode_uleb(ia.off) when :uleb; ed << encode_uleb(ia) when :sleb; ed << encode_uleb(ia, true) when :blocksig ia = ia.id if ia.kind_of?(BlockSignature) ed << encode_uleb(ia, true) when :br_table ed << encode_uleb(ia.ary.length) ia.ary.each { |a| ed << encode_uleb(a) } ed << encode_uleb(ia.default) end } ed end
encode_uleb(val, signed=false)
click to toggle source
# File metasm/cpu/webasm/encode.rb, line 71 def encode_uleb(val, signed=false) # TODO labels ? v = Expression[val].reduce raise "need numeric value for #{val}" if not v.kind_of?(::Integer) out = EncodedData.new while v > 0x7f or v < -0x40 or (signed and v > 0x3f) out << Expression[0x80 | (v&0x7f)].encode(:u8, @endianness) v >>= 7 end out << Expression[v & 0x7f].encode(:u8, @endianness) end
fix_fwdemu_binding(di, fbd)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 271 def fix_fwdemu_binding(di, fbd) ori = fbd fbd = {} ori.each { |k, v| if k.kind_of?(Indirection) and not k.target.lexpr.kind_of?(Indirection) # dont fixup store8 etc fbd[k.bind(:opstack => ori[:opstack]).reduce_rec] = v else fbd[k] = v end } fbd end
get_backtrace_binding(di)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 262 def get_backtrace_binding(di) if binding = backtrace_binding[di.opcode.name] binding[di] || {} else puts "unhandled instruction to backtrace: #{di}" if $VERBOSE {:incomplete_binding => Expression[1]} end end
get_xrefs_x(dasm, di)
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 285 def get_xrefs_x(dasm, di) if di.opcode.props[:stopexec] case di.opcode.name when 'return', 'end' return [Indirection[:callstack, 8]] end end return [] if not di.opcode.props[:setip] di.misc ? [di.misc[:x]].flatten : [] end
init()
click to toggle source
# File metasm/cpu/webasm/opcodes.rb, line 26 def init @opcode_list = [] @valid_props = { :setip => true, :stopexec => true, :saveip => true } addop 'unreachable', 0x00, :stopexec addop 'nop', 0x01 addop 'block', 0x02, :blocksig # arg = signature (block_type) addop 'loop', 0x03, :blocksig # ^ addop 'if', 0x04, :blocksig, :setip # ^ addop 'else', 0x05, :setip, :stopexec addop 'end', 0x0b, :stopexec # end of function (default with no function context) addop 'end', 0x0b # end of if/else/block/loop addop 'br', 0x0c, :uleb, :setip, :stopexec # arg = depth to break up to addop 'br_if', 0x0d, :uleb, :setip addop 'br_table', 0x0e, :br_table, :setip, :stopexec addop 'return', 0x0f, :stopexec addop 'call', 0x10, :uleb, :setip, :saveip, :stopexec # function index addop 'call_indirect', 0x11, :uleb, :uleb, :setip, :saveip, :stopexec # type index for target function signature ; table index where the function indexes come from (fixed 0) addop 'drop', 0x1a addop 'select', 0x1b addop 'get_local', 0x20, :uleb addop 'set_local', 0x21, :uleb addop 'tee_local', 0x22, :uleb addop 'get_global', 0x23, :uleb addop 'set_global', 0x24, :uleb addop 'i32.load', 0x28, :mem addop 'i64.load', 0x29, :mem addop 'f32.load', 0x2a, :mem addop 'f64.load', 0x2b, :mem addop 'i32.load8_s', 0x2c, :mem addop 'i32.load8_u', 0x2d, :mem addop 'i32.load16_s', 0x2e, :mem addop 'i32.load16_u', 0x2f, :mem addop 'i64.load8_s', 0x30, :mem addop 'i64.load8_u', 0x31, :mem addop 'i64.load16_s', 0x32, :mem addop 'i64.load16_u', 0x33, :mem addop 'i64.load32_s', 0x34, :mem addop 'i64.load32_u', 0x35, :mem addop 'i32.store', 0x36, :mem addop 'i64.store', 0x37, :mem addop 'f32.store', 0x38, :mem addop 'f64.store', 0x39, :mem addop 'i32.store8', 0x3a, :mem addop 'i32.store16', 0x3b, :mem addop 'i64.store8', 0x3c, :mem addop 'i64.store16', 0x3d, :mem addop 'i64.store32', 0x3e, :mem addop 'current_memory', 0x3f, :uleb # resv1 addop 'grow_memory', 0x40, :uleb # resv1 addop 'i32.const', 0x41, :sleb addop 'i64.const', 0x42, :sleb addop 'f32.const', 0x43, :f32 addop 'f64.const', 0x44, :f64 addop 'i32.eqz', 0x45 addop 'i32.eq', 0x46 addop 'i32.ne', 0x47 addop 'i32.lt_s', 0x48 addop 'i32.lt_u', 0x49 addop 'i32.gt_s', 0x4a addop 'i32.gt_u', 0x4b addop 'i32.le_s', 0x4c addop 'i32.le_u', 0x4d addop 'i32.ge_s', 0x4e addop 'i32.ge_u', 0x4f addop 'i64.eqz', 0x50 addop 'i64.eq', 0x51 addop 'i64.ne', 0x52 addop 'i64.lt_s', 0x53 addop 'i64.lt_u', 0x54 addop 'i64.gt_s', 0x55 addop 'i64.gt_u', 0x56 addop 'i64.le_s', 0x57 addop 'i64.le_u', 0x58 addop 'i64.ge_s', 0x59 addop 'i64.ge_u', 0x5a addop 'f32.eq', 0x5b addop 'f32.ne', 0x5c addop 'f32.lt', 0x5d addop 'f32.gt', 0x5e addop 'f32.le', 0x5f addop 'f32.ge', 0x60 addop 'f64.eq', 0x61 addop 'f64.ne', 0x62 addop 'f64.lt', 0x63 addop 'f64.gt', 0x64 addop 'f64.le', 0x65 addop 'f64.ge', 0x66 addop 'i32.clz', 0x67 addop 'i32.ctz', 0x68 addop 'i32.popcnt', 0x69 addop 'i32.add', 0x6a addop 'i32.sub', 0x6b addop 'i32.mul', 0x6c addop 'i32.div_s', 0x6d addop 'i32.div_u', 0x6e addop 'i32.rem_s', 0x6f addop 'i32.rem_u', 0x70 addop 'i32.and', 0x71 addop 'i32.or', 0x72 addop 'i32.xor', 0x73 addop 'i32.shl', 0x74 addop 'i32.shr_s', 0x75 addop 'i32.shr_u', 0x76 addop 'i32.rotl', 0x77 addop 'i32.rotr', 0x78 addop 'i64.clz', 0x79 addop 'i64.ctz', 0x7a addop 'i64.popcnt', 0x7b addop 'i64.add', 0x7c addop 'i64.sub', 0x7d addop 'i64.mul', 0x7e addop 'i64.div_s', 0x7f addop 'i64.div_u', 0x80 addop 'i64.rem_s', 0x81 addop 'i64.rem_u', 0x82 addop 'i64.and', 0x83 addop 'i64.or', 0x84 addop 'i64.xor', 0x85 addop 'i64.shl', 0x86 addop 'i64.shr_s', 0x87 addop 'i64.shr_u', 0x88 addop 'i64.rotl', 0x89 addop 'i64.rotr', 0x8a addop 'f32.abs', 0x8b addop 'f32.neg', 0x8c addop 'f32.ceil', 0x8d addop 'f32.floor', 0x8e addop 'f32.trunc', 0x8f addop 'f32.nearest', 0x90 addop 'f32.sqrt', 0x91 addop 'f32.add', 0x92 addop 'f32.sub', 0x93 addop 'f32.mul', 0x94 addop 'f32.div', 0x95 addop 'f32.min', 0x96 addop 'f32.max', 0x97 addop 'f32.copysign', 0x98 addop 'f64.abs', 0x99 addop 'f64.neg', 0x9a addop 'f64.ceil', 0x9b addop 'f64.floor', 0x9c addop 'f64.trunc', 0x9d addop 'f64.nearest', 0x9e addop 'f64.sqrt', 0x9f addop 'f64.add', 0xa0 addop 'f64.sub', 0xa1 addop 'f64.mul', 0xa2 addop 'f64.div', 0xa3 addop 'f64.min', 0xa4 addop 'f64.max', 0xa5 addop 'f64.copysign', 0xa6 addop 'i32.wrap/i64', 0xa7 addop 'i32.trunc_s/f32', 0xa8 addop 'i32.trunc_u/f32', 0xa9 addop 'i32.trunc_s/f64', 0xaa addop 'i32.trunc_u/f64', 0xab addop 'i64.extend_s/i32', 0xac addop 'i64.extend_u/i32', 0xad addop 'i64.trunc_s/f32', 0xae addop 'i64.trunc_u/f32', 0xaf addop 'i64.trunc_s/f64', 0xb0 addop 'i64.trunc_u/f64', 0xb1 addop 'f32.convert_s/i32', 0xb2 addop 'f32.convert_u/i32', 0xb3 addop 'f32.convert_s/i64', 0xb4 addop 'f32.convert_u/i64', 0xb5 addop 'f32.demote/f64', 0xb6 addop 'f64.convert_s/i32', 0xb7 addop 'f64.convert_u/i32', 0xb8 addop 'f64.convert_s/i64', 0xb9 addop 'f64.convert_u/i64', 0xba addop 'f64.promote/f32', 0xbb addop 'i32.reinterpret/f32', 0xbc addop 'i64.reinterpret/f64', 0xbd addop 'f32.reinterpret/i32', 0xbe addop 'f64.reinterpret/i64', 0xbf end
init_backtrace_binding()
click to toggle source
# File metasm/cpu/webasm/decode.rb, line 158 def init_backtrace_binding @backtrace_binding ||= {} typesz = Hash.new(8).update 'i32' => 4, 'f32' => 4 opstack = lambda { |off, sz| Indirection[Expression[:opstack, :+, off].reduce, sz] } add_opstack = lambda { |delta, hash| { :opstack => Expression[:opstack, :+, delta].reduce }.update hash } globsz = lambda { |di| glob_nr = Expression[di.instruction.args.first].reduce g = @wasm_file.get_global_nr(glob_nr) g ? typesz[g[:type]] : 8 } global = lambda { |di| glob_nr = Expression[di.instruction.args.first].reduce g = @wasm_file.get_global_nr(glob_nr) n = g && g[:module] ? "#{g[:module]}_#{g[:field]}" : "global_#{glob_nr}" Indirection[n, globsz[di]] } locsz = lambda { |di| loc_nr = Expression[di.instruction.args.first].reduce ci = @wasm_file.code_info[di.misc[:code_start]] next typesz[ci[:params][loc_nr]] if loc_nr < ci[:params].length loc_nr -= ci[:params].length next typesz[ci[:local_var][loc_nr]] if ci[:local_var][loc_nr] 8 } local = lambda { |di| loc_nr = Expression[di.instruction.args.first].reduce Indirection[[:local_base, :+, loc_nr*8], locsz[di]] } opcode_list.map { |ol| ol.name }.uniq.each { |opname| sz = (opname[1, 2] == '32' ? 4 : 8) @backtrace_binding[opname] ||= case opname when 'call', 'call_indirect' lambda { |di| stack_off = 0 if opname == 'call' f = @wasm_file.get_function_nr(di.misc[:tg_func_nr]) proto = f ? f[:type] : {} # TODO use local_base h = { :callstack => Expression[:callstack, :+, 8], Indirection[:callstack, 8] => Expression[di.next_addr] } proto_params_offset = 0 else proto = @wasm_file.type[di.instruction.args.first.reduce] h = { :callstack => Expression[:callstack, :+, 8], Indirection[:callstack, 8] => Expression[di.next_addr], 'func_idx' => Expression[opstack[0, 4]] } stack_off += 8 proto_params_offset = 1 end stack_off -= 8*proto[:ret].to_a.length stack_off += 8*proto[:params].to_a.length h.update :opstack => Expression[:opstack, :+, stack_off] proto[:ret].to_a.each_with_index { |rt, i| h.update opstack[8*i, typesz[rt]] => Expression["ret_#{i}"] } proto[:params].to_a.each_with_index { |pt, i| h.update "param_#{i}" => Expression[opstack[8*(proto[:params].length-i-1+proto_params_offset), typesz[pt]]] } h } when 'if', 'br_if'; lambda { |di| add_opstack[ 8, :flag => Expression[opstack[0, 8]]] } when 'block', 'loop', 'br', 'nop', 'else'; lambda { |di| {} } when 'end', 'return'; lambda { |di| di.opcode.props[:stopexec] ? { :callstack => Expression[:callstack, :-, 8] } : {} } when 'drop'; lambda { |di| add_opstack[8, {}] } when 'select'; lambda { |di| add_opstack[16, opstack[0, 8] => Expression[[opstack[8, 8], :*, [1, :-, opstack[0, 8]]], :|, [opstack[16, 8], :*, opstack[0, 8]]]] } when 'get_local'; lambda { |di| add_opstack[-8, opstack[0, locsz[di]] => Expression[local[di]]] } when 'set_local'; lambda { |di| add_opstack[ 8, local[di] => Expression[opstack[0, locsz[di]]]] } when 'tee_local'; lambda { |di| add_opstack[ 0, local[di] => Expression[opstack[0, locsz[di]]]] } when 'get_global'; lambda { |di| add_opstack[-8, opstack[0, globsz[di]] => Expression[global[di]]] } when 'set_global'; lambda { |di| add_opstack[ 8, global[di] => Expression[opstack[0, globsz[di]]]] } when /\.load(.*)/ mode = $1; memsz = (mode.include?('32') ? 4 : mode.include?('16') ? 2 : mode.include?('8') ? 1 : sz) lambda { |di| add_opstack[ 0, opstack[0, sz] => Expression[Indirection[[opstack[0, 4], :+, [:mem, :+, di.instruction.args[1].off]], memsz]]] } when /\.store(.*)/ mode = $1; memsz = (mode.include?('32') ? 4 : mode.include?('16') ? 2 : mode.include?('8') ? 1 : sz) lambda { |di| add_opstack[ 16, Indirection[[opstack[8, 4], :+, [:mem, :+, di.instruction.args[1].off]], memsz] => Expression[opstack[0, sz], :&, (1 << (8*memsz)) - 1]] } when /\.const/; lambda { |di| add_opstack[-8, opstack[0, sz] => Expression[di.instruction.args.first.reduce]] } when /\.eqz/; lambda { |di| add_opstack[ 0, opstack[0, 8] => Expression[opstack[0, sz], :==, 0]] } when /\.eq/; lambda { |di| add_opstack[ 8, opstack[0, 8] => Expression[opstack[8, sz], :==, opstack[0, sz]]] } when /\.ne/; lambda { |di| add_opstack[ 8, opstack[0, 8] => Expression[opstack[8, sz], :!=, opstack[0, sz]]] } when /\.lt/; lambda { |di| add_opstack[ 8, opstack[0, 8] => Expression[opstack[8, sz], :<, opstack[0, sz]]] } when /\.gt/; lambda { |di| add_opstack[ 8, opstack[0, 8] => Expression[opstack[8, sz], :>, opstack[0, sz]]] } when /\.le/; lambda { |di| add_opstack[ 8, opstack[0, 8] => Expression[opstack[8, sz], :<=, opstack[0, sz]]] } when /\.ge/; lambda { |di| add_opstack[ 8, opstack[0, 8] => Expression[opstack[8, sz], :>=, opstack[0, sz]]] } when /\.(clz|ctz|popcnt)/; lambda { |di| add_opstack[ 0, :bits => Expression[opstack[0, sz]]] } when /\.add/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :+, opstack[0, sz]]] } when /\.sub/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :-, opstack[0, sz]]] } when /\.mul/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :*, opstack[0, sz]]] } when /\.div/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :/, opstack[0, sz]]] } when /\.rem/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :%, opstack[0, sz]]] } when /\.and/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :&, opstack[0, sz]]] } when /\.or/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :|, opstack[0, sz]]] } when /\.xor/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :^, opstack[0, sz]]] } when /\.shl/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :<<, opstack[0, sz]]] } when /\.shr/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[opstack[8, sz], :>>, opstack[0, sz]]] } when /\.rotl/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[[opstack[8, sz], :<<, opstack[0, sz]], :|, [opstack[8, sz], :>>, [8*sz, :-, opstack[0, sz]]]]] } when /\.rotr/; lambda { |di| add_opstack[ 8, opstack[0, sz] => Expression[[opstack[8, sz], :>>, opstack[0, sz]], :|, [opstack[8, sz], :<<, [8*sz, :-, opstack[0, sz]]]]] } when /f.*\.(abs|neg|ceil|floor|trunc|nearest|sqrt|copysign)/; lambda { |di| add_opstack[0, :incomplete_binding => 1] } when /f.*\.(min|max)/; lambda { |di| add_opstack[8, :incomplete_binding => 1] } when /i32.wrap/; lambda { |di| add_opstack[ 0, opstack[0, 4] => Expression[opstack[0, 8]]] } when /i64.extend/; lambda { |di| add_opstack[ 0, opstack[0, 8] => Expression[opstack[0, 4]]] } when /trunc|convert|promote|demote|reinterpret/; lambda { |di| add_opstack[0, :incomplete_binding => 1] } end } @backtrace_binding end
init_opcode_list()
click to toggle source
# File metasm/cpu/webasm/main.rb, line 77 def init_opcode_list init end
parse_arg_valid?(o, spec, arg)
click to toggle source
# File metasm/cpu/webasm/encode.rb, line 53 def parse_arg_valid?(o, spec, arg) spec and arg end
parse_argument(lexer)
click to toggle source
# File metasm/cpu/webasm/encode.rb, line 12 def parse_argument(lexer) lexer = AsmPreprocessor.new(lexer) if lexer.kind_of? String lexer.skip_space return if not tok = lexer.readtok if tok.type == :punct and tok.raw == '[' # Memref or BrTable ary = [] loop do # XXX empty array for BrTable ? ary << parse_argument(lexer) raise tok, 'bad ptr' if not ary.last.kind_of?(Expression) lexer.skip_space tok2 = lexer.readtok if tok2 and tok2.type == :punct and tok2.raw == ']' break elsif not tok2 or tok2.type != :punct or tok2.raw != ',' raise tok, "unexpected #{tok2 ? 'eof' : tok2.raw}" end end lexer.skip_space tok2 = lexer.readtok if tok2 and tok2.type == :string and tok2.raw == 'or' # BrTable df = parse_argument(lexer) BrTable.new(ary, df) else raise tok, 'bad Memref/BrTable' if ary.length != 1 lexer.unreadtok(tok2) if tok2 Memref.new(ary[0]) end elsif WasmFile::TYPE.index(tok.raw) BlockSignature.new(WasmFile::TYPE.index(tok.raw)) else lexer.unreadtok tok expr = Expression.parse(lexer) lexer.skip_space expr end end
parse_instruction_mnemonic(lexer)
click to toggle source
# File metasm/cpu/webasm/encode.rb, line 57 def parse_instruction_mnemonic(lexer) return if not tok = lexer.readtok tok = tok.dup while ntok = lexer.nexttok and ntok.type == :punct and (ntok.raw == '.' or ntok.raw == '/') tok.raw << lexer.readtok.raw ntok = lexer.readtok raise tok, 'invalid opcode name' if not ntok or ntok.type != :string tok.raw << ntok.raw end raise tok, 'invalid opcode' if not opcode_list_byname[tok.raw] tok end
wasm_type_to_type(t)
click to toggle source
# File metasm/cpu/webasm/decompile.rb, line 11 def wasm_type_to_type(t) case t when 'i32'; C::BaseType.new(:int) when 'i64'; C::BaseType.new(:longlong) when 'f32'; C::BaseType.new(:float) when 'f64'; C::BaseType.new(:double) when 'anyfunc'; C::Function.new(C::BaseType.new(:void)) when Hash ret = t[:ret].first ? wasm_type_to_type(t[:ret].first) : C::BaseType.new(:void) args = t[:params].map { |p| C::Variable.new(nil, wasm_type_to_type(p)) } C::Function.new(ret, args) end end