adds first version of the expanded as assembler from mikko
This commit is contained in:
parent
52e9542d73
commit
408b290b8a
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,7 +28,7 @@ pkg
|
||||
#
|
||||
# For MacOS:
|
||||
#
|
||||
#.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# For TextMate
|
||||
#*.tmproj
|
||||
|
18
lib/asm/README.markdown
Normal file
18
lib/asm/README.markdown
Normal file
@ -0,0 +1,18 @@
|
||||
Assembler in Ruby
|
||||
=================
|
||||
|
||||
Supporting arm, but aimed quite specifically at raspberry pi, arm v7, floating point included
|
||||
|
||||
Outputs ELF object files, with relocation support.
|
||||
|
||||
Constant table support exists but isn't very good. Some addressing modes
|
||||
are not supported or only partially supported.
|
||||
|
||||
Supported (pseudo)instructions:
|
||||
|
||||
- adc, add, and, bic, eor, orr, rsb, rsc, sbc, sub, cmn, cmp, teq, tst,
|
||||
mov, mvn, strb, str, ldrb, ldr, push, pop, b, bl, bx, swi
|
||||
- Conditional versions of above
|
||||
|
||||
Thanks to Cyndis for starting this arm/elf project in the first place: https://github.com/cyndis/as
|
||||
|
34
lib/asm/arm/addr_table_object.rb
Normal file
34
lib/asm/arm/addr_table_object.rb
Normal file
@ -0,0 +1,34 @@
|
||||
class Asm::ARM::AddrTableObject
|
||||
def initialize
|
||||
@table = []
|
||||
@const = []
|
||||
end
|
||||
|
||||
# TODO don't create new entry if there's already an entry for the same label/const
|
||||
def add_label(label)
|
||||
d = [label, Asm::LabelObject.new]
|
||||
@table << d
|
||||
d[1]
|
||||
end
|
||||
|
||||
def add_const(const)
|
||||
d = [const, Asm::LabelObject.new]
|
||||
@const << d
|
||||
d[1]
|
||||
end
|
||||
|
||||
def assemble(io, as)
|
||||
@table.each do |pair|
|
||||
target_label, here_label = *pair
|
||||
here_label.assemble io, as
|
||||
as.add_relocation io.tell, target_label, Asm::ARM::R_ARM_ABS32,
|
||||
Asm::ARM::Instruction::RelocHandler
|
||||
io.write_uint32 0
|
||||
end
|
||||
@const.each do |pair|
|
||||
const, here_label = *pair
|
||||
here_label.assemble io, as
|
||||
io.write_uint32 const
|
||||
end
|
||||
end
|
||||
end
|
54
lib/asm/arm/arm_assembler.rb
Normal file
54
lib/asm/arm/arm_assembler.rb
Normal file
@ -0,0 +1,54 @@
|
||||
require_relative 'assembler'
|
||||
|
||||
module Asm
|
||||
module Arm
|
||||
|
||||
# Relocation constants
|
||||
# Note that in this assembler, a relocation simply means any
|
||||
# reference to a label that can only be determined at assembly time
|
||||
# or later (as in the normal meaning)
|
||||
|
||||
R_ARM_PC24 = 0x01
|
||||
R_ARM_ABS32 = 0x02
|
||||
|
||||
# Unofficial (cant be used for extern relocations)
|
||||
R_ARM_PC12 = 0xF0
|
||||
|
||||
# TODO actually find the closest somehow
|
||||
def self.closest_addrtable(as)
|
||||
as.objects.find do |obj|
|
||||
obj.is_a?(Asm::ARM::AddrTableObject)
|
||||
end || (raise Asm::AssemblyError.new('could not find addrtable to use', nil))
|
||||
end
|
||||
|
||||
def self.write_resolved_relocation(io, addr, type)
|
||||
case type
|
||||
when R_ARM_PC24
|
||||
diff = addr - io.tell - 8
|
||||
packed = [diff >> 2].pack('l')
|
||||
io << packed[0,3]
|
||||
when R_ARM_ABS32
|
||||
packed = [addr].pack('l')
|
||||
io << packed
|
||||
when R_ARM_PC12
|
||||
diff = addr - io.tell - 8
|
||||
if (diff.abs > 2047)
|
||||
raise Asm::AssemblyError.new('offset too large for R_ARM_PC12 relocation',
|
||||
nil)
|
||||
end
|
||||
|
||||
val = diff.abs
|
||||
sign = (diff>0)?1:0
|
||||
|
||||
curr = io.read_uint32
|
||||
io.seek(-4, IO::SEEK_CUR)
|
||||
|
||||
io.write_uint32 (curr & ~0b00000000100000000000111111111111) |
|
||||
val | (sign << 23)
|
||||
else
|
||||
raise 'unknown relocation type'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
106
lib/asm/arm/builder_a.rb
Normal file
106
lib/asm/arm/builder_a.rb
Normal file
@ -0,0 +1,106 @@
|
||||
module Asm
|
||||
module Arm
|
||||
# ADDRESSING MODE 1
|
||||
# Complete!
|
||||
class BuilderA
|
||||
include Asm::ARM::InstructionTools
|
||||
|
||||
def initialize
|
||||
@cond = 0b1110
|
||||
@inst_class = 0
|
||||
@i = 0
|
||||
@opcode = 0
|
||||
@s = 0
|
||||
@rn = 0
|
||||
@rd = 0
|
||||
@operand = 0
|
||||
end
|
||||
attr_accessor :cond, :inst_class, :i, :opcode, :s,
|
||||
:rn, :rd, :operand
|
||||
|
||||
def self.make(inst_class, opcode, s)
|
||||
a = new
|
||||
a.inst_class = inst_class
|
||||
a.opcode = opcode
|
||||
a.s = s
|
||||
a
|
||||
end
|
||||
|
||||
def calculate_u8_with_rr(arg)
|
||||
parts = arg.value.to_s(2).rjust(32,'0').scan(/^(0*)(.+?)0*$/).flatten
|
||||
pre_zeros = parts[0].length
|
||||
imm_len = parts[1].length
|
||||
if ((pre_zeros+imm_len) % 2 == 1)
|
||||
u8_imm = (parts[1]+'0').to_i(2)
|
||||
imm_len += 1
|
||||
else
|
||||
u8_imm = parts[1].to_i(2)
|
||||
end
|
||||
if (u8_imm.fits_u8?)
|
||||
# can do!
|
||||
rot_imm = (pre_zeros+imm_len) / 2
|
||||
if (rot_imm > 15)
|
||||
return nil
|
||||
end
|
||||
return u8_imm | (rot_imm << 8)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Build representation for source value
|
||||
def build_operand(arg)
|
||||
if (arg.is_a?(Asm::Parser::NumLiteralArgNode))
|
||||
if (arg.value.fits_u8?)
|
||||
# no shifting needed
|
||||
@operand = arg.value
|
||||
@i = 1
|
||||
elsif (op_with_rot = calculate_u8_with_rr(arg))
|
||||
@operand = op_with_rot
|
||||
@i = 1
|
||||
else
|
||||
raise Asm::AssemblyError.new(Asm::ERRSTR_NUMERIC_TOO_LARGE, arg)
|
||||
end
|
||||
elsif (arg.is_a?(Asm::Parser::RegisterArgNode))
|
||||
@operand = reg_ref(arg)
|
||||
@i = 0
|
||||
elsif (arg.is_a?(Asm::Parser::ShiftNode))
|
||||
rm_ref = reg_ref(arg.argument)
|
||||
@i = 0
|
||||
shift_op = {'lsl' => 0b000, 'lsr' => 0b010, 'asr' => 0b100,
|
||||
'ror' => 0b110, 'rrx' => 0b110}[arg.type]
|
||||
if (arg.type == 'ror' and arg.value.nil?)
|
||||
# ror #0 == rrx
|
||||
raise Asm::AssemblyError.new('cannot rotate by zero', arg)
|
||||
end
|
||||
|
||||
arg1 = arg.value
|
||||
if (arg1.is_a?(Asm::Parser::NumLiteralArgNode))
|
||||
if (arg1.value >= 32)
|
||||
raise Asm::AssemblyError.new('cannot shift by more than 31', arg1)
|
||||
end
|
||||
shift_imm = arg1.value
|
||||
elsif (arg1.is_a?(Asm::Parser::RegisterArgNode))
|
||||
shift_op |= 0x1;
|
||||
shift_imm = reg_ref(arg1) << 1
|
||||
elsif (arg.type == 'rrx')
|
||||
shift_imm = 0
|
||||
end
|
||||
|
||||
@operand = rm_ref | (shift_op << 4) | (shift_imm << 4+3)
|
||||
else
|
||||
raise Asm::AssemblyError.new(Asm::ERRSTR_INVALID_ARG, arg)
|
||||
end
|
||||
end
|
||||
|
||||
def write(io, as)
|
||||
val = operand | (rd << 12) | (rn << 12+4) |
|
||||
(s << 12+4+4) | (opcode << 12+4+4+1) |
|
||||
(i << 12+4+4+1+4) | (inst_class << 12+4+4+1+4+1) |
|
||||
(cond << 12+4+4+1+4+1+2)
|
||||
io.write_uint32 val
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
113
lib/asm/arm/builder_b.rb
Normal file
113
lib/asm/arm/builder_b.rb
Normal file
@ -0,0 +1,113 @@
|
||||
module Asm
|
||||
module Arm
|
||||
# ADDRESSING MODE 2
|
||||
# Implemented: immediate offset with offset=0
|
||||
class BuilderB
|
||||
include Asm::ARM::InstructionTools
|
||||
|
||||
def initialize
|
||||
@cond = 0b1110
|
||||
@inst_class = 0
|
||||
@i = 0 #I flag (third bit)
|
||||
@pre_post_index = 0 #P flag
|
||||
@add_offset = 0 #U flag
|
||||
@byte_access = 0 #B flag
|
||||
@w = 0 #W flag
|
||||
@load_store = 0 #L flag
|
||||
@rn = 0
|
||||
@rd = 0
|
||||
@operand = 0
|
||||
end
|
||||
attr_accessor :cond, :inst_class, :i, :pre_post_index, :add_offset,
|
||||
:byte_access, :w, :load_store, :rn, :rd, :operand
|
||||
|
||||
def self.make(inst_class, byte_access, load_store)
|
||||
a = new
|
||||
a.inst_class = inst_class
|
||||
a.byte_access = byte_access
|
||||
a.load_store = load_store
|
||||
a
|
||||
end
|
||||
|
||||
class MathReferenceArgNode < Asm::Parser::ReferenceArgNode
|
||||
attr_accessor :op, :right
|
||||
end
|
||||
def simplify_reference(arg)
|
||||
node = MathReferenceArgNode.new
|
||||
|
||||
if (arg.is_a?(Asm::Parser::MathNode))
|
||||
node.argument = arg.left
|
||||
node.op = arg.op
|
||||
node.right = arg.right
|
||||
else
|
||||
node.argument = arg
|
||||
end
|
||||
|
||||
node
|
||||
end
|
||||
|
||||
# Build representation for target address
|
||||
def build_operand(arg1)
|
||||
if (arg1.is_a?(Asm::Parser::ReferenceArgNode))
|
||||
argr = simplify_reference(arg1.argument)
|
||||
arg = argr.argument
|
||||
if (arg.is_a?(Asm::Parser::RegisterArgNode))
|
||||
@i = 0
|
||||
@pre_post_index = 1
|
||||
@w = 0
|
||||
@rn = reg_ref(arg)
|
||||
@operand = 0
|
||||
|
||||
if (argr.op and argr.right.is_a?(Asm::Parser::NumLiteralArgNode))
|
||||
val = argr.right.value
|
||||
if (val < 0)
|
||||
@add_offset = 0
|
||||
val *= -1
|
||||
else
|
||||
@add_offset = 1
|
||||
end
|
||||
if (val.abs > 4095)
|
||||
raise Asm::AssemblyError.new('reference offset too large/small (max 4095)', argr.right)
|
||||
end
|
||||
@operand = val
|
||||
elsif (argr.op)
|
||||
raise Asm::AssemblyError.new('reference offset must be an integer literal', argr.right)
|
||||
end
|
||||
else
|
||||
raise Asm::AssemblyError.new(Asm::ERRSTR_INVALID_ARG, arg)
|
||||
end
|
||||
elsif (arg1.is_a?(Asm::Parser::LabelEquivAddrArgNode) or arg1.is_a?(Asm::Parser::NumEquivAddrArgNode))
|
||||
@i = 0
|
||||
@pre_post_index = 1
|
||||
@w = 0
|
||||
@rn = 15 # pc
|
||||
@operand = 0
|
||||
@use_addrtable_reloc = true
|
||||
@addrtable_reloc_target = arg1
|
||||
else
|
||||
raise Asm::AssemblyError.new(Asm::ERRSTR_INVALID_ARG, arg1)
|
||||
end
|
||||
end
|
||||
|
||||
def write(io, as, ast_asm, inst)
|
||||
val = operand | (rd << 12) | (rn << 12+4) |
|
||||
(load_store << 12+4+4) | (w << 12+4+4+1) |
|
||||
(byte_access << 12+4+4+1+1) | (add_offset << 12+4+4+1+1+1) |
|
||||
(pre_post_index << 12+4+4+1+1+1+1) | (i << 12+4+4+1+1+1+1+1) |
|
||||
(inst_class << 12+4+4+1+1+1+1+1+1) | (cond << 12+4+4+1+1+1+1+1+1+2)
|
||||
if (@use_addrtable_reloc)
|
||||
closest_addrtable = Asm::ARM.closest_addrtable(as)
|
||||
if (@addrtable_reloc_target.is_a?(Asm::Parser::LabelEquivAddrArgNode))
|
||||
obj = ast_asm.object_for_label(@addrtable_reloc_target.label, inst)
|
||||
ref_label = closest_addrtable.add_label(obj)
|
||||
elsif (@addrtable_reloc_target.is_a?(Asm::Parser::NumEquivAddrArgNode))
|
||||
ref_label = closest_addrtable.add_const(@addrtable_reloc_target.value)
|
||||
end
|
||||
as.add_relocation io.tell, ref_label, Asm::ARM::R_ARM_PC12,
|
||||
Asm::ARM::Instruction::RelocHandler
|
||||
end
|
||||
io.write_uint32 val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
52
lib/asm/arm/builder_d.rb
Normal file
52
lib/asm/arm/builder_d.rb
Normal file
@ -0,0 +1,52 @@
|
||||
module Asm
|
||||
module Arm
|
||||
# ADDRESSING MODE 4
|
||||
class BuilderD
|
||||
include Asm::ARM::InstructionTools
|
||||
|
||||
def initialize
|
||||
@cond = 0b1110
|
||||
@inst_class = Asm::ARM::Instruction::OPC_STACK
|
||||
@pre_post_index = 0
|
||||
@up_down = 0
|
||||
@s = 0
|
||||
@write_base = 0
|
||||
@store_load = 0
|
||||
@rn = 0
|
||||
@operand = 0
|
||||
end
|
||||
attr_accessor :cond, :inst_class, :pre_post_index, :up_down,
|
||||
:s, :write_base, :store_load, :rn, :operand
|
||||
|
||||
def self.make(pre_post, up_down, write, store_load)
|
||||
a = new
|
||||
a.pre_post_index = pre_post
|
||||
a.up_down = up_down
|
||||
a.write_base = write
|
||||
a.store_load = store_load
|
||||
a
|
||||
end
|
||||
|
||||
# Build representation for source value
|
||||
def build_operand(arg)
|
||||
if (arg.is_a?(Asm::Parser::RegisterListArgNode))
|
||||
@operand = 0
|
||||
arg.registers.each do |reg_node|
|
||||
reg = reg_ref(reg_node)
|
||||
@operand |= (1 << reg)
|
||||
end
|
||||
else
|
||||
raise Asm::AssemblyError.new(Asm::ERRSTR_INVALID_ARG, arg)
|
||||
end
|
||||
end
|
||||
|
||||
def write(io, as)
|
||||
val = operand | (rn << 16) | (store_load << 16+4) |
|
||||
(write_base << 16+4+1) | (s << 16+4+1+1) | (up_down << 16+4+1+1+1) |
|
||||
(pre_post_index << 16+4+1+1+1+1) | (inst_class << 16+4+1+1+1+1+2) |
|
||||
(cond << 16+4+1+1+1+1+2+2)
|
||||
io.write_uint32 val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
126
lib/asm/arm/code_generator.rb
Normal file
126
lib/asm/arm/code_generator.rb
Normal file
@ -0,0 +1,126 @@
|
||||
require_relative 'arm_assembler'
|
||||
require_relative 'parser'
|
||||
require 'stringio'
|
||||
|
||||
module Asm
|
||||
module Arm
|
||||
|
||||
class CodeGenerator
|
||||
def initialize
|
||||
@asm = Asm::Assembler.new
|
||||
@externs = []
|
||||
end
|
||||
|
||||
def data(str)
|
||||
@asm.add_object Asm::DataObject.new(str)
|
||||
end
|
||||
|
||||
%w(r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12
|
||||
r13 r14 r15 a1 a2 a3 a4 v1 v2 v3 v4 v5 v6
|
||||
rfp sl fp ip sp lr pc
|
||||
).each { |reg|
|
||||
define_method(reg) {
|
||||
[:reg, reg]
|
||||
}
|
||||
}
|
||||
|
||||
def instruction(name, *args)
|
||||
node = Asm::Parser::InstructionNode.new
|
||||
node.opcode = name.to_s
|
||||
node.args = []
|
||||
|
||||
args.each { |arg|
|
||||
if (arg.is_a?(Array))
|
||||
if (arg[0] == :reg)
|
||||
node.args << Asm::Parser::RegisterArgNode.new { |n|
|
||||
n.name = arg[1]
|
||||
}
|
||||
end
|
||||
elsif (arg.is_a?(Integer))
|
||||
node.args << Asm::Parser::NumLiteralArgNode.new { |n|
|
||||
n.value = arg
|
||||
}
|
||||
elsif (arg.is_a?(Symbol))
|
||||
node.args << Asm::Parser::LabelRefArgNode.new { |n|
|
||||
n.label = arg.to_s
|
||||
}
|
||||
elsif (arg.is_a?(GeneratorLabel) or arg.is_a?(GeneratorExternLabel))
|
||||
node.args << arg
|
||||
else
|
||||
raise 'Invalid argument `%s\' for instruction' % arg.inspect
|
||||
end
|
||||
}
|
||||
|
||||
@asm.add_object Asm::ARM::Instruction.new(node)
|
||||
end
|
||||
|
||||
%w(adc add and bic eor orr rsb rsc sbc sub
|
||||
mov mvn cmn cmp teq tst b bl bx swi strb
|
||||
).each { |inst|
|
||||
define_method(inst) { |*args|
|
||||
instruction inst.to_sym, *args
|
||||
}
|
||||
define_method(inst+'s') { |*args|
|
||||
instruction (inst+'s').to_sym, *args
|
||||
}
|
||||
%w(al eq ne cs mi hi cc pl ls vc
|
||||
lt le ge gt vs
|
||||
).each { |cond_suffix|
|
||||
define_method(inst+cond_suffix) { |*args|
|
||||
instruction (inst+cond_suffix).to_sym, *args
|
||||
}
|
||||
define_method(inst+'s'+cond_suffix) { |*args|
|
||||
instruction (inst+'s'+cond_suffix).to_sym, *args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GeneratorLabel < Asm::LabelObject
|
||||
def initialize(asm)
|
||||
@asm = asm
|
||||
end
|
||||
def set!
|
||||
@asm.add_object self
|
||||
end
|
||||
end
|
||||
|
||||
class GeneratorExternLabel < Asm::LabelObject
|
||||
def initialize(name)
|
||||
@name = name
|
||||
extern!
|
||||
end
|
||||
attr_reader :name
|
||||
end
|
||||
|
||||
def label
|
||||
GeneratorLabel.new(@asm)
|
||||
end
|
||||
|
||||
def label!
|
||||
lbl = GeneratorLabel.new(@asm)
|
||||
lbl.set!
|
||||
lbl
|
||||
end
|
||||
|
||||
def extern(sym)
|
||||
if (lbl = @externs.find { |extern| extern.name == sym })
|
||||
lbl
|
||||
else
|
||||
@externs << lbl = GeneratorExternLabel.new(sym)
|
||||
@asm.add_object lbl
|
||||
lbl
|
||||
end
|
||||
end
|
||||
|
||||
def assemble
|
||||
io = StringIO.new
|
||||
@asm.assemble(io)
|
||||
io.string
|
||||
end
|
||||
|
||||
def relocations
|
||||
@asm.relocations
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
149
lib/asm/arm/instruction.rb
Normal file
149
lib/asm/arm/instruction.rb
Normal file
@ -0,0 +1,149 @@
|
||||
module Asm
|
||||
module Arm
|
||||
class Asm::ARM::Instruction
|
||||
include Asm::ARM::InstructionTools
|
||||
|
||||
COND_POSTFIXES = Regexp.union(%w(eq ne cs cc mi pl vs vc hi ls ge lt gt le al)).source
|
||||
def initialize(node, ast_asm = nil)
|
||||
@node = node
|
||||
@ast_asm = ast_asm
|
||||
opcode = node.opcode
|
||||
args = node.args
|
||||
|
||||
opcode = opcode.downcase
|
||||
@cond = :al
|
||||
if (opcode =~ /(#{COND_POSTFIXES})$/)
|
||||
@cond = $1.to_sym
|
||||
opcode = opcode[0..-3]
|
||||
end
|
||||
if (opcode =~ /s$/)
|
||||
@s = true
|
||||
opcode = opcode[0..-2]
|
||||
else
|
||||
@s = false
|
||||
end
|
||||
@opcode = opcode.downcase.to_sym
|
||||
@args = args
|
||||
end
|
||||
attr_reader :opcode, :args
|
||||
|
||||
OPC_DATA_PROCESSING = 0b00
|
||||
OPC_MEMORY_ACCESS = 0b01
|
||||
OPC_STACK = 0b10
|
||||
# These are used differently in the
|
||||
# instruction encoders
|
||||
OPCODES = {
|
||||
:adc => 0b0101, :add => 0b0100,
|
||||
:and => 0b0000, :bic => 0b1110,
|
||||
:eor => 0b0001, :orr => 0b1100,
|
||||
:rsb => 0b0011, :rsc => 0b0111,
|
||||
:sbc => 0b0110, :sub => 0b0010,
|
||||
|
||||
# for these Rn is sbz (should be zero)
|
||||
:mov => 0b1101,
|
||||
:mvn => 0b1111,
|
||||
# for these Rd is sbz and S=1
|
||||
:cmn => 0b1011,
|
||||
:cmp => 0b1010,
|
||||
:teq => 0b1001,
|
||||
:tst => 0b1000,
|
||||
|
||||
:b => 0b1010,
|
||||
:bl => 0b1011,
|
||||
:bx => 0b00010010
|
||||
}
|
||||
COND_BITS = {
|
||||
:al => 0b1110, :eq => 0b0000,
|
||||
:ne => 0b0001, :cs => 0b0010,
|
||||
:mi => 0b0100, :hi => 0b1000,
|
||||
:cc => 0b0011, :pl => 0b0101,
|
||||
:ls => 0b1001, :vc => 0b0111,
|
||||
:lt => 0b1011, :le => 0b1101,
|
||||
:ge => 0b1010, :gt => 0b1100,
|
||||
:vs => 0b0110
|
||||
}
|
||||
|
||||
RelocHandler = Asm::ARM.method(:write_resolved_relocation)
|
||||
|
||||
def assemble(io, as)
|
||||
s = @s ? 1 : 0
|
||||
case opcode
|
||||
when :adc, :add, :and, :bic, :eor, :orr, :rsb, :rsc, :sbc, :sub
|
||||
a = BuilderA.make(OPC_DATA_PROCESSING, OPCODES[opcode], s)
|
||||
a.cond = COND_BITS[@cond]
|
||||
a.rd = reg_ref(args[0])
|
||||
a.rn = reg_ref(args[1])
|
||||
a.build_operand args[2]
|
||||
a.write io, as
|
||||
when :cmn, :cmp, :teq, :tst
|
||||
a = BuilderA.make(OPC_DATA_PROCESSING, OPCODES[opcode], 1)
|
||||
a.cond = COND_BITS[@cond]
|
||||
a.rn = reg_ref(args[0])
|
||||
a.rd = 0
|
||||
a.build_operand args[1]
|
||||
a.write io, as
|
||||
when :mov, :mvn
|
||||
a = BuilderA.make(OPC_DATA_PROCESSING, OPCODES[opcode], s)
|
||||
a.cond = COND_BITS[@cond]
|
||||
a.rn = 0
|
||||
a.rd = reg_ref(args[0])
|
||||
a.build_operand args[1]
|
||||
a.write io, as
|
||||
when :strb, :str
|
||||
a = BuilderB.make(OPC_MEMORY_ACCESS, (opcode == :strb ? 1 : 0), 0)
|
||||
a.cond = COND_BITS[@cond]
|
||||
a.rd = reg_ref(args[1])
|
||||
a.build_operand args[0]
|
||||
a.write io, as, @ast_asm, self
|
||||
when :ldrb, :ldr
|
||||
a = BuilderB.make(OPC_MEMORY_ACCESS, (opcode == :ldrb ? 1 : 0), 1)
|
||||
a.cond = COND_BITS[@cond]
|
||||
a.rd = reg_ref(args[0])
|
||||
a.build_operand args[1]
|
||||
a.write io, as, @ast_asm, self
|
||||
when :push, :pop
|
||||
# downward growing, decrement before memory access
|
||||
# official ARM style stack as used by gas
|
||||
if (opcode == :push)
|
||||
a = BuilderD.make(1,0,1,0)
|
||||
else
|
||||
a = BuilderD.make(0,1,1,1)
|
||||
end
|
||||
a.cond = COND_BITS[@cond]
|
||||
a.rn = 13 # sp
|
||||
a.build_operand args[0]
|
||||
a.write io, as
|
||||
when :b, :bl
|
||||
arg = args[0]
|
||||
if (arg.is_a?(Asm::Parser::NumLiteralArgNode))
|
||||
jmp_val = arg.value >> 2
|
||||
packed = [jmp_val].pack('l')
|
||||
# signed 32-bit, condense to 24-bit
|
||||
# TODO add check that the value fits into 24 bits
|
||||
io << packed[0,3]
|
||||
elsif (arg.is_a?(Asm::LabelObject) or arg.is_a?(Asm::Parser::LabelRefArgNode))
|
||||
arg = @ast_asm.object_for_label(arg.label, self) if arg.is_a?(Asm::Parser::LabelRefArgNode)
|
||||
as.add_relocation(io.tell, arg, Asm::ARM::R_ARM_PC24, RelocHandler)
|
||||
io << "\x00\x00\x00"
|
||||
end
|
||||
io.write_uint8 OPCODES[opcode] | (COND_BITS[@cond] << 4)
|
||||
when :bx
|
||||
rm = reg_ref(args[0])
|
||||
io.write_uint32 rm | (0b1111111111110001 << 4) | (OPCODES[:bx] << 16+4) |
|
||||
(COND_BITS[@cond] << 16+4+8)
|
||||
when :swi
|
||||
arg = args[0]
|
||||
if (arg.is_a?(Asm::Parser::NumLiteralArgNode))
|
||||
packed = [arg.value].pack('L')[0,3]
|
||||
io << packed
|
||||
io.write_uint8 0b1111 | (COND_BITS[@cond] << 4)
|
||||
else
|
||||
raise Asm::AssemblyError.new(Asm::ERRSTR_INVALID_ARG, arg)
|
||||
end
|
||||
else
|
||||
raise Asm::AssemblyError.new("unknown instruction #{opcode}", @node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
lib/asm/arm/instruction_tools.rb
Normal file
25
lib/asm/arm/instruction_tools.rb
Normal file
@ -0,0 +1,25 @@
|
||||
module Asm
|
||||
module Arm
|
||||
module Asm::ARM::InstructionTools
|
||||
def reg_ref(arg)
|
||||
if (not arg.is_a?(Asm::Parser::RegisterArgNode))
|
||||
raise Asm::AssemblyError.new('argument must be a register', arg)
|
||||
end
|
||||
|
||||
ref =
|
||||
{'r0' => 0, 'r1' => 1, 'r2' => 2, 'r3' => 3, 'r4' => 4, 'r5' => 5,
|
||||
'r6' => 6, 'r7' => 7, 'r8' => 8, 'r9' => 9, 'r10' => 10, 'r11' => 11,
|
||||
'r12' => 12, 'r13' => 13, 'r14' => 14, 'r15' => 15, 'a1' => 0, 'a2' => 1,
|
||||
'a3' => 2, 'a4' => 3, 'v1' => 4, 'v2' => 5, 'v3' => 6, 'v4' => 7, 'v5' => 8,
|
||||
'v6' => 9, 'rfp' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13,
|
||||
'lr' => 14, 'pc' => 15}[arg.name.downcase]
|
||||
|
||||
if (not ref)
|
||||
raise Asm::AssemblyError.new('unknown register %s' % arg.name.downcase, arg)
|
||||
end
|
||||
|
||||
ref
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
lib/asm/assembler.rb
Normal file
43
lib/asm/assembler.rb
Normal file
@ -0,0 +1,43 @@
|
||||
module Asm
|
||||
ERRSTR_NUMERIC_TOO_LARGE = 'cannot fit numeric literal argument in operand'
|
||||
ERRSTR_INVALID_ARG = 'invalid operand argument'
|
||||
|
||||
class Assembler
|
||||
def initialize
|
||||
@objects = []
|
||||
@label_objects = []
|
||||
@label_callbacks = []
|
||||
@relocations = []
|
||||
end
|
||||
attr_reader :relocations, :objects
|
||||
|
||||
def add_object(obj)
|
||||
@objects << obj
|
||||
end
|
||||
|
||||
def add_relocation(*args)
|
||||
@relocations << Asm::Relocation.new(*args)
|
||||
end
|
||||
|
||||
def register_label_callback(label, io_pos, &block)
|
||||
@label_callbacks << [label, io_pos, block]
|
||||
end
|
||||
|
||||
def assemble(io)
|
||||
@objects.each do |obj|
|
||||
obj.assemble io, self
|
||||
end
|
||||
|
||||
@relocations.delete_if do |reloc|
|
||||
io.seek reloc.position
|
||||
if (reloc.label.extern?)
|
||||
reloc.handler.call(io, io.tell, reloc.type)
|
||||
else
|
||||
reloc.handler.call(io, reloc.label.address, reloc.type)
|
||||
end
|
||||
not reloc.label.extern?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
12
lib/asm/assembly_error.rb
Normal file
12
lib/asm/assembly_error.rb
Normal file
@ -0,0 +1,12 @@
|
||||
module Asm
|
||||
class AssemblyError < StandardError
|
||||
def initialize(message, node)
|
||||
super(message)
|
||||
|
||||
@node = node
|
||||
end
|
||||
attr_reader :node
|
||||
end
|
||||
|
||||
end
|
||||
|
85
lib/asm/ast_assembler.rb
Normal file
85
lib/asm/ast_assembler.rb
Normal file
@ -0,0 +1,85 @@
|
||||
module Asm
|
||||
class AstAssembler
|
||||
def initialize(asm_arch)
|
||||
@asm_arch = asm_arch
|
||||
|
||||
@symbols = {}
|
||||
@inst_label_context = {}
|
||||
|
||||
@asm = Asm::Assembler.new
|
||||
end
|
||||
|
||||
def assembler
|
||||
@asm
|
||||
end
|
||||
|
||||
def load_ast(ast)
|
||||
label_breadcrumb = []
|
||||
ast.children.each do |cmd|
|
||||
if (cmd.is_a?(Asm::Parser::LabelNode))
|
||||
m = /^\/+/.match(cmd.name)
|
||||
count = m ? m[0].length : 0
|
||||
label_breadcrumb = label_breadcrumb[0,count]
|
||||
label_breadcrumb << cmd.name[count..-1]
|
||||
@asm.add_object object_for_label(label_breadcrumb.join('/'))
|
||||
elsif (cmd.is_a?(Asm::Parser::InstructionNode))
|
||||
inst = @asm_arch::Instruction.new(cmd, self)
|
||||
@asm.add_object inst
|
||||
@inst_label_context[inst] = label_breadcrumb
|
||||
elsif (cmd.is_a?(Asm::Parser::DirectiveNode))
|
||||
if (cmd.name == 'global')
|
||||
symbol_for_label(cmd.value)[:linkage] = Elf::Constants::STB_GLOBAL
|
||||
elsif (cmd.name == 'extern')
|
||||
object_for_label(cmd.value).extern!
|
||||
elsif (cmd.name == 'hexdata')
|
||||
bytes = cmd.value.strip.split(/\s+/).map do |hex|
|
||||
hex.to_i(16)
|
||||
end.pack('C*')
|
||||
@asm.add_object Asm::DataObject.new(bytes)
|
||||
elsif (cmd.name == "asciz")
|
||||
str = eval(cmd.value) + "\x00"
|
||||
@asm.add_object Asm::DataObject.new(str)
|
||||
elsif (defined?(Asm::ARM) and cmd.name == 'addrtable')
|
||||
@asm.add_object Asm::ARM::AddrTableObject.new
|
||||
else
|
||||
raise Asm::AssemblyError.new('unknown directive', cmd)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# instruction is user for label context
|
||||
def symbol_for_label(name, instruction=nil)
|
||||
if (instruction)
|
||||
context = @inst_label_context[instruction]
|
||||
m = /^(\/*)(.+)/.match(name)
|
||||
breadcrumb = context[0,m[1].length]
|
||||
breadcrumb << m[2]
|
||||
qual_name = breadcrumb.join('/')
|
||||
else
|
||||
qual_name = name
|
||||
end
|
||||
|
||||
if (not @symbols[qual_name])
|
||||
@symbols[name] = {:label => Asm::LabelObject.new, :linkage => Elf::Constants::STB_LOCAL, :name => qual_name}
|
||||
end
|
||||
@symbols[qual_name]
|
||||
end
|
||||
|
||||
def object_for_label(name, instruction=nil)
|
||||
symbol_for_label(name, instruction)[:label]
|
||||
end
|
||||
|
||||
def assemble(io)
|
||||
@asm.assemble io
|
||||
end
|
||||
|
||||
def symbols
|
||||
@symbols.values
|
||||
end
|
||||
|
||||
def relocations
|
||||
@asm.relocations
|
||||
end
|
||||
end
|
||||
end
|
149
lib/asm/command_line.rb
Normal file
149
lib/asm/command_line.rb
Normal file
@ -0,0 +1,149 @@
|
||||
require_relative 'parser'
|
||||
require_relative 'assembler'
|
||||
require_relative 'objectwriter'
|
||||
require 'optparse'
|
||||
require 'ostruct'
|
||||
|
||||
module Asm
|
||||
class CommandLine
|
||||
def initialize
|
||||
options = OpenStruct.new
|
||||
options.output_file = "a.out"
|
||||
options.target = :arm
|
||||
|
||||
opts = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: as [options] <input file>"
|
||||
|
||||
opts.separator ""
|
||||
opts.separator "Options:"
|
||||
|
||||
opts.on("-t", "--target TARGET",
|
||||
"Specify target architecture (arm [default], ttk91)") { |o|
|
||||
options.target = o.to_sym
|
||||
if (not [:arm, :ttk91].include?(options.target))
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
}
|
||||
|
||||
opts.on("-o", "--output FILENAME",
|
||||
"Specify output filename for object file") { |o|
|
||||
options.output_file = o
|
||||
}
|
||||
|
||||
opts.on("-s", "--show-ast",
|
||||
"Show parse tree") { |o|
|
||||
options.show_ast = true
|
||||
}
|
||||
|
||||
opts.on_tail("-h", "--help", "Show this message") {
|
||||
puts opts
|
||||
exit
|
||||
}
|
||||
end
|
||||
|
||||
opts.parse!(ARGV)
|
||||
|
||||
options.input_file = ARGV.shift
|
||||
if (not options.input_file)
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
|
||||
@options = options
|
||||
end
|
||||
attr_reader :options
|
||||
|
||||
def run
|
||||
begin
|
||||
if (options.input_file == '-')
|
||||
code = $stdin.read
|
||||
else
|
||||
code = File.read(options.input_file)
|
||||
end
|
||||
rescue => err
|
||||
puts 'as: could not read input file: ' + err.message
|
||||
exit 2
|
||||
end
|
||||
|
||||
begin
|
||||
ast = Asm::Parser.parse(code)
|
||||
rescue Asm::ParseError => err
|
||||
puts 'as: parse error on line %d, column %d' % [err.line+1, err.column+1]
|
||||
line = code.split("\n")[err.line]
|
||||
puts line.gsub(/\s/, ' ')
|
||||
puts ' ' * (err.column-1) + '^'
|
||||
puts ' ' + err.message
|
||||
exit 3
|
||||
end
|
||||
|
||||
if (options.show_ast)
|
||||
require 'pp'
|
||||
pp ast
|
||||
exit 0
|
||||
end
|
||||
|
||||
case options.target
|
||||
when :arm
|
||||
require_relative 'arm_assembler.rb'
|
||||
as_module = Asm::ARM
|
||||
as_target = Elf::Constants::TARGET_ARM
|
||||
when :ttk91
|
||||
require_relative 'ttk91_assembler.rb'
|
||||
as_module = Asm::TTK91
|
||||
as_target = Elf::Constants::TARGET_TTK91
|
||||
end
|
||||
|
||||
asm = Asm::AstAssembler.new(as_module)
|
||||
begin
|
||||
asm.load_ast ast
|
||||
data = StringIO.new
|
||||
asm.assemble(data)
|
||||
symbols = asm.symbols
|
||||
rescue Asm::AssemblyError => err
|
||||
if (err.node)
|
||||
puts 'as: assembly error on line %d, column %d' % [
|
||||
err.node.line+1, err.node.column+1]
|
||||
line = code.split("\n")[err.node.line]
|
||||
puts line.gsub(/\s/, ' ')
|
||||
puts ' ' * (err.node.column-1) + '^'
|
||||
puts ' ' + err.message
|
||||
else
|
||||
puts 'as: ' + err.message
|
||||
end
|
||||
exit 4
|
||||
end
|
||||
|
||||
writer = Asm::ObjectWriter.new(as_target)
|
||||
writer.set_text data.string
|
||||
|
||||
reloc_name_ref = {}
|
||||
|
||||
symbols.each { |symbol|
|
||||
label = symbol[:label]
|
||||
if (label.extern?)
|
||||
reloc_name_ref[label] = symbol[:name]
|
||||
writer.add_reloc_symbol symbol[:name]
|
||||
else
|
||||
writer.add_symbol symbol[:name], symbol[:label].address, symbol[:linkage]
|
||||
end
|
||||
}
|
||||
|
||||
asm.relocations.each { |reloc|
|
||||
writer.add_reloc reloc.position, reloc_name_ref[reloc.label], reloc.type
|
||||
}
|
||||
|
||||
begin
|
||||
writer.save(options.output_file)
|
||||
rescue => err
|
||||
puts 'as: cannot save output file: ' + err.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
if (__FILE__ == $0)
|
||||
Asm::CommandLine.new.run
|
||||
end
|
11
lib/asm/data_object.rb
Normal file
11
lib/asm/data_object.rb
Normal file
@ -0,0 +1,11 @@
|
||||
module Asm
|
||||
class DataObject
|
||||
def initialize(data)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def assemble(io, as)
|
||||
io << @data
|
||||
end
|
||||
end
|
||||
end
|
32
lib/asm/label_object.rb
Normal file
32
lib/asm/label_object.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module Asm
|
||||
|
||||
class LabelObject
|
||||
def initialize
|
||||
@address = nil
|
||||
@extern = false
|
||||
end
|
||||
attr_writer :address
|
||||
|
||||
def address
|
||||
return 0 if extern?
|
||||
|
||||
if (@address.nil?)
|
||||
raise 'Tried to use label object that has not been set'
|
||||
end
|
||||
@address
|
||||
end
|
||||
|
||||
def assemble(io, as)
|
||||
self.address = io.tell
|
||||
end
|
||||
|
||||
def extern?
|
||||
@extern
|
||||
end
|
||||
|
||||
def extern!
|
||||
@extern = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
48
lib/asm/object_writer.rb
Normal file
48
lib/asm/object_writer.rb
Normal file
@ -0,0 +1,48 @@
|
||||
require_relative 'elfobject'
|
||||
|
||||
module Asm
|
||||
|
||||
class ObjectWriter
|
||||
def initialize(target)
|
||||
@object = ELF::ObjectFile.new(target)
|
||||
|
||||
sym_strtab = ELF::StringTableSection.new(".strtab")
|
||||
@object.add_section sym_strtab
|
||||
@symbol_table = ELF::SymbolTableSection.new(".symtab", sym_strtab)
|
||||
@object.add_section @symbol_table
|
||||
|
||||
@text = ELF::TextSection.new(".text")
|
||||
@object.add_section @text
|
||||
|
||||
@reloc_table = ELF::RelocationTableSection.new(".text.rel", @symbol_table, @text)
|
||||
@object.add_section @reloc_table
|
||||
end
|
||||
|
||||
def set_text(text)
|
||||
@text.text = text
|
||||
add_symbol "_start", 0
|
||||
end
|
||||
|
||||
def add_symbol(name, offset, linkage = Elf::Constants::STB_GLOBAL)
|
||||
@symbol_table.add_func_symbol name, offset, @text, linkage
|
||||
end
|
||||
|
||||
def add_reloc_symbol(name)
|
||||
@symbol_table.add_func_symbol name, 0, nil, Elf::Constants::STB_GLOBAL
|
||||
end
|
||||
|
||||
def add_reloc(offset, label, type)
|
||||
@reloc_table.add_reloc offset, label, type
|
||||
end
|
||||
|
||||
def save(filename)
|
||||
File.open(filename, 'wb') { |fp|
|
||||
write fp
|
||||
}
|
||||
end
|
||||
|
||||
def write(io)
|
||||
@object.write io
|
||||
end
|
||||
end
|
||||
end
|
269
lib/asm/parser.rb
Normal file
269
lib/asm/parser.rb
Normal file
@ -0,0 +1,269 @@
|
||||
require_relative 'str_scanner'
|
||||
|
||||
module AS
|
||||
class ParseError < StandardError
|
||||
def initialize(message, s)
|
||||
super(message)
|
||||
|
||||
@line = s.line
|
||||
@column = s.column
|
||||
end
|
||||
attr_reader :line, :column
|
||||
end
|
||||
end
|
||||
|
||||
class Asm::Parser
|
||||
def initialize(str)
|
||||
scanner = Asm::Scanner.new(str)
|
||||
|
||||
@ast = parse_toplevel scanner
|
||||
end
|
||||
attr_reader :ast
|
||||
|
||||
def self.parse(str)
|
||||
new(str).ast
|
||||
end
|
||||
|
||||
class Node
|
||||
def initialize(s = nil)
|
||||
if (s)
|
||||
@line = s.prev_line
|
||||
@column = s.prev_column
|
||||
else
|
||||
@line = 0
|
||||
@column = 0
|
||||
end
|
||||
|
||||
yield self if block_given?
|
||||
end
|
||||
attr_reader :line, :column
|
||||
end
|
||||
|
||||
class ToplevelNode < Node
|
||||
attr_accessor :children
|
||||
end
|
||||
def parse_toplevel(s)
|
||||
node = ToplevelNode.new(s)
|
||||
node.children = []
|
||||
while (not s.eos?)
|
||||
node.children << parse(s)
|
||||
end
|
||||
node
|
||||
end
|
||||
|
||||
def parse(s)
|
||||
s.scan /\s*/
|
||||
node = nil
|
||||
%w(comment directive label instruction).each { |em|
|
||||
if (node = send('parse_'+em, s))
|
||||
break
|
||||
end
|
||||
}
|
||||
raise Asm::ParseError.new('could not parse element', s) unless node
|
||||
s.scan /\s*/
|
||||
node
|
||||
end
|
||||
|
||||
class CommentNode < Node; end
|
||||
def parse_comment(s)
|
||||
if (s.scan(/;.*?$/))
|
||||
CommentNode.new(s)
|
||||
end
|
||||
end
|
||||
|
||||
class DirectiveNode < Node
|
||||
attr_accessor :name, :value
|
||||
end
|
||||
def parse_directive(s)
|
||||
if (m = s.scan(/\.(\w+)(?:(?!$)\s+(.+)\s*?$)?/))
|
||||
DirectiveNode.new(s) { |n|
|
||||
n.name = m[0]
|
||||
n.value = m[1]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class LabelNode < Node
|
||||
attr_accessor :name
|
||||
end
|
||||
def parse_label(s)
|
||||
if (m = s.scan(/(\/*\w+):/))
|
||||
LabelNode.new(s) { |n|
|
||||
n.name = m[0]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class InstructionNode < Node
|
||||
attr_accessor :opcode, :args
|
||||
end
|
||||
def parse_instruction(s)
|
||||
if (m = s.scan(/(\w+)/))
|
||||
node = InstructionNode.new(s) { |n|
|
||||
n.opcode = m[0]
|
||||
n.args = []
|
||||
}
|
||||
if (not s.scan(/\s*($|;)/))
|
||||
loop {
|
||||
arg = parse_arg(s)
|
||||
node.args << arg
|
||||
break if not s.scan(/\s*,/)
|
||||
}
|
||||
end
|
||||
node
|
||||
end
|
||||
end
|
||||
|
||||
class ArgNode < Node
|
||||
end
|
||||
def parse_arg(s)
|
||||
s.scan /\s*/
|
||||
node = nil
|
||||
%w(reference register register_list num_literal label_ref).each { |em|
|
||||
if (node = send('parse_'+em, s))
|
||||
break
|
||||
end
|
||||
}
|
||||
raise Asm::ParseError.new('expected argument but none found', s) unless node
|
||||
|
||||
if (node2 = parse_arg_op(s))
|
||||
node2.argument = node
|
||||
node = node2
|
||||
end
|
||||
|
||||
s.scan /\s*/
|
||||
node
|
||||
end
|
||||
|
||||
def parse_arg_op(s)
|
||||
s.scan /\s*/
|
||||
node = nil
|
||||
%w(shift math).each do |em|
|
||||
if (node = send('parse_'+em, s))
|
||||
break
|
||||
end
|
||||
end
|
||||
s.scan /\s*/
|
||||
node
|
||||
end
|
||||
|
||||
class ShiftNode < Node
|
||||
attr_accessor :type, :value, :argument
|
||||
end
|
||||
def parse_shift(s)
|
||||
if (m = s.scan(/(lsl|lsr|asr|ror|rrx)\s+/i))
|
||||
op = m[0].downcase
|
||||
if (op == 'rrx' or arg = parse_arg(s))
|
||||
ShiftNode.new(s) { |n|
|
||||
n.type = m[0].downcase
|
||||
n.value = arg
|
||||
}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MathNode < Node
|
||||
attr_accessor :left, :right, :op
|
||||
alias_method :argument, :left
|
||||
alias_method :argument=, :left=
|
||||
end
|
||||
def parse_math(s)
|
||||
if (m = s.scan_str(/[\+\-]/))
|
||||
if (arg1 = parse_arg(s))
|
||||
MathNode.new(s) do |n|
|
||||
n.right = arg1
|
||||
n.op = m
|
||||
end
|
||||
else
|
||||
raise Asm::ParseError.new('expected right side for arithmetic op', s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
REGISTER_REGEXP = Regexp.union(*%w(r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12
|
||||
r13 r14 r15 a1 a2 a3 a4 v1 v2 v3 v4 v5 v6
|
||||
rfp sl fp ip sp lr pc
|
||||
))
|
||||
class RegisterArgNode < ArgNode
|
||||
attr_accessor :name
|
||||
end
|
||||
def parse_register(s)
|
||||
if (m = s.scan_str(REGISTER_REGEXP))
|
||||
RegisterArgNode.new(s) { |n|
|
||||
n.name = m
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class RegisterListArgNode < ArgNode
|
||||
attr_accessor :registers
|
||||
end
|
||||
def parse_register_list(s)
|
||||
if (m = s.scan(/\{/))
|
||||
node = RegisterListArgNode.new(s) do |n|
|
||||
n.registers = []
|
||||
end
|
||||
loop do
|
||||
s.scan /\s*/
|
||||
reg = parse_register(s)
|
||||
if (not reg)
|
||||
return nil
|
||||
end
|
||||
s.scan /\s*,?/
|
||||
|
||||
node.registers << reg
|
||||
|
||||
if (s.scan(/\}/))
|
||||
break
|
||||
end
|
||||
end
|
||||
node
|
||||
end
|
||||
end
|
||||
|
||||
class NumLiteralArgNode < ArgNode
|
||||
attr_accessor :value
|
||||
end
|
||||
class NumEquivAddrArgNode < NumLiteralArgNode
|
||||
end
|
||||
def parse_num_literal(s)
|
||||
if (m = s.scan(/(=?)#(-?(?:0x)?[0-9A-Fa-f]+)/))
|
||||
(m[0] == '=' ? NumEquivAddrArgNode : NumLiteralArgNode).new(s) { |n|
|
||||
n.value = Integer(m[1])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class LabelRefArgNode < ArgNode
|
||||
attr_accessor :label, :label_object
|
||||
end
|
||||
class LabelEquivAddrArgNode < LabelRefArgNode
|
||||
end
|
||||
def parse_label_ref(s)
|
||||
if (m = s.scan(/(=?)(\/*\w+)/))
|
||||
(m[0] == '=' ? LabelEquivAddrArgNode : LabelRefArgNode).new(s) { |n|
|
||||
n.label = m[1]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class ReferenceArgNode < ArgNode
|
||||
attr_accessor :argument
|
||||
end
|
||||
def parse_reference(s)
|
||||
if (m = s.scan(/\[/))
|
||||
arg = parse_arg(s)
|
||||
if (arg and s.scan(/\]/))
|
||||
ReferenceArgNode.new(s) do |n|
|
||||
n.argument = arg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (__FILE__ == $0)
|
||||
p Asm::Parser.parse ARGV[0]
|
||||
end
|
12
lib/asm/relocation.rb
Normal file
12
lib/asm/relocation.rb
Normal file
@ -0,0 +1,12 @@
|
||||
module Asm
|
||||
class Relocation
|
||||
def initialize(pos, label, type, handler)
|
||||
@position = pos
|
||||
@label = label
|
||||
@type = type
|
||||
@handler = handler
|
||||
end
|
||||
attr_reader :position, :label, :type, :handler
|
||||
end
|
||||
|
||||
end
|
65
lib/asm/str_scanner.rb
Normal file
65
lib/asm/str_scanner.rb
Normal file
@ -0,0 +1,65 @@
|
||||
module AS; end
|
||||
|
||||
if (not defined? RUBY_ENGINE or not RUBY_ENGINE == 'rbx')
|
||||
class Regexp
|
||||
def match_start(str, idx)
|
||||
Regexp.compile('\A(?:'+source+')').match(str[idx..-1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Asm::Scanner
|
||||
def initialize(str)
|
||||
@string = str
|
||||
@pos = 0
|
||||
@line = 0
|
||||
@column = 0
|
||||
end
|
||||
attr_accessor :string, :pos, :line, :column, :prev_line, :prev_column
|
||||
|
||||
def rest
|
||||
string[pos..-1]
|
||||
end
|
||||
|
||||
def advance_str(str)
|
||||
self.prev_line = line
|
||||
self.prev_column = column
|
||||
self.pos += str.length
|
||||
self.line += str.count("\n")
|
||||
if (str.include?("\n"))
|
||||
self.column = str.length - str.rindex("\n")
|
||||
else
|
||||
self.column += str.length
|
||||
end
|
||||
end
|
||||
|
||||
def scan(regexp)
|
||||
if (match = regexp.match_start(rest, 0))
|
||||
advance_str match.to_s
|
||||
match.captures
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def scan_str(regexp)
|
||||
if (match = regexp.match_start(rest, 0))
|
||||
advance_str match.to_s
|
||||
match.to_s
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def lookahead(regexp)
|
||||
if (match = regexp.match_start(rest, 0))
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def eos?
|
||||
pos == string.length
|
||||
end
|
||||
end
|
123
lib/asm/streamreader.rb
Normal file
123
lib/asm/streamreader.rb
Normal file
@ -0,0 +1,123 @@
|
||||
module StreamReader
|
||||
def read_binary(size, count, type)
|
||||
d = __sr_read(size*count)
|
||||
ret = d.unpack(type*count)
|
||||
return ret if ret.length > 1
|
||||
return ret[0]
|
||||
end
|
||||
def read_uint32(n=1)
|
||||
return read_binary(4,n,'L')
|
||||
end
|
||||
def read_uint16(n=1)
|
||||
return read_binary(2,n,'S')
|
||||
end
|
||||
def read_uint8(n=1)
|
||||
return read_binary(1,n,'C')
|
||||
end
|
||||
def read_uint64(n=1)
|
||||
return read_binary(8,n,'Q')
|
||||
end
|
||||
def read_sint64(n=1)
|
||||
return read_binary(8,n,'q')
|
||||
end
|
||||
def read_cstr_fixed(length)
|
||||
return __sr_read(length).gsub("\000",'')
|
||||
end
|
||||
def read_cstr_terminated
|
||||
return __sr_gets(0.chr)
|
||||
end
|
||||
def read_cstr_prefixed
|
||||
len = read_uint8
|
||||
return __sr_read(len)
|
||||
end
|
||||
def read_float(n=1)
|
||||
return read_binary(4,n,'F')
|
||||
end
|
||||
def read_double(n=1)
|
||||
return read_binary(8,n,'D')
|
||||
end
|
||||
def read_sint16(n=1)
|
||||
return read_binary(2,n,'s')
|
||||
end
|
||||
def read_sint32(n=1)
|
||||
return read_binary(4,n,'l')
|
||||
end
|
||||
def read_data(len)
|
||||
__sr_read(len)
|
||||
end
|
||||
end
|
||||
|
||||
module StreamWriter
|
||||
def write_binary(values, type)
|
||||
d = values.pack(type * values.length)
|
||||
__sr_write(d)
|
||||
end
|
||||
def write_uint32(*args)
|
||||
return write_binary(args,'L')
|
||||
end
|
||||
def write_uint16(*args)
|
||||
return write_binary(args,'S')
|
||||
end
|
||||
def write_uint8(*args)
|
||||
return write_binary(args,'C')
|
||||
end
|
||||
def write_uint64(*args)
|
||||
return write_binary(args,'Q')
|
||||
end
|
||||
def write_sint64(*args)
|
||||
return write_binary(args,'q')
|
||||
end
|
||||
def write_cstr_fixed(str, len)
|
||||
return __sr_write(str.ljust(len, 0.chr))
|
||||
end
|
||||
def write_cstr_terminated(str)
|
||||
return __sr_write(str + 0.chr)
|
||||
end
|
||||
def write_cstr_prefixed(str)
|
||||
write_uint8(str.length)
|
||||
return __sr_write(str)
|
||||
end
|
||||
def write_str(str)
|
||||
return __sr_write(str)
|
||||
end
|
||||
def write_float(*args)
|
||||
return write_binary(args,'F')
|
||||
end
|
||||
def write_double(*args)
|
||||
return write_binary(args,'D')
|
||||
end
|
||||
def write_sint16(*args)
|
||||
return write_binary(args,'s')
|
||||
end
|
||||
def write_sint32(*args)
|
||||
return write_binary(args,'l')
|
||||
end
|
||||
def write_data(str)
|
||||
return __sr_write(str)
|
||||
end
|
||||
end
|
||||
|
||||
class IO
|
||||
include StreamReader
|
||||
include StreamWriter
|
||||
|
||||
def __sr_read(len)
|
||||
read(len)
|
||||
end
|
||||
def __sr_write(str)
|
||||
write(str)
|
||||
end
|
||||
end
|
||||
|
||||
require 'stringio'
|
||||
class StringIO
|
||||
include StreamReader
|
||||
include StreamWriter
|
||||
|
||||
def __sr_read(len)
|
||||
read(len)
|
||||
end
|
||||
def __sr_write(str)
|
||||
write(str)
|
||||
end
|
||||
end
|
@ -0,0 +1,5 @@
|
||||
class Numeric
|
||||
def fits_u8?
|
||||
self >= 0 and self <= 255
|
||||
end
|
||||
end
|
59
lib/elf/constants.rb
Normal file
59
lib/elf/constants.rb
Normal file
@ -0,0 +1,59 @@
|
||||
module ELF
|
||||
module Constants
|
||||
ET_NONE = 0
|
||||
ET_REL = 1
|
||||
ET_EXEC = 2
|
||||
ET_DYN = 3
|
||||
ET_CORE = 4
|
||||
|
||||
EM_NONE = 0
|
||||
EM_M32 = 1
|
||||
EM_SPARC = 2
|
||||
EM_386 = 3
|
||||
EM_68K = 4
|
||||
EM_88K = 5
|
||||
EM_860 = 7
|
||||
EM_MIPS = 8
|
||||
EM_ARM = 40
|
||||
|
||||
EV_NONE = 0
|
||||
EV_CURRENT = 1
|
||||
|
||||
ELFCLASSNONE = 0
|
||||
ELFCLASS32 = 1
|
||||
ELFCLASS64 = 2
|
||||
|
||||
ELFDATANONE = 0
|
||||
ELFDATA2LSB = 1
|
||||
ELFDATA2MSB = 2
|
||||
|
||||
SHT_NULL = 0
|
||||
SHT_PROGBITS = 1
|
||||
SHT_SYMTAB = 2
|
||||
SHT_STRTAB = 3
|
||||
SHT_RELA = 4
|
||||
SHT_HASH = 5
|
||||
SHT_DYNAMIC = 6
|
||||
SHT_NOTE = 7
|
||||
SHT_NOBITS = 8
|
||||
SHT_REL = 9
|
||||
SHT_SHLIB = 10
|
||||
SHT_DYNSYM = 11
|
||||
|
||||
SHF_WRITE = 0x1
|
||||
SHF_ALLOC = 0x2
|
||||
SHF_EXECINSTR = 0x4
|
||||
|
||||
STB_LOCAL = 0
|
||||
STB_GLOBAL = 1
|
||||
STB_WEAK = 2
|
||||
|
||||
ABI_SYSTEMV = 0
|
||||
ABI_ARM = 0x61
|
||||
|
||||
ARM_INFLOOP = "\x08\xf0\x4f\xe2"
|
||||
|
||||
TARGET_ARM = [ELFCLASS32, ELFDATA2LSB, ABI_ARM, EM_ARM]
|
||||
TARGET_X86 = [ELFCLASS32, ELFDATA2LSB, ABI_SYSTEMV, EM_386]
|
||||
end
|
||||
end
|
18
lib/elf/null_section.rb
Normal file
18
lib/elf/null_section.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module Elf
|
||||
class NullSection < Section
|
||||
def initialize
|
||||
super('')
|
||||
end
|
||||
|
||||
def write(io)
|
||||
end
|
||||
|
||||
def type
|
||||
Elf::Constants::SHT_NULL
|
||||
end
|
||||
|
||||
def alignment
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
89
lib/elf/object_file.rb
Normal file
89
lib/elf/object_file.rb
Normal file
@ -0,0 +1,89 @@
|
||||
module Elf
|
||||
class ObjectFile
|
||||
include ELF
|
||||
|
||||
def initialize(target)
|
||||
@target = target
|
||||
|
||||
@sections = []
|
||||
add_section NullSection.new
|
||||
end
|
||||
|
||||
def add_section(section)
|
||||
@sections << section
|
||||
section.index = @sections.length - 1
|
||||
end
|
||||
|
||||
def write(io)
|
||||
io << "\x7fELF"
|
||||
io.write_uint8 @target[0]
|
||||
io.write_uint8 @target[1]
|
||||
io.write_uint8 EV_CURRENT
|
||||
io.write_uint8 @target[2]
|
||||
io << "\x00" * 8 # pad
|
||||
|
||||
io.write_uint16 ET_REL
|
||||
io.write_uint16 @target[3]
|
||||
io.write_uint32 EV_CURRENT
|
||||
io.write_uint32 0 # entry point
|
||||
io.write_uint32 0 # no program header table
|
||||
sh_offset_pos = io.tell
|
||||
io.write_uint32 0 # section header table offset
|
||||
io.write_uint32 0 # no flags
|
||||
io.write_uint16 52 # header length
|
||||
io.write_uint16 0 # program header length
|
||||
io.write_uint16 0 # program header count
|
||||
io.write_uint16 40 # section header length
|
||||
|
||||
shstrtab = StringTableSection.new(".shstrtab")
|
||||
@sections << shstrtab
|
||||
@sections.each { |section|
|
||||
shstrtab.add_string section.name
|
||||
}
|
||||
|
||||
io.write_uint16 @sections.length # section header count
|
||||
|
||||
io.write_uint16 @sections.length-1 # section name string table index
|
||||
|
||||
# write sections
|
||||
|
||||
section_data = []
|
||||
@sections.each { |section|
|
||||
offset = io.tell
|
||||
section.write(io)
|
||||
size = io.tell - offset
|
||||
section_data << {:section => section, :offset => offset,
|
||||
:size => size}
|
||||
}
|
||||
|
||||
# write section headers
|
||||
|
||||
sh_offset = io.tell
|
||||
|
||||
section_data.each { |data|
|
||||
section, offset, size = data[:section], data[:offset], data[:size]
|
||||
# write header first
|
||||
io.write_uint32 shstrtab.index_for(section.name)
|
||||
io.write_uint32 section.type
|
||||
io.write_uint32 section.flags
|
||||
io.write_uint32 section.addr
|
||||
if (section.type == SHT_NOBITS)
|
||||
raise 'SHT_NOBITS not handled yet'
|
||||
elsif (section.type == SHT_NULL)
|
||||
io.write_uint32 0
|
||||
io.write_uint32 0
|
||||
else
|
||||
io.write_uint32 offset
|
||||
io.write_uint32 size
|
||||
end
|
||||
io.write_uint32 section.link
|
||||
io.write_uint32 section.info
|
||||
io.write_uint32 section.alignment
|
||||
io.write_uint32 section.ent_size
|
||||
}
|
||||
|
||||
io.seek sh_offset_pos
|
||||
io.write_uint32 sh_offset
|
||||
end
|
||||
end
|
||||
end
|
41
lib/elf/relocation_table_section.rb
Normal file
41
lib/elf/relocation_table_section.rb
Normal file
@ -0,0 +1,41 @@
|
||||
module Elf
|
||||
class RelocationTableSection < Section
|
||||
def initialize(name, symtab, text_section)
|
||||
super(name)
|
||||
|
||||
@symtab = symtab
|
||||
@text_section = text_section
|
||||
|
||||
@relocs = []
|
||||
end
|
||||
|
||||
def add_reloc(offset, name, type)
|
||||
@relocs << [offset, name, type]
|
||||
end
|
||||
|
||||
def type
|
||||
Elf::Constants::SHT_REL
|
||||
end
|
||||
|
||||
def ent_size
|
||||
8
|
||||
end
|
||||
|
||||
def link
|
||||
@symtab.index
|
||||
end
|
||||
|
||||
def info
|
||||
@text_section.index
|
||||
end
|
||||
|
||||
def write(io)
|
||||
@relocs.each { |reloc|
|
||||
name_idx = @symtab.index_for_name(reloc[1])
|
||||
io.write_uint32 reloc[0]
|
||||
# +1 because entry number 0 is und
|
||||
io.write_uint32 reloc[2] | ((name_idx+1) << 8)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
31
lib/elf/section.rb
Normal file
31
lib/elf/section.rb
Normal file
@ -0,0 +1,31 @@
|
||||
module Elf
|
||||
class Section
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
attr_accessor :name, :index
|
||||
|
||||
def type
|
||||
raise 'Reimplement #type'
|
||||
end
|
||||
def flags
|
||||
0
|
||||
end
|
||||
def addr
|
||||
0
|
||||
end
|
||||
def link
|
||||
0
|
||||
end
|
||||
def info
|
||||
0
|
||||
end
|
||||
def alignment
|
||||
1
|
||||
end
|
||||
def ent_size
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
29
lib/elf/string_table_section.rb
Normal file
29
lib/elf/string_table_section.rb
Normal file
@ -0,0 +1,29 @@
|
||||
module Elf
|
||||
class StringTableSection < Section
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
@string_data = "\x00"
|
||||
@indices = {"" => 0}
|
||||
end
|
||||
|
||||
def add_string(str)
|
||||
return if @indices[str]
|
||||
|
||||
@indices[str] = @string_data.length
|
||||
@string_data << str << "\x00"
|
||||
end
|
||||
|
||||
def index_for(str)
|
||||
@indices[str]
|
||||
end
|
||||
|
||||
def write(io)
|
||||
io << @string_data
|
||||
end
|
||||
|
||||
def type
|
||||
Elf::Constants::SHT_STRTAB
|
||||
end
|
||||
end
|
||||
end
|
77
lib/elf/symbol_table_section.rb
Normal file
77
lib/elf/symbol_table_section.rb
Normal file
@ -0,0 +1,77 @@
|
||||
module Elf
|
||||
class SymbolTableSection < Section
|
||||
def initialize(name, strtab)
|
||||
super(name)
|
||||
|
||||
@strtab = strtab
|
||||
|
||||
@symbols = []
|
||||
end
|
||||
|
||||
def add_func_symbol(name, value, text_section, linkage)
|
||||
@strtab.add_string name
|
||||
arr = [name, value, text_section, linkage]
|
||||
if (linkage == Elf::Constants::STB_LOCAL)
|
||||
@symbols.unshift arr
|
||||
else
|
||||
@symbols.push arr
|
||||
end
|
||||
end
|
||||
|
||||
def index_for_name(name)
|
||||
@symbols.each_with_index { |sym, idx|
|
||||
if (sym[0] == name)
|
||||
return idx
|
||||
end
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
def type
|
||||
Elf::Constants::SHT_SYMTAB
|
||||
end
|
||||
|
||||
def ent_size
|
||||
16
|
||||
end
|
||||
|
||||
def link
|
||||
@strtab.index
|
||||
end
|
||||
|
||||
def info
|
||||
i = -1
|
||||
@symbols.each_with_index { |sym, idx|
|
||||
if (sym[4] == Elf::Constants::STB_LOCAL)
|
||||
i = idx
|
||||
end
|
||||
}
|
||||
i + 1
|
||||
end
|
||||
|
||||
def write(io)
|
||||
# write undefined symbol
|
||||
io.write_uint32 0
|
||||
io.write_uint32 0
|
||||
io.write_uint32 0
|
||||
io.write_uint8 Elf::Constants::STB_LOCAL << 4
|
||||
io.write_uint8 0
|
||||
io.write_uint16 0
|
||||
|
||||
# write other symbols
|
||||
@symbols.each { |sym|
|
||||
io.write_uint32 @strtab.index_for(sym[0])
|
||||
io.write_uint32 sym[1]
|
||||
io.write_uint32 0
|
||||
io.write_uint8((sym[3] << 4) + 0)
|
||||
io.write_uint8 0
|
||||
if (sym[2])
|
||||
io.write_uint16 sym[2].index
|
||||
else
|
||||
# undefined symbol
|
||||
io.write_uint16 0
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
21
lib/elf/text_section.rb
Normal file
21
lib/elf/text_section.rb
Normal file
@ -0,0 +1,21 @@
|
||||
module Elf
|
||||
class TextSection < Section
|
||||
attr_accessor :text
|
||||
|
||||
def write(io)
|
||||
io << text
|
||||
end
|
||||
|
||||
def type
|
||||
Elf::Constants::SHT_PROGBITS
|
||||
end
|
||||
|
||||
def flags
|
||||
Elf::Constants::SHF_ALLOC | Elf::Constants::SHF_EXECINSTR
|
||||
end
|
||||
|
||||
def alignment
|
||||
4
|
||||
end
|
||||
end
|
||||
end
|
27
test/code_generator.rb
Normal file
27
test/code_generator.rb
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
if (__FILE__ == $0)
|
||||
gen = Asm::ARMCodeGenerator.new
|
||||
|
||||
gen.instance_eval {
|
||||
mov r0, 5
|
||||
loop_start = label
|
||||
loop_start.set!
|
||||
subs r0, r0, 1
|
||||
bne loop_start
|
||||
bx lr
|
||||
}
|
||||
|
||||
require 'objectwriter'
|
||||
require 'tempfile'
|
||||
writer = Asm::ObjectWriter.new(Elf::Constants::TARGET_ARM)
|
||||
writer.set_text gen.assemble
|
||||
|
||||
|
||||
begin
|
||||
writer.save('arm_as_generated.o')
|
||||
rescue => err
|
||||
puts 'as: cannot save output file: ' + err.message
|
||||
exit
|
||||
end
|
||||
|
||||
end
|
19
test/elf_object.rb
Normal file
19
test/elf_object.rb
Normal file
@ -0,0 +1,19 @@
|
||||
if (__FILE__ == $0)
|
||||
obj = ELF::ObjectFile.new ELF::TARGET_ARM
|
||||
|
||||
sym_strtab = ELF::StringTableSection.new(".strtab")
|
||||
obj.add_section sym_strtab
|
||||
symtab = ELF::SymbolTableSection.new(".symtab", sym_strtab)
|
||||
obj.add_section symtab
|
||||
|
||||
text_section = ELF::TextSection.new(".text")
|
||||
obj.add_section text_section
|
||||
|
||||
symtab.add_func_symbol "_start", 0, text_section, ELF::STB_GLOBAL
|
||||
|
||||
fp = File.open("test.o", "wb")
|
||||
obj.write fp
|
||||
|
||||
fp.close
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user