diff --git a/bin/alienist b/bin/alienist index 0ab929b..57522fc 100755 --- a/bin/alienist +++ b/bin/alienist @@ -53,5 +53,10 @@ File.open(filename, "rb") do |io| require 'alienist/console' IRB.start_session binding + else # Assume dumping for now + require 'alienist/dumper/ruby_json_dumper' + + # FIXME Hook up io explicitly + Alienist::Dumper::RubyJSONDumper.dump(snapshot) end end diff --git a/lib/alienist/#reader.rb# b/lib/alienist/#reader.rb# deleted file mode 100644 index f60dabf..0000000 --- a/lib/alienist/#reader.rb# +++ /dev/null @@ -1,111 +0,0 @@ -module Alienist - class Reader - attr_accessor :identifier_size - - SHORT, INT, LONG = 2, 4, 8 - - def initialize(io, debug=0) - @io = io - @debug = debug - end - - def pos - @io.pos - end - - def small_id? - @identifier_size == 4 - end - - def read(amount, label="debug") - str = @io.read amount - puts "#{label}: '#{str}' (#{str.length})" if @debug >= 9 && str - str - end - - def read_boolean - read_byte('boolean') == '1' - end - - def read_byte(label="byte") - byte = read(1, label) - return nil unless byte - byte.unpack('C')[0] - end - - def read_id2(label="id2") - val = 0 - bytes = read_bytes @identifier_size - bytes.each_byte do |b| - val <<= 8 - val |= b & 0xff - end - val - end - - def read_bytes(amount) - read amount, "bytes" - end - - def read_char(label='char') - read(SHORT, label).unpack('a')[0] - end - - def read_float(label="float") - read(INT, label).unpack('f')[0] - end - - def read_id - small_id? ? read_int("id") : read_long("id") - end - - def read_ids(number_of_ids=1) - ids = [] - number_of_ids.times do - ids << read_id - end - ids - end - - def read_int(label="int") - read(INT, label).unpack('i!>')[0] - end - - def read_short(label="ushort") - read(SHORT, label).unpack('s>')[0] - end - - def read_unsigned_short(label="ushort") - read(SHORT, label).unpack('s!>')[0] - end - - def read_double(label="double") - read(LONG, label).unpack('d')[0] - end - - def read_long(label="long") - read(LONG, label).unpack('l!>')[0] - end - - def read_timestamp - read_int "timestamp" - end - - def read_date - read_long "date" - end - - def read_type - read_byte "type" - end - - def seek(absolute_amount) - @io.seek absolute_amount, IO::SEEK_SET - end - - def skip_bytes(amount, label="") - puts "skipping #{amount} for #{label}" if @debug >= 7 - @io.seek amount, IO::SEEK_CUR - end - end -end diff --git a/lib/alienist/.#reader.rb b/lib/alienist/.#reader.rb deleted file mode 120000 index 5f728fa..0000000 --- a/lib/alienist/.#reader.rb +++ /dev/null @@ -1 +0,0 @@ -enebo@Thomass-MacBook-Pro.local.472 \ No newline at end of file diff --git a/lib/alienist/dumper/ruby_json_dumper.rb b/lib/alienist/dumper/ruby_json_dumper.rb new file mode 100644 index 0000000..06603e7 --- /dev/null +++ b/lib/alienist/dumper/ruby_json_dumper.rb @@ -0,0 +1,37 @@ +module Alienist + module Dumper + class RubyJSONDumper + # Hmmm I don't want to do this by hand but I need to not dump my + # entire POROs + def self.dump(snapshot) + puts "{" + snapshot.ruby_classes.each do |name, cls| + dump_pair 'name', name, ' ' + dump_pair 'size', cls.size, ' ', '' + dump_pair 'id', cls.id, ' ', '' + puts " \"instances\": {" + cls.ruby_instances.each do |obj| + dump_pair 'id', obj.id, ' ', '' + dump_pair 'size', obj.size, ' ', '' + dump_pair 'data', dump_type_data(snapshot, name, obj), ' ' + end + puts " }," + end + puts "}" + end + + def self.dump_type_data(snapshot, name, obj) + case name + when 'Fixnum' + obj.field('value').value + else + "" + end + end + + def self.dump_pair(key, value, indent, del=%q{"}) + puts "#{indent}\"#{key}\": #{del}#{value}#{del}," + end + end + end +end diff --git a/lib/alienist/model/java/#java_class.rb# b/lib/alienist/model/java/#java_class.rb# deleted file mode 100644 index a9b3c15..0000000 --- a/lib/alienist/model/java/#java_class.rb# +++ /dev/null @@ -1,66 +0,0 @@ -module Alienist - module Model - module Java - class JavaClass - attr_reader :instances, :subclasses, :fields, :static_fields, :name - attr_reader :total_field_count, :field_values, :super_class - - def initialize(snapshot, id, name, super_id, classloader_id, signers_id, - protection_domain_id, instance_size) - @snapshot = snapshot - @classloader_id = classloader_id - @id, @name, @super_id, @signers_id = id, name, super_id, signers_id - @protection_domain_id, @instance_size = protection_domain_id, instance_size - @instances = [] - @subclasses = [] - @fields = [] - @static_fields = {} - end - - # We resolve after all classes have been added to the system - def resolve - @super_class = @snapshot.id2class @super_id - # all classes but java.lang.Object - @super_class.add_subclass self if @super_class - @snapshot.java_lang_class.add_instance self - end - - def add_subclass(cls) - @subclasses << cls - end - - def add_instance(object) - @instances << object - end - - ## - # Yield to all fields in this class and all superclasses to yield - # in reverse-natural-order (how heap stores field info) - def instance_fields(&block) - @fields.each { |field| block[field] } - @super_class.instance_fields(&block) if @super_class - end - - def heap_order_field_names - names = @fields.map(&:name) - names_rest = @super_class ? @super_class.heap_order_field_names : nil - names.concat names_rest if names_rest # feeling dumb - names - end - - - def inspect - <<-EOS -Name: #{@name} #{@id} - Fields : #{@fields.join(", ")} - SFields: #{@static_fields.values.join(", ")} - SClass: #{@super_class ? @super_class.name : ""} #{@super_id} - subcls: #{@subclasses.map(&:name).join(", ")} -EOS - end - alias :to_s :inspect - - end - end - end -end diff --git a/lib/alienist/model/java/.#java_class.rb b/lib/alienist/model/java/.#java_class.rb deleted file mode 120000 index 5f728fa..0000000 --- a/lib/alienist/model/java/.#java_class.rb +++ /dev/null @@ -1 +0,0 @@ -enebo@Thomass-MacBook-Pro.local.472 \ No newline at end of file diff --git a/lib/alienist/model/java/java_class.rb b/lib/alienist/model/java/java_class.rb index 9883c5b..a9b3c15 100644 --- a/lib/alienist/model/java/java_class.rb +++ b/lib/alienist/model/java/java_class.rb @@ -4,7 +4,6 @@ module Java class JavaClass attr_reader :instances, :subclasses, :fields, :static_fields, :name attr_reader :total_field_count, :field_values, :super_class - attr_reader :ruby_instances def initialize(snapshot, id, name, super_id, classloader_id, signers_id, protection_domain_id, instance_size) @@ -16,7 +15,6 @@ def initialize(snapshot, id, name, super_id, classloader_id, signers_id, @subclasses = [] @fields = [] @static_fields = {} - @ruby_instances = [] end # We resolve after all classes have been added to the system diff --git a/lib/alienist/model/java/java_object.rb b/lib/alienist/model/java/java_object.rb index 61b6541..6de0b00 100644 --- a/lib/alienist/model/java/java_object.rb +++ b/lib/alienist/model/java/java_object.rb @@ -2,12 +2,12 @@ module Alienist module Model module Java class JavaObject - attr_reader :id, :name, :signature, :cls + attr_reader :id, :name, :signature, :cls, :size attr_accessor :field_values, :display_value attr_reader :ruby_instances # FIXME: - def initialize(id, serial, class_id, field_io_offset) - @id, @serial, @class_id = id, serial, class_id + def initialize(id, serial, class_id, field_io_offset, size) + @id, @serial, @class_id, @size = id, serial, class_id, size @field_io_offset = field_io_offset @ruby_instances = [] end diff --git a/lib/alienist/parser.rb b/lib/alienist/parser.rb index dd974f7..fb5cc4f 100644 --- a/lib/alienist/parser.rb +++ b/lib/alienist/parser.rb @@ -26,6 +26,7 @@ def initialize(io, snapshot, debug=0) def parse version = read_version_header @io.identifier_size = @io.read_int + @snapshot.minimum_object_size = 2 * @io.identifier_size # Don't know the 2 ids? creation_date = @io.read_date @snapshot.parsing(self) do loop do @@ -172,16 +173,16 @@ def read_primitive_array_dump def read_instance_dump read_section do |id, serial| - class_id, bytes_following = @io.read_id, @io.read_int + class_id, length = @io.read_id, @io.read_int value_offset = @io.pos # We skip field values until whole system loaded so that # all classes can be resolved. we save position for that # later parsing into the memory image for that object. - @io.skip_bytes bytes_following, "instance_dump" + @io.skip_bytes length, "instance_dump" puts "+I 0x#{id.to_s(16)} 0x#{class_id.to_s(16)}" if @debug > 10 - @snapshot.add_instance id, serial, class_id, value_offset + @snapshot.add_instance id, serial, class_id, value_offset, length end end diff --git a/lib/alienist/snapshot/base_snapshot.rb b/lib/alienist/snapshot/base_snapshot.rb index fe34b9e..25137e7 100644 --- a/lib/alienist/snapshot/base_snapshot.rb +++ b/lib/alienist/snapshot/base_snapshot.rb @@ -5,6 +5,8 @@ module Snapshot # This is using a base-class for this because the parser would be incapable # of executing if the snapshot did not have these methods. class BaseSnapshot + attr_accessor :minimum_object_size + def initialize @class_name_from_id = {} # {id -> class_name} @class_name_from_serial = {} # {serial_no -> class_name} @@ -85,7 +87,7 @@ def add_class(id, name, super_id, classloader_id, signers_id, def add_field(class_ref, name_id, type) end - def add_instance(id, serial, class_id, field_io_offset) + def add_instance(id, serial, class_id, field_io_offset, length) end def add_object_array(id, serial, length, class_id, field_io_offset) diff --git a/lib/alienist/snapshot/memory_snapshot.rb b/lib/alienist/snapshot/memory_snapshot.rb index 9d0836f..da5100b 100644 --- a/lib/alienist/snapshot/memory_snapshot.rb +++ b/lib/alienist/snapshot/memory_snapshot.rb @@ -24,14 +24,12 @@ def initialize @instances = {} # id -> java_object @ruby_class_from_name = {} # name -> ruby_class @name_from_ruby_class = {} # ruby_class -> name - @ruby_class_from_id = {} # id -> ruby_class end def pretty_display?(obj) false end - def create_pretty_display(obj) end @@ -59,8 +57,9 @@ def add_field(cls, name_id, signature) cls.fields << JavaField.new(name_id, name, signature) end - def add_instance(id, serial, class_id, field_io_offset) - object = JavaObject.new id, serial, class_id, field_io_offset + def add_instance(id, serial, class_id, field_io_offset, length) + object = JavaObject.new id, serial, class_id, field_io_offset, + length + minimum_object_size @instances[id] = object object @@ -82,6 +81,10 @@ def add_static_field(cls, name_id, signature, value) cls.static_fields[name] = JavaStatic.new field, value end + def ruby_name2class(name) + @ruby_class_from_name[name] + end + def name2class(name) @class_from_name[name] end @@ -90,6 +93,10 @@ def id2class(id) @class_from_id[id] end + def ruby_classes + @ruby_class_from_name + end + def classes @class_from_id.values end