class HeapInfo::ProcessInfo

For {Process} to record basic process information.

{Process} has a process_info object iff the process exists (pid not nil). Mainly records segments' base.

Constants

EXPORT

Methods to be transparent to process. e.g. process.libc alias to process.info.libc.

Attributes

auxv[R]

@return [Hash{Symbol => Integer}] The parsed auxv hash. @example

auxv
#=> {:ld_base => 4152033280, :random => 4294374299}
bits[R]

@return [Integer] 32 or 64.

elf[R]

@return [HeapInfo::Segment]

ld[R]

@return [HeapInfo::Segment]

libc[R]

@return [HeapInfo::Libc]

program[R]

@return [HeapInfo::Segment]

stack[R]

@return [HeapInfo::Segment]

Public Class Methods

new(process) click to toggle source

Instantiate a {ProcessInfo} object.

@param [HeapInfo::Process] process Load information from maps/memory for process.

# File lib/heapinfo/process_info.rb, line 39
def initialize(process)
  @pid = process.pid
  options = process.instance_variable_get(:@options)
  maps = load_maps
  @bits = bits_of(Helper.exe_of(@pid))
  @program = Segment.find(maps, File.readlink("/proc/#{@pid}/exe"))
  # well.. stack is a strange case because it will grow in runtime..
  # should i detect stack base growing..?
  @stack = Segment.find(maps, '[stack]')
  @auxv = parse_auxv(Helper.auxv_of(@pid))
  ld_seg = maps.find { |m| m[0] == @auxv[:ld_base] } # nil if static-linked elf
  @ld = ld_seg.nil? ? Nil.new : Segment.new(@auxv[:ld_base], ld_seg.last)
  @libc = Libc.find(
    maps, match_maps(maps, options[:libc]),
    bits: @bits,
    ld_name: @ld.name,
    dumper: ->(*args) { process.dump(*args) },
    method_heap: method(:heap)
  )
end

Public Instance Methods

heap() click to toggle source

Heap will not be mmapped if the process not use heap yet, so create a lazy loading method. Will re-read maps when heap segment not found yet.

Special handling here because heap might not be initialized in the beginning.

@return [HeapInfo::Segment] The {Segment} of heap.

# File lib/heapinfo/process_info.rb, line 66
def heap
  @heap ||= Segment.find(load_maps, '[heap]')
end
segments() click to toggle source

Return segemnts load currently. @return [Hash{Symbol => Segment}] The segments in hash format.

# File lib/heapinfo/process_info.rb, line 72
def segments
  EXPORT.map do |sym|
    seg = __send__(sym)
    [sym, seg] if seg.is_a?(Segment)
  end.compact.to_h
end
to_segment(sym) click to toggle source

Convert symbol to segment.

@return [HeapInfo::Segment?]

The segment object.
# File lib/heapinfo/process_info.rb, line 83
def to_segment(sym)
  return nil unless EXPORT.include?(sym)
  seg = __send__(sym)
  return nil unless seg.is_a?(Segment)
  seg
end

Private Instance Methods

bits_of(elf) click to toggle source
# File lib/heapinfo/process_info.rb, line 112
def bits_of(elf)
  elf[4] == "\x01" ? 32 : 64
end
load_maps() click to toggle source
# File lib/heapinfo/process_info.rb, line 92
def load_maps
  Helper.parse_maps(Helper.maps_of(@pid))
end
match_maps(maps, pattern) click to toggle source
# File lib/heapinfo/process_info.rb, line 116
def match_maps(maps, pattern)
  maps.map { |s| s[3] }.find { |seg| pattern.is_a?(Regexp) ? seg =~ pattern : seg.include?(pattern) }
end
parse_auxv(str) click to toggle source
# File lib/heapinfo/process_info.rb, line 96
def parse_auxv(str)
  auxv = {}
  sio = StringIO.new(str)
  fetch = -> { Helper.unpack(@bits / 8, sio.read(@bits / 8)) }
  loop do
    type = fetch.call
    val = fetch.call
    case type
    when 7 then auxv[:ld_base] = val # AT_BASE
    when 25 then auxv[:random] = val # AT_RANDOM
    end
    break if type.zero?
  end
  auxv
end