Skip to content

Instantly share code, notes, and snippets.

@dylanahsmith
Last active January 18, 2019 20:55
Show Gist options
  • Save dylanahsmith/5715949 to your computer and use it in GitHub Desktop.
Save dylanahsmith/5715949 to your computer and use it in GitHub Desktop.
Print contents of marshal dump for inspection and debugging
#!/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