lib/heapinfo/chunk.rb
# frozen_string_literal: true
require 'heapinfo/helper'
module HeapInfo
# The object of a heap chunk
class Chunk
# @return [Integer] 4 or 8 according to 32bit or 64bit, respectively.
attr_reader :size_t
# @return [Integer] previous chunk size
attr_reader :prev_size
# @return [String] chunk data
attr_reader :data
# @return [Integer] Base address of this chunk
attr_reader :base
# Instantiate a {HeapInfo::Chunk} object
#
# @param [Integer] size_t 4 or 8
# @param [Integer] base Start address of this chunk
# @param [Proc] dumper For dump more information of this chunk
# @param [Boolean] head
# For specific if is fake chunk in +arena+.
# If +head+ is +true+, will not load +size+ and +prev_size+ (since it's meaningless)
# @example
# HeapInfo::Chunk.new 8, 0x602000, ->(addr, len) { [0,0x21, 0xda4a].pack('Q*')[addr-0x602000, len] }
# # create a chunk with chunk size 0x21
def initialize(size_t, base, dumper, head: false)
raise ArgumentError, 'size_t can be either 4 or 8' unless [4, 8].include?(size_t)
define_singleton_method(:dump) { |*args| dumper.call(*args) }
@size_t = size_t
@base = base
sz = dump(@base, size_t * 2)
if head # no need to read size if is bin
@data = dump(@base + size_t * 2, size_t * 4)
return
end
@prev_size = Helper.unpack(size_t, sz[0, size_t])
@size = Helper.unpack(size_t, sz[size_t..-1])
r_size = [size - size_t * 2, size_t * 4].min # don't read too much data
r_size = [r_size, 0].max # prevent negative size
@data = dump(@base + size_t * 2, r_size)
end
# Hook +#to_s+ for pretty printing
# @return [String]
def to_s
ret = Helper.color(format("#<%s:%#x>\n", self.class.to_s, @base), sev: :klass)
ret += "flags = [#{flags.map { |f| Helper.color(":#{f}", sev: :sym) }.join(',')}]\n"
ret += "size = #{Helper.color(format('%#x', size))} (#{bintype})\n"
ret += "prev_size = #{Helper.color(format('%#x', @prev_size))}\n" unless flags.include? :prev_inuse
ret += "data = #{Helper.color(@data.inspect)}#{'...' if @data.length < size - size_t * 2}\n"
ret
end
# The chunk flags record in low three bits of size
# @return [Array<Symbol>] flags of chunk
# @example
# c = [0, 0x25].pack("Q*").to_chunk
# c.flags
# # [:non_main_arena, :prev_inuse]
def flags
mask = @size - size
flag = []
flag << :non_main_arena unless (mask & 4).zero?
flag << :mmapped unless (mask & 2).zero?
flag << :prev_inuse unless (mask & 1).zero?
flag
end
# Ask if chunk not belongs to main arena.
#
# @return [Boolean] +true|false+ if chunk not belongs to main arena
def non_main_arena?
flags.include? :non_main_arena
end
# Ask if chunk is mmapped.
#
# @return [Boolean] +true|false+ if chunk is mmapped
def mmapped?
flags.include? :mmapped
end
# Ask if chunk has set the prev-inuse bit.
#
# @return [Boolean] +true|false+ if the +prev_inuse+ bit has been set
def prev_inuse?
flags.include? :prev_inuse
end
# Size of chunk
# @return [Integer] The chunk size without flag masks
def size
@size & -8
end
# Bin type of this chunk
# @return [Symbol] Bin type is simply determined according to +#size+
# @example
# [c.size, c.size_t]
# #=> [80, 8]
# c.bintype
# #=> :fast
# @example
# [c.size, c.size_t]
# #=> [80, 4]
# c.bintype
# #=> :small
# @example
# c.size
# #=> 135168
# c.bintype
# #=> :mmap
def bintype
sz = size
return :unknown if sz < @size_t * 4
return :fast if sz <= @size_t * 16
return :small if sz <= @size_t * 0x7e
return :large if sz <= @size_t * 0x3ffe # is this correct?
:mmap
end
end
end