SIFTファイルを生成してデバッグできるようになったが、いくつか問題があるのでデバッグしていこう。
SniperシミュレータのQEMUフロントエンドにおいて、RISC-V命令のメモリアクセスのレコード機能についてデバッグする。
実行時に以下のようなデバッグ出力が表示され、lw命令のrs1レジスタがnilになっていることが判明した。
Load/store instruction: 13 rs1: 17 (expected: rv_ireg_x17 if < 32, rv_freg_f-15 if >= 32) imm: 0 regs[dec.rs1]: (nil)
rs1: 17はx17レジスタ(rv_ireg_x17)を指しているはずであるが、regs[17]がnilになっていた。
メモリアクセスの記録が1回目ではされず、2回目で記録されるという問題が発生していた。
0000000000044254 03 a7 08 00
0000000000044258 e3 1a 87 fe
-- taken
000000000004424c 21 05
000000000004424e 91 08
0000000000044250 63 07 aa 28
-- addr 7944029a30e8
-- not taken
0000000000044254 03 a7 08 00
0x44254のlw命令が1回目では記録されず、2回目で記録されていた。
- メモリアクセス記録の問題
処理順序を調査した結果、以下のような流れになっていることが判明した。
- 前の命令を送信(
execution.num_addressesは前回の値) - 現在の命令のメモリアドレスを処理(
execution.num_addressesを更新) execution.inst = instで保存
このため、最初の命令がexecution.instに保存された時点では、まだメモリアドレスが処理されていなかった。
- 処理順序の理解
メモリアクセス記録の問題については、実は設計上の意図であることが判明した。QEMUフロントエンドは1命令遅れで送信する設計になっている。
理由は、分岐判定(taken/not taken)に次の命令のアドレスが必要だからである。
auto taken = addr + size != (inst ? inst->get_address() : 0);
この判定は、前の命令のアドレス+サイズが次の命令のアドレスと一致しない場合、分岐が取られたと判断します。
処理の流れ:
1. 前の命令を送信(execution.num_addressesは前回の値)
2. 現在の命令のメモリアドレスを処理(execution.num_addressesを更新)
3. execution.inst = instで保存
この設計により、各命令のメモリアドレスが正しく記録される。
- メモリアドレス不足時の対応
trace_thread.ccにおいて、メモリアドレスが不足している場合にアサートで停止するのではなく、ゼロを埋めるように修正した。
// 修正前 LOG_ASSERT_ERROR(mem_idx < inst.num_addresses, "Did not receive enough data addresses"); mem_address = inst.addresses[mem_idx]; // 修正後 if (mem_idx >= inst.num_addresses) { mem_address = 0; } else { mem_address = inst.addresses[mem_idx]; }
これにより、メモリアドレスが不足している場合でも、シミュレーションが継続できるようになった。