mirror of
https://github.com/samsonjs/compiler.git
synced 2026-03-25 08:45:52 +00:00
1040 lines
26 KiB
Ruby
1040 lines
26 KiB
Ruby
# A very basic x86 assembler library for Ruby. Generally the
|
|
# instructions implemented are the minimum needed by the compiler this
|
|
# is written for. x86 is just too big.
|
|
#
|
|
# sjs
|
|
# may 2009
|
|
#
|
|
# Refer to the Intel[1] or AMD documentationon on x86 for explanations
|
|
# of Mod-R/M encoding, the Scale-Index-Base (SIB) byte, opcode groups.
|
|
#
|
|
# The start and exit shell codes were obtained by disassembling
|
|
# minimal binaries on the respective platforms.
|
|
|
|
require 'asm/asm'
|
|
require 'asm/varproxy'
|
|
|
|
module Assembler
|
|
|
|
class Binary < AssemblerBase
|
|
|
|
include Registers
|
|
|
|
DEBUG_OUTPUT = false
|
|
|
|
# 0.size gives the real answer, we only do x86-32 though
|
|
MachineBytes = 4
|
|
MachineBits = MachineBytes * 8
|
|
MinSigned = -1 * 2**(MachineBits-1)
|
|
MaxSigned = 2**(MachineBits-1) - 1
|
|
MinUnsigned = 0
|
|
MaxUnsigned = 2**MachineBits - 1
|
|
SignedInt = MinSigned..MaxSigned
|
|
SignedByte = -128..127
|
|
|
|
# This is used for encoding instructions. Just as the equivalent
|
|
# assembly would contain "BITS 32", binary is generated for 32-bit
|
|
# protected mode.
|
|
DefaultOperandSize = :dword
|
|
|
|
SizeMap = {:byte => 8, :word => 16, :dword => 32}
|
|
|
|
X86_start = {
|
|
'linux' => [],
|
|
'darwin' => [ 0x55, # push ebp
|
|
0x89, 0xe5, # mov ebp, esp
|
|
0x81, 0xec, 8, 0, 0, 0 # sub esp, 8
|
|
]
|
|
}
|
|
|
|
X86_exit = {
|
|
'linux' => [ 0x89, 0xc3, # mov ebx, eax (exit code)
|
|
0xb8, 1, 0, 0, 0, # mov eax, 1
|
|
0xcd, 0x80 # int 0x80
|
|
],
|
|
|
|
'darwin' => [ 0xc9, # leave
|
|
0xc3 # ret
|
|
]
|
|
}
|
|
|
|
attr_reader :ip
|
|
|
|
def initialize(platform, symtab, objwriter_class)
|
|
super(platform)
|
|
@symtab = symtab
|
|
@objwriter_class = objwriter_class
|
|
# @objwriter = objwriter
|
|
|
|
# Almost a byte array, except for addresses.
|
|
#
|
|
# Addresses take the form [:<type>, <name>]
|
|
# where <type> is one of: var, const, or label
|
|
#
|
|
# NOTE the type is redundant because of VariableProxy#const?
|
|
# and labels are just strings.
|
|
#
|
|
# however, we could accept strings for variable names
|
|
# if we keep the type tag. something to think about.
|
|
@ir = []
|
|
|
|
# Our instruction pointer, or the number of bytes written.
|
|
@ip = 0
|
|
|
|
# Map locations in the byte array to var proxies so we can
|
|
# resolve address operations on the 2nd pass.
|
|
@proxies = {}
|
|
|
|
# Always include the _main entry point in our symbol table. It begins at the
|
|
# beginning of the __TEXT segment, 0x0.
|
|
@symtab.deflabel('_main', @ip)
|
|
|
|
X86_start[@platform].each {|byte| emit_byte(byte)}
|
|
end
|
|
|
|
def output
|
|
X86_exit[@platform].each {|byte| emit_byte(byte)}
|
|
|
|
byte_array = resolve_labels
|
|
|
|
#puts "1st pass: " + byte_array.inspect if DEBUG_OUTPUT
|
|
|
|
binary = package(byte_array)
|
|
|
|
@symtab.calculate_offsets(binary.length)
|
|
if DEBUG_OUTPUT
|
|
puts ">>> text offset: 0x#{@symtab.text_offset.to_s(16)}"
|
|
puts ">>> const offset: 0x#{@symtab.const_offset.to_s(16)}"
|
|
puts ">>> bss offset: 0x#{@symtab.bss_offset.to_s(16)}"
|
|
end
|
|
|
|
# Now that we know where everything lies do the 2nd pass
|
|
# calculating and filling in final var and const addresses.
|
|
#
|
|
# outline:
|
|
# - resolve all variable proxies in @proxies replacing
|
|
# the 4 bytes (0xff) with the real address
|
|
|
|
bss_offset = @symtab.bss_offset
|
|
const_offset = @symtab.const_offset
|
|
@proxies.each do |i, proxy|
|
|
#puts ">>> Resolving #{proxy.name}" if DEBUG_OUTPUT
|
|
var = @symtab.var(proxy.name)
|
|
base_addr = if proxy.const?
|
|
const_offset + @symtab.const(proxy.name)
|
|
else
|
|
bss_offset + @symtab.var(proxy.name)
|
|
end
|
|
#puts ">>> Replacing #{byte_array[i,4].map{|x|'0x' + x.to_s(16)}.inspect} with #{num_to_quad(proxy.resolve(base_addr)).map{|x|'0x' + x.to_s(16)}.inspect}" if DEBUG_OUTPUT
|
|
byte_array[i, 4] = num_to_quad(proxy.resolve(base_addr))
|
|
end
|
|
|
|
binary = package(byte_array)
|
|
|
|
#puts "2nd pass: " + byte_array.inspect if DEBUG_OUTPUT
|
|
|
|
objwriter = @objwriter_class.new
|
|
objwriter.text(binary)
|
|
objwriter.const(@symtab.const_data) if @symtab.const_size > 0
|
|
objwriter.bss(@symtab.bss_size) if @symtab.bss_size > 0
|
|
objwriter.reloc(@symtab.reloc_info)
|
|
objwriter.symtab(@symtab)
|
|
objwriter.serialize
|
|
end
|
|
|
|
def resolve_labels
|
|
bytes_read = 0
|
|
bytes = []
|
|
@ir.each_with_index do |x, i|
|
|
if x.is_a?(Numeric)
|
|
bytes << x
|
|
bytes_read += 1
|
|
|
|
elsif addr?(x)
|
|
# remember this so we can replace the bogus addr later
|
|
@proxies[bytes_read] = x[1]
|
|
|
|
# add a relocation entry for this address
|
|
@symtab.reloc(bytes_read)
|
|
|
|
# fill in said bogus addr
|
|
bytes += [0xff, 0xff, 0xff, 0xff]
|
|
|
|
bytes_read += 4
|
|
|
|
|
|
# TODO find out if we should calculate addrs as offsets rather than
|
|
# absolute as they are done now. (ok for Mach-O, maybe not ELF)
|
|
elsif label?(x)
|
|
# the actual eip points to the next instruction already, so should we.
|
|
real_ip = bytes_read + 4
|
|
name = x[1]
|
|
addr = @symtab.lookup_label(name) - real_ip # dest - src to get relative addr
|
|
#puts "resolved label: #{x} = 0x#{@symtab.lookup_label(name).to_s(16)} (rel: 0x#{addr.to_s(16)}, ip = 0x#{real_ip.to_s(16)}, bytes_read = 0x#{bytes_read.to_s(16)})" if DEBUG_OUTPUT
|
|
|
|
|
|
bytes += num_to_quad(addr)
|
|
bytes_read += 4
|
|
|
|
else
|
|
raise "unknown value in the IR at #{bytes_read} - #{x.inspect}"
|
|
end
|
|
end
|
|
|
|
return bytes
|
|
end
|
|
|
|
def package(bytes)
|
|
bytes.pack('c*')
|
|
end
|
|
|
|
# Silly semantics, but labels don't count as an address since they
|
|
# don't need to be deferred.
|
|
def addr?(x)
|
|
x.is_a?(Array) && [:var, :const].include?(x[0])
|
|
end
|
|
|
|
def label?(x)
|
|
x.is_a?(Array) && x[0] == :label
|
|
end
|
|
|
|
# XXX this should probably evaluate the value somehow
|
|
def defconst(name, bytes, value)
|
|
@symtab.defconst(name, bytes, value)
|
|
return const(name)
|
|
end
|
|
|
|
# Define a variable with the given name and size in bytes.
|
|
def defvar(name, bytes=MachineBytes)
|
|
unless @symtab.var?(name)
|
|
@symtab.defvar(name, bytes)
|
|
else
|
|
STDERR.puts "[warning] attempted to redefine #{name}"
|
|
end
|
|
return var(name)
|
|
end
|
|
|
|
def var(name)
|
|
STDERR.puts "[error] undefined variable #{name}" unless var?(name)
|
|
# TODO bail on undefined vars
|
|
VariableProxy.new(name)
|
|
end
|
|
|
|
def const(name)
|
|
STDERR.puts "[error] undefined variable #{name}" unless const?(name)
|
|
# TODO bail on undefined consts
|
|
VariableProxy.new(name, true)
|
|
end
|
|
|
|
def var?(name)
|
|
@symtab.var?(name)
|
|
end
|
|
|
|
def const?(name)
|
|
@symtab.const?(name)
|
|
end
|
|
|
|
# Define a variable unless it exists.
|
|
def var!(name, bytes=MachineBytes)
|
|
if var?(name)
|
|
var(name)
|
|
else
|
|
defvar(name, bytes)
|
|
end
|
|
end
|
|
|
|
# Count the bytes that were encoded in the given block.
|
|
def asm
|
|
# stash the current number of bytes written
|
|
instruction_offset = @ip
|
|
|
|
print "0x#{@ip.to_s(16).rjust(4, '0')}\t" if DEBUG_OUTPUT
|
|
|
|
yield
|
|
|
|
# return the number of bytes written
|
|
@ip - instruction_offset
|
|
|
|
puts if DEBUG_OUTPUT
|
|
end
|
|
|
|
|
|
def emit_byte(byte)
|
|
|
|
##### The joke's on me! Array#pack('c*') already does this. It is nice to see
|
|
# in the debugging output though, so it stays for now.
|
|
#
|
|
# Convert negative native ints into signed bytes.
|
|
#
|
|
# Calculate the signed byte as the difference between -1 (0xff) and some
|
|
# number, X. When byte == -1 we want X == 0, so X == -byte - 1.
|
|
# Since -byte == ~byte + 1, then -byte - 1 == ~byte + 1 - 1 == ~byte,
|
|
# and X == ~byte. We want the *signed byte* -1, so we use 0xff,
|
|
# *not* -1. Ruby sees our signed bytes as positive ints 0-255.
|
|
#
|
|
byte = 0xff - ~byte if byte < 0 && byte >= -128
|
|
|
|
# make sure it's a byte
|
|
raise "not a byte: #{byte.inspect}" unless byte == byte & 0xff
|
|
|
|
byte = byte & 0xff
|
|
### end of pointless code
|
|
|
|
print (byte >= 0 && byte < 0x10 ? '0' : '') + byte.to_s(16) + ' ' if DEBUG_OUTPUT
|
|
|
|
@ir << byte
|
|
@ip += 1
|
|
end
|
|
|
|
# addresses are emited as arrays of bytes, prefixed with :var, :const, or :label
|
|
def emit_addr(type, name)
|
|
placeholder = [type, name]
|
|
puts placeholder.inspect if DEBUG_OUTPUT
|
|
@ir << placeholder
|
|
|
|
# all addresses are 32-bits and jumps are all 32-bit relative
|
|
@ip += 4
|
|
end
|
|
|
|
def emit_var(name_or_proxy)
|
|
proxy = name_or_proxy.is_a?(VariableProxy) ? name_or_proxy : var(name_or_proxy)
|
|
emit_addr(:var, proxy)
|
|
end
|
|
|
|
def emit_const(name)
|
|
proxy = name_or_proxy.is_a?(VariableProxy) ? name_or_proxy : const(name_or_proxy)
|
|
emit_addr(:const, proxy)
|
|
end
|
|
|
|
def emit_label(name)
|
|
print "<#{name}> " if DEBUG_OUTPUT
|
|
emit_addr(:label, name)
|
|
end
|
|
|
|
def emit_dword(num)
|
|
num_to_quad(num).each { |byte| emit_byte(byte) }
|
|
end
|
|
|
|
def mklabel(suffix=nil)
|
|
@symtab.unique_label(suffix)
|
|
end
|
|
|
|
def deflabel(name)
|
|
puts "\n#{name} (0x#{@ip.to_s(16)}):" if DEBUG_OUTPUT
|
|
@symtab.deflabel(name, @ip)
|
|
end
|
|
|
|
def emit_modrm(addr, reg=0)
|
|
mod = 0
|
|
rm = 0
|
|
disp8 = nil
|
|
disp32 = nil
|
|
sib = nil
|
|
var = nil # variable proxy
|
|
|
|
# effective address
|
|
if addr.is_a?(Array)
|
|
eff_addr = addr[1] || addr[0] # works with or without size prefix
|
|
raise "invalid effective address: #{addr.inspect}" unless eff_addr
|
|
case eff_addr
|
|
|
|
when RegisterProxy
|
|
|
|
# Simple register addressing, e.g. [ESI].
|
|
#
|
|
# mod == 00
|
|
if eff_addr.register?
|
|
mod = 0
|
|
|
|
# [ESP] and [EBP] can't be encoded directly. The
|
|
# workaround is to use SIB to emit the code for [ESP+0]
|
|
# and [EBP+0] instead.
|
|
#
|
|
# To emit [ESP+0] we use SIB with scale=1 index=0 base=ESP.
|
|
if eff_addr == ESP
|
|
rm = 4 # SIB
|
|
sib = mk_sib(1, 0, eff_addr)
|
|
|
|
# For [EBP+0] we can encode [EBP]+disp8 directly.
|
|
elsif eff_addr == EBP
|
|
mod = 1
|
|
rm = eff_addr.regnum
|
|
disp8 = 0
|
|
else
|
|
rm = eff_addr.regnum
|
|
end
|
|
|
|
# Bare displacements, e.g. [32] or [0x1234abcd]
|
|
elsif eff_addr.index? && eff_addr.index.is_a?(Numeric)
|
|
|
|
# disp8, mod == 01
|
|
if SignedByte === eff_addr.index
|
|
mod = 1
|
|
disp8 = eff_addr.index
|
|
|
|
# disp32, mod == 10
|
|
elsif SignedRange === eff_addr.index
|
|
mod = 2
|
|
disp32 = eff_addr.index
|
|
|
|
else
|
|
raise "address must fit in 32 bits, this doesn't: #{eff_addr.index}"
|
|
end
|
|
|
|
# SIB
|
|
elsif eff_addr.index?
|
|
# scale-index-base, mod == 00 and rm == 100
|
|
rm = 4
|
|
sib = mk_sib(eff_addr.scale || 1, eff_addr.index, eff_addr.base)
|
|
|
|
else
|
|
raise "unsupported effective address: #{addr.inspect}"
|
|
end
|
|
|
|
# disp32, mod == 00
|
|
when Numeric
|
|
mod = 0
|
|
rm = 5 # 101
|
|
disp32 = eff_addr
|
|
|
|
when VariableProxy
|
|
mod = 0
|
|
rm = 5
|
|
var = eff_addr
|
|
|
|
else
|
|
raise "unsupported effective address: #{addr.inspect}"
|
|
end
|
|
|
|
# register content, mod == 11
|
|
elsif addr.register?
|
|
mod = 3
|
|
rm = addr.regnum
|
|
|
|
# XXX TODO elsif addr.respond_to?(:name)
|
|
# (VariableProxy) => [:(var|const), addr.name]
|
|
#
|
|
# i.e. a pointer to that var
|
|
|
|
else
|
|
raise "unsupported effective address: #{addr.inspect}"
|
|
end
|
|
|
|
emit_byte((mod << 6) | (reg << 3) | rm)
|
|
emit_byte(sib) if sib
|
|
|
|
emit_byte(disp8) if disp8
|
|
|
|
emit_dword(disp32) if disp32
|
|
emit_var(var) if var
|
|
end
|
|
|
|
|
|
def mk_sib(scale, index, base)
|
|
if [1,2,4,8].include?(scale)
|
|
scale = log2(scale).to_i
|
|
else
|
|
raise "unsupported SIB scale: #{scale}, should be 1, 2, 4, or 8"
|
|
end
|
|
if index == 0
|
|
index = 4
|
|
elsif index.respond_to?(:regnum)
|
|
index = index.regnum
|
|
end
|
|
base = base.regnum if base.respond_to?(:regnum)
|
|
return (scale << 6) | (index << 3) | base
|
|
end
|
|
|
|
|
|
def register?(op, size=DefaultOperandSize)
|
|
op.is_a?(RegisterProxy) && op.size == size ||
|
|
op.respond_to?(:size) && op.size == SizeMap[size]
|
|
end
|
|
|
|
def immediate?(op, size=DefaultOperandSize)
|
|
bits = SizeMap[size] || size
|
|
op.is_a?(Numeric) && op >= -(2 ** bits / 2) && op <= (2 ** bits - 1)
|
|
end
|
|
|
|
# Return true if op is a valid operand of the specified size.
|
|
# (:byte, :word, :dword)
|
|
#
|
|
# Valid operands are:
|
|
#
|
|
# * registers
|
|
#
|
|
# * effective addresses (wrapped in an array to look like nasm code)
|
|
#
|
|
# XXX This method is pretty ugly.
|
|
def rm?(op, size=DefaultOperandSize)
|
|
# register
|
|
register?(op, size) ||
|
|
|
|
# [register/memory]
|
|
(op.is_a?(Array) && op.size == 1 &&
|
|
[Numeric, RegisterProxy, VariableProxy].any?{|c| c == op[0].class}) ||
|
|
|
|
# [<size>, memory]
|
|
(op.is_a?(Array) && op.size == 2 && op[0] == size &&
|
|
[Numeric, RegisterProxy, VariableProxy].any?{|c| c == op[1].class})
|
|
end
|
|
|
|
def offset?(addr, size=DefaultOperandSize)
|
|
addr.is_a?(Array) && (addr[0].is_a?(Numeric) || addr[0].is_a?(VariableProxy))
|
|
end
|
|
|
|
def constant?(op)
|
|
immediate?(op) || offset?(op)
|
|
end
|
|
|
|
|
|
# Convert a number to a quad of bytes, discarding excess bits.
|
|
# Little endian!
|
|
def num_to_quad(num)
|
|
[
|
|
num & 0xff,
|
|
(num >> 8) & 0xff,
|
|
(num >> 16) & 0xff,
|
|
(num >> 24) & 0xff
|
|
]
|
|
end
|
|
|
|
def log2(x, tol=1e-13)
|
|
result = 0.0
|
|
|
|
# Integer part
|
|
while x < 1
|
|
resultp -= 1
|
|
x *= 2
|
|
end
|
|
while x >= 2
|
|
result += 1
|
|
x /= 2
|
|
end
|
|
|
|
# Fractional part
|
|
fp = 1.0
|
|
while fp >= tol
|
|
fp /= 2
|
|
x *= x
|
|
if x >= 2
|
|
x /= 2
|
|
result += fp
|
|
end
|
|
end
|
|
result
|
|
end
|
|
|
|
|
|
# 9 versions of the mov instruction are supported:
|
|
# 1. mov reg32, immediate32
|
|
# 2a. mov reg32, r/m32
|
|
# 2b. mov eax, memoffset32
|
|
# 3a. mov r/m32, reg32
|
|
# 3b. mov memoffset32, eax
|
|
# 4. mov r/m32, immediate32
|
|
# 5. mov r/m8, reg8
|
|
# 6. mov reg8, r/m8
|
|
# 7. mov r/m8, imm8
|
|
def mov(dest, src)
|
|
|
|
# These 2 are used in the same way, just the name differs to make the
|
|
# meaning clear. They are 4-byte values that are emited at the end if
|
|
# they are non-nil. Only one of them will be emited, and if both are
|
|
# non-nil that one is immediate.
|
|
immediate = nil
|
|
offset = nil
|
|
|
|
# This is an array of arguments to be passed to emit_modrm, if it is set.
|
|
modrm = nil
|
|
|
|
# version 1: mov r32, imm32
|
|
if register?(dest) && immediate?(src)
|
|
opcode = 0xb8 + dest.regnum # dest encoded in instruction
|
|
immediate = src
|
|
|
|
# version 2a: mov r32, r/m32
|
|
elsif register?(dest) && rm?(src)
|
|
# version 2b: mov eax, moffs32
|
|
if dest == EAX && offset?(src)
|
|
opcode = 0xa1
|
|
offset = src[0]
|
|
else
|
|
opcode = 0x8b
|
|
modrm = [src, dest.regnum]
|
|
end
|
|
|
|
# version 3a: mov r/m32, r32
|
|
elsif rm?(dest) && register?(src)
|
|
# version 3b: mov moffs32, eax
|
|
if offset?(dest) && src == EAX
|
|
opcode = 0xa3
|
|
offset = dest[0]
|
|
else
|
|
opcode = 0x89
|
|
modrm = [dest, src.regnum]
|
|
end
|
|
|
|
# version 4: mov r/m32, imm32
|
|
elsif rm?(dest) && immediate?(src)
|
|
opcode = 0xc7
|
|
modrm = [dest, 0]
|
|
immediate = src
|
|
|
|
# version 5: mov r/m8, r8
|
|
elsif rm?(dest, :byte) && register?(src, :byte)
|
|
opcode = 0x88
|
|
modrm = [dest, src.regnum]
|
|
|
|
# version 6: mov r8, r/m8
|
|
elsif register?(dest, :byte) && rm?(src, :byte)
|
|
opcode = 0x8a
|
|
modrm = [src, dest.regnum]
|
|
|
|
# version 7: mov r/m8, imm8
|
|
elsif rm?(dest, :byte) && immediate?(src, :byte)
|
|
opcode = 0xc6
|
|
modrm = [dest, 0]
|
|
immediate_byte = src
|
|
|
|
else
|
|
# puts "rm?(dest): #{rm?(dest)}\t\trm?(src): #{rm?(src)}"
|
|
# puts "register?(dest): #{register?(dest)}\t\tregister?(src): #{register?(src)}"
|
|
# puts "immediate?(dest): #{immediate?(dest)}\t\timmediate?(src): #{immediate?(src)}"
|
|
# puts "offset?(dest): #{offset?(dest)}\t\toffset?(src): #{offset?(src)}"
|
|
# puts "rm?(dest, :byte): #{rm?(dest)}\t\trm?(src, :byte): #{rm?(src, :byte)}"
|
|
# puts "immediate?(dest, :byte): #{immediate?(dest)}\t\timmediate?(src, :byte): #{immediate?(src, :byte)}"
|
|
raise "unsupported MOV instruction, #{dest.inspect}, #{src.inspect}"
|
|
end
|
|
|
|
dword = immediate || offset
|
|
|
|
asm do
|
|
emit_byte(opcode)
|
|
emit_modrm(*modrm) if modrm
|
|
|
|
if dword.is_a?(VariableProxy)
|
|
if dword.const?
|
|
emit_const(dword)
|
|
else
|
|
emit_var(dword)
|
|
end
|
|
|
|
elsif dword
|
|
emit_dword(dword)
|
|
|
|
elsif immediate_byte
|
|
emit_byte(immediate_byte)
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
def movzx(dest, src)
|
|
|
|
# movzx Gv, ??
|
|
if register?(dest)
|
|
|
|
opcode = case
|
|
when rm?(src, :byte): 0xb6 # movzx Gv, Eb
|
|
when rm?(src, :word): 0xb7 # movzx Gv, Ew
|
|
else
|
|
raise "unsupported MOVZX instruction, dest=#{dest.inspect} << src=#{src.inspect} >>"
|
|
end
|
|
asm do
|
|
emit_byte(0x0f)
|
|
emit_byte(opcode)
|
|
emit_modrm(src, dest.regnum)
|
|
end
|
|
|
|
else
|
|
|
|
raise "unimplemented MOVZX instruction, << dest=#{dest.inspect} >> src=#{src.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
def xchg(dest, src)
|
|
if dest == EAX && register?(src)
|
|
asm { emit_byte(0x90 + src.regnum) }
|
|
# swap the args if EAX comes last so we only need to handle one case below.
|
|
elsif src == EAX && register?(dest)
|
|
xchg(src, dest)
|
|
elsif rm?(dest) && register?(src)
|
|
asm do
|
|
emit_byte(0x87)
|
|
emit_modrm(dest, src.regnum)
|
|
end
|
|
elsif register?(dest) && rm?(src)
|
|
asm do
|
|
emit_byte(0x87)
|
|
emit_modrm(src, dest.regnum)
|
|
end
|
|
else
|
|
raise "unsupported XCHG instruction, dest=#{dest.inspect} src=#{src.inspect}"
|
|
end
|
|
end
|
|
|
|
# convert double to quad (sign-extend EAX into EDX)
|
|
def cdq
|
|
asm { emit_byte(0x99) }
|
|
end
|
|
|
|
|
|
def add(dest, src)
|
|
# add r/m32, imm8
|
|
if rm?(dest) && immediate?(src, :byte)
|
|
asm do
|
|
emit_byte(0x83)
|
|
emit_modrm(dest, 0)
|
|
emit_byte(src)
|
|
end
|
|
|
|
# add r/m32, imm32
|
|
elsif rm?(dest) && immediate?(src)
|
|
asm do
|
|
emit_byte(0x81)
|
|
emit_modrm(dest, 0)
|
|
emit_dword(src)
|
|
end
|
|
|
|
# add eax, imm32
|
|
elsif dest == EAX && immediate?(src)
|
|
asm do
|
|
emit_byte(0x05)
|
|
emit_dword(src)
|
|
end
|
|
|
|
# add reg32, r/m32
|
|
elsif register?(dest) && rm?(src)
|
|
asm do
|
|
emit_byte(0x03)
|
|
emit_modrm(src, dest.regnum)
|
|
end
|
|
|
|
else
|
|
raise "unsupported ADD instruction, dest=#{dest.inspect} src=#{src.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
def sub(dest, src)
|
|
# sub r/m32, imm8
|
|
if rm?(dest) && immediate?(src, :byte)
|
|
asm do
|
|
emit_byte(0x83)
|
|
emit_modrm(dest, 5)
|
|
emit_byte(src)
|
|
end
|
|
|
|
# sub r/m32, imm32
|
|
elsif rm?(dest) && immediate?(src)
|
|
asm do
|
|
emit_byte(0x81)
|
|
emit_modrm(dest, 5)
|
|
emit_dword(src)
|
|
end
|
|
|
|
# sub r/m32, reg32
|
|
elsif rm?(dest) && register?(src)
|
|
asm do
|
|
emit_byte(0x29)
|
|
emit_modrm(dest, src.regnum)
|
|
end
|
|
|
|
# sub reg32, r/m32
|
|
elsif register?(dest) && rm?(src)
|
|
asm do
|
|
emit_byte(0x2b)
|
|
emit_modrm(src, dest.regnum)
|
|
end
|
|
|
|
else
|
|
raise "unsupported SUB instruction, dest=#{dest.inspect} src=#{src.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
# Signed multiply.
|
|
def imul(*ops)
|
|
case ops.size
|
|
|
|
when 1
|
|
group3(ops[0], 5, 'IMUL')
|
|
|
|
when 2
|
|
dest, src = ops
|
|
raise "unsupported IMUL instruction, dest=#{dest.inspect} src=#{src.inspect}"
|
|
|
|
else
|
|
raise ArgumentError, "IMUL accepts exactly 1 or 2 operands (got #{ops.inspect})"
|
|
end
|
|
end
|
|
|
|
# Unsigned multiply.
|
|
def mul(op)
|
|
group3(op, 4, 'MUL')
|
|
end
|
|
|
|
|
|
# Signed divide.
|
|
def idiv(op)
|
|
group3(op, 7, 'IDIV')
|
|
end
|
|
|
|
# Unsigned divide.
|
|
def div(op)
|
|
group3(op, 6, 'DIV')
|
|
end
|
|
|
|
|
|
def inc(op)
|
|
asm do
|
|
if register?(op)
|
|
emit_byte(0x40 + regnum(op))
|
|
elsif rm?(op)
|
|
# emit_byte(0xff)
|
|
raise "unimplemented"
|
|
else
|
|
raise "unsupported op #{op}, wanted r32 or r/m32"
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
def dec(op)
|
|
if register?(op)
|
|
# dec reg32
|
|
asm { emit_byte(0x48 + op.regnum) }
|
|
else
|
|
raise "unsupported DEC instruction, op=#{op.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
def shr(op, n)
|
|
|
|
# shr r/m??, imm8
|
|
if SignedByte === n
|
|
|
|
opcode = register?(op, :byte) ? 0xc0 : 0xc1
|
|
|
|
asm do
|
|
emit_byte(opcode)
|
|
emit_modrm(op, 5)
|
|
emit_byte(n)
|
|
end
|
|
|
|
else
|
|
raise "unsupported SHR instruction, op=#{op.inspect}, n=#{n.inspect}"
|
|
end
|
|
|
|
end
|
|
|
|
|
|
def and_(dest, src)
|
|
if rm?(dest) && register?(src)
|
|
asm do
|
|
emit_byte(0x21)
|
|
emit_modrm(dest, src.regnum)
|
|
end
|
|
elsif rm?(dest, 8) && immediate?(src, 8)
|
|
asm do
|
|
emit_byte(0x80)
|
|
emit_modrm(dest, 4)
|
|
emit_byte(src)
|
|
end
|
|
else
|
|
raise "unsupported AND instruction: dest=#{dest.inspect}, src=#{src.inspect}"
|
|
end
|
|
end
|
|
alias_method :and, :and_
|
|
|
|
|
|
def xor(dest, src)
|
|
# xor r/m32, reg32
|
|
if rm?(dest) && register?(src)
|
|
asm do
|
|
emit_byte(0x31)
|
|
emit_modrm(dest, src.regnum)
|
|
end
|
|
|
|
else
|
|
raise "unsupported XOR instruction, dest=#{dest.inspect} src=#{src.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
def not_(op)
|
|
group3(op, 2, 'NOT')
|
|
end
|
|
alias_method :not, :not_
|
|
|
|
|
|
def neg(op)
|
|
group3(op, 3, 'NEG')
|
|
end
|
|
|
|
|
|
def push(op)
|
|
# push reg32
|
|
if register?(op)
|
|
asm { emit_byte(0x50 + op.regnum) }
|
|
|
|
elsif immediate?(op, :byte)
|
|
asm do
|
|
emit_byte(0x6a)
|
|
emit_byte(op)
|
|
end
|
|
|
|
elsif immediate?(op)
|
|
asm do
|
|
emit_byte(0x68)
|
|
emit_dword(op)
|
|
end
|
|
|
|
else
|
|
raise "unsupported PUSH instruction: op=#{op.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
def pop(op)
|
|
# pop reg32
|
|
if register?(op)
|
|
asm { emit_byte(0x58 + op.regnum) }
|
|
|
|
else
|
|
raise "unsupported POP instruction: op=#{op.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
def cmp(op1, op2)
|
|
# cmp r/m32, reg32
|
|
if rm?(op1) && register?(op2)
|
|
asm do
|
|
emit_byte(0x39)
|
|
emit_modrm(op1, op2.regnum)
|
|
end
|
|
|
|
# cmp eax, imm32
|
|
elsif op1 == EAX && immediate?(op2)
|
|
asm do
|
|
emit_byte(0x3d)
|
|
emit_dword(op2)
|
|
end
|
|
|
|
else
|
|
raise "unsupported CMP instruction: op1=#{op1.inspect} op2=#{op2.inspect}"
|
|
end
|
|
end
|
|
|
|
|
|
# Only jmp rel32 is supported.
|
|
def jmp(label)
|
|
asm do
|
|
emit_byte(0xe9)
|
|
emit_label(label)
|
|
end
|
|
end
|
|
|
|
# These all jump near (rel32).
|
|
JccOpcodeMap = Hash.new { |key| raise "unsupported Jcc instruction: #{key}" }.
|
|
merge({
|
|
:jc => 0x82, # carry (CF=1)
|
|
:je => 0x84, # equal (ZF=1) --- same as jz
|
|
:jg => 0x8f, # greater (ZF=0 and SF=OF)
|
|
:jl => 0x8c, # less than (SF!=OF)
|
|
:jne => 0x85, # not equal (ZF=0) --- same as jnz
|
|
:jng => 0x8e, # not greater than (ZF=1 or SF!=OF)
|
|
:jnl => 0x8d, # not less than (SF=OF)
|
|
:jnz => 0x85, # not zero (ZF=0)
|
|
:jo => 0x80, # overflow (OF=1)
|
|
:js => 0x88, # sign (SF=1)
|
|
:jz => 0x84 # zero (ZF=1)
|
|
})
|
|
|
|
# Only Jcc rel32 is supported.
|
|
def jcc(instruction, label)
|
|
opcode = JccOpcodeMap[instruction]
|
|
asm do
|
|
emit_byte(0x0f)
|
|
emit_byte(opcode)
|
|
emit_label(label)
|
|
end
|
|
end
|
|
|
|
JccOpcodeMap.keys.each do |name|
|
|
define_method(name) do |label|
|
|
jcc(name, label)
|
|
end
|
|
end
|
|
|
|
|
|
def lea(r32, mem)
|
|
asm do
|
|
emit_byte(0x8d)
|
|
emit_modrm(mem, r32.regnum)
|
|
end
|
|
end
|
|
|
|
|
|
def int(n)
|
|
asm do
|
|
emit_byte(0xcd)
|
|
emit_byte(n)
|
|
end
|
|
end
|
|
|
|
|
|
def ret
|
|
asm { emit_byte(0xc3) }
|
|
end
|
|
|
|
|
|
def leave
|
|
asm { emit_byte(0xc9) }
|
|
end
|
|
|
|
|
|
# NOTE: LOOP only accepts a 1-byte signed offset. Don't use it.
|
|
def loop_(label)
|
|
real_ip = @ip + 2 # loop instruction is 2 bytes
|
|
delta = @symtab.lookup_label(label) - real_ip
|
|
unless SignedByte === delta
|
|
raise "LOOP can only jump -128 to 127 bytes, #{label} is #{delta} bytes away"
|
|
end
|
|
|
|
asm do
|
|
emit_byte(0xe2)
|
|
emit_byte(delta)
|
|
end
|
|
end
|
|
alias_method :loop, :loop_
|
|
|
|
|
|
# Opcode group #3. 1-byte opcode, 1 operand (r/m8 or r/m32).
|
|
#
|
|
# Members of this group are: DIV, IDIV, MUL, IMUL, NEG, NOT, and TEST.
|
|
def group3(op, reg, instruction)
|
|
opcode =
|
|
if rm?(op, 8)
|
|
0xf6
|
|
elsif rm?(op)
|
|
0xf7
|
|
else
|
|
raise "unsupported #{instruction} instruction: op=#{op.inspect}"
|
|
end
|
|
|
|
asm do
|
|
emit_byte(opcode)
|
|
emit_modrm(op, reg)
|
|
end
|
|
end
|
|
|
|
|
|
end # class Binary
|
|
|
|
end # module Assembler
|