Created
September 6, 2016 18:36
-
-
Save fuzyll/519d0254ec5b103c2227adb74a9d32d5 to your computer and use it in GitHub Desktop.
Rubinius 2.5.2 Disassembler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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