FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

GDBのプロトコルを読み解く(GDBクライアントからの受信パケットの解析)

前回、GDBパケット通信の確立方法について学んだ。次は、実際にどのようなパケットが送信されているのか、観察してみることにしよう。

msyksphinz.hatenablog.com

https://github.com/msyksphinz/swimmer_iss/tree/feature/gdb_implgithub.com

GDBリモートサーバで、クライアントからのパケットを取得する

まずはor1ksimのgdbサーバを間借りして、GDBからのリモートターゲット接続、その際のプロトコルを取得してみよう。 or1ksimのgdbサーバをC++で簡単に移植して、パケットを解析できる環境を作った。

void GdbEnv::HandleClientRequest ()
{
    struct rsp_buf *buf = GetPacket (); /* Message sent to us */

    // Null packet means we hit EOF or the link was closed for some other
    // reason. Close the client and return
    if (NULL == buf) {
        CloseRSPClient ();
        return;
    }

    std::cout << "Packet received " << buf->data << ": " << buf->len << "chars\n";

HandleClientRequest()は、GDBクライアントからのパケットを受信する関数だ。GetPacket()はここではとりあえず置いておいて、std::cout で何が出力されるか見てみよう。 GDBクライアントからリモートターゲット(つまりISS)へ接続すると、ISSが以下のような出力を出す。

  • GDBクライアント側
(gdb) target remote :2000
  • GDBサーバ側
$ ./swimmer_mips64 --gdb
Swimmer-RISCV
  Version 20160118 Revision 5858f70
  developed by msyksphinz <msyksphinz@gmail.com>
Listening for RSP on port 2000
Remote debugging from host0.0.0.0
Packet received qSupported:multiprocess+;qRelocInsn+: 36chars

何やら、"qSupported"などというパケットが受信されてきたらしい。これは何だろう。

GetPacket()を読み解いてGDBパケット通信を読み解く

GDBリモートサーバの資料によると、GDBのパケットは以下のような構造をしている。

http://embecosm.com/appnotes/ean4/images/packet-format.png

なるほど、最初に$、最後に#、そしてチェックサムだ。 これはGDBサーバのパケット受信関数を見ても分かる。

    while (1) {
        unsigned char  checksum;        /* The checksum we have computed */
        int            count;       /* Index into the buffer */
        int          ch;        /* Current character */

        /* Wait around for the start character ('$'). Ignore all other
           characters */
        ch = GetRSPChar ();
        while (ch != '$')  {
            if (-1 == ch) {
                return  NULL;       /* Connection failed */
            }

            ch = GetRSPChar ();
        }
...
        /* If we have a valid end of packet char, validate the checksum */
        if ('#' == ch) {
            unsigned char  xmitcsum;    /* The checksum in the packet */

            ch = GetRSPChar ();
            if (-1 == ch) {
                return  NULL;       /* Connection failed */
            }
            xmitcsum = Hex (ch) << 4;

            ch = GetRSPChar ();
            if (-1 == ch) {
                return  NULL;       /* Connection failed */
            }
...

このようにして最初と最後を除いたパケットを取得し、文字列として返している訳だ。 さらに先頭文字によって様々なパケットに分けられるらしい。

Howto: GDB Remote Serial Protocol

  • Packets requiring no acknowledgment. These commands are: f, i, I, k, R, t and vFlashDone.
  • Packets requiring a simple acknowledgment packet. The acknowledgment is either OK, Enn (where nn is an error number) or for some commands an empty packet (meaning "unsupported"). These commands are: !, A, D, G, H, M, P, Qxxxx, T, vFlashErase, vFlashWrite, X, z and Z.
  • Packets that return result data or an error code.. These commands are: ?, c, C, g, m, p, qxxxx, s, S and most vxxxx.
  • Deprecated packets which should no longer be used. These commands are b, B, d and r.

なるほどなあ。ここではqSupportedなので、クライアントからの要求に対してデータを返さなければならない。

qSupported. Report the features supported by the RSP server. As a minimum, just the packet size can be reported.

なるほど、ここではGDBサーバの受信バッファサイズを返せばいいのね。

  else if (0 == strncmp ("qSupported", buf->data, strlen ("qSupported")))
    {
      char  reply[GDB_BUF_MAX];
      sprintf (reply, "PacketSize=%x", GDB_BUF_MAX);
      put_str_packet (reply);

次回は、GDBサーバからGDBクライアントへのパケットの送信について見てみよう。