コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2020年12月10日 >>> 2020年12月10日
link もっと後

2020年12月10日

QEMU RISC-Vエミュレーションで例外が起きるときの仕組み

目次: RISC-V

RISC-VではCPUが単精度浮動小数点F拡張や、倍精度浮動小数点D拡張の命令(flw, fldなど)に対応していても、mstatusのFSビットでFPUを有効にしていないと、命令実行時にIllegal Instruction例外が発生します。

QEMUはこのチェックをどこで行っているのかメモしておきます。

QEMU RISC-V例外発生部分

// qemu/target/riscv/translate.c

static void decode_opc(CPURISCVState *env, DisasContext *ctx, uint16_t opcode)
{
    /* check for compressed insn */
    if (extract16(opcode, 0, 2) != 3) {
        if (!has_ext(ctx, RVC)) {
            gen_exception_illegal(ctx);
        } else {
            ctx->pc_succ_insn = ctx->base.pc_next + 2;
            if (!decode_insn16(ctx, opcode)) {
                /* fall back to old decoder */
                decode_RV32_64C(ctx, opcode);
            }
        }
    } else {
        uint32_t opcode32 = opcode;
        opcode32 = deposit32(opcode32, 16, 16,
                             translator_lduw(env, ctx->base.pc_next + 2));
        ctx->pc_succ_insn = ctx->base.pc_next + 4;
        if (!decode_insn32(ctx, opcode32)) {    //★この関数がfalseを返す★
            gen_exception_illegal(ctx);
        }
    }
}

static void gen_exception_illegal(DisasContext *ctx)
{
    generate_exception(ctx, RISCV_EXCP_ILLEGAL_INST);    //★Illegal Instruction例外★
}

関数decode_insn32() で命令をデコードしfalseが返ってきたら、例外を発生させます。このdecode_insn32() という関数は自動生成されており、ビルドディレクトリの下にあります。RV64向けなら下記のようなパスです。

QEMU RISC-V命令デコード部分

// (build_dir)/libqemu-riscv64-softmmu.fa.p/decode-insn32.c.inc

static bool decode_insn32(DisasContext *ctx, uint32_t insn)
{
    switch (insn & 0x0000007f) {

    ...

    case 0x00000007:
        /* ........ ........ ........ .0000111 */
        switch ((insn >> 12) & 0x7) {

        ...

        case 0x3:
            /* ........ ........ .011.... .0000111 */
            /* ../target/riscv/insn32.decode:199 */
            decode_insn32_extract_i(ctx, &u.f_i, insn);
            if (trans_fld(ctx, &u.f_i)) return true;    //★このifが成立「しない」★
            break;

...


static void decode_insn32_extract_i(DisasContext *ctx, arg_i *a, uint32_t insn)
{
    a->imm = sextract32(insn, 20, 12);
    a->rs1 = extract32(insn, 15, 5);
    a->rd = extract32(insn, 7, 5);
}


// qemu/target/riscv/insn_trans/trans_rvd.c.inc

static bool trans_fld(DisasContext *ctx, arg_fld *a)
{
    REQUIRE_FPU;    //★このマクロ内でreturn false★
    REQUIRE_EXT(ctx, RVD);
    TCGv t0 = tcg_temp_new();
    gen_get_gpr(t0, a->rs1);
    tcg_gen_addi_tl(t0, t0, a->imm);

    tcg_gen_qemu_ld_i64(cpu_fpr[a->rd], t0, ctx->mem_idx, MO_TEQ);

    mark_fs_dirty(ctx);
    tcg_temp_free(t0);
    return true;
}


// qemu/target/riscv/insn_trans/trans_rvf.c.inc

#define REQUIRE_FPU do {\
    if (ctx->mstatus_fs == 0) \
        return false;                       \
} while (0)

大体の仕組みは把握できましたので、実際にこの部分を通るかどうか見ましょう。

実行して確かめる

Illegal Instruction例外は実行中に何度も発生する例外ではありません。カーネル内で1度でも発生するとZephyrはログを出して止まります。すなわちQEMUも例外を発生させるのは1度だけです。

何度も通る処理だとブレーク条件が必要でややこしいですが、1度しか通らない処理なら単純にREQUIRE_FPUの行でブレークすれば良いです。条件ctx->mstatus_fs == 0が成立するかどうか簡単に確認できます。

GDBでmstatus_fsの値を確認する
$ gdb qemu/build/qemu-system-riscv64

(gdb) b trans_rvd.c.inc:23

Breakpoint 1 at 0x555555a858b8: file ../target/riscv/insn_trans/trans_rvd.c.inc, line 23.

(gdb) r

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff440c700 (LWP 1675592)]
[New Thread 0x7ffff3a89700 (LWP 1675593)]
*** Booting Zephyr OS build zephyr-v2.4.0-2328-g4f33cf942643  ***
Running test suite test_sprintf
===================================================================
START - test_sprintf_double
[Switching to Thread 0x7ffff3a89700 (LWP 1675593)]

Thread 3 "qemu-system-ris" hit Breakpoint 1, trans_fld (ctx=0x7ffff3a883d0,
    a=0x7ffff3a88290) at ../target/riscv/insn_trans/trans_rvd.c.inc:23
23          REQUIRE_FPU;

(gdb) l

18       * this program.  If not, see <http://www.gnu.org/licenses/>.
19       */
20
21      static bool trans_fld(DisasContext *ctx, arg_fld *a)
22      {
23          REQUIRE_FPU;        //★ここで止めた★
24          REQUIRE_EXT(ctx, RVD);
25          TCGv t0 = tcg_temp_new();
26          gen_get_gpr(t0, a->rs1);
27          tcg_gen_addi_tl(t0, t0, a->imm);

(gdb) p ctx->mstatus_fs

$1 = 0

ちょっとしたメモのつもりが長くなってしまいました。まあいいや。

編集者:すずき(2021/09/21 22:59)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2020年12月10日 >>> 2020年12月10日
link もっと後

管理用メニュー

link 記事を新規作成

<2020>
<<<12>>>
--12345
6789101112
13141516171819
20212223242526
2728293031--

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/26 03:20