class HeapInfo::Dumper

@api private

Class for memory dump relation works

Constants

DUMP_BYTES

Default dump length

Public Class Methods

new(mem_filename, &block) click to toggle source

Instantiate a {HeapInfo::Dumper} object

@param [String] mem_filename The filename that can be access for dump. Should be /proc/[pid]/mem. @param [Proc] block

Use for get segment info.
See {Dumper#base_len_of} for more information.
# File lib/heapinfo/dumper.rb, line 20
def initialize(mem_filename, &block)
  @filename = mem_filename
  @info = block || ->(*) { HeapInfo::Nil.new }
  need_permission unless dumpable?
end

Public Instance Methods

cstring(address) click to toggle source

Dump data from address until reach null-byte.

@return [String]

# File lib/heapinfo/dumper.rb, line 80
def cstring(address)
  base = base_of(address)
  len = 1
  cur = ''
  loop do
    cur << (dump(base + len - 1, len) || '')
    break if cur.index("\x00")
    len <<= 1
    return cur if cur.size != len - 1 # reached undumpable memory
  end
  cur[0, cur.index("\x00")]
end
dump(*args) click to toggle source

A helper for {HeapInfo::Process} to dump memory. @param [Mixed] args The use input commands, see examples of {HeapInfo::Process#dump}. @return [String, nil] Dump results. If error happend, nil is returned. @example

p dump(:elf, 4)
#=> "\x7fELF"
# File lib/heapinfo/dumper.rb, line 32
def dump(*args)
  return need_permission unless dumpable?
  base, len = base_len_of(*args)
  file = mem_f
  file.pos = base
  mem = file.readpartial len
  file.close
  mem
rescue => e # rubocop:disable Style/RescueStandardError
  raise e if e.is_a? ArgumentError
  nil
end
dump_chunks(*args) click to toggle source

Return the dump result as chunks. see {HeapInfo::Chunks} and {HeapInfo::Chunk} for more information.

Note: Same as {#dump}, need permission of attaching another process. @return [HeapInfo::Chunks] An array of chunk(s). @param [Mixed] args Same as arguments of {#dump}.

# File lib/heapinfo/dumper.rb, line 51
def dump_chunks(*args)
  base = base_of(*args)
  dump(*args).to_chunks(bits: @info[:bits], base: base)
end
find(pattern, from, length, rel) click to toggle source

Search a specific value/string/regexp in memory. #find only return the first matched address. @param [Integer, String, Regexp] pattern

The desired search pattern, can be value(+Integer+), string, or regular expression.

@param [Integer, String, Symbol] from

Start address for searching, can be segment(+Symbol+)
or segments with offset. See examples for more information.

@param [Integer] length The length limit for searching. @param [Boolean] rel Return relative or absolute. @return [Integer, nil] The first matched address, nil is returned when no such pattern found. @example

find(/E.F/, :elf, false)
#=> 4194305
find(0x4141414141414141, 'heap+0x10', 0x1000, false)
#=> 6291472
find('/bin/sh', :libc, true)
#=> 1622391 # 0x18c177
# File lib/heapinfo/dumper.rb, line 110
def find(pattern, from, length, rel)
  from = base_of(from)
  length = 1 << 40 if length.is_a? Symbol
  ret = case pattern
        when Integer then find_integer(pattern, from, length)
        when String then find_string(pattern, from, length)
        when Regexp then find_regexp(pattern, from, length)
        end
  ret -= from if ret && rel
  ret
end
scan(pattern, from, length) click to toggle source

@return [Array<Integer>]

# File lib/heapinfo/dumper.rb, line 123
def scan(pattern, from, length)
  from = base_of(from)
  cur = from
  result = []
  loop do
    addr = find(pattern, cur, length, false)
    break if addr.nil?
    result << addr - from
    cur = addr + @match_length
  end
  result
end
x(count, address) click to toggle source

Show dump results like in gdb's command x.

Details are in {HeapInfo::Process#x}. @param [Integer] count The number of result need to dump. @param [Symbol, String, Integer] address The base address to be dumped. @return [void] @example

x 3, 0x400000
# 0x400000:       0x00010102464c457f      0x0000000000000000
# 0x400010:       0x00000001003e0002
# File lib/heapinfo/dumper.rb, line 66
def x(count, address)
  commands = [address, count * size_t]
  base = base_of(*commands)
  res = dump(*commands).unpack(size_t == 4 ? 'L*' : 'Q*')
  str = res.group_by.with_index { |_, i| i / (16 / size_t) }.map do |round, values|
    Helper.hex(base + round * 16) + ":\t" +
      values.map { |v| Helper.color(format("0x%0#{size_t * 2}x", v)) }.join("\t")
  end.join("\n")
  puts str
end

Private Instance Methods

base_len_of(arg, len = DUMP_BYTES) click to toggle source

Get the base address and length.

+@info+ will be used for getting the segment base, so we can support use symbol as base address. @param [Integer, Symbol, String] arg The base address, see examples. @param [Integer] len An integer. @example

base_len_of(123, 321) #=> [123, 321]
base_len_of(123) #=> [123, DUMP_BYTES]
base_len_of(:heap, 10) #=> [0x603000, 10] # assume heap base @ 0x603000
base_len_of('heap+0x30', 10) #=> [0x603030, 10]
base_len_of('elf+0x3*2-1') #=> [0x400005, DUMP_BYTES]
# File lib/heapinfo/dumper.rb, line 170
def base_len_of(arg, len = DUMP_BYTES)
  segments = @info.call(:segments) || {}
  segments = segments.each_with_object({}) do |(k, seg), memo|
    memo[k] = seg.base
  end
  base = case arg
         when Integer then arg
         when Symbol then segments[arg]
         when String then Helper.evaluate(arg, store: segments)
         end
  raise ArgumentError, "Invalid base: #{arg.inspect}" unless base.is_a?(Integer) # invalid usage
  [base, len]
end
base_of(*args) click to toggle source
# File lib/heapinfo/dumper.rb, line 184
def base_of(*args)
  base_len_of(*args)[0]
end
batch_dumper(from, remain_size) { |str| ... } click to toggle source
# File lib/heapinfo/dumper.rb, line 205
def batch_dumper(from, remain_size)
  page_size = 0x1000
  while remain_size > 0
    dump_size = [remain_size, page_size].min
    str = dump(from, dump_size)
    break if str.nil? # unreadable
    break unless (idx = yield(str)).nil?
    break if str.length < dump_size # remain is unreadable
    remain_size -= str.length
    from += str.length
  end
  return if idx.nil?
  from + idx
end
dumpable?() click to toggle source

use /proc//mem for memory dump, must sure have permission

# File lib/heapinfo/dumper.rb, line 147
def dumpable?
  mem_f.close
  true
rescue Errno::EACCES, Errno::ENOENT
  false
end
find_integer(value, from, length) click to toggle source
# File lib/heapinfo/dumper.rb, line 188
def find_integer(value, from, length)
  @match_length = size_t
  find_string([value].pack(size_t == 4 ? 'L*' : 'Q*'), from, length)
end
find_regexp(pattern, from, length) click to toggle source
# File lib/heapinfo/dumper.rb, line 198
def find_regexp(pattern, from, length)
  batch_dumper(from, length) do |str|
    str.match(pattern).tap { |m| @match_length = m[0].size if m }
    str =~ pattern
  end
end
find_string(string, from, length) click to toggle source
# File lib/heapinfo/dumper.rb, line 193
def find_string(string, from, length)
  @match_length = string.size
  batch_dumper(from, length) { |str| str.index(string) }
end
mem_f() click to toggle source
# File lib/heapinfo/dumper.rb, line 154
def mem_f
  File.open(@filename)
end
need_permission() click to toggle source
# File lib/heapinfo/dumper.rb, line 138
def need_permission
  msg = 'Could not attach to process. ' \
        'Check the setting of /proc/sys/kernel/yama/ptrace_scope, ' \
        'or try again as the root user. ' \
        'For more details, see /etc/sysctl.d/10-ptrace.conf'
  puts Helper.color(msg, sev: :fatal)
end
size_t() click to toggle source
# File lib/heapinfo/dumper.rb, line 220
def size_t
  @info[:bits] / 8
end