Last active
January 18, 2019 20:55
-
-
Save dylanahsmith/5715949 to your computer and use it in GitHub Desktop.
Print contents of marshal dump for inspection and debugging
This file contains hidden or 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
#!/usr/bin/env ruby | |
# frozen_string_literal: true | |
filename = ARGV.shift | |
class MarshalPrinter | |
TYPE_NIL = '0' | |
TYPE_TRUE = 'T' | |
TYPE_FALSE = 'F' | |
TYPE_FIXNUM = 'i' | |
TYPE_EXTENDED = 'e' | |
TYPE_UCLASS = 'C' | |
TYPE_OBJECT = 'o' | |
TYPE_DATA = 'd' | |
TYPE_USERDEF = 'u' | |
TYPE_USRMARSHAL = 'U' | |
TYPE_FLOAT = 'f' | |
TYPE_BIGNUM = 'l' | |
TYPE_STRING = '"' | |
TYPE_REGEXP = '/' | |
TYPE_ARRAY = '[' | |
TYPE_HASH = '{' | |
TYPE_HASH_DEF = '}' | |
TYPE_STRUCT = 'S' | |
TYPE_MODULE_OLD = 'M' | |
TYPE_CLASS = 'c' | |
TYPE_MODULE = 'm' | |
TYPE_SYMBOL = ':' | |
TYPE_SYMLINK = ';' | |
TYPE_IVAR = 'I' | |
TYPE_LINK = '@' | |
def self.print(io) | |
new(io).print | |
end | |
def initialize(io) | |
@io = io | |
@symbols = [] | |
@entry = -1 | |
@indent = 0 | |
end | |
def log(message) | |
log_partial message | |
flush_log_line | |
end | |
def log_partial(message) | |
@line ||= " " * @indent | |
@line << message | |
end | |
def flush_log_line | |
if @line | |
puts @line | |
@line = nil | |
end | |
end | |
def indent | |
@indent += 2 | |
yield | |
ensure | |
@indent -= 2 | |
end | |
def print | |
print_marshal_version | |
print_value | |
ensure | |
pos = @io.pos | |
@io.seek(0, IO::SEEK_END) | |
length = @io.pos | |
@io.seek(pos, IO::SEEK_SET) | |
puts "#{pos} / #{length} bytes read" | |
end | |
private | |
def print_marshal_version | |
major = @io.readbyte | |
minor = @io.readbyte | |
log "Marshal version #{major}.#{minor}" | |
end | |
def print_value | |
case type = @io.readchar | |
when TYPE_NIL | |
log "nil" | |
when TYPE_TRUE | |
log "true" | |
when TYPE_FALSE | |
log "false" | |
when TYPE_FIXNUM | |
log "#{read_long.inspect}" | |
when TYPE_FLOAT | |
log "#{read_float.inspect}, entry: #{new_entry}" | |
when TYPE_STRING | |
log "#{read_string.inspect}, entry: #{new_entry}" | |
when TYPE_SYMBOL | |
log "#{read_new_symbol.inspect}" | |
when TYPE_SYMLINK | |
log "#{read_symlink.inspect}" | |
when TYPE_ARRAY | |
print_array | |
when TYPE_HASH | |
print_hash | |
when TYPE_HASH_DEF | |
read_hash_def | |
when TYPE_OBJECT | |
print_object | |
when TYPE_CLASS | |
log "TYPE_CLASS #{read_string}, entry: #{new_entry}" | |
when TYPE_IVAR | |
log "TYPE_IVAR" | |
indent do | |
log_partial "OBJECT:" | |
print_value | |
end | |
indent do | |
log_partial "IVARS:" | |
indent { print_ivars } | |
end | |
when TYPE_STRUCT | |
print_struct | |
when TYPE_UCLASS | |
print_uclass | |
when TYPE_USERDEF | |
print_userdef | |
when TYPE_USRMARSHAL | |
name = read_unique | |
log "TYPE_USRMARSHAL #{name}, entry: #{new_entry}" | |
print_value | |
when TYPE_BIGNUM | |
sign = @io.readchar | |
word_length = read_long | |
data = io_read(word_length * 2) | |
words = data.unpack("S<*") | |
number = words.reverse.reduce(0) { |num, word| num << 16 | word } | |
case sign | |
when '-' | |
number = -number | |
when '+' | |
else | |
raise ArgumentError, "unexpected TYPE_BIGNUM sign #{sign}" | |
end | |
log "TYPE_BIGNUM #{number}, entry: #{new_entry}" | |
when TYPE_EXTENDED | |
raise ArgumentError, "unsupported marshal object type (TYPE_EXTENDED)" | |
when TYPE_DATA | |
raise ArgumentError, "unsupported marshal object type (TYPE_DATA)" | |
when TYPE_REGEXP | |
raise ArgumentError, "unsupported marshal object type (TYPE_REGEXP)" | |
when TYPE_MODULE_OLD | |
raise ArgumentError, "unsupported marshal object type (TYPE_MODULE_OLD)" | |
when TYPE_MODULE | |
raise ArgumentError, "unsupported marshal object type (TYPE_MODULE)" | |
when TYPE_LINK | |
print_link | |
else | |
raise ArgumentError, "invalid marshal object type (#{type})" | |
end | |
end | |
def read_long | |
c = @io.readchar.unpack("c").first | |
if c == 0 | |
return 0 | |
elsif c > 0 | |
return c - 5 if c > 4 | |
buf = io_read(c) | |
buf << "\x00" * (4 - c) | |
buf.unpack("l").first | |
else | |
return c + 5 if c < -4 | |
c = -c | |
buf = io_read(c) | |
buf << "\xff" * (4 - c) | |
buf.unpack("l").first | |
end | |
end | |
def read_float | |
case str = read_string | |
when "nan" | |
Float::NAN | |
when "inf" | |
Float::INFINITY | |
when "-inf" | |
-Float::INFINITY | |
else | |
str, mantissa = str.split("\x00", 2) | |
f = Float(str) | |
if mantissa | |
raise ArgumentError, "unsupported binary mantissa loading from marshaled float" | |
end | |
f | |
end | |
end | |
def read_string | |
len = read_long | |
io_read(len) | |
end | |
def read_new_symbol | |
str = read_string | |
sym = str.to_sym | |
@symbols << sym | |
sym | |
end | |
def read_symlink | |
index = read_long | |
@symbols[index] || raise(ArgumentError, "marshal symlink index out of range") | |
end | |
def read_symbol | |
type = @io.readchar | |
case type | |
when TYPE_SYMBOL | |
read_new_symbol | |
when TYPE_SYMLINK | |
read_symlink | |
when TYPE_IVAR | |
raise ArgumentError, "unsupported symbol type (TYPE_IVAR)" | |
else | |
raise ArgumentError, "invalid marshal symbol type (#{type.inspect})" | |
end | |
end | |
def read_unique | |
read_symbol.to_s | |
end | |
def print_link | |
index = read_long | |
log "TYPE_LINK entry: #{index}" | |
end | |
def print_array | |
flush_log_line | |
len = read_long | |
log "TYPE_ARRAY length: #{len}, entry: #{new_entry}" | |
indent do | |
len.times do | |
print_value | |
end | |
end | |
end | |
def print_hash | |
flush_log_line | |
len = read_long | |
log "TYPE_HASH length: #{len}, entry: #{new_entry}" | |
indent do | |
len.times do | |
print_value | |
log_partial "=> " | |
indent{ print_value } | |
end | |
end | |
end | |
def read_hash_def | |
print_hash | |
indent do | |
log_partial "DEFAULT: " | |
print_value | |
end | |
end | |
def print_object | |
klass_name = read_unique | |
log "TYPE_OBJECT #{klass_name}, entry: #{new_entry}" | |
indent do | |
print_ivars | |
end | |
end | |
def print_ivars | |
flush_log_line | |
len = read_long | |
len.times do | |
name = read_symbol | |
log_partial "#{name.inspect} => " | |
indent { print_value } | |
end | |
end | |
def print_struct | |
flush_log_line | |
klass_name = read_unique | |
len = read_long | |
log "TYPE_STRUCT #{klass_name}" | |
indent do | |
len.times do |i| | |
member = read_symbol | |
log_partial "#{member.inspect} => " | |
indent { print_value } | |
end | |
end | |
end | |
def print_uclass | |
flush_log_line | |
klass_name = read_unique | |
log "TYPE_UCLASS #{klass_name}, entry: #{new_entry}" | |
indent { print_value } | |
end | |
def print_userdef | |
flush_log_line | |
klass_name = read_unique | |
data = read_string | |
log "TYPE_USERDEF #{klass_name}, size: #{data.size}, entry: #{new_entry}" | |
indent do | |
i = 0 | |
log(data.byteslice(i, 16).bytes.map{ |b| sprintf("%02x", b) }.join(' ')) | |
end | |
end | |
def new_entry | |
@entry += 1 | |
end | |
def io_read(len) | |
data = @io.read(len) | |
raise EOFError unless data && data.size == len | |
data | |
end | |
end | |
obj = File.open(filename) do |file| | |
MarshalPrinter.print(file) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment