class Fisk
Constants
- RetryRequest
- VERSION
Public Class Methods
# File lib/fisk.rb, line 420 def initialize @instructions = [] @labels = {} # A set of temp registers recorded as we see them (not at allocation time) @temp_registers = Set.new yield self if block_given? end
Public Instance Methods
Instance eval's a given block and writes encoded instructions to buf
. For example:
fisk = Fisk.new fisk.asm do mov r9, imm64(32) end
# File lib/fisk.rb, line 624 def asm buf = StringIO.new(''.b), metadata: {}, &block instance_eval(&block) write_to buf, metadata: metadata buf end
Assign registers to any temporary registers. Only registers in list
will be used when selecting register assignments
# File lib/fisk.rb, line 524 def assign_registers list, local: false temp_registers = @temp_registers # This mutates the temp registers, setting their end_point based on # the CFG self.cfg unless local temp_registers.each do |reg| unless reg.end_point raise Errors::UnreleasedRegisterError, "Register #{reg.name} hasn't been released" end end temp_registers = temp_registers.sort_by(&:start_point) active = [] free_registers = list.reverse register_count = list.length temp_registers.each do |temp_reg| # expire old intervals active, dead = active.sort_by(&:end_point).partition do |j| j.end_point >= temp_reg.start_point end # Add unused registers back to the free register list dead.each { |tr| free_registers << tr.register } if active.length == register_count raise NotImplementedError, "Register spilled" end temp_reg.register = free_registers.pop active << temp_reg end end
Return a list of basic blocks for the instructions
# File lib/fisk.rb, line 448 def basic_blocks cfg.blocks end
Return a cfg for these instructions. The CFG
includes connected basic blocks as well as any temporary registers
# File lib/fisk.rb, line 454 def cfg CFG.build @instructions end
Insert a comment at the current position in the instructions.
# File lib/fisk.rb, line 511 def comment message @instructions << Comment.new(message) self end
# File lib/fisk.rb, line 685 def gen_with_insn insns, params forms = insns.forms.find_all do |insn| if insn.operands.length == params.length params.zip(insn.operands).all? { |want_op, have_op| want_op.works?(have_op.type) } else false end end if forms.length == 0 valid_forms = insns.forms.map { |form| " #{insns.name} #{form.operands.map(&:type).join(", ")}" }.join "\n" msg = <<~eostr Couldn't find instruction #{insns.name} #{params.map(&:type).join(", ")} Valid forms: #{valid_forms} eostr raise NotImplementedError, msg end form = forms.first insn = nil params.each do |param| if param.unresolved? if insns.name =~ /^J/ # I hope all jump instructions start with J! insn = UnresolvedJumpInstruction.new(insns, form, params.first) else # If it's not a jump instruction, assume unresolved RIP relative 😬 insn = UnresolvedRIPInstruction.new(insns, form, params) end end if param.temp_register? if param.end_point raise Errors::UseAfterInvalidationError, "Register #{param.name} used after release" end @temp_registers << param param.start_point ||= @instructions.length end end insn ||= Instruction.new(insns, form, params) @instructions << insn self end
Create a signed immediate value of the right width
# File lib/fisk.rb, line 573 def imm val if val >= -0x7F - 1 && val <= 0x7F imm8 val elsif val >= -0x7FFF - 1 && val <= 0x7FFF imm16 val elsif val >= -0x7FFFFFFF - 1 && val <= 0x7FFFFFFF imm32 val elsif val >= -0x7FFFFFFFFFFFFFFF - 1 && val <= 0x7FFFFFFFFFFFFFFF imm64 val else raise ArgumentError, "#{val} is larger than a 64 bit int" end end
Create a label to be used with jump instructions. For example:
fisk.jmp(fisk.label(:foo)) fisk.int(lit(3)) fisk.put_label(:foo)
# File lib/fisk.rb, line 464 def label name UnknownLabel.new(name) end
Record a block that will be called during instruction encoding. The block will be yielded the current position of the buffer.
For example:
patch_position = nil fisk.nop fisk.lazy { |pos| patch_location = pos } fisk.jmp(fisk.label(:foo)) fisk.lazy { |_| fisk.mov(fisk.r8, fisk.imm64(patch_location)) } fisk.write_to(buf)
The first lazy block will be yielded the position of the buffer immediately after encoding the `nop` instruction and before encoding the `jmp` instruction. The second lazy block will be yielded to after the `jmp` instruction has been encoded. This gives you a chance to write the buffer position from certain points in to your assembly
# File lib/fisk.rb, line 498 def lazy &block @instructions << Lazy.new(block) self end
# File lib/fisk.rb, line 612 def lit val Lit.new val end
# File lib/fisk.rb, line 224 def m x, displacement = 0 M.new x, displacement end
# File lib/fisk.rb, line 220 def m64 x, displacement = 0 M64.new x, displacement end
# File lib/fisk.rb, line 568 def moffs64 val MOffs64.new val end
Insert a label named name
at the current position in the instructions.
# File lib/fisk.rb, line 504 def put_label name @instructions << Label.new(name) self end
Allocate and return a new register. These registers will be replaced with real registers when `assign_registers` is called.
# File lib/fisk.rb, line 518 def register name = "temp" Registers::Temp.new name, "r64" end
# File lib/fisk.rb, line 608 def rel32 val Rel32.new val end
# File lib/fisk.rb, line 604 def rel8 val Rel8.new val end
Releases all registers that haven't already been released
# File lib/fisk.rb, line 440 def release_all_registers @temp_registers.each do |reg| next if reg.end_point release_register reg end end
Mark a temporary register as “done being used” at this point in the instructions. Using the register after passing the register to this method results in undefined behavior.
# File lib/fisk.rb, line 431 def release_register reg if reg.end_point raise Errors::AlreadyReleasedError, "register #{reg.name} already released at #{reg.end_point}" end reg.end_point = (@instructions.length - 1) end
# File lib/fisk.rb, line 216 def rip displacement = 0 Registers::Rip.new(displacement) end
Encodes all instructions and returns a binary string with the encoded instructions.
# File lib/fisk.rb, line 632 def to_binary(metadata: {}) io = StringIO.new ''.b write_to io, metadata: metadata io.string end
Create an unsigned immediate value of the right width
# File lib/fisk.rb, line 588 def uimm val if val < 0 raise ArgumentError, "#{val} is negative" elsif val <= 0xFF imm8 val elsif val <= 0xFFFF imm16 val elsif val <= 0xFFFFFFFF imm32 val elsif val <= 0xFFFFFFFFFFFFFFFF imm64 val else raise ArgumentError, "#{val} is too large for a 64 bit int" end end
Encode all instructions and write them to buffer
. buffer
should be an IO object.
# File lib/fisk.rb, line 642 def write_to buffer, metadata: {} labels = {} comments = {} unresolved = [] instructions = @instructions.dup backup = @instructions.dup @instructions.clear while insn = instructions.shift if insn.label? labels[insn.name] = buffer.pos elsif insn.comment? comments.update({buffer.pos => insn.message}) { |_, *lines| lines.join($/) } elsif insn.lazy? insn.encode buffer, labels instructions.unshift(*@instructions) @instructions.clear else if insn.retry? retry_req = RetryRequest.new(insn, buffer.pos) unresolved << retry_req end write_instruction insn, buffer, labels end end metadata[:comments] = comments @instructions = backup return if unresolved.empty? pos = buffer.pos unresolved.each do |req| insn = req.insn buffer.seek req.io_seek_pos, IO::SEEK_SET insn.encode buffer, labels end buffer.seek pos, IO::SEEK_SET buffer end
Private Instance Methods
# File lib/fisk.rb, line 741 def write_instruction insn, buffer, labels insn.encode buffer, labels end