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,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
|