class GdbFlasher::ServerConnection

Public Class Methods

new() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 14
def initialize
  @socket = nil
end

Public Instance Methods

close() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 80
def close
  @socket.close
  @socket = nil
end
connect(address) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 18
def connect(address)
  raise "Already connected" unless @socket.nil?

  delimiter = address.rindex ':'
  raise ArgumentError, "Port must be specified" if delimiter.nil?
  host = address[0...delimiter]
  port = address[delimiter + 1..-1].to_i

  @buf = ""
  @features = {}
  @need_ack = true

  begin
    @socket = TCPSocket.new host, port
    @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1

    # Query stub features
    command("qSupported").split(';').each do |feature|
      if feature[-1] == '-'
        @features.delete feature[0...-1].intern
      elsif feature[-1] == '+'
        @features[feature[0...-1].intern] = true
      else
        sep = feature.index '='

        if sep.nil?
          raise "Unexpected item in qSupported response: #{sep}"
        end

        @features[feature[0...sep].intern] = feature[sep + 1..-1]
      end
    end

    if @features[:PacketSize].nil?
      raise "PacketSize isn't present in qSupported response"
    end

    if @features[:QStartNoAckMode]
      response = command("QStartNoAckMode")

      if response != "OK"
        raise "Unable to enter NoAck mode: #{response}"
      end

      @need_ack = false
    end

    # Load target description
    if @features[:"qXfer:features:read"]
      description = read_xfer "features", "target.xml"

      # TODO: parse target description and use register map from it.
    end

  rescue Exception => e
    @socket.close unless @socket.nil?
    @socket = nil

    raise e
  end
end
continue() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 167
def continue
  parse_stop_reply command("c")
end
get_stop_reply() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 183
def get_stop_reply
  parse_stop_reply command("?")
end
insert_breakpoint(type, address, kind) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 151
def insert_breakpoint(type, address, kind)
  response = command "Z#{type.to_s 16},#{address.to_s 16},#{kind.to_s 16}"

  if response != "OK"
    raise "Breakpoint insertion failed: #{response}"
  end
end
read_memory(base, size) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 125
def read_memory(base, size)
  max_size = (@features[:PacketSize].to_i - 19) & ~1
  offset = 0
  data = ""
  size *= 2

  while offset < size
    chunk_size = [ max_size, size - offset ].min

    response = command "m#{base.to_s 16},#{(chunk_size / 2).to_s 16}"

    if response[0] == "E"
      raise "Memory read failed: #{response}"
    end

    data += response

    base += chunk_size / 2
    offset += chunk_size
  end

  Array.new(data.length / 2) do |i|
    data[i * 2..i * 2 + 1].to_i 16
  end.pack("C*")
end
read_registers() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 85
def read_registers
  response = command "g"

  if (response[0] != 'E' || response.length != 3) && response.length % 8 != 0
    raise "Malformed 'g' response"
  end

  if response[0] == 'E'
    raise "GDB server error: #{response[1..2].to_i 16}"
  end

  Array.new response.length / 8 do |i|
    big2native response[i * 8...(i + 1) * 8].to_i(16)
  end
end
remove_breakpoint(type, address, kind) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 159
def remove_breakpoint(type, address, kind)
  response = command "z#{type.to_s 16},#{address.to_s 16},#{kind.to_s 16}"

  if response != "OK"
    raise "Breakpoint removal failed: #{response}"
  end
end
reset() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 175
def reset
  reply = command "r"

  if reply != "OK"
    raise "Reset failed: #{reply}"
  end
end
step() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 171
def step
  parse_stop_reply command("s")
end
write_memory(base, string) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 105
def write_memory(base, string)
  max_size = (@features[:PacketSize].to_i - 19) & ~1

  offset = 0
  data = string.unpack("C*").map { |byte| sprintf "%02X", byte }.join

  while offset < data.length
    chunk_size = [ max_size, data.length - offset ].min

    response = command "M#{base.to_s 16},#{(chunk_size / 2).to_s 16}:#{data[offset...offset + chunk_size]}"

    if response != "OK"
      raise "Memory write failed: #{response}"
    end

    base += chunk_size / 2
    offset += chunk_size
  end
end
write_register(reg, value) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 187
def write_register(reg, value)
  reply = command sprintf("P%x=%08x", reg, big2native(value))

  if reply != "OK"
    raise "Register write failed: #{reply}"
  end
end
write_registers(regs) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 101
def write_registers(regs)
  send_packet "G" + regs.map { |r| sprintf "%08x", native2big(r) }.join
end

Protected Instance Methods

big2native(v) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 330
def big2native(v)
  [ v ].pack("L").unpack("N")[0]
end
command(s, extra = {}) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 244
def command(s, extra = {})
  send_packet s, extra

  response = receive_packet
end
native2big(v) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 326
def native2big(v)
  [ v ].pack("N").unpack("L")[0]
end
parse_stop_reply(reply) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 197
def parse_stop_reply(reply)
  case reply[0]
  when 'S'
    StopReply.new reply[1..2].to_i(16)

  when 'T'
    pairs = reply[3..-1].split ';'
    values = {}
    pairs.each do |pair|
      key, value = pair.split ':'

      if key.match /^[0-9a-fA-F]+/
        values[key.to_i 16] = value.to_i 16
      else
        values[key] = value
      end
    end

    StopReply.new reply[1..2].to_i(16), values

  else
    raise "Unknown stop reply: #{reply}"
  end
end
read_xfer(object, annex) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 222
def read_xfer(object, annex)
  offset = 0
  size = @features[:PacketSize].to_i - 4
  contents = ""

  loop do
    response = command "qXfer:#{object}:read:#{annex}:#{offset}:#{size}"

    offset += response.length - 1
    contents += response[1..-1]

    if response[0] == 'l'
      break
    elsif response[0] != 'm'
      raise "qXfer failed: #{response}"

    end
  end

  contents
end
receive_packet() click to toggle source
# File lib/gdbflasher/server_connection.rb, line 295
def receive_packet
  loop do
    @buf += @socket.readpartial 4096

    if @buf[0] != '$'
      raise "Invalid response from server"
    end

    idx = @buf.index '#'

    if !idx.nil? && idx + 2 <= @buf.length
      message = @buf.slice! 0..idx + 2

      data = message[1..-4]

      if @need_ack
        checksum = data.sum 8
        if checksum != message[-2..-1].to_i(16)
          @socket.write '-'
        else
          @socket.write '+'

          return data
        end
      else
        return data
      end
    end
  end
end
send_packet(data, extra = {}) click to toggle source
# File lib/gdbflasher/server_connection.rb, line 250
def send_packet(data, extra = {})
  data = data.dup

  if extra[:escape]
    i = 0

    while i < data.length
      byte = data[i]

      if byte == '$' || byte == '#' || byte == 0x7d.chr
        data.insert i, 0x7d.chr

        i += 1
      end

      i += 1
    end
  end

  message = sprintf "$%s#%02x", data, data.sum(8)

  if !@features[:PacketSize].nil? && @features[:PacketSize].to_i < message.length
    raise "Internal error: message is too long"
  end

  @socket.write message

  if @need_ack
    loop do
      ack = @socket.read 1

      case ack
      when '+'
        break

      when '-'
        @socket.write message

      else
        raise "Unexpected response from server: #{ack}"
      end
    end
  end
end