Skip to content

Instantly share code, notes, and snippets.

@fuzyll
Created September 6, 2016 18:36
Show Gist options
  • Save fuzyll/519d0254ec5b103c2227adb74a9d32d5 to your computer and use it in GitHub Desktop.
Save fuzyll/519d0254ec5b103c2227adb74a9d32d5 to your computer and use it in GitHub Desktop.
Rubinius 2.5.2 Disassembler
# Rubinius 2.5.2 disassembler created to solve Sullivan Square in BKP2015 by fuzyll
module Rubinius
# from vm/instructions.def
# used $(grep -e "^instruction" instructions.def | cut -d" " -f2) and some regexes/multiline editing in vim to get this
InstructionMap = {
"0" => { :name => "noop", :args => [] },
"1" => { :name => "push_nil", :args => [] },
"2" => { :name => "push_true", :args => [] },
"3" => { :name => "push_false", :args => [] },
"4" => { :name => "push_int", :args => [:number] },
"5" => { :name => "push_self", :args => [] },
"6" => { :name => "set_literal", :args => [:literal] },
"7" => { :name => "push_literal", :args => [:literal] },
"8" => { :name => "goto", :args => [:location] },
"9" => { :name => "goto_if_false", :args => [:location] },
"10" => { :name => "goto_if_true", :args => [:location] },
"11" => { :name => "goto_if_nil", :args => [:location] },
"12" => { :name => "goto_if_not_nil", :args => [:location] },
"13" => { :name => "goto_if_undefined", :args => [:location] },
"14" => { :name => "goto_if_not_undefined", :args => [:location] },
"15" => { :name => "goto_if_equal", :args => [:location] },
"16" => { :name => "goto_if_not_equal", :args => [:location] },
"17" => { :name => "ret", :args => [] },
"18" => { :name => "swap_stack", :args => [] },
"19" => { :name => "dup_top", :args => [] },
"20" => { :name => "dup_many", :args => [:count] },
"21" => { :name => "pop", :args => [] },
"22" => { :name => "pop_many", :args => [:count] },
"23" => { :name => "rotate", :args => [:counter] },
"24" => { :name => "move_down", :args => [:positions] },
"25" => { :name => "set_local", :args => [:local] },
"26" => { :name => "push_local", :args => [:local] },
"27" => { :name => "push_local_depth", :args => [:depth, :index] },
"28" => { :name => "set_local_depth", :args => [:depth, :index] },
"29" => { :name => "passed_arg", :args => [:index] },
"30" => { :name => "push_current_exception", :args => [] },
"31" => { :name => "clear_exception", :args => [] },
"32" => { :name => "push_exception_state", :args => [] },
"33" => { :name => "restore_exception_state", :args => [] },
"34" => { :name => "raise_exc", :args => [] },
"35" => { :name => "setup_unwind", :args => [:ip, :type] },
"36" => { :name => "pop_unwind", :args => [] },
"37" => { :name => "raise_return", :args => [] },
"38" => { :name => "ensure_return", :args => [] },
"39" => { :name => "raise_break", :args => [] },
"40" => { :name => "reraise", :args => [] },
"41" => { :name => "make_array", :args => [:count] },
"42" => { :name => "cast_array", :args => [] },
"43" => { :name => "shift_array", :args => [] },
"44" => { :name => "set_ivar", :args => [:literal] },
"45" => { :name => "push_ivar", :args => [:literal] },
"46" => { :name => "push_const", :args => [:literal] },
"47" => { :name => "set_const", :args => [:literal] },
"48" => { :name => "set_const_at", :args => [:literal] },
"49" => { :name => "find_const", :args => [:literal] },
"50" => { :name => "push_cpath_top", :args => [] },
"51" => { :name => "push_const_fast", :args => [:literal] },
"52" => { :name => "find_const_fast", :args => [:literal] },
"53" => { :name => "set_call_flags", :args => [:flags] },
"54" => { :name => "allow_private", :args => [] },
"55" => { :name => "send_method", :args => [:literal] },
"56" => { :name => "send_stack", :args => [:literal, :count] },
"57" => { :name => "send_stack_with_block", :args => [:literal, :count] },
"58" => { :name => "send_stack_with_splat", :args => [:literal, :count] },
"59" => { :name => "send_super_stack_with_block", :args => [:literal, :count] },
"60" => { :name => "send_super_stack_with_splat", :args => [:literal, :count] },
"61" => { :name => "push_block", :args => [] },
"62" => { :name => "passed_blockarg", :args => [:count] },
"63" => { :name => "create_block", :args => [:literal] },
"64" => { :name => "cast_for_single_block_arg", :args => [] },
"65" => { :name => "cast_for_multi_block_arg", :args => [] },
"66" => { :name => "cast_for_splat_block_arg", :args => [] },
"67" => { :name => "yield_stack", :args => [:count] },
"68" => { :name => "yield_splat", :args => [:count] },
"69" => { :name => "string_append", :args => [] },
"70" => { :name => "string_build", :args => [:count] },
"71" => { :name => "string_dup", :args => [] },
"72" => { :name => "push_scope", :args => [] },
"73" => { :name => "add_scope", :args => [] },
"74" => { :name => "push_variables", :args => [] },
"75" => { :name => "check_interrupts", :args => [] },
"76" => { :name => "yield_debugger", :args => [] },
"77" => { :name => "is_nil", :args => [] },
"78" => { :name => "check_serial", :args => [:literal, :serial] },
"79" => { :name => "check_serial_private", :args => [:literal, :serial] },
"80" => { :name => "push_my_field", :args => [:index] },
"81" => { :name => "store_my_field", :args => [:index] },
"82" => { :name => "kind_of", :args => [] },
"83" => { :name => "instance_of", :args => [] },
"84" => { :name => "meta_push_neg_1", :args => [] },
"85" => { :name => "meta_push_0", :args => [] },
"86" => { :name => "meta_push_1", :args => [] },
"87" => { :name => "meta_push_2", :args => [] },
"88" => { :name => "meta_send_op_plus", :args => [:literal] },
"89" => { :name => "meta_send_op_minus", :args => [:literal] },
"90" => { :name => "meta_send_op_equal", :args => [:literal] },
"91" => { :name => "meta_send_op_lt", :args => [:literal] },
"92" => { :name => "meta_send_op_gt", :args => [:literal] },
"93" => { :name => "meta_send_op_tequal", :args => [:literal] },
"94" => { :name => "meta_send_call", :args => [:literal, :count] },
"95" => { :name => "push_my_offset", :args => [:index] },
"96" => { :name => "zsuper", :args => [:literal] },
"97" => { :name => "push_block_arg", :args => [] },
"98" => { :name => "push_undef", :args => [] },
"99" => { :name => "push_stack_local", :args => [:which] },
"100" => { :name => "set_stack_local", :args => [:which] },
"101" => { :name => "push_has_block", :args => [] },
"102" => { :name => "push_proc", :args => [] },
"103" => { :name => "check_frozen", :args => [] },
"104" => { :name => "cast_multi_value", :args => [] },
"105" => { :name => "invoke_primitive", :args => [:literal, :count] },
"106" => { :name => "push_rubinius", :args => [] },
"107" => { :name => "call_custom", :args => [:literal, :count] },
"108" => { :name => "meta_to_s", :args => [:literal] },
"109" => { :name => "push_type", :args => [] },
"110" => { :name => "push_mirror", :args => [] }
}
class Disassembler
def initialize(file)
# read raw file into an array (split on newlines)
@raw = []
File.open(file, 'rb') do |f|
@raw = f.read.split("\n")
end
raise "Not a valid Rubinius bytecode file" unless '!RBIX' == @raw[0]
# parse it up
@stamp = @raw[1]
@version = @raw[2].to_i
@index = 3
@blocks = []
unmarshal()
end
def unmarshal
# from rubinius::UnMarshaller::unmarshal() in vm/marshal.cpp
tag = @raw[@index]
@index += 1
return nil if tag == "n"
return true if tag == "t"
return false if tag == "f"
return get_int() if tag == "I"
return get_rational() if tag == "R"
return get_complex() if tag == "C"
return get_string() if tag == "s"
return get_string().to_sym() if tag == "x"
return get_tuple() if tag == "p"
return get_float() if tag == "f"
return get_iseq() if tag == "i"
return get_compiled_code() if tag == "M"
return get_constant() if tag == "c"
return get_encoding() if tag == "E"
raise "Unrecognised tag #{tag.inspect} at #{@index}"
end
def get_int
int = @raw[@index].to_i
@index += 1
return int
end
def get_rational
raise "Rational NYI"
end
def get_complex
raise "Complex NYI"
end
def get_string
encoding = get_encoding
size = @raw[@index].to_i
str = @raw[@index+1].encode(encoding)
# FIXME: warning below should be a raised exception, but i've encountered it often enough that it's
# obviously a problem with this function that i haven't tracked down yet
puts "String #{str.inspect} at index #{@index+1} was not #{size} bytes in size" if str.length != size
@index += 2
return str
end
def get_tuple
size = @raw[@index].to_i
@index += 1
body = []
size.times do
body << unmarshal()
end
return body
end
def get_float
raise "Float NYI"
end
def get_iseq
count = @raw[@index].to_i
@index += 1
i = 0
operations = []
while i < count do
if InstructionMap.include? @raw[@index+i]
insn = InstructionMap[@raw[@index+i]]
args = {}
if insn[:args].length != 0
insn[:args].length.times do |j|
args[insn[:args][j]] = @raw[@index+i+j+1].to_i
end
end
operations << { :name => insn[:name], :args => args }
i += 1 + insn[:args].length
else
operations << { :name => "unknown", :args => []}
i += 1
end
end
@index += count
return operations
end
def get_compiled_code
# verify compiled code version
version = @raw[@index].to_i
if version != 1
raise "Unsupported version (#{version}) of compiled code at index #{@index}"
end
@index += 1
# unmarshal the whole block
# (this is from rubinius::UnMarshaller::get_compiled_code in vm/marshal.cpp)
block = {
:metadata => unmarshal(),
:primitive => unmarshal(),
:name => unmarshal(),
:iseq => unmarshal(),
:stack_size => unmarshal(),
:local_count => unmarshal(),
:required_args => unmarshal(),
:post_args => unmarshal(),
:total_args => unmarshal(),
:splat => unmarshal(),
:keywords => unmarshal(),
:arity => unmarshal(),
:literals => unmarshal(),
:lines => unmarshal(),
:file => unmarshal(),
:local_names => unmarshal()
}
# resolve all literal and local table lookups
data = []
block[:iseq].each do |insn|
if insn[:args].include? :literal
index = insn[:args][:literal]
literal = block[:literals][index]
insn[:args][:literal] = literal
elsif insn[:args].include? :local
index = insn[:args][:local]
local = block[:local_names][index]
insn[:args][:local] = local
end
data << insn
end
block[:iseq] = data
# add it to our list of blocks, and return
@blocks << block
return
end
def get_constant
raise "Constant NYI"
end
def get_encoding
raise "Called get_encoding on something that wasn't an encoding at index #{@index}" if @raw[@index] != "E"
enc = @raw[@index+2]
raise "Encoding #{enc} is not of length #{@raw[@index+1].to_i}" if enc.length != @raw[@index+1].to_i
@index += 3
return enc
end
def print_blocks
@blocks.each do |block|
puts "\# stack size: #{block[:stack_size]}"
puts "\# locals: #{block[:local_count]}"
puts "\# arguments: #{block[:required_args]} / #{block[:post_args]} / #{block[:total_args]}"
puts "\# lines: #{block[:lines]}"
puts "\# literals: #{block[:literals]}"
puts "\# locals: #{block[:local_names]}"
puts "def #{block[:name].to_s}"
num = 0
block[:iseq].each do |insn|
puts " #{num}:\t#{insn[:name]} #{insn[:args] if not insn[:args].empty?}"
num += 1 + insn[:args].length
end
puts ""
end
end
end
end
# if file is run directly, dump contents of .rbc files to STDOUT
if __FILE__ == $0
if ARGV.size > 0
while rbc = ARGV.shift
disassembler = Rubinius::Disassembler.new(rbc)
puts "\#\#\# #{rbc} \#\#\#"
puts ""
disassembler.print_blocks()
puts ""
puts ""
end
else
puts "Usage: #{__FILE__} <rbc_file> [<rbc_file> ...]"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment