class HeapInfo::Dumper
@api private
Class for memory dump relation works
Constants
- DUMP_BYTES
Default dump length
Public Class Methods
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
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
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
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
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
@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
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
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
# File lib/heapinfo/dumper.rb, line 184 def base_of(*args) base_len_of(*args)[0] end
# 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
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
# 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
# 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
# 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
# File lib/heapinfo/dumper.rb, line 154 def mem_f File.open(@filename) end
# 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
# File lib/heapinfo/dumper.rb, line 220 def size_t @info[:bits] / 8 end