metasm 1.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.
- 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
|