heapinfo 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +130 -0
- data/lib/heapinfo.rb +75 -0
- data/lib/heapinfo/arena.rb +193 -0
- data/lib/heapinfo/chunk.rb +96 -0
- data/lib/heapinfo/chunks.rb +20 -0
- data/lib/heapinfo/dumper.rb +204 -0
- data/lib/heapinfo/ext/string.rb +29 -0
- data/lib/heapinfo/helper.rb +123 -0
- data/lib/heapinfo/libc.rb +46 -0
- data/lib/heapinfo/nil.rb +27 -0
- data/lib/heapinfo/process.rb +205 -0
- data/lib/heapinfo/segment.rb +34 -0
- data/lib/heapinfo/tools/get_arena.c +29 -0
- data/lib/heapinfo/version.rb +3 -0
- data/spec/chunk_spec.rb +40 -0
- data/spec/chunks_spec.rb +25 -0
- data/spec/dumper_spec.rb +79 -0
- data/spec/files/32bit_maps +23 -0
- data/spec/files/64bit_maps +29 -0
- data/spec/files/victim.cpp +32 -0
- data/spec/helper_spec.rb +73 -0
- data/spec/nil_spec.rb +15 -0
- data/spec/process_spec.rb +157 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/string_spec.rb +18 -0
- metadata +81 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module HeapInfo
|
2
|
+
# Self-defined array for collecting chunk(s)
|
3
|
+
class Chunks
|
4
|
+
# Instantiate a <tt>HeapInfo::Chunks</tt> object
|
5
|
+
def initialize
|
6
|
+
@chunks = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method_sym, *arguments, &block)
|
10
|
+
return super unless @chunks.respond_to? method_sym
|
11
|
+
@chunks.send(method_sym, *arguments, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Hook <tt>#to_s</tt> for pretty printing.
|
15
|
+
# @return [String]
|
16
|
+
def to_s
|
17
|
+
@chunks.map(&:to_s).join("\n")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module HeapInfo
|
2
|
+
# Class for memory dump relation works
|
3
|
+
class Dumper
|
4
|
+
# Default dump length
|
5
|
+
DUMP_BYTES = 8
|
6
|
+
|
7
|
+
# Instantiate a <tt>HeapInfo::Dumper</tt> object
|
8
|
+
#
|
9
|
+
# @param [Hash] segments With values <tt>HeapInfo::Segment</tt>
|
10
|
+
# @param [String] mem_filename The filename that can be access for dump. Should be <tt>/proc/[pid]/mem</tt>.
|
11
|
+
def initialize(segments, mem_filename)
|
12
|
+
@segments, @filename = segments, mem_filename
|
13
|
+
need_permission unless dumpable?
|
14
|
+
end
|
15
|
+
|
16
|
+
# A helper for <tt>HeapInfo::Process</tt> to dump memory.
|
17
|
+
# @param [Mixed] args The use input commands, see examples of <tt>HeapInfo::Process#dump</tt>.
|
18
|
+
# @return [String, NilClass] Dump results. If error happend, <tt>nil</tt> is returned.
|
19
|
+
# @example
|
20
|
+
# p dump(:elf, 4)
|
21
|
+
# # => "\x7fELF"
|
22
|
+
def dump(*args)
|
23
|
+
return need_permission unless dumpable?
|
24
|
+
base, offset, len = Dumper.parse_cmd(args)
|
25
|
+
if base.instance_of?(Symbol) and @segments[base].instance_of?(Segment)
|
26
|
+
addr = @segments[base].base
|
27
|
+
elsif base.is_a? Integer
|
28
|
+
addr = base
|
29
|
+
else
|
30
|
+
fail # invalid usage
|
31
|
+
end
|
32
|
+
file = mem_f
|
33
|
+
file.pos = addr + offset
|
34
|
+
mem = file.read len
|
35
|
+
file.close
|
36
|
+
mem
|
37
|
+
rescue
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return the dump result as chunks.
|
42
|
+
# see <tt>HeapInfo::Chunks</tt> and <tt>HeapInfo::Chunk</tt> for more information.
|
43
|
+
#
|
44
|
+
# Note: Same as <tt>dump</tt>, need permission of attaching another process.
|
45
|
+
# @return [HeapInfo::Chunks] An array of chunk(s).
|
46
|
+
# @param [Mixed] args Same as arguments of <tt>#dump</tt>
|
47
|
+
def dump_chunks(*args)
|
48
|
+
base = base_of(*args)
|
49
|
+
dump(*args).to_chunks(bits: @segments[:bits], base: base)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Show dump results like in gdb's command <tt>x</tt>.
|
53
|
+
#
|
54
|
+
# Details are in <tt>HeapInfo:Process#x</tt>
|
55
|
+
# @param [Integer] count The number of result need to dump.
|
56
|
+
# @param [Mixed] commands Same format as <tt>#dump(*args)</tt>.
|
57
|
+
# @param [IO] io <tt>IO</tt> that use for printing.
|
58
|
+
# @return [NilClass] The return value of <tt>io.puts</tt>.
|
59
|
+
# @example
|
60
|
+
# x 3, 0x400000
|
61
|
+
# # 0x400000: 0x00010102464c457f 0x0000000000000000
|
62
|
+
# # 0x400010: 0x00000001003e0002
|
63
|
+
def x(count, *commands, io: $stdout)
|
64
|
+
commands = commands + [count * size_t]
|
65
|
+
base = base_of(*commands)
|
66
|
+
res = dump(*commands).unpack(size_t == 4 ? "L*" : "Q*")
|
67
|
+
str = res.group_by.with_index{|_, i| i / (16 / size_t) }.map do |round, values|
|
68
|
+
"%#x:\t" % (base + round * 16) + values.map{|v| Helper.color "0x%0#{size_t * 2}x" % v}.join("\t")
|
69
|
+
end.join("\n")
|
70
|
+
io.puts str
|
71
|
+
end
|
72
|
+
|
73
|
+
# Search a specific value/string/regexp in memory.
|
74
|
+
# <tt>#find</tt> only return the first matched address.
|
75
|
+
# @param [Integer, String, Regexp] pattern The desired search pattern, can be value(<tt>Integer</tt>), string, or regular expression.
|
76
|
+
# @param [Integer, String, Symbol] from Start address for searching, can be segment(<tt>Symbol</tt>) or segments with offset. See examples for more information.
|
77
|
+
# @param [Integer] length The length limit for searching.
|
78
|
+
# @return [Integer, NilClass] The first matched address, <tt>nil</tt> is returned when no such pattern found.
|
79
|
+
# @example
|
80
|
+
# find(/E.F/, :elf)
|
81
|
+
# # => 4194305
|
82
|
+
# find(0x4141414141414141, 'heap+0x10', 0x1000)
|
83
|
+
# # => 6291472
|
84
|
+
# find('/bin/sh', :libc)
|
85
|
+
# # => 140662379588827
|
86
|
+
def find(pattern, from, length)
|
87
|
+
from = base_of(from)
|
88
|
+
length = 1 << 40 if length.is_a? Symbol
|
89
|
+
case pattern
|
90
|
+
when Integer; find_integer(pattern, from, length)
|
91
|
+
when String; find_string(pattern, from, length)
|
92
|
+
when Regexp; find_regexp(pattern, from, length)
|
93
|
+
else; nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Parse the dump command into <tt>[base, offset, length]</tt>
|
99
|
+
# @param [Array] args The command, see examples for more information
|
100
|
+
# @return [Array<Symbol, Integer>] <tt>[base, offset, length]</tt>, while <tt>base</tt> can be a [Symbol] or an [Integer]. <tt>length</tt> has default value equal to <tt>8</tt>.
|
101
|
+
# @example
|
102
|
+
# HeapInfo::Dumper.parse_cmd([:heap, 32, 10])
|
103
|
+
# # [:heap, 32, 10]
|
104
|
+
# HeapInfo::Dumper.parse_cmd(['heap+0x10, 10'])
|
105
|
+
# # [:heap, 16, 10]
|
106
|
+
# HeapInfo::Dumper.parse_cmd(['heap+0x10'])
|
107
|
+
# # [:heap, 16, 8]
|
108
|
+
# HeapInfo::Dumper.parse_cmd([0x400000, 4])
|
109
|
+
# # [0x400000, 0, 4]
|
110
|
+
def self.parse_cmd(args)
|
111
|
+
args = split_cmd args
|
112
|
+
return :fail unless args.size == 3
|
113
|
+
len = args[2].nil? ? DUMP_BYTES : Integer(args[2])
|
114
|
+
offset = Integer(args[1])
|
115
|
+
base = args[0]
|
116
|
+
base = Helper.integer?(base) ? Integer(base) : base.delete(':').to_sym
|
117
|
+
[base, offset, len]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Helper for <tt>#parse_cmd</tt>.
|
121
|
+
#
|
122
|
+
# Split commands to exactly three parts: <tt>[base, offset, length]</tt>
|
123
|
+
# <tt>length</tt> is <tt>nil</tt> if not present.
|
124
|
+
# @param [Array] args
|
125
|
+
# @return [Array<String>] <tt>[base, offset, length]</tt> in string expression.
|
126
|
+
# @example
|
127
|
+
# HeapInfo::Dumper.split_cmd([:heap, 32, 10])
|
128
|
+
# # ['heap', '32', '10']
|
129
|
+
# HeapInfo::Dumper.split_cmd([':heap+0x10, 10'])
|
130
|
+
# # [':heap', '0x10', '10']
|
131
|
+
# HeapInfo::Dumper.split_cmd([':heap+0x10'])
|
132
|
+
# # [':heap', '0x10', nil]
|
133
|
+
# HeapInfo::Dumper.split_cmd([0x400000, 4])
|
134
|
+
# # ['4194304', 0, '4']
|
135
|
+
def self.split_cmd(args)
|
136
|
+
args = args.join(',').delete(' ').split(',').reject(&:empty?) # 'heap, 100', 32 => 'heap', '100', '32'
|
137
|
+
return [] if args.empty?
|
138
|
+
if args[0].include? '+' # 'heap+0x1'
|
139
|
+
args.unshift(*args.shift.split('+', 2))
|
140
|
+
elsif args.size <= 2 # no offset given
|
141
|
+
args.insert(1, 0)
|
142
|
+
end
|
143
|
+
args << nil if args.size <= 2 # no length given
|
144
|
+
args[0, 3]
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def need_permission
|
149
|
+
puts Helper.color(%q(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), sev: :fatal)
|
150
|
+
end
|
151
|
+
|
152
|
+
# use /proc/[pid]/mem for memory dump, must sure have permission
|
153
|
+
def dumpable?
|
154
|
+
mem_f.close
|
155
|
+
true
|
156
|
+
rescue => e
|
157
|
+
if e.is_a? Errno::EACCES
|
158
|
+
false
|
159
|
+
else
|
160
|
+
throw e
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def mem_f
|
165
|
+
File.open(@filename)
|
166
|
+
end
|
167
|
+
|
168
|
+
def base_of(*args)
|
169
|
+
base, offset, _ = Dumper.parse_cmd(args)
|
170
|
+
base = @segments[base].base if @segments[base].is_a? Segment
|
171
|
+
base + offset
|
172
|
+
end
|
173
|
+
|
174
|
+
def find_integer(value, from, length)
|
175
|
+
find_string([value].pack(size_t == 4 ? "L*" : "Q*"), from, length)
|
176
|
+
end
|
177
|
+
|
178
|
+
def find_string(string, from ,length)
|
179
|
+
batch_dumper(from, length) {|str| str.index string}
|
180
|
+
end
|
181
|
+
|
182
|
+
def find_regexp(pattern, from ,length)
|
183
|
+
batch_dumper(from, length) {|str| str =~ pattern}
|
184
|
+
end
|
185
|
+
|
186
|
+
def batch_dumper(from, remain_size)
|
187
|
+
page_size = 0x1000
|
188
|
+
while remain_size > 0
|
189
|
+
dump_size = [remain_size, page_size].min
|
190
|
+
str = dump(from, dump_size)
|
191
|
+
break if str.nil? # unreadable
|
192
|
+
break unless (idx = yield(str)).nil?
|
193
|
+
break if str.length < dump_size # remain is unreadable
|
194
|
+
remain_size -= str.length
|
195
|
+
from += str.length
|
196
|
+
end
|
197
|
+
return if idx.nil?
|
198
|
+
from + idx
|
199
|
+
end
|
200
|
+
def size_t
|
201
|
+
@segments[:bits] / 8
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HeapInfo
|
2
|
+
module Ext
|
3
|
+
module String
|
4
|
+
# Methods to be mixed into String
|
5
|
+
module InstanceMethods
|
6
|
+
def to_chunk(bits: 64, base: 0)
|
7
|
+
size_t = bits / 8
|
8
|
+
dumper = lambda{|addr, len| self[addr-base, len]}
|
9
|
+
Chunk.new(size_t, base, dumper)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_chunks(bits: 64, base: 0)
|
13
|
+
size_t = bits / 8
|
14
|
+
chunks = Chunks.new
|
15
|
+
cur = 0
|
16
|
+
while cur + size_t * 2 <= self.length
|
17
|
+
now_chunk = self[cur, size_t * 2].to_chunk
|
18
|
+
sz = now_chunk.size
|
19
|
+
chunks << self[cur, sz + 1].to_chunk(bits: bits, base: base + cur) # +1 for dump prev_inuse
|
20
|
+
cur += sz
|
21
|
+
end
|
22
|
+
chunks
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
::String.send(:include, HeapInfo::Ext::String::InstanceMethods)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module HeapInfo
|
2
|
+
# Some helper functions
|
3
|
+
module Helper
|
4
|
+
# Get the process id of a process
|
5
|
+
# @param [String] prog The request process name
|
6
|
+
# @return [Fixnum] process id
|
7
|
+
def self.pidof(prog)
|
8
|
+
# plz, don't cmd injection your self :p
|
9
|
+
pid = `pidof #{prog}`.strip.to_i
|
10
|
+
return nil if pid == 0 # process not exists yet
|
11
|
+
throw "pidof #{prog} fail" unless pid.between?(2, 65535)
|
12
|
+
pid
|
13
|
+
#TODO: handle when multi processes exists
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create read <tt>/proc/[pid]/*</tt> methods
|
17
|
+
%w(exe maps).each do |method|
|
18
|
+
self.define_singleton_method("#{method}_of".to_sym) do |pid|
|
19
|
+
begin
|
20
|
+
IO.binread("/proc/#{pid}/#{method}")
|
21
|
+
rescue
|
22
|
+
throw "reading /proc/#{pid}/#{method} error"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parse the contents of <tt>/proc/[pid]/maps</tt>.
|
28
|
+
#
|
29
|
+
# @param [String] content The file content of <tt>/proc/[pid]/maps</tt>
|
30
|
+
# @return [Array] In form of <tt>[[start, end, permission, name], ...]</tt>. See examples.
|
31
|
+
# @example
|
32
|
+
# HeapInfo::Helper.parse_maps(<<EOS
|
33
|
+
# 00400000-0040b000 r-xp 00000000 ca:01 271708 /bin/cat
|
34
|
+
# 00bc4000-00be5000 rw-p 00000000 00:00 0 [heap]
|
35
|
+
# 7f2788315000-7f2788316000 r--p 00022000 ca:01 402319 /lib/x86_64-linux-gnu/ld-2.19.so
|
36
|
+
# EOS
|
37
|
+
# )
|
38
|
+
# # [[0x400000, 0x40b000, 'r-xp', '/bin/cat'],
|
39
|
+
# # [0xbc4000, 0xbe5000, 'rw-p', '[heap]'],
|
40
|
+
# # [0x7f2788315000, 0x7f2788316000, 'r--p', '/lib/x86_64-linux-gnu/ld-2.19.so']]
|
41
|
+
def self.parse_maps(content)
|
42
|
+
lines = content.split("\n")
|
43
|
+
lines.map do |line|
|
44
|
+
s = line.scan(/^([0-9a-f]+)-([0-9a-f]+)\s([rwxp-]{4})[^\/|\[]*([\/|\[].+)$/)[0]
|
45
|
+
next nil if s.nil?
|
46
|
+
s[0],s[1] = s[0,2].map{|h|h.to_i(16)}
|
47
|
+
s
|
48
|
+
end.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
# Color codes for pretty print
|
52
|
+
COLOR_CODE = {
|
53
|
+
esc_m: "\e[0m",
|
54
|
+
normal_s: "\e[38;5;1m", # red
|
55
|
+
integer: "\e[38;5;12m", # light blue
|
56
|
+
fatal: "\e[38;5;197m", # dark red
|
57
|
+
bin: "\e[38;5;120m", # light green
|
58
|
+
klass: "\e[38;5;155m", # pry like
|
59
|
+
sym: "\e[38;5;229m", # pry like
|
60
|
+
}
|
61
|
+
# Wrapper color codes for for pretty inspect
|
62
|
+
# @param [String] s Contents for wrapper
|
63
|
+
# @param [Symbol?] sev Specific which kind of color want to use, valid symbols are defined in <tt>#COLOR_CODE</tt>.
|
64
|
+
# If this argument is not present, will detect according to the content of <tt>s</tt>
|
65
|
+
# @return [String] wrapper with color codes.
|
66
|
+
def self.color(s, sev: nil)
|
67
|
+
s = s.to_s
|
68
|
+
color = ''
|
69
|
+
cc = COLOR_CODE
|
70
|
+
if cc.keys.include?(sev)
|
71
|
+
color = cc[sev]
|
72
|
+
elsif s =~ /^(0x)?[0-9a-f]+$/ # integers
|
73
|
+
color = cc[:integer]
|
74
|
+
else #normal string
|
75
|
+
color = cc[:normal_s]
|
76
|
+
end
|
77
|
+
"#{color}#{s.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Unpack strings to integer.
|
81
|
+
#
|
82
|
+
# Like the <tt>p32</tt> and <tt>p64</tt> in pwntools.
|
83
|
+
#
|
84
|
+
# @param [Integer] size_t Either 4 or 8.
|
85
|
+
# @param [String] data String to be unpack.
|
86
|
+
# @return [Integer] Unpacked result.
|
87
|
+
# @example
|
88
|
+
# HeapInfo::Helper.unpack(4, "\x12\x34\x56\x78")
|
89
|
+
# # 0x78563412
|
90
|
+
# HeapInfo::Helper.unpack(8, "\x12\x34\x56\x78\xfe\xeb\x90\x90")
|
91
|
+
# # 0x9090ebfe78563412
|
92
|
+
def self.unpack(size_t, data)
|
93
|
+
data.unpack(size_t == 4 ? 'L*' : 'Q*')[0]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Retrieve pure class name(without module) of an object
|
97
|
+
# @param [Object] obj Any instance
|
98
|
+
# @return [String] Class name of <tt>obj</tt>
|
99
|
+
# @example
|
100
|
+
# # suppose obj is an instance of HeapInfo::Chunk
|
101
|
+
# Helper.class_name(obj)
|
102
|
+
# # => 'Chunk'
|
103
|
+
def self.class_name(obj)
|
104
|
+
obj.class.name.split('::').last || obj.class.name
|
105
|
+
end
|
106
|
+
|
107
|
+
# For checking a string is actually an integer
|
108
|
+
# @param [String] str String to be checked
|
109
|
+
# @return [Boolean] If <tt>str</tt> can be converted into integer
|
110
|
+
# @example
|
111
|
+
# Helper.integer? '1234'
|
112
|
+
# # => true
|
113
|
+
# Helper.integer? '0x1234'
|
114
|
+
# # => true
|
115
|
+
# Helper.integer? '0xheapoverflow'
|
116
|
+
# # => false
|
117
|
+
def self.integer?(str)
|
118
|
+
!!Integer(str)
|
119
|
+
rescue ArgumentError, TypeError
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module HeapInfo
|
2
|
+
class Libc < Segment
|
3
|
+
def main_arena_offset
|
4
|
+
return @_main_arena_offset if @_main_arena_offset
|
5
|
+
return nil unless exhaust_search :main_arena
|
6
|
+
@_main_arena_offset
|
7
|
+
end
|
8
|
+
|
9
|
+
def main_arena
|
10
|
+
return @_main_arena.reload if @_main_arena
|
11
|
+
off = main_arena_offset
|
12
|
+
return if off.nil?
|
13
|
+
@_main_arena = Arena.new(off + self.base, process.bits, lambda{|*args|process.send(:dumper).dump(*args)})
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find(maps, name, process)
|
17
|
+
obj = super(maps, name)
|
18
|
+
obj.send(:process=, process)
|
19
|
+
obj
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
private
|
24
|
+
attr_accessor :process
|
25
|
+
# only for searching offset of main_arena now
|
26
|
+
def exhaust_search(symbol)
|
27
|
+
return false if symbol != :main_arena
|
28
|
+
# TODO: read from cache
|
29
|
+
@_main_arena_offset = resolve_main_arena_offset
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def resolve_main_arena_offset
|
34
|
+
tmp_elf = HeapInfo::TMP_DIR + "/get_arena"
|
35
|
+
libc_file = HeapInfo::TMP_DIR + "/libc.so.6"
|
36
|
+
ld_file = HeapInfo::TMP_DIR + "/ld.so"
|
37
|
+
flags = "-w #{@process.bits == 32 ? '-m32' : ''}"
|
38
|
+
%x(cp #{self.name} #{libc_file} && \
|
39
|
+
cp #{@process.ld.name} #{ld_file} && \
|
40
|
+
chdir #{File.expand_path('../tools', __FILE__)} && \
|
41
|
+
gcc #{flags} get_arena.c -o #{tmp_elf} 2>&1 > /dev/null && \
|
42
|
+
#{ld_file} --library-path #{HeapInfo::TMP_DIR} #{tmp_elf} && \
|
43
|
+
rm #{tmp_elf} #{libc_file} #{ld_file}).to_i(16)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/heapinfo/nil.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module HeapInfo
|
2
|
+
# Self defined <tt>nil</tt> like class.
|
3
|
+
#
|
4
|
+
# Be the return values of <tt>#dump</tt> or <tt>#dump_chunks</tt>, to prevent use the return value for calculating accidentally while exploiting remote.
|
5
|
+
class Nil
|
6
|
+
%i(nil? inspect to_s).each do |method_sym|
|
7
|
+
define_method(method_sym){|*args, &block| nil.send(method_sym, *args, &block)}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Hook all missing methods
|
11
|
+
# @return [HeapInfo::Nil] return <tt>self</tt> so that it can be a <tt>nil</tt> chain.
|
12
|
+
# @example
|
13
|
+
# # h.dump would return Nil when process not found
|
14
|
+
# p h.dump(:heap)[8,8].unpack("Q*)
|
15
|
+
# # => nil
|
16
|
+
def method_missing(method_sym, *args, &block)
|
17
|
+
return nil.send(method_sym, *args, &block) if nil.respond_to? method_sym
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# To prevent error raised when using <tt>puts Nil.new</tt>
|
22
|
+
# @return [Array] Empty array
|
23
|
+
def to_ary
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|