lowlevel interface to the gdbserver protocol
# File metasm/os/gdbremote.rb, line 243 def initialize(io, cpu='Ia32') cpu = Metasm.const_get(cpu).new if cpu.kind_of? String raise 'unknown cpu' if not cpu.kind_of? CPU setup_arch(cpu) @cpu = cpu case io when IO; @io = io when /^ser:(.*)/; @io = File.open($1, 'rb+') when /^udp:\[?(.*)\]?:(.*?)$/; @io = UDPSocket.new ; @io.connect($1, $2) when /^(?:tcp:)?\[?(..+)\]?:(.*?)$/; @io = TCPSocket.open($1, $2) else raise "unknown target #{io.inspect}" end gdb_setup end
# File metasm/os/gdbremote.rb, line 225 def break @io.write("\33"") end
# File metasm/os/gdbremote.rb, line 294 def check_target(timeout=0) return if not msg = gdb_readresp(timeout) case msg[0] when S sig = unhex(msg[1, 2]).unpack('C').first { :state => :stopped, :info => "signal #{sig} #{PTrace::SIGNAL[sig]}" } when T sig = unhex(msg[1, 2]).unpack('C').first ret = { :state => :stopped, :info => "signal #{sig} #{PTrace::SIGNAL[sig]}" } ret.update msg[3..-1].split(';').inject({}) { |h, s| k, v = s.split(':', 2) ; h.update k => (v || true) } # 'thread' -> pid when W code = unhex(msg[1, 2]).unpack('C').first { :state => :dead, :info => "exited with code #{code}" } when X sig = unhex(msg[1, 2]).unpack('C').first { :state => :dead, :info => "signal #{sig} #{PTrace::SIGNAL[sig]}" } else log "check_target: unhandled #{msg.inspect}" { :state => :unknown } end end
# File metasm/os/gdbremote.rb, line 217 def continue gdb_send('c') end
# File metasm/os/gdbremote.rb, line 233 def detach gdb_send('D') end
compute the hex checksum used in gdb protocol
# File metasm/os/gdbremote.rb, line 18 def gdb_csum(buf) '%02x' % (buf.unpack('C*').inject(0) { |cs, c| cs + c } & 0xff) end
# File metasm/os/gdbremote.rb, line 134 def gdb_msg(*a) gdb_readresp if gdb_send(*a) end
return buf, or nil on error / csum error waits IO.select(timeout) between each char outstr is used internally only to handle multiline output string
# File metasm/os/gdbremote.rb, line 65 def gdb_readresp(timeout=nil, outstr=nil) @recv_ctx ||= {} @recv_ctx[:state] ||= :nosync buf = nil while @recv_ctx if !@recv_ctx[:rbuf] return unless IO.select([@io], nil, nil, timeout) if @io.kind_of?(UDPSocket) raise Errno::EPIPE if not @recv_ctx[:rbuf] = @io.recvfrom(65536)[0] else raise Errno::EPIPE if not c = @io.read(1) end end if @recv_ctx[:rbuf] c = @recv_ctx[:rbuf].slice!(0, 1) @recv_ctx.delete :rbuf if @recv_ctx[:rbuf] == '' end case @recv_ctx[:state] when :nosync if c == '$' @recv_ctx[:state] = :data @recv_ctx[:buf] = '' end when :data if c == '#' @recv_ctx[:state] = :csum1 @recv_ctx[:cs] = '' else @recv_ctx[:buf] << c end when :csum1 @recv_ctx[:cs] << c @recv_ctx[:state] = :csum2 when :csum2 cs = @recv_ctx[:cs] << c buf = @recv_ctx[:buf] @recv_ctx = nil if cs.downcase == gdb_csum(buf).downcase @io.write '+' else log "transmit error" @io.write '-' return end end end case buf when /^E(..)$/ e = $1.to_i(16) log "error #{e} (#{PTrace::ERRNO.index(e)})" return when /^O([0-9a-fA-F]*)$/ if not outstr first = true outstr = '' end outstr << unhex($1) ret = gdb_readresp(timeout, outstr) outstr.split("\n").each { |o| log 'gdb: ' + o } if first return ret end log "gdb_readresp: got #{buf[0, 64].inspect}#{'...' if buf.length > 64}" if $DEBUG buf end
send the buffer, waits ack return true on success
# File metasm/os/gdbremote.rb, line 24 def gdb_send(cmd, buf='') buf = cmd + buf buf = '$' << buf << '#' << gdb_csum(buf) log "gdb_send #{buf.inspect}" if $DEBUG 5.times { @io.write buf out = '' loop do break if not IO.select([@io], nil, nil, 0.2) raise Errno::EPIPE if not ack = @io.read(1) case ack when '+' return true when '-' log "gdb_send: ack neg" if $DEBUG break when nil return else out << ack end end log "no ack, got #{out.inspect}" if out != '' } log "send error #{cmd.inspect} (no ack)" false end
# File metasm/os/gdbremote.rb, line 260 def gdb_setup pnd = '' pnd << @io.read(1) while IO.select([@io], nil, nil, 0.2) log "startpending: #{pnd.inspect}" if pnd != '' gdb_msg('q', 'Supported') #gdb_msg('Hc', '-1') #gdb_msg('qC') if not gdb_msg('?') log "nobody on the line, waiting for someone to wake up" IO.select([@io], nil, nil, nil) log "who's there ?" end end
read memory (small blocks prefered)
# File metasm/os/gdbremote.rb, line 203 def getmem(addr, len) return '' if len == 0 if mem = quiet_during { gdb_msg('m', hexl(addr) << ',' << hexl(len)) } and mem != '' unhex(unrle(mem)) end end
send a binary buffer as a rle hex-encoded
# File metasm/os/gdbremote.rb, line 169 def hex(buf) buf.unpack('H*').first end
send an integer as a long hex packed with leading 0 stripped
# File metasm/os/gdbremote.rb, line 167 def hexl(int) @pack_netint[[int]].unpack('H*').first.sub(/^0+(.)/, '\1') end
# File metasm/os/gdbremote.rb, line 229 def kill gdb_send('k') end
# File metasm/os/gdbremote.rb, line 317 def log(s) puts s if $DEBUG and logger return if quiet logger ? logger.log(s) : puts(s) end
# File metasm/os/gdbremote.rb, line 54 def quiet_during pq = quiet @quiet = true yield ensure @quiet = pq end
monitor, aka remote command
# File metasm/os/gdbremote.rb, line 238 def rcmd(cmd) gdb_msg('qRcmd,' + hex(cmd)) end
retrieve remote regs
# File metasm/os/gdbremote.rb, line 178 def read_regs if buf = gdb_msg('g') regs = unhex(unrle(buf)) p @unpack_int[regs].map { |v| '%x' % v } if $DEBUG if regs.length < @regmsgsize # retry once, was probably a response to something else puts "bad regs size!" if $DEBUG buf = gdb_msg('g') regs = unhex(unrle(buf)) if buf if not buf or regs.length < @regmsgsize raise "regs buffer recv is too short ! (#{regs.length} < #{@regmsgsize})" end end Hash[*@gdbregs.zip(@unpack_int[regs]).flatten] end end
use qSymbol to retrieve a symbol value (uint)
# File metasm/os/gdbremote.rb, line 287 def request_symbol(name) resp = gdb_msg('qSymbol:', hex(name)) if resp and a = resp.split(':')[1] @unpack_netint[unhex(a)].first end end
rle-compress a buffer a character followed by '*' followed by 'x' is asc(x)-28 repetitions of the char eg '0* ' => '0' * (asc(' ') - 28) = '0000' for the count character, it must be 32 <= char < 126 and not be '+' '-' '#' or '$'
# File metasm/os/gdbremote.rb, line 158 def rle(buf) buf.gsub(RLE_RE) { chr, len = $1, $2.length+1 chr + '*' + (len+28).chr } end
send the reg values
# File metasm/os/gdbremote.rb, line 196 def send_regs(r = {}) return if r.empty? regs = r.values_at(*@gdbregs) gdb_msg('G', hex(@pack_int[regs])) end
# File metasm/os/gdbremote.rb, line 275 def set_hwbp(type, addr, len=1, set=true) set = (set ? 'Z' : 'z') type = { 'r' => '3', 'w' => '2', 'x' => '1', 's' => '0' }[type.to_s] || raise("invalid bp type #{type.inspect}") gdb_msg(set, type << ',' << hexl(addr) << ',' << hexl(len)) true end
write memory (small blocks prefered)
# File metasm/os/gdbremote.rb, line 211 def setmem(addr, data) len = data.length return if len == 0 raise 'writemem error' if not gdb_msg('M', hexl(addr) << ',' << hexl(len) << ':' << rle(hex(data))) end
setup the various function used to pack ints & the reg list according to a target CPU
# File metasm/os/gdbremote.rb, line 328 def setup_arch(cpu) @ptrsz = cpu.size case cpu.shortname when /^ia32/ @ptrsz = 32 @gdbregs = GDBREGS_IA32 @regmsgsize = 4 * @gdbregs.length when 'x64' @gdbregs = GDBREGS_X64 @regmsgsize = 8 * @gdbregs.length when 'arm' @gdbregs = cpu.dbg_register_list @regmsgsize = 4 * @gdbregs.length when 'arm64' @gdbregs = cpu.dbg_register_list @regmsgsize = 8 * @gdbregs.length when 'mips' @gdbregs = cpu.dbg_register_list @regmsgsize = cpu.size/8 * @gdbregs.length else # we can still use readmem/kill and other generic commands # XXX serverside setregs may fail if we give an incorrect regbuf size puts "unsupported GdbServer CPU #{cpu.shortname}" @gdbregs = [*0..32].map { |i| "r#{i}".to_sym } @regmsgsize = 0 end # yay life ! # do as if cpu is littleendian, fixup at the end case @ptrsz when 16 @pack_netint = lambda { |i| i.pack('n*') } @unpack_netint = lambda { |s| s.unpack('n*') } @pack_int = lambda { |i| i.pack('v*') } @unpack_int = lambda { |s| s.unpack('v*') } when 32 @pack_netint = lambda { |i| i.pack('N*') } @unpack_netint = lambda { |s| s.unpack('N*') } @pack_int = lambda { |i| i.pack('V*') } @unpack_int = lambda { |s| s.unpack('V*') } when 64 bswap = lambda { |s| s.scan(/.{8}/).map { |ss| ss.reverse }.join } @pack_netint = lambda { |i| i.pack('Q*') } @unpack_netint = lambda { |s| s.unpack('Q*') } @pack_int = lambda { |i| bswap[i.pack('Q*')] } @unpack_int = lambda { |s| bswap[s].unpack('Q*') } if [1].pack('Q')[0] == \1 # ruby interpreter littleendian @pack_netint, @pack_int = @pack_int, @pack_netint @unpack_netint, @unpack_int = @unpack_int, @unpack_netint end else raise "GdbServer: unsupported cpu size #{@ptrsz}" end # if target cpu is bigendian, use netint everywhere if cpu.endianness == :big @pack_int = @pack_netint @unpack_int = @unpack_netint end end
# File metasm/os/gdbremote.rb, line 221 def singlestep gdb_send('s') end
decode an rle hex-encoded buffer
# File metasm/os/gdbremote.rb, line 171 def unhex(buf) buf = buf[/^[a-fA-F0-9]*/] buf = '0' + buf if buf.length & 1 == 1 [buf].pack('H*') end
decompress rle-encoded data
# File metasm/os/gdbremote.rb, line 165 def unrle(buf) buf.gsub(/(.)\*(.)/) { $1 * ($2.unpack('C').first-28) } end
# File metasm/os/gdbremote.rb, line 282 def unset_hwbp(type, addr, len=1) set_hwbp(type, addr, len, false) end