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,527 @@
|
|
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
|
+
|
7
|
+
require 'metasm/os/main'
|
8
|
+
require 'socket'
|
9
|
+
|
10
|
+
module Metasm
|
11
|
+
# lowlevel interface to the gdbserver protocol
|
12
|
+
class GdbClient
|
13
|
+
GDBREGS_IA32 = %w[eax ecx edx ebx esp ebp esi edi eip eflags cs ss ds es fs gs].map { |r| r.to_sym } # XXX [77] = 'orig_eax'
|
14
|
+
GDBREGS_X64 = %w[rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 rip rflags cs ss ds es fs gs].map { |r| r.to_sym }
|
15
|
+
|
16
|
+
# compute the hex checksum used in gdb protocol
|
17
|
+
def gdb_csum(buf)
|
18
|
+
'%02x' % (buf.unpack('C*').inject(0) { |cs, c| cs + c } & 0xff)
|
19
|
+
end
|
20
|
+
|
21
|
+
# send the buffer, waits ack
|
22
|
+
# return true on success
|
23
|
+
def gdb_send(cmd, buf='')
|
24
|
+
buf = cmd + buf
|
25
|
+
buf = '$' << buf << '#' << gdb_csum(buf)
|
26
|
+
|
27
|
+
5.times {
|
28
|
+
@io.write buf
|
29
|
+
loop do
|
30
|
+
break if not IO.select([@io], nil, nil, 0.2)
|
31
|
+
raise Errno::EPIPE if not ack = @io.read(1)
|
32
|
+
case ack
|
33
|
+
when '+'
|
34
|
+
return true
|
35
|
+
when '-'
|
36
|
+
puts "gdb_send: ack neg" if $DEBUG
|
37
|
+
break
|
38
|
+
when nil
|
39
|
+
return
|
40
|
+
end
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
log "send error #{cmd.inspect} (no ack)"
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def quiet_during
|
49
|
+
pq = quiet
|
50
|
+
@quiet = true
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
@quiet = pq
|
54
|
+
end
|
55
|
+
|
56
|
+
# return buf, or nil on error / csum error
|
57
|
+
# waits IO.select(timeout) between each char
|
58
|
+
# outstr is used internally only to handle multiline output string
|
59
|
+
def gdb_readresp(timeout=nil, outstr=nil)
|
60
|
+
@recv_ctx ||= {}
|
61
|
+
@recv_ctx[:state] ||= :nosync
|
62
|
+
buf = nil
|
63
|
+
|
64
|
+
while @recv_ctx
|
65
|
+
return unless IO.select([@io], nil, nil, timeout)
|
66
|
+
raise Errno::EPIPE if not c = @io.read(1)
|
67
|
+
|
68
|
+
case @recv_ctx[:state]
|
69
|
+
when :nosync
|
70
|
+
if c == '$'
|
71
|
+
@recv_ctx[:state] = :data
|
72
|
+
@recv_ctx[:buf] = ''
|
73
|
+
end
|
74
|
+
when :data
|
75
|
+
if c == '#'
|
76
|
+
@recv_ctx[:state] = :csum1
|
77
|
+
@recv_ctx[:cs] = ''
|
78
|
+
else
|
79
|
+
@recv_ctx[:buf] << c
|
80
|
+
end
|
81
|
+
when :csum1
|
82
|
+
@recv_ctx[:cs] << c
|
83
|
+
@recv_ctx[:state] = :csum2
|
84
|
+
when :csum2
|
85
|
+
cs = @recv_ctx[:cs] << c
|
86
|
+
buf = @recv_ctx[:buf]
|
87
|
+
@recv_ctx = nil
|
88
|
+
if cs.downcase == gdb_csum(buf).downcase
|
89
|
+
@io.write '+'
|
90
|
+
else
|
91
|
+
log "transmit error"
|
92
|
+
@io.write '-'
|
93
|
+
return
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
case buf
|
99
|
+
when /^E(..)$/
|
100
|
+
e = $1.to_i(16)
|
101
|
+
log "error #{e} (#{PTrace::ERRNO.index(e)})"
|
102
|
+
return
|
103
|
+
when /^O([0-9a-fA-F]*)$/
|
104
|
+
if not outstr
|
105
|
+
first = true
|
106
|
+
outstr = ''
|
107
|
+
end
|
108
|
+
outstr << unhex($1)
|
109
|
+
ret = gdb_readresp(timeout, outstr)
|
110
|
+
outstr.split("\n").each { |e| log 'gdb: ' + e } if first
|
111
|
+
return ret
|
112
|
+
end
|
113
|
+
|
114
|
+
puts "gdb_readresp: got #{buf[0, 64].inspect}#{'...' if buf.length > 64}" if $DEBUG
|
115
|
+
buf
|
116
|
+
end
|
117
|
+
|
118
|
+
def gdb_msg(*a)
|
119
|
+
gdb_readresp if gdb_send(*a)
|
120
|
+
end
|
121
|
+
|
122
|
+
# rle: build the regexp that will match repetitions of a character, skipping counts leading to invalid char
|
123
|
+
rng = [3..(125-29)]
|
124
|
+
[?+, ?-, ?#, ?$].sort.each { |invalid|
|
125
|
+
invalid = invalid.unpack('C').first if invalid.kind_of? String
|
126
|
+
invalid -= 29
|
127
|
+
rng.each_with_index { |r, i|
|
128
|
+
if r.include? invalid
|
129
|
+
replace = [r.begin..invalid-1, invalid+1..r.end]
|
130
|
+
replace.delete_if { |r_| r_.begin > r_.end }
|
131
|
+
rng[i, 1] = replace
|
132
|
+
end
|
133
|
+
}
|
134
|
+
}
|
135
|
+
repet = rng.reverse.map { |r| "\\1{#{r.begin},#{r.end}}" }.join('|')
|
136
|
+
RLE_RE = /(.)(#{repet})/m
|
137
|
+
|
138
|
+
# rle-compress a buffer
|
139
|
+
# a character followed by '*' followed by 'x' is asc(x)-28 repetitions of the char
|
140
|
+
# eg '0* ' => '0' * (asc(' ') - 28) = '0000'
|
141
|
+
# for the count character, it must be 32 <= char < 126 and not be '+' '-' '#' or '$'
|
142
|
+
def rle(buf)
|
143
|
+
buf.gsub(RLE_RE) {
|
144
|
+
chr, len = $1, $2.length+1
|
145
|
+
chr + '*' + (len+28).chr
|
146
|
+
}
|
147
|
+
end
|
148
|
+
# decompress rle-encoded data
|
149
|
+
def unrle(buf) buf.gsub(/(.)\*(.)/) { $1 * ($2.unpack('C').first-28) } end
|
150
|
+
# send an integer as a long hex packed with leading 0 stripped
|
151
|
+
def hexl(int) @pack_netint[[int]].unpack('H*').first.sub(/^0+(.)/, '\\1') end
|
152
|
+
# send a binary buffer as a rle hex-encoded
|
153
|
+
def hex(buf) buf.unpack('H*').first end
|
154
|
+
# decode an rle hex-encoded buffer
|
155
|
+
def unhex(buf)
|
156
|
+
buf = buf[/^[a-fA-F0-9]*/]
|
157
|
+
buf = '0' + buf if buf.length & 1 == 1
|
158
|
+
[buf].pack('H*')
|
159
|
+
end
|
160
|
+
|
161
|
+
# retrieve remote regs
|
162
|
+
def read_regs
|
163
|
+
if buf = gdb_msg('g')
|
164
|
+
regs = unhex(unrle(buf))
|
165
|
+
p @unpack_int[regs].map { |v| '%x' % v } if $DEBUG
|
166
|
+
if regs.length < @regmsgsize
|
167
|
+
# retry once, was probably a response to something else
|
168
|
+
puts "bad regs size!" if $DEBUG
|
169
|
+
buf = gdb_msg('g')
|
170
|
+
regs = unhex(unrle(buf)) if buf
|
171
|
+
if not buf or regs.length < @regmsgsize
|
172
|
+
raise "regs buffer recv is too short !"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
Hash[*@gdbregs.zip(@unpack_int[regs]).flatten]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# send the reg values
|
180
|
+
def send_regs(r = {})
|
181
|
+
return if r.empty?
|
182
|
+
regs = r.values_at(*@gdbregs)
|
183
|
+
gdb_msg('G', hex(@pack_int[regs]))
|
184
|
+
end
|
185
|
+
|
186
|
+
# read memory (small blocks prefered)
|
187
|
+
def getmem(addr, len)
|
188
|
+
return '' if len == 0
|
189
|
+
if mem = quiet_during { gdb_msg('m', hexl(addr) << ',' << hexl(len)) } and mem != ''
|
190
|
+
unhex(unrle(mem))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# write memory (small blocks prefered)
|
195
|
+
def setmem(addr, data)
|
196
|
+
len = data.length
|
197
|
+
return if len == 0
|
198
|
+
raise 'writemem error' if not gdb_msg('M', hexl(addr) << ',' << hexl(len) << ':' << rle(hex(data)))
|
199
|
+
end
|
200
|
+
|
201
|
+
def continue
|
202
|
+
gdb_send('c')
|
203
|
+
end
|
204
|
+
|
205
|
+
def singlestep
|
206
|
+
gdb_send('s')
|
207
|
+
end
|
208
|
+
|
209
|
+
def break
|
210
|
+
@io.write("\3")
|
211
|
+
end
|
212
|
+
|
213
|
+
def kill
|
214
|
+
gdb_send('k')
|
215
|
+
end
|
216
|
+
|
217
|
+
def detach
|
218
|
+
gdb_send('D')
|
219
|
+
end
|
220
|
+
|
221
|
+
# monitor, aka remote command
|
222
|
+
def rcmd(cmd)
|
223
|
+
gdb_msg('qRcmd,' + hex(cmd))
|
224
|
+
end
|
225
|
+
|
226
|
+
attr_accessor :io, :cpu, :gdbregs
|
227
|
+
def initialize(io, cpu='Ia32')
|
228
|
+
cpu = Metasm.const_get(cpu).new if cpu.kind_of? String
|
229
|
+
raise 'unknown cpu' if not cpu.kind_of? CPU
|
230
|
+
setup_arch(cpu)
|
231
|
+
@cpu = cpu
|
232
|
+
|
233
|
+
case io
|
234
|
+
when IO; @io = io
|
235
|
+
when /^udp:(.*):(.*?)$/i; @io = UDPSocket.new ; @io.connect($1, $2)
|
236
|
+
when /^(?:tcp:)?(.*):(.*?)$/i; @io = TCPSocket.open($1, $2) # XXX matches C:\fail
|
237
|
+
# TODO pipe, serial port, etc ; also check ipv6
|
238
|
+
else raise "unknown target #{io.inspect}"
|
239
|
+
end
|
240
|
+
|
241
|
+
gdb_setup
|
242
|
+
end
|
243
|
+
|
244
|
+
def gdb_setup
|
245
|
+
gdb_msg('q', 'Supported')
|
246
|
+
#gdb_msg('Hc', '-1')
|
247
|
+
#gdb_msg('qC')
|
248
|
+
if not gdb_msg('?')
|
249
|
+
log "nobody on the line, waiting for someone to wake up"
|
250
|
+
IO.select([@io], nil, nil, nil)
|
251
|
+
log "who's there ?"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def set_hwbp(type, addr, len=1, set=true)
|
256
|
+
set = (set ? 'Z' : 'z')
|
257
|
+
type = { 'r' => '3', 'w' => '2', 'x' => '1', 's' => '0' }[type.to_s] || raise("invalid bp type #{type.inspect}")
|
258
|
+
gdb_msg(set, type << ',' << hexl(addr) << ',' << hexl(len))
|
259
|
+
true
|
260
|
+
end
|
261
|
+
|
262
|
+
def unset_hwbp(type, addr, len=1)
|
263
|
+
set_hwbp(type, addr, len, false)
|
264
|
+
end
|
265
|
+
|
266
|
+
# use qSymbol to retrieve a symbol value (uint)
|
267
|
+
def request_symbol(name)
|
268
|
+
resp = gdb_msg('qSymbol:', hex(name))
|
269
|
+
if resp and a = resp.split(':')[1]
|
270
|
+
@unpack_netint[unhex(a)].first
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def check_target(timeout=0)
|
275
|
+
return if not msg = gdb_readresp(timeout)
|
276
|
+
case msg[0]
|
277
|
+
when ?S
|
278
|
+
sig = unhex(msg[1, 2]).unpack('C').first
|
279
|
+
{ :state => :stopped, :info => "signal #{sig} #{PTrace::SIGNAL[sig]}" }
|
280
|
+
when ?T
|
281
|
+
sig = unhex(msg[1, 2]).unpack('C').first
|
282
|
+
ret = { :state => :stopped, :info => "signal #{sig} #{PTrace::SIGNAL[sig]}" }
|
283
|
+
ret.update msg[3..-1].split(';').inject({}) { |h, s| k, v = s.split(':', 2) ; h.update k => (v || true) } # 'thread' -> pid
|
284
|
+
when ?W
|
285
|
+
code = unhex(msg[1, 2]).unpack('C').first
|
286
|
+
{ :state => :dead, :info => "exited with code #{code}" }
|
287
|
+
when ?X
|
288
|
+
sig = unhex(msg[1, 2]).unpack('C').first
|
289
|
+
{ :state => :dead, :info => "signal #{sig} #{PTrace::SIGNAL[sig]}" }
|
290
|
+
else
|
291
|
+
log "check_target: unhandled #{msg.inspect}"
|
292
|
+
{ :state => :unknown }
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
attr_accessor :logger, :quiet
|
297
|
+
def log(s)
|
298
|
+
return if quiet
|
299
|
+
@logger ||= $stdout
|
300
|
+
@logger.puts s
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
# setup the various function used to pack ints & the reg list
|
305
|
+
# according to a target CPU
|
306
|
+
def setup_arch(cpu)
|
307
|
+
case cpu.shortname
|
308
|
+
when 'ia32'
|
309
|
+
@gdbregs = GDBREGS_IA32
|
310
|
+
@regmsgsize = 4 * @gdbregs.length
|
311
|
+
when 'x64'
|
312
|
+
@gdbregs = GDBREGS_X64
|
313
|
+
@regmsgsize = 8 * @gdbregs.length
|
314
|
+
when 'arm'
|
315
|
+
@gdbregs = cpu.dbg_register_list
|
316
|
+
@regmsgsize = 4 * @gdbregs.length
|
317
|
+
else
|
318
|
+
# we can still use readmem/kill and other generic commands
|
319
|
+
# XXX serverside setregs may fail if we give an incorrect regbuf size
|
320
|
+
puts "unsupported GdbServer CPU #{cpu.shortname}"
|
321
|
+
@gdbregs = [*0..32].map { |i| "r#{i}".to_sym }
|
322
|
+
@regmsgsize = 0
|
323
|
+
end
|
324
|
+
|
325
|
+
# yay life !
|
326
|
+
# do as if cpu is littleendian, fixup at the end
|
327
|
+
case cpu.size
|
328
|
+
when 16
|
329
|
+
@pack_netint = lambda { |i| i.pack('n*') }
|
330
|
+
@unpack_netint = lambda { |s| s.unpack('n*') }
|
331
|
+
@pack_int = lambda { |i| i.pack('v*') }
|
332
|
+
@unpack_int = lambda { |s| s.unpack('v*') }
|
333
|
+
when 32
|
334
|
+
@pack_netint = lambda { |i| i.pack('N*') }
|
335
|
+
@unpack_netint = lambda { |s| s.unpack('N*') }
|
336
|
+
@pack_int = lambda { |i| i.pack('V*') }
|
337
|
+
@unpack_int = lambda { |s| s.unpack('V*') }
|
338
|
+
when 64
|
339
|
+
bswap = lambda { |s| s.scan(/.{8}/m).map { |ss| ss.reverse }.join }
|
340
|
+
@pack_netint = lambda { |i| i.pack('Q*') }
|
341
|
+
@unpack_netint = lambda { |s| s.unpack('Q*') }
|
342
|
+
@pack_int = lambda { |i| bswap[i.pack('Q*')] }
|
343
|
+
@unpack_int = lambda { |s| bswap[s].unpack('Q*') }
|
344
|
+
if [1].pack('Q')[0] == ?\1 # ruby interpreter littleendian
|
345
|
+
@pack_netint, @pack_int = @pack_int, @pack_netint
|
346
|
+
@unpack_netint, @unpack_int = @unpack_int, @unpack_netint
|
347
|
+
end
|
348
|
+
else raise "GdbServer: unsupported cpu size #{cpu.size}"
|
349
|
+
end
|
350
|
+
|
351
|
+
# if target cpu is bigendian, use netint everywhere
|
352
|
+
if cpu.endianness == :big
|
353
|
+
@pack_int = @pack_netint
|
354
|
+
@unpack_int = @unpack_netint
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# virtual string to access the remote process memory
|
360
|
+
class GdbRemoteString < VirtualString
|
361
|
+
attr_accessor :gdb
|
362
|
+
|
363
|
+
def initialize(gdb, addr_start=0, length=nil)
|
364
|
+
@gdb = gdb
|
365
|
+
length ||= 1 << (@gdb.cpu.size rescue 32)
|
366
|
+
@pagelength = 512
|
367
|
+
super(addr_start, length)
|
368
|
+
end
|
369
|
+
|
370
|
+
def dup(addr=@addr_start, len=@length)
|
371
|
+
self.class.new(@gdb, addr, len)
|
372
|
+
end
|
373
|
+
|
374
|
+
def rewrite_at(addr, data)
|
375
|
+
len = data.length
|
376
|
+
off = 0
|
377
|
+
while len > @pagelength
|
378
|
+
@gdb.setmem(addr+off, data[off, @pagelength])
|
379
|
+
off += @pagelength
|
380
|
+
len -= @pagelength
|
381
|
+
end
|
382
|
+
@gdb.setmem(addr+off, data[off, len])
|
383
|
+
end
|
384
|
+
|
385
|
+
def get_page(addr, len=@pagelength)
|
386
|
+
@gdb.getmem(addr, len)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# this class implements a high-level API using the gdb-server network debugging protocol
|
391
|
+
class GdbRemoteDebugger < Debugger
|
392
|
+
attr_accessor :gdb, :check_target_timeout
|
393
|
+
def initialize(url, cpu='Ia32')
|
394
|
+
@gdb = GdbClient.new(url, cpu)
|
395
|
+
@gdb.logger = self
|
396
|
+
@cpu = @gdb.cpu
|
397
|
+
@memory = GdbRemoteString.new(@gdb)
|
398
|
+
@reg_val_cache = {}
|
399
|
+
@regs_dirty = false
|
400
|
+
# when checking target, if no message seen since this much seconds, send a 'status' query
|
401
|
+
@check_target_timeout = 1
|
402
|
+
super()
|
403
|
+
end
|
404
|
+
|
405
|
+
def invalidate
|
406
|
+
sync_regs
|
407
|
+
@reg_val_cache.clear
|
408
|
+
super()
|
409
|
+
end
|
410
|
+
|
411
|
+
def get_reg_value(r)
|
412
|
+
return @reg_val_cache[r] || 0 if @state != :stopped
|
413
|
+
sync_regs
|
414
|
+
@reg_val_cache = @gdb.read_regs || {} if @reg_val_cache.empty?
|
415
|
+
@reg_val_cache[r] || 0
|
416
|
+
end
|
417
|
+
def set_reg_value(r, v)
|
418
|
+
@reg_val_cache[r] = v
|
419
|
+
@regs_dirty = true
|
420
|
+
end
|
421
|
+
|
422
|
+
def sync_regs
|
423
|
+
@gdb.send_regs(@reg_val_cache) if @regs_dirty and not @reg_val_cache.empty?
|
424
|
+
@regs_dirty = false
|
425
|
+
end
|
426
|
+
|
427
|
+
def do_check_target
|
428
|
+
return if @state == :dead
|
429
|
+
t = Time.now
|
430
|
+
@last_check_target ||= t
|
431
|
+
if @state == :running and t - @last_check_target > @check_target_timeout
|
432
|
+
@gdb.io.write '$?#' << @gdb.gdb_csum('?')
|
433
|
+
@last_check_target = t
|
434
|
+
end
|
435
|
+
return unless i = @gdb.check_target(0.01)
|
436
|
+
invalidate if i[:state] == :stopped and @state != :stopped
|
437
|
+
@state, @info = i[:state], i[:info]
|
438
|
+
@info = nil if @info =~ /TRAP/
|
439
|
+
end
|
440
|
+
|
441
|
+
def do_wait_target
|
442
|
+
return unless i = @gdb.check_target(nil)
|
443
|
+
invalidate if i[:state] == :stopped and @state != :stopped
|
444
|
+
@state, @info = i[:state], i[:info]
|
445
|
+
@info = nil if @info =~ /TRAP/
|
446
|
+
end
|
447
|
+
|
448
|
+
def do_continue(*a)
|
449
|
+
return if @state != :stopped
|
450
|
+
@state = :running
|
451
|
+
@info = 'continue'
|
452
|
+
@gdb.continue
|
453
|
+
@last_check_target = Time.now
|
454
|
+
end
|
455
|
+
|
456
|
+
def do_singlestep(*a)
|
457
|
+
return if @state != :stopped
|
458
|
+
@state = :running
|
459
|
+
@info = 'singlestep'
|
460
|
+
@gdb.singlestep
|
461
|
+
@last_check_target = Time.now
|
462
|
+
end
|
463
|
+
|
464
|
+
def break
|
465
|
+
@gdb.break
|
466
|
+
end
|
467
|
+
|
468
|
+
def kill(sig=nil)
|
469
|
+
# TODO signal nr
|
470
|
+
@gdb.kill
|
471
|
+
@state = :dead
|
472
|
+
@info = 'killed'
|
473
|
+
end
|
474
|
+
|
475
|
+
def detach
|
476
|
+
super() # remove breakpoints & stuff
|
477
|
+
@gdb.detach
|
478
|
+
@state = :dead
|
479
|
+
@info = 'detached'
|
480
|
+
end
|
481
|
+
|
482
|
+
# set to true to use the gdb msg to handle bpx, false to set 0xcc ourself
|
483
|
+
attr_accessor :gdb_bpx
|
484
|
+
def enable_bp(addr)
|
485
|
+
return if not b = @breakpoint[addr]
|
486
|
+
b.state = :active
|
487
|
+
case b.type
|
488
|
+
when :bpx
|
489
|
+
if gdb_bpx
|
490
|
+
@gdb.set_hwbp('s', addr, 1)
|
491
|
+
else
|
492
|
+
@cpu.dbg_enable_bp(self, addr, b)
|
493
|
+
end
|
494
|
+
when :hw
|
495
|
+
@gdb.set_hwbp(b.mtype, addr, b.mlen)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def disable_bp(addr)
|
500
|
+
return if not b = @breakpoint[addr]
|
501
|
+
b.state = :inactive
|
502
|
+
case b.type
|
503
|
+
when :bpx
|
504
|
+
if gdb_bpx
|
505
|
+
@gdb.unset_hwbp('s', addr, 1)
|
506
|
+
else
|
507
|
+
@cpu.dbg_disable_bp(self, addr, b)
|
508
|
+
end
|
509
|
+
when :hw
|
510
|
+
@gdb.unset_hwbp(b.mtype, addr, b.mlen)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def check_pre_run(*a)
|
515
|
+
sync_regs
|
516
|
+
super(*a)
|
517
|
+
end
|
518
|
+
|
519
|
+
def loadallsyms
|
520
|
+
puts 'loadallsyms unsupported'
|
521
|
+
end
|
522
|
+
|
523
|
+
def ui_command_setup(ui)
|
524
|
+
ui.new_command('monitor', 'send a remote command to run on the target') { |arg| @gdb.rcmd(arg) }
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|