metasm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- metadata +255 -0
@@ -0,0 +1,1686 @@
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
3
|
+
#
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
5
|
+
|
6
|
+
require 'metasm/main'
|
7
|
+
|
8
|
+
module Metasm
|
9
|
+
# this module regroups OS-related functions
|
10
|
+
# (eg. find_process, inject_shellcode)
|
11
|
+
# a 'class' just to be able to inherit from it...
|
12
|
+
class OS
|
13
|
+
# represents a running process with a few information, and defines methods to get more interaction (#memory, #debugger)
|
14
|
+
class Process
|
15
|
+
attr_accessor :pid, :path, :modules
|
16
|
+
class Module
|
17
|
+
attr_accessor :path, :addr, :size
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(pid=nil)
|
21
|
+
@pid = pid
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
mod = File.basename(path) rescue nil
|
26
|
+
"#{pid}: ".ljust(6) << (mod || '<unknown>')
|
27
|
+
end
|
28
|
+
def inspect
|
29
|
+
'<Process:' + ["pid: #@pid", modules.to_a.map { |m| " #{'%X' % m.addr} #{m.path}" }].join("\n") + '>'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns the Process whose pid is name (if name is an Integer) or first module path includes name (string)
|
34
|
+
def self.find_process(name)
|
35
|
+
case name
|
36
|
+
when nil
|
37
|
+
when Integer
|
38
|
+
list_processes.find { |pr| pr.pid == name }
|
39
|
+
else
|
40
|
+
list_processes.find { |pr| pr.path.to_s.include? name.to_s } or
|
41
|
+
(find_process(Integer(name)) if name =~ /^(0x[0-9a-f]+|[0-9]+)$/i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# create a new debuggee process stopped at start
|
46
|
+
def self.create_process(path)
|
47
|
+
dbg = create_debugger(path)
|
48
|
+
pr = open_process(dbg.pid)
|
49
|
+
pr.debugger = dbg
|
50
|
+
pr.memory = dbg.memory
|
51
|
+
pr
|
52
|
+
end
|
53
|
+
|
54
|
+
# return the platform-specific version
|
55
|
+
def self.current
|
56
|
+
case RUBY_PLATFORM
|
57
|
+
when /mswin|mingw|cygwin/i; WinOS
|
58
|
+
when /linux/i; LinOS
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# This class implements an objects that behaves like a regular string, but
|
64
|
+
# whose real data is dynamically fetched or generated on demand
|
65
|
+
# its size is immutable
|
66
|
+
# implements a page cache
|
67
|
+
# substrings are Strings (small substring) or another VirtualString
|
68
|
+
# (a kind of 'window' on the original VString, when the substring length is > 4096)
|
69
|
+
class VirtualString
|
70
|
+
# formats parameters for reading
|
71
|
+
def [](from, len=nil)
|
72
|
+
if not len and from.kind_of? Range
|
73
|
+
b = from.begin
|
74
|
+
e = from.end
|
75
|
+
b = b + length if b < 0
|
76
|
+
e = e + length if e < 0
|
77
|
+
len = e - b
|
78
|
+
len += 1 if not from.exclude_end?
|
79
|
+
from = b
|
80
|
+
end
|
81
|
+
from = from + length if from < 0
|
82
|
+
|
83
|
+
return nil if from > length or (from == length and not len)
|
84
|
+
len = length - from if len and from + len > length
|
85
|
+
return '' if len == 0
|
86
|
+
|
87
|
+
read_range(from, len)
|
88
|
+
end
|
89
|
+
|
90
|
+
# formats parameters for overwriting portion of the string
|
91
|
+
def []=(from, len, val=nil)
|
92
|
+
raise TypeError, 'cannot modify frozen virtualstring' if frozen?
|
93
|
+
|
94
|
+
if not val
|
95
|
+
val = len
|
96
|
+
len = nil
|
97
|
+
end
|
98
|
+
if not len and from.kind_of? Range
|
99
|
+
b = from.begin
|
100
|
+
e = from.end
|
101
|
+
b = b + length if b < 0
|
102
|
+
e = e + length if e < 0
|
103
|
+
len = e - b
|
104
|
+
len += 1 if not from.exclude_end?
|
105
|
+
from = b
|
106
|
+
elsif not len
|
107
|
+
len = 1
|
108
|
+
val = val.chr
|
109
|
+
end
|
110
|
+
from = from + length if from < 0
|
111
|
+
|
112
|
+
raise IndexError, 'Index out of string' if from > length
|
113
|
+
raise IndexError, 'Cannot modify virtualstring length' if val.length != len or from + len > length
|
114
|
+
|
115
|
+
write_range(from, val)
|
116
|
+
end
|
117
|
+
|
118
|
+
# returns the full raw data
|
119
|
+
def realstring
|
120
|
+
ret = ''
|
121
|
+
addr = 0
|
122
|
+
len = length
|
123
|
+
while len > @pagelength
|
124
|
+
ret << self[addr, @pagelength]
|
125
|
+
addr += @pagelength
|
126
|
+
len -= @pagelength
|
127
|
+
end
|
128
|
+
ret << self[addr, len]
|
129
|
+
end
|
130
|
+
|
131
|
+
# alias to realstring
|
132
|
+
# for bad people checking respond_to? :to_str (like String#<<)
|
133
|
+
# XXX alias does not work (not virtual (a la C++))
|
134
|
+
def to_str
|
135
|
+
realstring
|
136
|
+
end
|
137
|
+
|
138
|
+
# forwards unhandled messages to a frozen realstring
|
139
|
+
def method_missing(m, *args, &b)
|
140
|
+
if ''.respond_to? m
|
141
|
+
puts "Using VirtualString.realstring for #{m} from:", caller if $DEBUG
|
142
|
+
realstring.freeze.send(m, *args, &b)
|
143
|
+
else
|
144
|
+
super(m, *args, &b)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# avoid triggering realstring from method_missing if possible
|
149
|
+
def empty?
|
150
|
+
length == 0
|
151
|
+
end
|
152
|
+
|
153
|
+
# avoid triggering realstring from method_missing if possible
|
154
|
+
# heavily used in to find 0-terminated strings in ExeFormats
|
155
|
+
def index(chr, base=0)
|
156
|
+
return if base >= length or base <= -length
|
157
|
+
if i = self[base, 64].index(chr) or i = self[base, @pagelength].index(chr)
|
158
|
+
base + i
|
159
|
+
else
|
160
|
+
realstring.index(chr, base)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# '=~' does not go through method_missing
|
165
|
+
def =~(o)
|
166
|
+
realstring =~ o
|
167
|
+
end
|
168
|
+
|
169
|
+
# implements a read page cache
|
170
|
+
|
171
|
+
# the real address of our first byte
|
172
|
+
attr_accessor :addr_start
|
173
|
+
# our length
|
174
|
+
attr_accessor :length
|
175
|
+
# array of [addr, raw data], sorted by first == last accessed
|
176
|
+
attr_accessor :pagecache
|
177
|
+
# maximum length of self.pagecache (number of cached pages)
|
178
|
+
attr_accessor :pagecache_len
|
179
|
+
def initialize(addr_start, length)
|
180
|
+
@addr_start = addr_start
|
181
|
+
@length = length
|
182
|
+
@pagecache = []
|
183
|
+
@pagecache_len = 4
|
184
|
+
@pagelength ||= 4096 # must be (1 << x)
|
185
|
+
end
|
186
|
+
|
187
|
+
# returns wether a page is valid or not
|
188
|
+
def page_invalid?(addr)
|
189
|
+
cache_get_page(@addr_start+addr)[2]
|
190
|
+
end
|
191
|
+
|
192
|
+
# invalidates the page cache
|
193
|
+
def invalidate
|
194
|
+
@pagecache.clear
|
195
|
+
end
|
196
|
+
|
197
|
+
# returns the @pagelength-bytes page starting at addr
|
198
|
+
# return nil if the page is invalid/inaccessible
|
199
|
+
# addr is page-aligned by the caller
|
200
|
+
# addr is absolute
|
201
|
+
#def get_page(addr, len=@pagelength)
|
202
|
+
#end
|
203
|
+
|
204
|
+
# searches the cache for a page containing addr, updates if not found
|
205
|
+
def cache_get_page(addr)
|
206
|
+
addr &= ~(@pagelength-1)
|
207
|
+
i = 0
|
208
|
+
@pagecache.each { |c|
|
209
|
+
if addr == c[0]
|
210
|
+
# most recently used first
|
211
|
+
@pagecache.unshift @pagecache.delete_at(i) if i != 0
|
212
|
+
return c
|
213
|
+
end
|
214
|
+
i += 1
|
215
|
+
}
|
216
|
+
@pagecache.pop if @pagecache.length >= @pagecache_len
|
217
|
+
c = [addr]
|
218
|
+
p = get_page(addr)
|
219
|
+
c << p.to_s.ljust(@pagelength, "\0")
|
220
|
+
c << true if not p
|
221
|
+
@pagecache.unshift c
|
222
|
+
c
|
223
|
+
end
|
224
|
+
|
225
|
+
# reads a range from the page cache
|
226
|
+
# returns a new VirtualString (using dup) if the request is bigger than @pagelength bytes
|
227
|
+
def read_range(from, len)
|
228
|
+
from += @addr_start
|
229
|
+
if not len
|
230
|
+
base, page = cache_get_page(from)
|
231
|
+
page[from - base]
|
232
|
+
elsif len <= @pagelength
|
233
|
+
base, page = cache_get_page(from)
|
234
|
+
s = page[from - base, len]
|
235
|
+
if from+len-base > @pagelength # request crosses a page boundary
|
236
|
+
base, page = cache_get_page(from+len)
|
237
|
+
s << page[0, from+len-base]
|
238
|
+
end
|
239
|
+
s
|
240
|
+
else
|
241
|
+
# big request: return a new virtual page
|
242
|
+
dup(from, len)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# rewrites a segment of data
|
247
|
+
# the length written is the length of the content (a VirtualString cannot grow/shrink)
|
248
|
+
def write_range(from, content)
|
249
|
+
invalidate
|
250
|
+
rewrite_at(from + @addr_start, content)
|
251
|
+
end
|
252
|
+
|
253
|
+
# overwrites a section of the original data
|
254
|
+
#def rewrite_at(addr, content)
|
255
|
+
#end
|
256
|
+
end
|
257
|
+
|
258
|
+
# on-demand reading of a file
|
259
|
+
class VirtualFile < VirtualString
|
260
|
+
# returns a new VirtualFile of the whole file content (defaults readonly)
|
261
|
+
# returns a String if the file is small (<4096o) and readonly access
|
262
|
+
def self.read(path, mode='rb')
|
263
|
+
raise 'no filename specified' if not path
|
264
|
+
if sz = File.size(path) <= 4096 and (mode == 'rb' or mode == 'r')
|
265
|
+
File.open(path, mode) { |fd| fd.read }
|
266
|
+
else
|
267
|
+
File.open(path, mode) { |fd| new fd, 0, sz }
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# the underlying file descriptor
|
272
|
+
attr_accessor :fd
|
273
|
+
|
274
|
+
# creates a new virtual mapping of a section of the file
|
275
|
+
# the file descriptor must be seekable
|
276
|
+
def initialize(fd, addr_start = 0, length = nil)
|
277
|
+
@fd = fd.dup
|
278
|
+
if not length
|
279
|
+
@fd.seek(0, File::SEEK_END)
|
280
|
+
length = @fd.tell - addr_start
|
281
|
+
end
|
282
|
+
super(addr_start, length)
|
283
|
+
end
|
284
|
+
|
285
|
+
def dup(addr = @addr_start, len = @length)
|
286
|
+
self.class.new(@fd, addr, len)
|
287
|
+
end
|
288
|
+
|
289
|
+
# reads an aligned page from the file, at file offset addr
|
290
|
+
def get_page(addr, len=@pagelength)
|
291
|
+
@fd.pos = addr
|
292
|
+
@fd.read len
|
293
|
+
end
|
294
|
+
|
295
|
+
def page_invalid?(addr)
|
296
|
+
false
|
297
|
+
end
|
298
|
+
|
299
|
+
# overwrite a section of the file
|
300
|
+
def rewrite_at(addr, data)
|
301
|
+
@fd.pos = addr
|
302
|
+
@fd.write data
|
303
|
+
end
|
304
|
+
|
305
|
+
# returns the full content of the file
|
306
|
+
def realstring
|
307
|
+
@fd.pos = @addr_start
|
308
|
+
@fd.read(@length)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# this class implements a high-level debugging API (abstract superclass)
|
313
|
+
class Debugger
|
314
|
+
class Breakpoint
|
315
|
+
attr_accessor :address,
|
316
|
+
# context where the bp was defined
|
317
|
+
:pid, :tid,
|
318
|
+
# bool: oneshot ?
|
319
|
+
:oneshot,
|
320
|
+
# current bp state: :active, :inactive (internal use), :disabled (user-specified)
|
321
|
+
:state,
|
322
|
+
# type: type of breakpoint (:bpx = soft, :hw = hard)
|
323
|
+
:type,
|
324
|
+
# Expression if this is a conditionnal bp
|
325
|
+
# may be a Proc, String or Expression, evaluated every time the breakpoint hits
|
326
|
+
# if it returns 0 or false, the breakpoint is ignored
|
327
|
+
:condition,
|
328
|
+
# Proc to run if this bp has a callback
|
329
|
+
:action,
|
330
|
+
# Proc to run to emulate the overwritten instr behavior
|
331
|
+
# used to avoid unset/singlestep/re-set, more multithread friendly
|
332
|
+
:emul_instr,
|
333
|
+
# internal data, cpu-specific (overwritten byte for a softbp, memory type/size for hwbp..)
|
334
|
+
:internal,
|
335
|
+
# reference breakpoints sharing a target implementation (same hw debug register, soft bp addr...)
|
336
|
+
# shared is an array of Breakpoints, the same Array object in all shared breakpoints
|
337
|
+
# owner is a hash key => shared (dbg.breakpoint)
|
338
|
+
# key is an identifier for the Bp class in owner (bp.address)
|
339
|
+
:hash_shared, :hash_owner, :hash_key,
|
340
|
+
# user-defined breakpoint-specific stuff
|
341
|
+
:userdata
|
342
|
+
|
343
|
+
# append the breakpoint to hash_owner + hash_shared
|
344
|
+
def add(owner=@hash_owner)
|
345
|
+
@hash_owner = owner
|
346
|
+
@hash_key ||= @address
|
347
|
+
return add_bpm if @type == :bpm
|
348
|
+
if pv = owner[@hash_key]
|
349
|
+
@hash_shared = pv.hash_shared
|
350
|
+
@internal ||= pv.internal
|
351
|
+
@emul_instr ||= pv.emul_instr
|
352
|
+
else
|
353
|
+
owner[@hash_key] = self
|
354
|
+
@hash_shared = []
|
355
|
+
end
|
356
|
+
@hash_shared << self
|
357
|
+
end
|
358
|
+
|
359
|
+
# register a bpm: add references to all page start covered in @hash_owner
|
360
|
+
def add_bpm
|
361
|
+
m = @address + @internal[:len]
|
362
|
+
a = @address & -0x1000
|
363
|
+
@hash_shared = [self]
|
364
|
+
|
365
|
+
@internal ||= {}
|
366
|
+
@internal[:orig_prot] ||= {}
|
367
|
+
while a < m
|
368
|
+
if pv = @hash_owner[a]
|
369
|
+
if not pv.hash_shared.include?(self)
|
370
|
+
pv.hash_shared.concat @hash_shared-pv.hash_shared
|
371
|
+
@hash_shared.each { |bpm| bpm.hash_shared = pv.hash_shared }
|
372
|
+
end
|
373
|
+
@internal[:orig_prot][a] = pv.internal[:orig_prot][a]
|
374
|
+
else
|
375
|
+
@hash_owner[a] = self
|
376
|
+
end
|
377
|
+
a += 0x1000
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# delete the breakpoint from hash_shared, and hash_owner if empty
|
382
|
+
def del
|
383
|
+
return del_bpm if @type == :bpm
|
384
|
+
@hash_shared.delete self
|
385
|
+
if @hash_shared.empty?
|
386
|
+
@hash_owner.delete @hash_key
|
387
|
+
elsif @hash_owner[@hash_key] == self
|
388
|
+
@hash_owner[@hash_key] = @hash_shared.first
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# unregister a bpm
|
393
|
+
def del_bpm
|
394
|
+
m = @address + @internal[:len]
|
395
|
+
a = @address & -0x1000
|
396
|
+
@hash_shared.delete self
|
397
|
+
while a < m
|
398
|
+
pv = @hash_owner[a]
|
399
|
+
if pv == self
|
400
|
+
if opv = @hash_shared.find { |bpm|
|
401
|
+
bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] > a
|
402
|
+
}
|
403
|
+
@hash_owner[a] = opv
|
404
|
+
else
|
405
|
+
@hash_owner.delete a
|
406
|
+
|
407
|
+
# split hash_shared on disjoint ranges
|
408
|
+
prev_shared = @hash_shared.find_all { |bpm|
|
409
|
+
bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] <= a
|
410
|
+
}
|
411
|
+
|
412
|
+
prev_shared.each { |bpm|
|
413
|
+
bpm.hash_shared = prev_shared
|
414
|
+
@hash_shared.delete bpm
|
415
|
+
}
|
416
|
+
end
|
417
|
+
end
|
418
|
+
a += 0x1000
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# per-process data
|
424
|
+
attr_accessor :memory, :cpu, :disassembler, :breakpoint, :breakpoint_memory,
|
425
|
+
:modulemap, :symbols, :symbols_len
|
426
|
+
# per-thread data
|
427
|
+
attr_accessor :state, :info, :breakpoint_thread, :singlestep_cb, :run_method,
|
428
|
+
:run_args, :breakpoint_cause
|
429
|
+
|
430
|
+
# which/where per-process/thread stuff is stored
|
431
|
+
attr_accessor :pid_stuff, :tid_stuff, :pid_stuff_list, :tid_stuff_list
|
432
|
+
|
433
|
+
# global debugger callbacks, called whenever such event occurs
|
434
|
+
attr_accessor :callback_singlestep, :callback_bpx, :callback_hwbp, :callback_bpm,
|
435
|
+
:callback_exception, :callback_newthread, :callback_endthread,
|
436
|
+
:callback_newprocess, :callback_endprocess, :callback_loadlibrary
|
437
|
+
|
438
|
+
# global switches, specify wether to break on exception/thread event
|
439
|
+
# can be a Proc that is evaluated (arg = info parameter of the evt_func)
|
440
|
+
# trace_children is a bool to tell if we should debug subprocesses spawned
|
441
|
+
# by the target
|
442
|
+
attr_accessor :pass_all_exceptions, :ignore_newthread, :ignore_endthread,
|
443
|
+
:trace_children
|
444
|
+
|
445
|
+
# link to the user-interface object if available
|
446
|
+
attr_accessor :gui
|
447
|
+
|
448
|
+
# initializes the disassembler internal data - subclasses should call super()
|
449
|
+
def initialize
|
450
|
+
@pid_stuff = {}
|
451
|
+
@tid_stuff = {}
|
452
|
+
@log_proc = nil
|
453
|
+
@state = :dead
|
454
|
+
@info = ''
|
455
|
+
# stuff saved when we switch pids
|
456
|
+
@pid_stuff_list = [:memory, :cpu, :disassembler, :symbols, :symbols_len,
|
457
|
+
:modulemap, :breakpoint, :breakpoint_memory, :tid, :tid_stuff,
|
458
|
+
:dead_process]
|
459
|
+
@tid_stuff_list = [:state, :info, :breakpoint_thread, :singlestep_cb,
|
460
|
+
:run_method, :run_args, :breakpoint_cause, :dead_thread]
|
461
|
+
@callback_loadlibrary = lambda { |h| loadsyms(h[:address]) ; continue }
|
462
|
+
@callback_newprocess = lambda { |h| log "process #{@pid} created" }
|
463
|
+
@callback_endprocess = lambda { |h| log "process #{@pid} died" }
|
464
|
+
initialize_newpid
|
465
|
+
initialize_newtid
|
466
|
+
end
|
467
|
+
|
468
|
+
def shortname; self.class.name.split('::').last.downcase; end
|
469
|
+
|
470
|
+
attr_reader :pid
|
471
|
+
# change pid and associated cached data
|
472
|
+
# this will also re-load the previously selected tid for this process
|
473
|
+
def pid=(npid)
|
474
|
+
return if npid == pid
|
475
|
+
raise "invalid pid" if not check_pid(npid)
|
476
|
+
swapout_pid
|
477
|
+
@pid = npid
|
478
|
+
swapin_pid
|
479
|
+
end
|
480
|
+
alias set_pid pid=
|
481
|
+
|
482
|
+
attr_reader :tid
|
483
|
+
def tid=(ntid)
|
484
|
+
return if ntid == tid
|
485
|
+
raise "invalid tid" if not check_tid(ntid)
|
486
|
+
swapout_tid
|
487
|
+
@tid = ntid
|
488
|
+
swapin_tid
|
489
|
+
end
|
490
|
+
alias set_tid tid=
|
491
|
+
|
492
|
+
# creates stuff related to a new process being debugged
|
493
|
+
# includes disassembler, modulemap, symbols, breakpoints
|
494
|
+
# subclasses should check that @pid maps to a real process and raise() otherwise
|
495
|
+
# to be called with @pid/@tid set, calls initialize_memory+initialize_cpu
|
496
|
+
def initialize_newpid
|
497
|
+
return if not pid
|
498
|
+
@pid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) }
|
499
|
+
|
500
|
+
@symbols = {}
|
501
|
+
@symbols_len = {}
|
502
|
+
@modulemap = {}
|
503
|
+
@breakpoint = {}
|
504
|
+
@breakpoint_memory = {}
|
505
|
+
@tid_stuff = {}
|
506
|
+
initialize_cpu
|
507
|
+
initialize_memory
|
508
|
+
initialize_disassembler
|
509
|
+
end
|
510
|
+
|
511
|
+
# subclasses should check that @tid maps to a real thread and raise() otherwise
|
512
|
+
def initialize_newtid
|
513
|
+
return if not tid
|
514
|
+
@tid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) }
|
515
|
+
|
516
|
+
@state = :stopped
|
517
|
+
@info = 'new'
|
518
|
+
@breakpoint_thread = {}
|
519
|
+
gui.swapin_tid if @disassembler and gui.respond_to?(:swapin_tid)
|
520
|
+
end
|
521
|
+
|
522
|
+
# initialize the disassembler from @cpu/@memory
|
523
|
+
def initialize_disassembler
|
524
|
+
return if not @memory or not @cpu
|
525
|
+
@disassembler = Shellcode.decode(@memory, @cpu).disassembler
|
526
|
+
gui.swapin_pid if gui.respond_to?(:swapin_pid)
|
527
|
+
end
|
528
|
+
|
529
|
+
# we're switching focus from one pid to another, save current pid data
|
530
|
+
def swapout_pid
|
531
|
+
return if not pid
|
532
|
+
swapout_tid
|
533
|
+
gui.swapout_pid if gui.respond_to?(:swapout_pid)
|
534
|
+
@pid_stuff[@pid] ||= {}
|
535
|
+
@pid_stuff_list.each { |fld|
|
536
|
+
@pid_stuff[@pid][fld] = instance_variable_get("@#{fld}")
|
537
|
+
}
|
538
|
+
end
|
539
|
+
|
540
|
+
# we're switching focus from one tid to another, save current tid data
|
541
|
+
def swapout_tid
|
542
|
+
return if not tid
|
543
|
+
gui.swapout_tid if gui.respond_to?(:swapout_tid)
|
544
|
+
@tid_stuff[@tid] ||= {}
|
545
|
+
@tid_stuff_list.each { |fld|
|
546
|
+
@tid_stuff[@tid][fld] = instance_variable_get("@#{fld}")
|
547
|
+
}
|
548
|
+
end
|
549
|
+
|
550
|
+
# we're switching focus from one pid to another, load current pid data
|
551
|
+
def swapin_pid
|
552
|
+
return initialize_newpid if not @pid_stuff[@pid]
|
553
|
+
|
554
|
+
@pid_stuff_list.each { |fld|
|
555
|
+
instance_variable_set("@#{fld}", @pid_stuff[@pid][fld])
|
556
|
+
}
|
557
|
+
swapin_tid
|
558
|
+
gui.swapin_pid if gui.respond_to?(:swapin_pid)
|
559
|
+
end
|
560
|
+
|
561
|
+
# we're switching focus from one tid to another, load current tid data
|
562
|
+
def swapin_tid
|
563
|
+
return initialize_newtid if not @tid_stuff[@tid]
|
564
|
+
|
565
|
+
@tid_stuff_list.each { |fld|
|
566
|
+
instance_variable_set("@#{fld}", @tid_stuff[@tid][fld])
|
567
|
+
}
|
568
|
+
gui.swapin_tid if gui.respond_to?(:swapin_tid)
|
569
|
+
end
|
570
|
+
|
571
|
+
# delete references to the current pid
|
572
|
+
# switch to another pid, set @state = :dead if none available
|
573
|
+
def del_pid
|
574
|
+
@pid_stuff.delete @pid
|
575
|
+
if @pid = @pid_stuff.keys.first
|
576
|
+
swapin_pid
|
577
|
+
else
|
578
|
+
@state = :dead
|
579
|
+
@info = ''
|
580
|
+
@tid = nil
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
# delete references to the current thread
|
585
|
+
# calls del_pid if no tid left
|
586
|
+
def del_tid
|
587
|
+
@tid_stuff.delete @tid
|
588
|
+
if @tid = @tid_stuff.keys.first
|
589
|
+
swapin_tid
|
590
|
+
else
|
591
|
+
del_pid
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
# change the debugger to a specific pid/tid
|
596
|
+
# if given a block, run the block and then restore the original pid/tid
|
597
|
+
# pid may be an object that respond to #pid/#tid
|
598
|
+
def switch_context(npid, ntid=nil)
|
599
|
+
if npid.respond_to? :pid
|
600
|
+
ntid ||= npid.tid
|
601
|
+
npid = npid.pid
|
602
|
+
end
|
603
|
+
oldpid = pid
|
604
|
+
oldtid = tid
|
605
|
+
set_pid npid
|
606
|
+
set_tid ntid if ntid
|
607
|
+
if block_given?
|
608
|
+
# shortcut begin..ensure overhead
|
609
|
+
return yield if oldpid == pid and oldtid == tid
|
610
|
+
|
611
|
+
begin
|
612
|
+
yield
|
613
|
+
ensure
|
614
|
+
set_pid oldpid
|
615
|
+
set_tid oldtid
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
alias set_context switch_context
|
620
|
+
|
621
|
+
# iterate over all pids, yield in the context of this pid
|
622
|
+
def each_pid
|
623
|
+
# ensure @pid is last, so that we finish in the current context
|
624
|
+
lst = @pid_stuff.keys - [@pid]
|
625
|
+
lst << @pid
|
626
|
+
return lst if not block_given?
|
627
|
+
lst.each { |p|
|
628
|
+
set_pid p
|
629
|
+
yield
|
630
|
+
}
|
631
|
+
end
|
632
|
+
|
633
|
+
# iterate over all tids of the current process, yield in its context
|
634
|
+
def each_tid
|
635
|
+
lst = @tid_stuff.keys - [@tid]
|
636
|
+
lst << @tid
|
637
|
+
return lst if not block_given?
|
638
|
+
lst.each { |t|
|
639
|
+
set_tid t
|
640
|
+
yield
|
641
|
+
}
|
642
|
+
end
|
643
|
+
|
644
|
+
# iterate over all tids of all pids, yield in their context
|
645
|
+
def each_pid_tid
|
646
|
+
each_pid { each_tid { yield } }
|
647
|
+
end
|
648
|
+
|
649
|
+
|
650
|
+
# create a thread/process breakpoint
|
651
|
+
# addr can be a numeric address, an Expression that is resolved, or
|
652
|
+
# a String that is parsed+resolved
|
653
|
+
# info's keys are set to the breakpoint
|
654
|
+
# standard keys are :type, :oneshot, :condition, :action
|
655
|
+
# returns the Breakpoint object
|
656
|
+
def add_bp(addr, info={})
|
657
|
+
info[:pid] ||= @pid
|
658
|
+
info[:tid] ||= @tid if info[:pid] == @pid
|
659
|
+
|
660
|
+
b = Breakpoint.new
|
661
|
+
info.each { |k, v|
|
662
|
+
b.send("#{k}=", v)
|
663
|
+
}
|
664
|
+
|
665
|
+
switch_context(b) {
|
666
|
+
addr = resolve_expr(addr) if not addr.kind_of? ::Integer
|
667
|
+
b.address = addr
|
668
|
+
|
669
|
+
b.hash_owner ||= case b.type
|
670
|
+
when :bpm; @breakpoint_memory
|
671
|
+
when :hwbp; @breakpoint_thread
|
672
|
+
when :bpx; @breakpoint
|
673
|
+
end
|
674
|
+
# XXX bpm may hash_share with an :active, but be larger and still need enable()
|
675
|
+
b.add
|
676
|
+
|
677
|
+
enable_bp(b) if not info[:state]
|
678
|
+
}
|
679
|
+
|
680
|
+
b
|
681
|
+
end
|
682
|
+
|
683
|
+
# remove a breakpoint
|
684
|
+
def del_bp(b)
|
685
|
+
disable_bp(b)
|
686
|
+
b.del
|
687
|
+
end
|
688
|
+
|
689
|
+
# activate an inactive breakpoint
|
690
|
+
def enable_bp(b)
|
691
|
+
return if b.state == :active
|
692
|
+
if not b.hash_shared.find { |bb| bb.state == :active }
|
693
|
+
switch_context(b) {
|
694
|
+
if not b.internal
|
695
|
+
init_bpx(b) if b.type == :bpx
|
696
|
+
b.internal ||= {}
|
697
|
+
b.hash_shared.each { |bb| bb.internal ||= b.internal }
|
698
|
+
end
|
699
|
+
do_enable_bp(b)
|
700
|
+
}
|
701
|
+
end
|
702
|
+
b.state = :active
|
703
|
+
end
|
704
|
+
|
705
|
+
# deactivate an active breakpoint
|
706
|
+
def disable_bp(b, newstate = :inactive)
|
707
|
+
return if b.state != :active
|
708
|
+
b.state = newstate
|
709
|
+
return if b.hash_shared.find { |bb| bb.state == :active }
|
710
|
+
switch_context(b) {
|
711
|
+
do_disable_bp(b)
|
712
|
+
}
|
713
|
+
end
|
714
|
+
|
715
|
+
|
716
|
+
# delete all breakpoints defined in the current thread
|
717
|
+
def del_all_breakpoints_thread
|
718
|
+
@breakpoint_thread.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) }
|
719
|
+
end
|
720
|
+
|
721
|
+
# delete all breakpoints for the current process and all its threads
|
722
|
+
def del_all_breakpoints
|
723
|
+
each_tid { del_all_breakpoints_thread }
|
724
|
+
@breakpoint.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) }
|
725
|
+
@breakpoint_memory.values.uniq.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) }
|
726
|
+
end
|
727
|
+
|
728
|
+
# calls do_enable_bpm for bpms, or @cpu.dbg_enable_bp
|
729
|
+
def do_enable_bp(b)
|
730
|
+
if b.type == :bpm; do_enable_bpm(b)
|
731
|
+
else @cpu.dbg_enable_bp(self, b)
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
# calls do_disable_bpm for bpms, or @cpu.dbg_disable_bp
|
736
|
+
def do_disable_bp(b)
|
737
|
+
if b.type == :bpm; do_disable_bpm(b)
|
738
|
+
else @cpu.dbg_disable_bp(self, b)
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
# called in the context of the target when a bpx is to be initialized
|
743
|
+
# will disassemble the code pointed, and try to initialize #emul_instr
|
744
|
+
def init_bpx(b)
|
745
|
+
@disassembler.disassemble_fast_block(b.address) # XXX configurable dasm method
|
746
|
+
if di = @disassembler.di_at(b.address) and
|
747
|
+
fdbd = @disassembler.get_fwdemu_binding(di, register_pc) and
|
748
|
+
not fdbd[:incomplete_binding] and not fdbd.index(Expression::Unknown) and
|
749
|
+
fdbd.keys.all? { |k| k.kind_of?(Symbol) or k.kind_of?(Indirection) }
|
750
|
+
|
751
|
+
puts di.instruction, fdbd.inspect
|
752
|
+
b.emul_instr = lambda { |dbg|
|
753
|
+
resv = lambda { |e|
|
754
|
+
r = e
|
755
|
+
flags = Expression[r].externals.uniq.find_all { |f| f.to_s =~ /flags?_(.+)/ }
|
756
|
+
if flags.first
|
757
|
+
bd = {}
|
758
|
+
flags.each { |f|
|
759
|
+
f.to_s =~ /flags?_(.+)/
|
760
|
+
bd[f] = dbg.get_flag_value($1.downcase.to_sym)
|
761
|
+
}
|
762
|
+
r = r.bind(bd)
|
763
|
+
end
|
764
|
+
dbg.resolve(r)
|
765
|
+
}
|
766
|
+
|
767
|
+
fdbd.map { |k, v|
|
768
|
+
k = Indirection[resv[k.pointer], k.len] if k.kind_of?(Indirection)
|
769
|
+
[k, resv[v]]
|
770
|
+
}.each { |k, v|
|
771
|
+
if k.to_s =~ /flags?_(.+)/
|
772
|
+
dbg.set_flag_value($1.downcase.to_sym, v)
|
773
|
+
elsif k.kind_of?(Symbol)
|
774
|
+
dbg.set_reg_value(k, v)
|
775
|
+
elsif k.kind_of?(Indirection)
|
776
|
+
dbg.memory_write_int(k.pointer, v, k.len)
|
777
|
+
end
|
778
|
+
}
|
779
|
+
}
|
780
|
+
b.hash_shared.each { |bb| bb.emul_instr = b.emul_instr }
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
# sets a breakpoint on execution
|
785
|
+
def bpx(addr, oneshot=false, cond=nil, &action)
|
786
|
+
h = { :type => :bpx }
|
787
|
+
h[:oneshot] = true if oneshot
|
788
|
+
h[:condition] = cond if cond
|
789
|
+
h[:action] = action if action
|
790
|
+
add_bp(addr, h)
|
791
|
+
end
|
792
|
+
|
793
|
+
# sets a hardware breakpoint
|
794
|
+
# mtype in :r :w :x
|
795
|
+
# mlen is the size of the memory zone to cover
|
796
|
+
# mlen may be constrained by the architecture
|
797
|
+
def hwbp(addr, mtype=:x, mlen=1, oneshot=false, cond=nil, &action)
|
798
|
+
h = { :type => :hwbp }
|
799
|
+
h[:hash_owner] = @breakpoint_thread
|
800
|
+
addr = resolve_expr(addr) if not addr.kind_of? ::Integer
|
801
|
+
h[:hash_key] = [addr, mtype, mlen]
|
802
|
+
h[:internal] = { :type => mtype, :len => mlen }
|
803
|
+
h[:oneshot] = true if oneshot
|
804
|
+
h[:condition] = cond if cond
|
805
|
+
h[:action] = action if action
|
806
|
+
add_bp(addr, h)
|
807
|
+
end
|
808
|
+
|
809
|
+
# sets a memory breakpoint
|
810
|
+
# mtype is :r :w :rw or :x
|
811
|
+
# mlen is the size of the memory zone to cover
|
812
|
+
def bpm(addr, mtype=:r, mlen=4096, oneshot=false, cond=nil, &action)
|
813
|
+
h = { :type => :bpm }
|
814
|
+
addr = resolve_expr(addr) if not addr.kind_of? ::Integer
|
815
|
+
h[:hash_key] = addr & -4096 # XXX actually referenced at addr, addr+4096, ... addr+len
|
816
|
+
h[:internal] = { :type => type, :len => mlen }
|
817
|
+
h[:oneshot] = true if oneshot
|
818
|
+
h[:condition] = cond if cond
|
819
|
+
h[:action] = action if action
|
820
|
+
add_bp(addr, h)
|
821
|
+
end
|
822
|
+
|
823
|
+
|
824
|
+
# define the lambda to use to log stuff (used by #puts)
|
825
|
+
def set_log_proc(l=nil, &b)
|
826
|
+
@log_proc = l || b
|
827
|
+
end
|
828
|
+
|
829
|
+
# show information to the user, uses log_proc if defined
|
830
|
+
def log(*a)
|
831
|
+
if @log_proc
|
832
|
+
a.each { |aa| @log_proc[aa] }
|
833
|
+
else
|
834
|
+
puts(*a)
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
|
839
|
+
# marks the current cache of memory/regs invalid
|
840
|
+
def invalidate
|
841
|
+
@memory.invalidate if @memory
|
842
|
+
end
|
843
|
+
|
844
|
+
# invalidates the EncodedData backend for the dasm sections
|
845
|
+
def dasm_invalidate
|
846
|
+
disassembler.sections.each_value { |s| s.data.invalidate if s.data.respond_to? :invalidate }
|
847
|
+
end
|
848
|
+
|
849
|
+
# return all breakpoints set on a specific address (or all bp)
|
850
|
+
def all_breakpoints(addr=nil)
|
851
|
+
ret = []
|
852
|
+
if addr
|
853
|
+
if b = @breakpoint[addr]
|
854
|
+
ret |= b.hash_shared
|
855
|
+
end
|
856
|
+
else
|
857
|
+
@breakpoint.each_value { |bb| ret |= bb.hash_shared }
|
858
|
+
end
|
859
|
+
|
860
|
+
@breakpoint_thread.each_value { |bb|
|
861
|
+
next if addr and bb.address != addr
|
862
|
+
ret |= bb.hash_shared
|
863
|
+
}
|
864
|
+
|
865
|
+
@breakpoint_memory.each_value { |m|
|
866
|
+
next if addr and (bb.address+bb.internal[:len] <= addr or bb.address > addr)
|
867
|
+
ret |= bb.hash_shared
|
868
|
+
}
|
869
|
+
|
870
|
+
ret
|
871
|
+
end
|
872
|
+
|
873
|
+
def find_breakpoint(addr=nil)
|
874
|
+
return @breakpoint[addr] if @breakpoint[addr] and (not block_given? or yield(@breakpoint[addr]))
|
875
|
+
all_breakpoints(addr).find { |b| yield b }
|
876
|
+
end
|
877
|
+
|
878
|
+
|
879
|
+
# to be called right before resuming execution of the target
|
880
|
+
# run_m is the method that should be called if the execution is stopped
|
881
|
+
# due to a side-effect of the debugger (bpx with wrong condition etc)
|
882
|
+
# returns nil if the execution should be avoided (just deleted the dead thread/process)
|
883
|
+
def check_pre_run(run_m, *run_a)
|
884
|
+
if @dead_process
|
885
|
+
del_pid
|
886
|
+
return
|
887
|
+
elsif @dead_thread
|
888
|
+
del_tid
|
889
|
+
return
|
890
|
+
elsif @state == :running
|
891
|
+
return
|
892
|
+
end
|
893
|
+
@cpu.dbg_check_pre_run(self) if @cpu.respond_to?(:dbg_check_pre_run)
|
894
|
+
@breakpoint_cause = nil
|
895
|
+
@run_method = run_m
|
896
|
+
@run_args = run_a
|
897
|
+
@state = :running
|
898
|
+
@info = nil
|
899
|
+
true
|
900
|
+
end
|
901
|
+
|
902
|
+
|
903
|
+
# called when the target stops due to a singlestep exception
|
904
|
+
def evt_singlestep(b=nil)
|
905
|
+
b ||= find_singlestep
|
906
|
+
return evt_exception(:type => 'singlestep') if not b
|
907
|
+
|
908
|
+
@state = :stopped
|
909
|
+
@info = 'singlestep'
|
910
|
+
@cpu.dbg_evt_singlestep(self) if @cpu.respond_to?(:dbg_evt_singlestep)
|
911
|
+
|
912
|
+
callback_singlestep[] if callback_singlestep
|
913
|
+
|
914
|
+
if cb = @singlestep_cb
|
915
|
+
@singlestep_cb = nil
|
916
|
+
cb.call # call last, as the cb may change singlestep_cb/state/etc
|
917
|
+
end
|
918
|
+
end
|
919
|
+
|
920
|
+
# returns true if the singlestep is due to us
|
921
|
+
def find_singlestep
|
922
|
+
return @cpu.dbg_find_singlestep(self) if @cpu.respond_to?(:dbg_find_singlestep)
|
923
|
+
@run_method == :singlestep
|
924
|
+
end
|
925
|
+
|
926
|
+
# called when the target stops due to a soft breakpoint exception
|
927
|
+
def evt_bpx(b=nil)
|
928
|
+
b ||= find_bp_bpx
|
929
|
+
return evt_exception(:type => 'breakpoint') if not b
|
930
|
+
|
931
|
+
@state = :stopped
|
932
|
+
@info = 'breakpoint'
|
933
|
+
@cpu.dbg_evt_bpx(self, b) if @cpu.respond_to?(:dbg_evt_bpx)
|
934
|
+
|
935
|
+
callback_bpx[b] if callback_bpx
|
936
|
+
|
937
|
+
post_evt_bp(b)
|
938
|
+
end
|
939
|
+
|
940
|
+
# return the breakpoint that is responsible for the evt_bpx
|
941
|
+
def find_bp_bpx
|
942
|
+
return @cpu.dbg_find_bpx(self) if @cpu.respond_to?(:dbg_find_bpx)
|
943
|
+
@breakpoint[pc]
|
944
|
+
end
|
945
|
+
|
946
|
+
# called when the target stops due to a hwbp exception
|
947
|
+
def evt_hwbp(b=nil)
|
948
|
+
b ||= find_bp_hwbp
|
949
|
+
return evt_exception(:type => 'hwbp') if not b
|
950
|
+
|
951
|
+
@state = :stopped
|
952
|
+
@info = 'hwbp'
|
953
|
+
@cpu.dbg_evt_hwbp(self, b) if @cpu.respond_to?(:dbg_evt_hwbp)
|
954
|
+
|
955
|
+
callback_hwbp[b] if callback_hwbp
|
956
|
+
|
957
|
+
post_evt_bp(b)
|
958
|
+
end
|
959
|
+
|
960
|
+
# return the breakpoint that is responsible for the evt_hwbp
|
961
|
+
def find_bp_hwbp
|
962
|
+
return @cpu.dbg_find_hwbp(self) if @cpu.respond_to?(:dbg_find_bpx)
|
963
|
+
@breakpoint_thread.find { |b| b.address == pc }
|
964
|
+
end
|
965
|
+
|
966
|
+
# called for archs where the same interrupt is generated for hwbp and singlestep
|
967
|
+
# checks if a hwbp matches, then call evt_hwbp, else call evt_singlestep (which
|
968
|
+
# will forward to evt_exception if singlestep does not match either)
|
969
|
+
def evt_hwbp_singlestep
|
970
|
+
if b = find_bp_hwbp
|
971
|
+
evt_hwbp(b)
|
972
|
+
else
|
973
|
+
evt_singlestep
|
974
|
+
end
|
975
|
+
end
|
976
|
+
|
977
|
+
# called when the target stops due to a memory exception caused by a memory bp
|
978
|
+
# called by evt_exception
|
979
|
+
def evt_bpm(b)
|
980
|
+
@state = :stopped
|
981
|
+
@info = 'bpm'
|
982
|
+
|
983
|
+
callback_bpm[b] if callback_bpm
|
984
|
+
|
985
|
+
post_evt_bp(b)
|
986
|
+
end
|
987
|
+
|
988
|
+
# return a bpm whose page coverage includes the fault described in info
|
989
|
+
def find_bp_bpm(info)
|
990
|
+
@breakpoint_memory[info[:fault_addr] & -0x1000]
|
991
|
+
end
|
992
|
+
|
993
|
+
# returns true if the fault described in info is valid to trigger b
|
994
|
+
def check_bpm_range(b, info)
|
995
|
+
return if b.address+b.internal[:len] <= info[:fault_addr]
|
996
|
+
return if b.address >= info[:fault_addr] + info[:fault_len]
|
997
|
+
case b.internal[:type]
|
998
|
+
when :x; info[:fault_addr] == pc # XXX
|
999
|
+
when :r; info[:fault_access] == :r
|
1000
|
+
when :w; info[:fault_access] == :w
|
1001
|
+
when :rw; true
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
# handles breakpoint conditions/callbacks etc
|
1006
|
+
def post_evt_bp(b)
|
1007
|
+
@breakpoint_cause = b
|
1008
|
+
|
1009
|
+
found_valid_active = false
|
1010
|
+
|
1011
|
+
# XXX may have many active bps with callback that continue/singlestep/singlestep{}...
|
1012
|
+
b.hash_shared.dup.map { |bb|
|
1013
|
+
# ignore inactive bps
|
1014
|
+
next if bb.state != :active
|
1015
|
+
|
1016
|
+
# ignore out-of-range bpms
|
1017
|
+
next if bb.type == :bpm and not check_bpm_range(bb, b.internal)
|
1018
|
+
|
1019
|
+
# check condition
|
1020
|
+
case bb.condition
|
1021
|
+
when nil; cd = 1
|
1022
|
+
when Proc; cd = bb.condition.call
|
1023
|
+
when String, Expression; cd = resolve_expr(bb.condition)
|
1024
|
+
else raise "unknown bp condition #{bb.condition.inspect}"
|
1025
|
+
end
|
1026
|
+
next if not cd or cd == 0
|
1027
|
+
|
1028
|
+
found_valid_active = true
|
1029
|
+
|
1030
|
+
# oneshot
|
1031
|
+
del_bp(bb) if bb.oneshot
|
1032
|
+
|
1033
|
+
# callback
|
1034
|
+
bb.action
|
1035
|
+
}.compact.each { |cb| cb.call }
|
1036
|
+
|
1037
|
+
# we did break due to a bp whose condition is not true: resume
|
1038
|
+
# (unless a callback already resumed)
|
1039
|
+
resume_badbreak(b) if not found_valid_active and @state == :stopped
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
# called whenever the target stops due to an exception
|
1043
|
+
# type may be:
|
1044
|
+
# * 'access violation', :fault_addr, :fault_len, :fault_access (:r/:w/:x)
|
1045
|
+
# anything else for other exceptions (access violation is special to handle bpm)
|
1046
|
+
# ...
|
1047
|
+
def evt_exception(info={})
|
1048
|
+
if info[:type] == 'access violation' and b = find_bp_bpm(info)
|
1049
|
+
info[:fault_len] ||= 1
|
1050
|
+
b.internal.update info
|
1051
|
+
return evt_bpm(b)
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
@state = :stopped
|
1055
|
+
@info = "exception #{info[:type]}"
|
1056
|
+
|
1057
|
+
callback_exception[info] if callback_exception
|
1058
|
+
|
1059
|
+
pass = pass_all_exceptions
|
1060
|
+
pass = pass[info] if pass.kind_of? Proc
|
1061
|
+
if pass
|
1062
|
+
pass_current_exception
|
1063
|
+
resume_badbreak
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
def evt_newthread(info={})
|
1068
|
+
@state = :stopped
|
1069
|
+
@info = 'new thread'
|
1070
|
+
|
1071
|
+
callback_newthread[info] if callback_newthread
|
1072
|
+
|
1073
|
+
ign = ignore_newthread
|
1074
|
+
ign = ign[info] if ign.kind_of? Proc
|
1075
|
+
if ign
|
1076
|
+
continue
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
def evt_endthread(info={})
|
1081
|
+
@state = :stopped
|
1082
|
+
@info = 'end thread'
|
1083
|
+
# mark the thread as to be deleted on next check_pre_run
|
1084
|
+
@dead_thread = true
|
1085
|
+
|
1086
|
+
callback_endthread[info] if callback_endthread
|
1087
|
+
|
1088
|
+
ign = ignore_endthread
|
1089
|
+
ign = ign[info] if ign.kind_of? Proc
|
1090
|
+
if ign
|
1091
|
+
continue
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def evt_newprocess(info={})
|
1096
|
+
@state = :stopped
|
1097
|
+
@info = 'new process'
|
1098
|
+
|
1099
|
+
callback_newprocess[info] if callback_newprocess
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
def evt_endprocess(info={})
|
1103
|
+
@state = :stopped
|
1104
|
+
@info = 'end process'
|
1105
|
+
@dead_process = true
|
1106
|
+
|
1107
|
+
callback_endprocess[info] if callback_endprocess
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
def evt_loadlibrary(info={})
|
1111
|
+
@state = :stopped
|
1112
|
+
@info = 'loadlibrary'
|
1113
|
+
|
1114
|
+
callback_loadlibrary[info] if callback_loadlibrary
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
# called when we did break due to a breakpoint whose condition is invalid
|
1118
|
+
# resume execution as if we never stopped
|
1119
|
+
# disable offending bp + singlestep if needed
|
1120
|
+
def resume_badbreak(b=nil)
|
1121
|
+
# ensure we didn't delete b
|
1122
|
+
if b and b.hash_shared.find { |bb| bb.state == :active }
|
1123
|
+
rm = @run_method
|
1124
|
+
if rm == :singlestep
|
1125
|
+
singlestep_bp(b)
|
1126
|
+
else
|
1127
|
+
@run_args = ra
|
1128
|
+
singlestep_bp(b) { send rm, *ra }
|
1129
|
+
end
|
1130
|
+
else
|
1131
|
+
send @run_method, *@run_args
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
# singlesteps over an active breakpoint and run its block
|
1136
|
+
# if the breakpoint provides an emulation stub, run that, otherwise
|
1137
|
+
# disable the breakpoint, singlestep, and re-enable
|
1138
|
+
def singlestep_bp(bp, &b)
|
1139
|
+
if be = bp.hash_shared.find { |bb| bb.emul_instr }
|
1140
|
+
@state = :stopped
|
1141
|
+
be.emul_instr[self]
|
1142
|
+
yield if block_given?
|
1143
|
+
else
|
1144
|
+
bp.hash_shared.each { |bb|
|
1145
|
+
disable_bp(bb, :temp_inactive) if bb.state == :active
|
1146
|
+
}
|
1147
|
+
# this *should* work with different bps stopping the current instr
|
1148
|
+
prev_sscb = @singlestep_cb
|
1149
|
+
singlestep {
|
1150
|
+
bp.hash_shared.each { |bb|
|
1151
|
+
enable_bp(bb) if bb.state == :temp_inactive
|
1152
|
+
}
|
1153
|
+
prev_sscb[] if prev_sscb
|
1154
|
+
yield if block_given?
|
1155
|
+
}
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
|
1160
|
+
# checks if the running target has stopped (nonblocking)
|
1161
|
+
def check_target
|
1162
|
+
do_check_target
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
# waits until the running target stops (due to a breakpoint, fault, etc)
|
1166
|
+
def wait_target
|
1167
|
+
do_wait_target while @state == :running
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
# resume execution of the target
|
1171
|
+
# bypasses a software breakpoint on pc if needed
|
1172
|
+
# thread breakpoints must be manually disabled before calling continue
|
1173
|
+
def continue
|
1174
|
+
if b = @breakpoint_cause and b.hash_shared.find { |bb| bb.state == :active }
|
1175
|
+
singlestep_bp(b) {
|
1176
|
+
next if not check_pre_run(:continue)
|
1177
|
+
do_continue
|
1178
|
+
}
|
1179
|
+
else
|
1180
|
+
return if not check_pre_run(:continue)
|
1181
|
+
do_continue
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
alias run continue
|
1185
|
+
|
1186
|
+
# continue ; wait_target
|
1187
|
+
def continue_wait
|
1188
|
+
continue
|
1189
|
+
wait_target
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
# resume execution of the target one instruction at a time
|
1193
|
+
def singlestep(&b)
|
1194
|
+
@singlestep_cb = b
|
1195
|
+
bp = @breakpoint_cause
|
1196
|
+
return if not check_pre_run(:singlestep)
|
1197
|
+
if bp and bp.hash_shared.find { |bb| bb.state == :active } and be = bp.hash_shared.find { |bb| bb.emul_instr }
|
1198
|
+
@state = :stopped
|
1199
|
+
be.emul_instr[self]
|
1200
|
+
invalidate
|
1201
|
+
evt_singlestep(true)
|
1202
|
+
else
|
1203
|
+
do_singlestep
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
# singlestep ; wait_target
|
1208
|
+
def singlestep_wait(&b)
|
1209
|
+
singlestep(&b)
|
1210
|
+
wait_target
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
# tests if the specified instructions should be stepover() using singlestep or
|
1214
|
+
# by putting a breakpoint at next_addr
|
1215
|
+
def need_stepover(di = di_at(pc))
|
1216
|
+
di and @cpu.dbg_need_stepover(self, di.address, di)
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
# stepover: singlesteps, but do not enter in subfunctions
|
1220
|
+
def stepover
|
1221
|
+
di = di_at(pc)
|
1222
|
+
if need_stepover(di)
|
1223
|
+
bpx di.next_addr, true, Expression[:tid, :==, @tid]
|
1224
|
+
continue
|
1225
|
+
else
|
1226
|
+
singlestep
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
# stepover ; wait_target
|
1231
|
+
def stepover_wait
|
1232
|
+
stepover
|
1233
|
+
wait_target
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
# checks if an instruction should stop the stepout() (eg it is a return instruction)
|
1237
|
+
def end_stepout(di = di_at(pc))
|
1238
|
+
di and @cpu.dbg_end_stepout(self, di.address, di)
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
# stepover until finding the last instruction of the function
|
1242
|
+
def stepout
|
1243
|
+
# TODO thread-local bps
|
1244
|
+
while not end_stepout
|
1245
|
+
stepover
|
1246
|
+
wait_target
|
1247
|
+
end
|
1248
|
+
do_singlestep
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
# set a singleshot breakpoint, run the process, and wait
|
1252
|
+
def go(target, cond=nil)
|
1253
|
+
bpx(target, true, cond)
|
1254
|
+
continue_wait
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
# continue_wait until @state == :dead
|
1258
|
+
def run_forever
|
1259
|
+
continue_wait until @state == :dead
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
# decode the Instruction at the address, use the @disassembler cache if available
|
1263
|
+
def di_at(addr)
|
1264
|
+
@disassembler.di_at(addr) || @disassembler.disassemble_instruction(addr)
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
# list the general purpose register names available for the target
|
1268
|
+
def register_list
|
1269
|
+
@cpu.dbg_register_list
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
# hash { register_name => register_size_in_bits }
|
1273
|
+
def register_size
|
1274
|
+
@cpu.dbg_register_size
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
# retrieves the name of the register holding the program counter (address of the next instruction)
|
1278
|
+
def register_pc
|
1279
|
+
@cpu.dbg_register_pc
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
# retrieve the name of the register holding the stack pointer
|
1283
|
+
def register_sp
|
1284
|
+
@cpu.dbg_register_sp
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
# then name of the register holding the cpu flags
|
1288
|
+
def register_flags
|
1289
|
+
@cpu.dbg_register_flags
|
1290
|
+
end
|
1291
|
+
|
1292
|
+
# list of flags available in the flag register
|
1293
|
+
def flag_list
|
1294
|
+
@cpu.dbg_flag_list
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
# retreive the value of the program counter register (eip)
|
1298
|
+
def pc
|
1299
|
+
get_reg_value(register_pc)
|
1300
|
+
end
|
1301
|
+
alias ip pc
|
1302
|
+
|
1303
|
+
# change the value of pc
|
1304
|
+
def pc=(v)
|
1305
|
+
set_reg_value(register_pc, v)
|
1306
|
+
end
|
1307
|
+
alias ip= pc=
|
1308
|
+
|
1309
|
+
# retrieve the value of the stack pointer register
|
1310
|
+
def sp
|
1311
|
+
get_reg_value(register_sp)
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
# update the stack pointer
|
1315
|
+
def sp=(v)
|
1316
|
+
set_reg_value(register_sp, v)
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
# retrieve the value of a flag (0/1)
|
1320
|
+
def get_flag_value(f)
|
1321
|
+
@cpu.dbg_get_flag(self, f)
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
# retrieve the value of a flag (true/false)
|
1325
|
+
def get_flag(f)
|
1326
|
+
get_flag_value(f) != 0
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
# change the value of a flag
|
1330
|
+
def set_flag_value(f, v)
|
1331
|
+
(v && v != 0) ? set_flag(f) : unset_flag(f)
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
# switch the value of a flag (true->false, false->true)
|
1335
|
+
def toggle_flag(f)
|
1336
|
+
set_flag_value(f, 1-get_flag_value(f))
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
# set the value of the flag to true
|
1340
|
+
def set_flag(f)
|
1341
|
+
@cpu.dbg_set_flag(self, f)
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
# set the value of the flag to false
|
1345
|
+
def unset_flag(f)
|
1346
|
+
@cpu.dbg_unset_flag(self, f)
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
# returns the name of the module containing addr or nil
|
1350
|
+
def addr2module(addr)
|
1351
|
+
@modulemap.keys.find { |k| @modulemap[k][0] <= addr and @modulemap[k][1] > addr }
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
# returns a string describing addr in term of symbol (eg 'libc.so.6!printf+2f')
|
1355
|
+
def addrname(addr)
|
1356
|
+
(addr2module(addr) || '???') + '!' +
|
1357
|
+
if s = @symbols[addr] ? addr : @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr }
|
1358
|
+
@symbols[s] + (addr == s ? '' : ('+%x' % (addr-s)))
|
1359
|
+
else '%08x' % addr
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
# same as addrname, but scan preceding addresses if no symbol matches
|
1364
|
+
def addrname!(addr)
|
1365
|
+
(addr2module(addr) || '???') + '!' +
|
1366
|
+
if s = @symbols[addr] ? addr :
|
1367
|
+
@symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } ||
|
1368
|
+
@symbols.keys.sort.find_all { |s_| s_ < addr and s_ + 0x10000 > addr }.max
|
1369
|
+
@symbols[s] + (addr == s ? '' : ('+%x' % (addr-s)))
|
1370
|
+
else '%08x' % addr
|
1371
|
+
end
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
# loads the symbols from a mapped module
|
1375
|
+
def loadsyms(addr, name='%08x'%addr.to_i)
|
1376
|
+
if addr.kind_of? String
|
1377
|
+
modules.each { |m|
|
1378
|
+
if m.path =~ /#{addr}/i
|
1379
|
+
addr = m.addr
|
1380
|
+
name = File.basename m.path
|
1381
|
+
break
|
1382
|
+
end
|
1383
|
+
}
|
1384
|
+
return if not addr.kind_of? Integer
|
1385
|
+
end
|
1386
|
+
return if not peek = @memory.get_page(addr, 4)
|
1387
|
+
if peek == "\x7fELF"
|
1388
|
+
cls = LoadedELF
|
1389
|
+
elsif peek[0, 2] == "MZ" and @memory[addr+@memory[addr+0x3c,4].unpack('V').first, 4] == "PE\0\0"
|
1390
|
+
cls = LoadedPE
|
1391
|
+
else return
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
begin
|
1395
|
+
e = cls.load @memory[addr, 0x1000_0000]
|
1396
|
+
e.load_address = addr
|
1397
|
+
e.decode_header
|
1398
|
+
e.decode_exports
|
1399
|
+
rescue
|
1400
|
+
# cache the error so that we dont hit it every time
|
1401
|
+
@modulemap[addr.to_s(16)] ||= [addr, addr+0x1000]
|
1402
|
+
return
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
if n = e.module_name and n != name
|
1406
|
+
name = n
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
@modulemap[name] ||= [addr, addr+e.module_size]
|
1410
|
+
|
1411
|
+
cnt = 0
|
1412
|
+
e.module_symbols.each { |n_, a, l|
|
1413
|
+
cnt += 1
|
1414
|
+
a += addr
|
1415
|
+
@disassembler.set_label_at(a, n_, false)
|
1416
|
+
@symbols[a] = n_ # XXX store "lib!sym" ?
|
1417
|
+
if l and l > 1; @symbols_len[a] = l
|
1418
|
+
else @symbols_len.delete a # we may overwrite an existing symbol, keep len in sync
|
1419
|
+
end
|
1420
|
+
}
|
1421
|
+
log "loaded #{cnt} symbols from #{name}"
|
1422
|
+
|
1423
|
+
true
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
# scan the target memory for loaded libraries, load their symbols
|
1427
|
+
def scansyms(addr=0, max=@memory.length-0x1000-addr)
|
1428
|
+
while addr <= max
|
1429
|
+
loadsyms(addr)
|
1430
|
+
addr += 0x1000
|
1431
|
+
end
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
# load symbols from all libraries found by the OS module
|
1435
|
+
def loadallsyms
|
1436
|
+
modules.each { |m|
|
1437
|
+
yield m.addr if block_given?
|
1438
|
+
loadsyms(m.addr, m.path)
|
1439
|
+
}
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
# see Disassembler#load_map
|
1443
|
+
def load_map(str, off=0)
|
1444
|
+
str = File.read(str) if File.exist?(str)
|
1445
|
+
sks = @disassembler.sections.keys.sort
|
1446
|
+
str.each_line { |l|
|
1447
|
+
case l.strip
|
1448
|
+
when /^([0-9A-F]+)\s+(\w+)\s+(\w+)/i # kernel.map style
|
1449
|
+
a = $1.to_i(16) + off
|
1450
|
+
n = $3
|
1451
|
+
when /^([0-9A-F]+):([0-9A-F]+)\s+([a-z_]\w+)/i # IDA style
|
1452
|
+
# see Disassembler for comments
|
1453
|
+
a = sks[$1.to_i(16)] + $2.to_i(16) + off
|
1454
|
+
n = $3
|
1455
|
+
else next
|
1456
|
+
end
|
1457
|
+
@disassembler.set_label_at(a, n, false)
|
1458
|
+
@symbols[a] = n
|
1459
|
+
}
|
1460
|
+
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
# parses the expression contained in arg
|
1464
|
+
def parse_expr(arg)
|
1465
|
+
parse_expr!(arg.dup)
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
# parses the expression contained in arg, updates arg to point after the expr
|
1469
|
+
def parse_expr!(arg)
|
1470
|
+
return if not e = IndExpression.parse_string!(arg) { |s|
|
1471
|
+
# handle 400000 -> 0x400000
|
1472
|
+
# XXX no way to override and force decimal interpretation..
|
1473
|
+
if s.length > 4 and not @disassembler.get_section_at(s.to_i) and @disassembler.get_section_at(s.to_i(16))
|
1474
|
+
s.to_i(16)
|
1475
|
+
else
|
1476
|
+
s.to_i
|
1477
|
+
end
|
1478
|
+
}
|
1479
|
+
|
1480
|
+
# resolve ambiguous symbol names/hex values
|
1481
|
+
bd = {}
|
1482
|
+
e.externals.grep(::String).each { |ex|
|
1483
|
+
if not v = register_list.find { |r| ex.downcase == r.to_s.downcase } ||
|
1484
|
+
(block_given? && yield(ex)) || symbols.index(ex)
|
1485
|
+
lst = symbols.values.find_all { |s| s.downcase.include? ex.downcase }
|
1486
|
+
case lst.length
|
1487
|
+
when 0
|
1488
|
+
if ex =~ /^[0-9a-f]+$/i and @disassembler.get_section_at(ex.to_i(16))
|
1489
|
+
v = ex.to_i(16)
|
1490
|
+
else
|
1491
|
+
raise "unknown symbol name #{ex}"
|
1492
|
+
end
|
1493
|
+
when 1
|
1494
|
+
v = symbols.index(lst.first)
|
1495
|
+
log "using #{lst.first} for #{ex}"
|
1496
|
+
else
|
1497
|
+
suggest = lst[0, 50].join(', ')
|
1498
|
+
suggest = suggest[0, 125] + '...' if suggest.length > 128
|
1499
|
+
raise "ambiguous symbol name #{ex}: #{suggest} ?"
|
1500
|
+
end
|
1501
|
+
end
|
1502
|
+
bd[ex] = v
|
1503
|
+
}
|
1504
|
+
e = e.bind(bd)
|
1505
|
+
|
1506
|
+
e
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
# resolves an expression involving register values and/or memory indirection using the current context
|
1510
|
+
# uses #register_list, #get_reg_value, @mem, @cpu
|
1511
|
+
# :tid/:pid resolve to current thread
|
1512
|
+
def resolve_expr(e)
|
1513
|
+
e = parse_expr(e) if e.kind_of? ::String
|
1514
|
+
bd = { :tid => @tid, :pid => @pid }
|
1515
|
+
Expression[e].externals.each { |ex|
|
1516
|
+
next if bd[ex]
|
1517
|
+
case ex
|
1518
|
+
when ::Symbol; bd[ex] = get_reg_value(ex)
|
1519
|
+
when ::String; bd[ex] = @symbols.index(ex) || 0
|
1520
|
+
end
|
1521
|
+
}
|
1522
|
+
Expression[e].bind(bd).reduce { |i|
|
1523
|
+
if i.kind_of? Indirection and p = i.pointer.reduce and p.kind_of? ::Integer
|
1524
|
+
i.len ||= @cpu.size/8
|
1525
|
+
p &= (1 << @cpu.size) - 1 if p < 0
|
1526
|
+
Expression.decode_imm(@memory, i.len, @cpu, p)
|
1527
|
+
end
|
1528
|
+
}
|
1529
|
+
end
|
1530
|
+
alias resolve resolve_expr
|
1531
|
+
|
1532
|
+
# return/yield an array of [addr, addr symbolic name] corresponding to the current stack trace
|
1533
|
+
def stacktrace(maxdepth=500, &b)
|
1534
|
+
@cpu.dbg_stacktrace(self, maxdepth, &b)
|
1535
|
+
end
|
1536
|
+
|
1537
|
+
# accepts a range or begin/end address to read memory, or a register name
|
1538
|
+
def [](arg0, arg1=nil)
|
1539
|
+
if arg1
|
1540
|
+
arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer
|
1541
|
+
arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer
|
1542
|
+
@memory[arg0, arg1].to_str
|
1543
|
+
elsif arg0.kind_of? ::Range
|
1544
|
+
arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range
|
1545
|
+
arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer
|
1546
|
+
@memory[arg0].to_str
|
1547
|
+
else
|
1548
|
+
get_reg_value(arg0)
|
1549
|
+
end
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
# accepts a range or begin/end address to write memory, or a register name
|
1553
|
+
def []=(arg0, arg1, val=nil)
|
1554
|
+
arg1, val = val, arg1 if not val
|
1555
|
+
if arg1
|
1556
|
+
arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer
|
1557
|
+
arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer
|
1558
|
+
@memory[arg0, arg1] = val
|
1559
|
+
elsif arg0.kind_of? ::Range
|
1560
|
+
arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range
|
1561
|
+
arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer
|
1562
|
+
@memory[arg0] = val
|
1563
|
+
else
|
1564
|
+
set_reg_value(arg0, val)
|
1565
|
+
end
|
1566
|
+
end
|
1567
|
+
|
1568
|
+
|
1569
|
+
# read an int from the target memory, int of sz bytes (defaults to cpu.size)
|
1570
|
+
def memory_read_int(addr, sz=@cpu.size/8)
|
1571
|
+
addr = resolve_expr(addr) if not addr.kind_of? ::Integer
|
1572
|
+
Expression.decode_imm(@memory, sz, @cpu, addr)
|
1573
|
+
end
|
1574
|
+
|
1575
|
+
# write an int in the target memory
|
1576
|
+
def memory_write_int(addr, val, sz=@cpu.size/8)
|
1577
|
+
addr = resolve_expr(addr) if not addr.kind_of? ::Integer
|
1578
|
+
val = resolve_expr(val) if not val.kind_of? ::Integer
|
1579
|
+
@memory[addr, sz] = Expression.encode_imm(val, sz, @cpu)
|
1580
|
+
end
|
1581
|
+
|
1582
|
+
# retrieve an argument (call at a function entrypoint)
|
1583
|
+
def func_arg(nr)
|
1584
|
+
@cpu.dbg_func_arg(self, nr)
|
1585
|
+
end
|
1586
|
+
def func_arg_set(nr, val)
|
1587
|
+
@cpu.dbg_func_arg_set(self, nr, val)
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
# retrieve a function returned value (call at func exitpoint)
|
1591
|
+
def func_retval
|
1592
|
+
@cpu.dbg_func_retval(self)
|
1593
|
+
end
|
1594
|
+
def func_retval_set(val)
|
1595
|
+
@cpu.dbg_func_retval_set(self, val)
|
1596
|
+
end
|
1597
|
+
def func_retval=(val)
|
1598
|
+
@cpu.dbg_func_retval_set(self, val)
|
1599
|
+
end
|
1600
|
+
|
1601
|
+
# retrieve a function return address (call at func entry/exit)
|
1602
|
+
def func_retaddr
|
1603
|
+
@cpu.dbg_func_retaddr(self)
|
1604
|
+
end
|
1605
|
+
def func_retaddr_set(addr)
|
1606
|
+
@cpu.dbg_func_retaddr_set(self, addr)
|
1607
|
+
end
|
1608
|
+
def func_retaddr=(addr)
|
1609
|
+
@cpu.dbg_func_retaddr_set(self, addr)
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
def load_plugin(plugin_filename)
|
1613
|
+
if not File.exist?(plugin_filename) and defined? Metasmdir
|
1614
|
+
# try autocomplete
|
1615
|
+
pf = File.join(Metasmdir, 'samples', 'dbg-plugins', plugin_filename)
|
1616
|
+
if File.exist?(pf)
|
1617
|
+
plugin_filename = pf
|
1618
|
+
elsif File.exist?(pf + '.rb')
|
1619
|
+
plugin_filename = pf + '.rb'
|
1620
|
+
end
|
1621
|
+
end
|
1622
|
+
if not File.exist?(plugin_filename) and File.exist?(plugin_filename + '.rb')
|
1623
|
+
plugin_filename += '.rb'
|
1624
|
+
end
|
1625
|
+
|
1626
|
+
instance_eval File.read(plugin_filename)
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
# return the list of memory mappings of the current process
|
1630
|
+
# array of [start, len, perms, infos]
|
1631
|
+
def mappings
|
1632
|
+
[[0, @memory.length]]
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
# return a list of Process::Modules (with a #path, #addr) for the current process
|
1636
|
+
def modules
|
1637
|
+
[]
|
1638
|
+
end
|
1639
|
+
|
1640
|
+
# list debugged pids
|
1641
|
+
def list_debug_pids
|
1642
|
+
@pid_stuff.keys | [@pid].compact
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
# return a list of OS::Process listing all alive processes (incl not debugged)
|
1646
|
+
# default version only includes current debugged pids
|
1647
|
+
def list_processes
|
1648
|
+
list_debug_pids.map { |p| OS::Process.new(p) }
|
1649
|
+
end
|
1650
|
+
|
1651
|
+
# check if pid is valid
|
1652
|
+
def check_pid(pid)
|
1653
|
+
list_processes.find { |p| p.pid == pid }
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
# list debugged tids
|
1657
|
+
def list_debug_tids
|
1658
|
+
@tid_stuff.keys | [@tid].compact
|
1659
|
+
end
|
1660
|
+
|
1661
|
+
# list of thread ids existing in the current process (incl not debugged)
|
1662
|
+
# default version only lists debugged tids
|
1663
|
+
alias list_threads list_debug_tids
|
1664
|
+
|
1665
|
+
# check if tid is valid for the current process
|
1666
|
+
def check_tid(tid)
|
1667
|
+
list_threads.include?(tid)
|
1668
|
+
end
|
1669
|
+
|
1670
|
+
# see EData#pattern_scan
|
1671
|
+
# scans only mapped areas of @memory, using os_process.mappings
|
1672
|
+
def pattern_scan(pat, start=0, len=@memory.length-start)
|
1673
|
+
ret = []
|
1674
|
+
mappings.each { |a, l, *o_|
|
1675
|
+
a = start if a < start
|
1676
|
+
l = start+len-a if a+l > start+len
|
1677
|
+
next if l <= 0
|
1678
|
+
EncodedData.new(@memory[a, l]).pattern_scan(pat) { |o|
|
1679
|
+
o += a
|
1680
|
+
ret << o if not block_given? or yield(o)
|
1681
|
+
}
|
1682
|
+
}
|
1683
|
+
ret
|
1684
|
+
end
|
1685
|
+
end
|
1686
|
+
end
|