Skip to content

Commit

Permalink
x64: should work now
Browse files Browse the repository at this point in the history
  • Loading branch information
jj committed Jul 8, 2009
1 parent d1037b7 commit 563e7bc
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 23 deletions.
2 changes: 1 addition & 1 deletion metasm/ia32/opcodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ def addop_post(op)
op32.name << '.i32'
op32.props[:opsz] = 32
@opcode_list << op32
elsif op.props[:strop] or op.props[:stropz]
elsif op.props[:strop] or op.props[:stropz] or op.args.include? :mrm_imm
# define adsz-override version for ambiguous opcodes (TODO allow movsd edi / movsd di)
op16 = dupe[op]
op16.name << '.a16'
Expand Down
4 changes: 2 additions & 2 deletions metasm/x86_64/decode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def decode_instr_op(edata, di)
}
field_val_r = lambda { |f|
v = field_val[f]
v |= 8 if v and pfx[:rex_r]
v |= 8 if v and (op.fields[f][1] == 3 ? pfx[:rex_r] : pfx[:rex_b]) # gruick ?
v
}

Expand Down Expand Up @@ -169,7 +169,7 @@ def decode_instr_op(edata, di)
def opsz(di)
if di and di.instruction.prefix and di.instruction.prefix[:rex_w]; 64
elsif di and di.instruction.prefix and di.instruction.prefix[:opsz]; 16
elsif di and di.opcode.name =~ /^(j|loop|(call|enter|leave|lgdt|lidt|lldt|ltr|pop|push|ret)$)/; 64
elsif di and di.opcode.props[:auto64]; 64
else 32
end
end
Expand Down
39 changes: 28 additions & 11 deletions metasm/x86_64/encode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
module Metasm
class X86_64
class ModRM
def self.encode_reg(reg, mregval = 0)
v = reg.kind_of?(Reg) ? reg.val_enc : reg.val & 7
0xc0 | (mregval << 3) | v
end

def encode(reg = 0, endianness = :little)
reg = reg.val if reg.kind_of? Ia32::Argument

Expand Down Expand Up @@ -121,7 +126,7 @@ def encode_instr_op(program, i, op)
when 16; pfx << 0x66
end
else
opsz = op.props[:argsz] || i.prefix[:sz]
opsz = op.props[:argsz] || i.prefix[:sz] || (64 if op.props[:auto64]) # XXX test push ax
oi.each { |oa, ia|
case oa
when :reg, :reg_eax, :modrm, :modrmA, :mrm_imm
Expand All @@ -131,7 +136,7 @@ def encode_instr_op(program, i, op)
}
opsz = op.props[:opsz] if op.props[:opsz] # XXX ?
case opsz
when 64; rex_w = 1 # TODO check autopromoted opcodes (push etc)
when 64; rex_w = 1 if not op.props[:auto64]
when 32
when 16; pfx << 0x66
end
Expand All @@ -143,16 +148,11 @@ def encode_instr_op(program, i, op)
mrm.encode(0, @endianness) # may reorder b/i, which must be correct for rex
rex_b = 1 if mrm.b and mrm.b.val_rex.to_i > 0
rex_x = 1 if mrm.i and mrm.i.val_rex.to_i > 0
if (mrm.b and mrm.b.sz == 32) or (mrm.i and mrm.i.sz == 32)
pfx << 0x67
adsz = 32 # XXX used only with mrm_imm (mov eax, [addr])
end
pfx << 0x67 if (mrm.b and mrm.b.sz == 32) or (mrm.i and mrm.i.sz == 32)
pfx << [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][mrm.seg.val] if mrm.seg
elsif op.props[:adsz] and op.propz[:adsz] == 32
pfx << 0x67
adsz = 32
end
adsz ||= @size


#
Expand All @@ -163,12 +163,29 @@ def encode_instr_op(program, i, op)
case oa
when :reg
set_field[oa, ia.val_enc]
rex_r = ia.val_rex # TODO ah bh vs rex
if op.fields[:reg][1] == 3
rex_r = ia.val_rex
else
rex_b = ia.val_rex
end
when :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :regxmm
set_field[oa, ia.val & 7]
rex_r = 1 if ia.val > 7
when :imm_val1, :imm_val3, :reg_cl, :reg_eax, :reg_dx, :regfp0
# implicit
when :modrm, :modrmA, :modrmmmx, :modrmxmm
# postpone, but we must set rex now
case ia
when ModRM
ia.encode(0, @endianness) # could swap b/i
rex_x = ia.i.val_rex if ia.i
rex_b = ia.b.val_rex if ia.b
when Reg
rex_b = ia.val_rex
else
rex_b = ia.val >> 3
end
postponed << [oa, ia]
else
postponed << [oa, ia]
end
Expand All @@ -188,7 +205,7 @@ def encode_instr_op(program, i, op)
postponed.first[1] = Expression[target, :-, postlabel]
end

if rex_w or rex_r or rex_b or rex_x
if rex_w == 1 or rex_r == 1 or rex_b == 1 or rex_x == 1 or i.args.grep(Reg).find { |r| r.sz == 8 and r.val >= 4 and r.val < 8 }
rex ||= 0x40
rex |= 1 if rex_b.to_i > 0
rex |= 2 if rex_x.to_i > 0
Expand Down Expand Up @@ -218,7 +235,7 @@ def encode_instr_op(program, i, op)
else
ed = ModRM.encode_reg(ia, regval)
end
when :mrm_imm; ed = ia.imm.encode("a#{adsz}".to_sym, @endianness)
when :mrm_imm; ed = ia.imm.encode("a#{o.props[:adsz] || 64}".to_sym, @endianness)
when :i8, :u8, :i16, :u16, :i32, :u32, :i64, :u64; ed = ia.encode(oa, @endianness)
when :i
type = opsz == 64 ? op.props[:imm64] ? :a64 : :i32 : :a32
Expand Down
9 changes: 3 additions & 6 deletions metasm/x86_64/opcodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ def init_cpu_constants

def init_386_common_only
super()
@valid_props |= [:imm64]
@valid_props |= [:imm64, :auto64]
@opcode_list.delete_if { |o| o.bin[0].to_i & 0xf0 == 0x40 } # now REX prefix
@opcode_list.each { |o| o.props[:imm64] = true if o.bin == [0xB8] } # mov reg, <true imm64>
@opcode_list.each { |o| o.props[:auto64] = true if o.name =~ /^(j|loop|(call|enter|leave|lgdt|lidt|lldt|ltr|pop|push|ret)$)/ } # operate in 64bit ignoring rex_w
end

# all x86_64 cpu understand <= sse2 instrs
Expand Down Expand Up @@ -94,12 +95,8 @@ def addop_post(op)
op64.name << '.i64'
op64.props[:opsz] = 64
@opcode_list << op64
elsif op.props[:strop] or op.props[:stropz]
elsif op.props[:strop] or op.props[:stropz] or op.args.include? :mrm_imm
# define adsz-override version for ambiguous opcodes (movsq)
op16 = dupe[op]
op16.name << '.a16'
op16.props[:adsz] = 16
@opcode_list << op16
op32 = dupe[op]
op32.name << '.a32'
op32.props[:adsz] = 32
Expand Down
15 changes: 12 additions & 3 deletions metasm/x86_64/parse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,22 @@ def parse_argument(lexer)
end
end

def parse_instruction_checkproto(i)
# check ah vs rex prefix
return if i.args.find { |a| a.kind_of? Reg and a.sz == 8 and a.val >= 16 and
op = opcode_list.find { |op_| op_.name == i.opname } and
((not op.props[:auto64] and i.args.find { |aa| aa.respond_to? :sz and aa.sz == 64 }) or
i.args.find { |aa| aa.kind_of? Reg and aa.val >= 8 and aa.val < 16 } or # XXX mov ah, cr12...
i.args.grep(ModRM).find { |aa| (aa.b and aa.b.val >= 8 and aa.b.val < 16) or (aa.i and aa.i.val >= 8 and aa.i.val < 16) })
}
super(i)
end

# check if the argument matches the opcode's argument spec
# TODO check ah vs dil/r15
# XXX imm range?
def parse_arg_valid?(o, spec, arg)
return if arg.kind_of? ModRM and ((arg.b and arg.b.val == 16 and arg.i) or (arg.i and arg.i.val == 16 and (arg.b or arg.s != 1)))
return if arg.kind_of? Reg and arg.sz >= 32 and arg.val == 16 # eip/rip only in modrm
#return if o.props[:only64] and arg.respond_to? :sz and arg.sz == 32 # TODO use :only64 in the opcode_list (push pop etc)
return if o.props[:auto64] and arg.respond_to? :sz and arg.sz == 322
super(o, spec, arg)
end
end
Expand Down
62 changes: 62 additions & 0 deletions tests/x86_64.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# This file is part of Metasm, the Ruby assembly manipulation suite
# Copyright (C) 2009 Yoann GUILLOT
#
# Licence is LGPL, see LICENCE in the top-level directory


require 'test/unit'
require 'metasm'

class TestX86_64 < Test::Unit::TestCase
@@cpu = Metasm::X86_64.new
def assemble(src, cpu=@@cpu)
Metasm::Shellcode.assemble(cpu, src).encode_string
end

def test_user
assert_equal(Metasm::X86_64, Metasm::Ia32.new(64).class)
end

def test_basic
assert_equal("\x90", assemble("nop"))
assert_equal("\x50", assemble("push rax"))
assert_equal("\x41\x50", assemble("push r8"))
assert_equal("\x6a\x02", assemble("push 2"))
assert_equal("\x68\x8e\0\0\0", assemble("push 142"))
assert_equal("\x48\xbb\xef\xcd\xab\x89\x67\x45\x23\x01", assemble("mov rbx, 0123456789abcdefh"))
assert_equal("\x8d\x05\x0c\0\0\0", assemble("lea eax, [rip+12]"))
assert_equal("\x8d\x04\x25\x0c\0\0\0", assemble("lea eax, [12]"))
end

def test_err
assert_raise(Metasm::ParseError) { assemble("add eax") }
assert_raise(Metasm::ParseError) { assemble("add add, ebx") }
assert_raise(Metasm::ParseError) { assemble("add 42, ebx") }
assert_raise(Metasm::ParseError) { assemble("add [bx]") }
assert_raise(Metasm::ParseError) { assemble("add [eip+4*eax]") }
assert_raise(Metasm::ParseError) { assemble("add ah, r8b") }
end

def disassemble(bin, cpu=@@cpu)
Metasm::Shellcode.disassemble(cpu, bin)
end

def test_dasm
d = disassemble("\x90")
assert_equal(Metasm::DecodedInstruction, d.decoded[0].class)
assert_equal('nop', d.decoded[0].opcode.name)
end

def test_rex
assert_equal("\xfe\xc0", assemble("inc al"))
assert_equal("\xfe\xc4", assemble("inc ah"))
assert_equal("\x40\xfe\xc4", assemble("inc spl"))
assert_equal("\x41\xfe\xc4", assemble("inc r12b"))
op = lambda { |s| disassemble(s).decoded[0].instruction.args.last }
assert_equal('al', op["\xfe\xc0"].to_s)
assert_equal('ah', op["\xfe\xc4"].to_s)
assert_equal('spl', op["\x40\xfe\xc4"].to_s)
assert_equal('r12b', op["\x41\xfe\xc4"].to_s)
assert_equal('dword ptr [rip+0ch]', op["\x8d\x05\x0c\0\0\0"].to_s)
end
end

0 comments on commit 563e7bc

Please sign in to comment.