FPGA開発日記

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

ISSにウォッチポイント機能を実装する

デバッグを効率的に進めるためには、ウォッチポイントの実装が不可欠だ。GDBでは、以下のウォッチポイントが実装されているようだ。

  • watch : 特定のメモリアドレスに対して書き込みが発生すると停止する
  • rwatch : 特定のメモリアドレスに対して読み込みが発生すると停止する

この機能を実現するために、GDBには以下のコマンドが定義されている。

  • Z2xxxxxxxx,size : アドレスxxxxxxxxにサイズsizeの書き込みが発生すると停止する(この際停止する場所は「書き込み後」で良い)。
  • Z3xxxxxxxx,size : アドレスxxxxxxxxにサイズsizeの読み込みが発生すると停止する(この際停止する場所は「読み込み後」で良い)。
  • Z4xxxxxxxx,size : アドレスxxxxxxxxにサイズsizeの読み込みまたは書き込みが発生する(この際停止する場所は「読み込みまたは書き込み後」で良い)。

まず、これらのコマンドを実現するためには、プログラムアドレスの時と同様に、ブレークポイントを格納するためのキューが必要だろう。

github.com

        // Set Write Memory BreakPoint
        if (IsEqualHead (packet_str, "Z2")) {
            int idx;
            for (idx = 3; packet_str[idx] != ',' && idx < packet_str.length(); idx++) {
                break_addr = (break_addr << 4) | Hex (packet_str[idx]);
            }
            if (idx >= packet_str.length()) {
                PutPacket ("E00");
            } else {
                m_env->AddWriteMemBreak (break_addr);
                PutPacket ("OK");
            }
        }

...

    std::set<Addr_t> m_queue_read_break;    // Read Memory break list
    std::set<Addr_t> m_queue_write_break;   // Write Memory break list

ブレークポイントを設定、解除、参照するためのメソッドを定義する。

void EnvBase::AddReadMemBreak (Addr_t addr)
{
    if (m_queue_read_break.find (addr) == m_queue_read_break.end()) {
        InfoPrint ("<set_read_watch : %08x>\n", addr);
        m_queue_read_break.insert (addr);
    } else {
        InfoPrint ("<set_read_watch : already inserted %08x>\n", addr);
    }
    return;
}


bool EnvBase::RemoveReadMemBreak (Addr_t addr)
{
    for (auto it = m_queue_read_break.begin();
         it != m_queue_read_break.end();
         it++) {
        if ((*it) == addr) {
            InfoPrint ("<remove_read_watch : %08x>\n", addr);
            m_queue_read_break.erase (it);
            return true;
        }
    }
    return false;
}


bool EnvBase::FindReadMemBreak (Addr_t addr)
{
    if (m_queue_read_break.find (addr) == m_queue_read_break.end()) {
        return false;
    } else {
        return true;
    }
}

メモリアクセスを実行する度に、ブレークポイントに引掛っているかをチェックしている。

    MemResult mem_result = m_env->LoadFromBus (mem_addr, Size_Byte, &res);
    m_env->CheckSetReadMemBreakPoint (mem_result);

ブレークポイントにヒットすると、メモリアクセスのフラグとして命令実行ステータスを変更し、プログラム実行のループから抜け出し、GDBに応答を返す。 このときに、GDBから応答が返ったときは、当該メモリアクセスの「実行後」であるが、PCは「当該命令」に存在していなければならない。

このため、メモリアクセスのブレークポイントに引っ掛かると、プログラムを進めることはせず、そのまま応答を返すようにする。

    if (IsMemBreakPoint ()) {
        return ExecAfterBreak;
    }
    if ((GetTrace()->IsDelayedSlot() == false) && (GetJumped () == false)) {
        ProceedPC ();      // Update PC
    }

実際に使ってみる

xv6の、cpu->schedulerにメモリアクセスが発生したところで停止させてみる。gdbスクリプトは以下のようになる。

rwatch cpu->scheduler

GDBインタフェース側の出力

remote Thread <main> In:                                                                                                                                                                           L??   PC: 0x1fc00000
Breakpoint 1 at 0x80107c64: file swtch.S, line 29.
Hardware read watchpoint 2: cpu->scheduler
...

   |21        kinit1(end, P2V(8*1024*1024)); // phys page allocator                                   |
   |22        kvmalloc();      // kernel page table                                                   |
   |23        mpinit();        // collect info about this machine                                     |
  >|24        cprintf("\ncpu%d: starting xv6\n\n", cpu->id);                                          |
   |25        picinit();       // interrupt controller                                                |
   |26        consoleinit();   // I/O devices & their interrupts                                      |
   |27        uartinit();      // serial port                                                         |
   |28        pinit();         // process table                                                       |

$ disassemble
   0x8010527c <+52>:    lui     v0,0x8011
=> 0x80105280 <+56>:    lw      v0,-15040(v0)
   0x80105284 <+60>:    lbu     v0,0(v0)
   0x80105288 <+64>:    move    a1,v0
   0x8010528c <+68>:    lui     v0,0x8011
   0x80105290 <+72>:    addiu   a0,v0,-18008
   0x80105294 <+76>:    jal     0x801007fc <cprintf>
   0x80105298 <+80>:    nop
   0x8010529c <+84>:    jal     0x801055d4 <picinit>
   0x801052a0 <+88>:    nop
   0x801052a4 <+92>:    jal     0x8010138c <consoleinit>
   0x801052a8 <+96>:    nop

ちゃんと実装できているようだ!