C言語で作るLC-3仮想マシン 第8回: ステップ実行で1命令ずつ進める | UNIX Cafe

* 当サイトでは、コンテンツの一部に広告を掲載しています。

System Note $ cat /proc/ai-disclosure

本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。

C言語で作るLC-3仮想マシン 第8回: ステップ実行で1命令ずつ進める | UNIX Cafe
目次

ステップ実行で1命令ずつ進める

前回は、VMに--profileオプションを追加し、実行した命令数の合計とopcodeごとの実行回数を数えられるようにしました。

プロファイル機能では、プログラム全体でどの命令が多く実行されたかを見られます。一方で、「次の命令へ進む前にいったん止めて、PCやレジスタを落ち着いて確認する」ことはまだできません。

第8回では、VMに--stepオプションを追加します。1命令実行するたびに状態を表示し、step>プロンプトでEnter入力を待つようにします。

GitHubではlc3-vm-cという名前で公開しています。

この記事時点のコードは、GitHubのlessons/08-stepに完成版として置いています。リポジトリ直下のlc3.cは、連載の進行に合わせて最新版へ更新していきます。

Static Badge Static Badge Static Badge Static Badge

今回作るステップ実行の全体像

第6回の--traceでは、VMが命令を実行するたびにPC、命令word、opcode、レジスタを表示しました。

第8回の--stepでは、そのtrace表示を使いながら、1命令ごとにEnter入力を待ちます。

fetch
  PCが指す命令wordを読む
  ↓
trace
  命令情報を表示する
  ↓
execute
  命令を実行する
  ↓
trace
  レジスタと条件フラグを表示する
  ↓
step
  Enter入力を待つ

今回追加する主な機能は、以下の通りです。

  • step_enabled: ステップ実行を有効にするフラグ
  • wait_for_step(): step>を表示してEnter入力を待つ関数
  • --stepオプション: ステップ実行を有効にする引数
  • step時のtrace自動ON: --stepだけでも命令情報を表示する

第8回で作業する場所は、VM本体の中ではかなり限られています。

lc3.c
├─ 補助関数
│  └─ wait_for_step() を追加
│
└─ main()
   ├─ step_enabled を追加
   ├─ --step オプションを追加
   ├─ step時にtrace_enabledを自動でONにする
   └─ 1命令実行後にwait_for_step()を呼ぶ

ステップ実行とは何か

ステップ実行とは、プログラムを一気に最後まで実行せず、1命令ずつ止めながら進める実行方法です。

通常実行では、Hello Worldはすぐに終わります。

./lc3 examples/hello.obj

実行結果は次のようになります。

Hello, World!
HALT

これだけでは、LEAR0にどの番地が入ったのか、PUTSの前後でR7がどう変わったのかが分かりません。

そこで、--traceを使って1命令ごとの情報を表示します。さらに--stepを使うと、表示を読んでからEnterで次へ進められます。

wait_for_step()を追加する

まず、ステップ実行用の入力待ち関数を追加します。

追加するコード: print_profile()の後、main()より前に追加します。(補助関数

// 追加: step実行時に、次の命令へ進むまで待つ
static void wait_for_step(void)
{
    int c;

    printf("step> ");
    fflush(stdout);

    do {
        c = getchar();
    } while (c != '\n' && c != EOF);

    printf("\n");
}

step>を表示したあと、Enterが押されるまでgetchar()で待ちます。Enter入力を受け取った後は、次のtrace表示との間に空行を入れて読みやすくしています。

第3回で端末を非カノニカルモードにしているため、入力はEnterまでまとめて待つ通常の端末動作とは少し違います。ただ、ここでは「Enterを押したら次へ進む」という用途に絞り、改行文字まで読み捨てる形にしています。

–stepオプションを受け取る

次に、main()の引数処理へ--stepを追加します。

第7回の時点では、--trace--profileを受け取れるようになっていました。

変更後のコード: main()の引数処理を、次の形に変更します。(main関数

int trace_enabled = 0;
int profile_enabled = 0;
int step_enabled = 0; // 追加するコード
const char *image_path = NULL;

for (int i = 1; i < argc; ++i) {
    if (strcmp(argv[i], "--trace") == 0) {
        trace_enabled = 1;
    } else if (strcmp(argv[i], "--profile") == 0) {
        profile_enabled = 1;
    // –stepオプションを受け取る:追加するコード
    } else if (strcmp(argv[i], "--step") == 0) {
        step_enabled = 1;
    } else if (argv[i][0] == '-') {
        fprintf(stderr, "unknown option: %s\n", argv[i]);
        fprintf(stderr, "usage: %s [--trace] [--profile] [--step] image.obj\n", argv[0]);
        return 2;
    } else if (image_path == NULL) {
        image_path = argv[i];
    } else {
        fprintf(stderr, "usage: %s [--trace] [--profile] [--step] image.obj\n", argv[0]);
        return 2;
    }
}

--stepを見つけたら、step_enabledを1にします。

usage表示にも[--step]を追加します。

step時はtraceも自動で有効にする

ステップ実行では、止まるだけではあまり意味がありません。どの命令で止まっているのかを一緒に見たいので、--stepが指定された場合はtrace_enabledも自動で有効にします。

追加するコード: 引数処理が終わり、image_path == NULLのチェックを済ませた後に追加(main関数

if (image_path == NULL) {
    fprintf(stderr, "usage: %s [--trace] [--profile] [--step] image.obj\n", argv[0]);
    return 2;
}

// step実行では、毎回止まる前に命令情報も見たいのでtraceを自動で有効にする
if (step_enabled) {
    trace_enabled = 1;
}

これで、次のように--stepだけを指定しても、命令情報とレジスタ情報が表示されます。

./lc3 --step examples/hello.obj

1命令ごとにEnterを待つ

最後に、実行ループの末尾でwait_for_step()を呼びます。

第7回の実行ループ末尾には、trace有効時にレジスタを表示する処理がありました。

if (trace_enabled) {
    trace_registers();
    if (running) {
        printf("\n");
    }
}

この直後に、step有効時だけ入力待ちを追加します。

追加するコード: trace_registers()の呼び出し後に追加します。(main関数

if (trace_enabled) {
    trace_registers();
    if (running) {
        printf("\n");
    }
}

// step有効時は、1命令実行するたびにEnter入力を待つ
if (step_enabled && running) {
    wait_for_step();
}

runningが0になった後は、もう次の命令へ進む必要がありません。そのため、TRAP HALTで停止した後にはstep>を出さないようにしています。

Hello Worldをステップ実行する

ビルドして、Hello Worldをステップ実行します。

cd lessons/08-step
make
./lc3 --step examples/hello.obj

最初にLEA命令が表示されます。

PC=3000 INSTR=E002 OP=LEA
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=0000 PC=3001 COND=P

step>

ここでEnterを押すと、次の命令へ進みます。

PC=3001 INSTR=F022 OP=TRAP PUTS
Hello, World!
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=3002 PC=3002 COND=P

step>

もう一度Enterを押すと、TRAP HALTまで進みます。

PC=3002 INSTR=F025 OP=TRAP HALT
HALT
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=3003 PC=3003 COND=P

この出力を見ると、Hello Worldは次の順番で動いていることが分かります。

  • x3000LEAで、文字列の先頭番地x3003R0へ入れる
  • x3001TRAP PUTSで、R0が指す文字列を表示する
  • x3002TRAP HALTで、VMを停止する

–trace、–profile、–stepを組み合わせる

第7回の引数処理を拡張しているので、--trace--profile--stepは組み合わせて使えます。

./lc3 --step examples/hello.obj
./lc3 --trace --step examples/hello.obj
./lc3 --profile --step examples/hello.obj

--stepを指定した場合は、--traceを書かなくてもtrace表示が自動で有効になります。

--profileも同時に指定すると、ステップ実行で1命令ずつ確認したあと、最後に命令数の集計も表示されます。

./lc3 --profile --step examples/hello.obj

PC=3000 INSTR=E002 OP=LEA
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=0000 PC=3001 COND=P

step> 
PC=3001 INSTR=F022 OP=TRAP PUTS
Hello, World!
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=3002 PC=3002 COND=P

step> 
PC=3002 INSTR=F025 OP=TRAP HALT
HALT
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=3003 PC=3003 COND=P

profile:
total instructions: 3

LEA             1
TRAP            2

2048やRogueで使うときの注意

今回の--stepは、Hello Worldのような短いLC-3プログラムを、数命令ずつ目で追うための機能です。2048.objrogue.objのようなゲームを普通に遊ぶためのモードではありません。

理由は2つあります。1つ目は、入力の扱いが分かりにくくなることです。ゲーム本体は、移動や操作のためにキーボード入力を読みます。一方、--stepもVM側でstep>を表示し、次の命令へ進むためにEnter入力を待ちます。

たとえば2048でwを押して盤面を動かしたい場面でも、その前後でVM側のstep>に対して何度もEnterを押す必要があります。同じ端末に「VMを進める入力」と「ゲームへ渡す入力」が並ぶため、今どちらの入力をしているのかが分かりにくくなります。

2つ目は、命令数が多いことです。ゲームは1画面を表示したり、1手分の処理を進めたりするだけでも、多くのLC-3命令を実行します。--stepでは1命令ごとに止まるため、ゲームの動きを少し見るだけでも大量のEnterが必要になります。

そのため、第8回ではHello Worldのような小さいプログラムを使って--stepを確認します。2048.objrogue.objでは、実行全体の傾向を見るなら--profile、流れをざっと追うなら--traceのほうが扱いやすいです。特定の場所だけで止めたい場合は、次回作るブレークポイント機能のほうが向いています。

今回の完成コード

ここまでの変更をまとめると、次の形になります。

完成コード: lc3.c の全文です。今回追加・変更した場所には、// 追加:// 変更:のコメントを付けています。実行確認は、このコードを入れ終わってから行います。

#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <string.h>
/* unix only */
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/termios.h>
#include <sys/mman.h>

enum
{
    R_R0 = 0,
    R_R1,
    R_R2,
    R_R3,
    R_R4,
    R_R5,
    R_R6,
    R_R7,
    R_PC, /* program counter */
    R_COND,
    R_COUNT
};
enum
{
    FL_POS = 1 << 0, /* P */
    FL_ZRO = 1 << 1, /* Z */
    FL_NEG = 1 << 2, /* N */
};
enum
{
    OP_BR = 0, /* branch */
    OP_ADD,    /* add */
    OP_LD,     /* load */
    OP_ST,     /* store */
    OP_JSR,    /* jump register */
    OP_AND,    /* bitwise and */
    OP_LDR,    /* load registar */
    OP_STR,    /* store registar */
    OP_RTI,    /* unused */
    OP_NOT,    /* bitwise not */
    OP_LDI,    /* load indirect */
    OP_STI,    /* store indirect */
    OP_JMP,    /* jump */
    OP_RES,    /* reserved (unused) */
    OP_LEA,    /* load effective address */
    OP_TRAP    /* execute trap */
};
enum
{
    MR_KBSR = 0xFE00,  /* keyboard status */
    MR_KBDR = 0xFE02   /* keyboard data */
};
enum
{
    TRAP_GETC = 0x20,  /* get character from keyboard, not echoed onto the terminal */
    TRAP_OUT = 0x21,   /* output a character */
    TRAP_PUTS = 0x22,  /* output a word string */
    TRAP_IN = 0x23,    /* get character from keyboard, echoed onto the terminal */
    TRAP_PUTSP = 0x24, /* output a byte string */
    TRAP_HALT = 0x25   /* halt the program */
};

#define MEMORY_MAX (1 << 16)
#define OPCODE_COUNT 16

uint16_t memory[MEMORY_MAX];  /* 65536 locations */
uint16_t reg[R_COUNT];

static uint64_t total_instructions;
static uint64_t op_counts[OPCODE_COUNT];

struct termios original_tio;

void disable_input_buffering(void)
{
    tcgetattr(STDIN_FILENO, &original_tio);
    struct termios new_tio = original_tio;
    new_tio.c_lflag &= ~ICANON & ~ECHO;
    tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
}

void restore_input_buffering(void)
{
    tcsetattr(STDIN_FILENO, TCSANOW, &original_tio);
}

uint16_t check_key(void)
{
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);

    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    return select(1, &readfds, NULL, NULL, &timeout) != 0;
}

void handle_interrupt(int signal)
{
    (void)signal;

    restore_input_buffering();
    printf("\n");
    exit(-2);
}

uint16_t sign_extend(uint16_t x, int bit_count)
{
    if ((x >> (bit_count - 1)) & 1) {
        x |= (0xFFFF << bit_count);
    }
    return x;
}

uint16_t swap16(uint16_t x)
{
    return (x << 8) | (x >> 8);
}

void update_flags(uint16_t r)
{
    if (reg[r] == 0)
    {
        reg[R_COND] = FL_ZRO;
    }
    else if (reg[r] >> 15) /* a 1 in the left-most bit indicates negative */
    {
        reg[R_COND] = FL_NEG;
    }
    else
    {
        reg[R_COND] = FL_POS;
    }
}

void read_image_file(FILE* file)
{
    /* the origin tells us where in memory to place the image */
    uint16_t origin;
    fread(&origin, sizeof(origin), 1, file);
    origin = swap16(origin);

    /* we know the maximum file size so we only need one fread */
    uint16_t max_read = MEMORY_MAX - origin;
    uint16_t* p = memory + origin;
    size_t read = fread(p, sizeof(uint16_t), max_read, file);

    /* swap to little endian */
    while (read-- > 0)
    {
        *p = swap16(*p);
        ++p;
    }
}

int read_image(const char* image_path)
{
    FILE* file = fopen(image_path, "rb");
    if (!file) { return 0; }
    read_image_file(file);
    fclose(file);
    return 1;
}

void mem_write(uint16_t address, uint16_t val)
{
    memory[address] = val;
}

uint16_t mem_read(uint16_t address)
{
    if (address == MR_KBSR)
    {
        if (check_key())
        {
            memory[MR_KBSR] = (1 << 15);
            memory[MR_KBDR] = getchar();
        }
        else
        {
            memory[MR_KBSR] = 0;
        }
    }
    return memory[address];
}

static const char *opcode_name(uint16_t op)
{
    switch (op) {
        case OP_BR: return "BR";
        case OP_ADD: return "ADD";
        case OP_LD: return "LD";
        case OP_ST: return "ST";
        case OP_JSR: return "JSR";
        case OP_AND: return "AND";
        case OP_LDR: return "LDR";
        case OP_STR: return "STR";
        case OP_RTI: return "RTI";
        case OP_NOT: return "NOT";
        case OP_LDI: return "LDI";
        case OP_STI: return "STI";
        case OP_JMP: return "JMP";
        case OP_RES: return "RES";
        case OP_LEA: return "LEA";
        case OP_TRAP: return "TRAP";
        default: return "UNKNOWN";
    }
}

static const char *trap_name(uint16_t trapvect)
{
    switch (trapvect) {
        case TRAP_GETC: return "GETC";
        case TRAP_OUT: return "OUT";
        case TRAP_PUTS: return "PUTS";
        case TRAP_IN: return "IN";
        case TRAP_PUTSP: return "PUTSP";
        case TRAP_HALT: return "HALT";
        default: return "UNKNOWN";
    }
}

static const char *cond_name(uint16_t cond)
{
    switch (cond) {
        case FL_POS: return "P";
        case FL_ZRO: return "Z";
        case FL_NEG: return "N";
        default: return "?";
    }
}

static void trace_instruction(uint16_t pc_before, uint16_t instr, uint16_t op)
{
    printf("PC=%04X INSTR=%04X OP=%s", pc_before, instr, opcode_name(op));

    if (op == OP_TRAP) {
        uint16_t trapvect = instr & 0xFF;
        printf(" %s", trap_name(trapvect));
    }

    printf("\n");
}

static void trace_registers(void)
{
    printf("R0=%04X R1=%04X R2=%04X R3=%04X ", reg[R_R0], reg[R_R1], reg[R_R2], reg[R_R3]);
    printf("R4=%04X R5=%04X R6=%04X R7=%04X ", reg[R_R4], reg[R_R5], reg[R_R6], reg[R_R7]);
    printf("PC=%04X COND=%s\n", reg[R_PC], cond_name(reg[R_COND]));
}

static void print_profile(void)
{
    printf("\nprofile:\n");
    printf("total instructions: %llu\n\n",
           (unsigned long long)total_instructions);

    for (uint16_t op = 0; op < OPCODE_COUNT; ++op) {
        if (op_counts[op] == 0) {
            continue;
        }

        printf("%-6s %10llu\n",
               opcode_name(op),
               (unsigned long long)op_counts[op]);
    }
}

// step実行時に、次の命令へ進むまで待つ:追加するコード
static void wait_for_step(void)
{
    int c;

    printf("step> ");
    fflush(stdout);

    do {
        c = getchar();
    } while (c != '\n' && c != EOF);

    printf("\n");
}

int main(int argc, const char* argv[])
{
    int trace_enabled = 0;
    int profile_enabled = 0;
    int step_enabled = 0; // 追加するコード
    const char *image_path = NULL;

    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--trace") == 0) {
            trace_enabled = 1;
        } else if (strcmp(argv[i], "--profile") == 0) {
            profile_enabled = 1;
        // –stepオプションを受け取る:追加するコード
        } else if (strcmp(argv[i], "--step") == 0) {
            step_enabled = 1;
        } else if (argv[i][0] == '-') {
            fprintf(stderr, "unknown option: %s\n", argv[i]);
            fprintf(stderr, "usage: %s [--trace] [--profile] [--step] image.obj\n", argv[0]);
            return 2;
        } else if (image_path == NULL) {
            image_path = argv[i];
        } else {
            fprintf(stderr, "usage: %s [--trace] [--profile] [--step] image.obj\n", argv[0]);
            return 2;
        }
    }

    if (image_path == NULL) {
        fprintf(stderr, "usage: %s [--trace] [--profile] [--step] image.obj\n", argv[0]);
        return 2;
    }

    // step実行では、毎回止まる前に命令情報も見たいのでtraceを自動で有効にする:追加するコード
    if (step_enabled) {
        trace_enabled = 1;
    }

    if (!read_image(image_path))
    {
        printf("failed to load image: %s\n", image_path);
        exit(1);
    }

    signal(SIGINT, handle_interrupt);
    disable_input_buffering();

    /* since exactly one condition flag should be set at any given time, set the Z flag */
    reg[R_COND] = FL_ZRO;

    /* set the PC to starting position */
    /* 0x3000 is the default */
    enum { PC_START = 0x3000 };
    reg[R_PC] = PC_START;

    int running = 1;
    while (running)
    {
        /* FETCH */
        uint16_t pc_before = reg[R_PC];

        uint16_t instr = mem_read(reg[R_PC]++);

        uint16_t op = instr >> 12;

        if (profile_enabled) {
            ++total_instructions;
            ++op_counts[op];
        }

        if (trace_enabled) {
            trace_instruction(pc_before, instr, op);
        }

        switch (op)
        {
            case OP_ADD:
                {
                    /* destination register (DR) */
                    uint16_t r0 = (instr >> 9) & 0x7;
                    /* first operand (SR1) */
                    uint16_t r1 = (instr >> 6) & 0x7;
                    /* whether we are in immediate mode */
                    uint16_t imm_flag = (instr >> 5) & 0x1;

                    if (imm_flag)
                    {
                        uint16_t imm5 = sign_extend(instr & 0x1F, 5);
                        reg[r0] = reg[r1] + imm5;
                    }
                    else
                    {
                        uint16_t r2 = instr & 0x7;
                        reg[r0] = reg[r1] + reg[r2];
                    }

                    update_flags(r0);
                }
                break;
            case OP_AND:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t r1 = (instr >> 6) & 0x7;
                    uint16_t imm_flag = (instr >> 5) & 0x1;

                    if (imm_flag)
                    {
                        uint16_t imm5 = sign_extend(instr & 0x1F, 5);
                        reg[r0] = reg[r1] & imm5;
                    }
                    else
                    {
                        uint16_t r2 = instr & 0x7;
                        reg[r0] = reg[r1] & reg[r2];
                    }
                    update_flags(r0);
                }
                break;
            case OP_NOT:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t r1 = (instr >> 6) & 0x7;

                    reg[r0] = ~reg[r1];
                    update_flags(r0);
                }
                break;
            case OP_BR:
                {
                    uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
                    uint16_t cond_flag = (instr >> 9) & 0x7;
                    if (cond_flag & reg[R_COND])
                    {
                        reg[R_PC] += pc_offset;
                    }
                }
                break;
            case OP_JMP:
                {
                    /* Also handles RET */
                    uint16_t r1 = (instr >> 6) & 0x7;
                    reg[R_PC] = reg[r1];
                }
                break;
            case OP_JSR:
                {
                    uint16_t long_flag = (instr >> 11) & 1;
                    reg[R_R7] = reg[R_PC];
                    if (long_flag)
                    {
                        uint16_t long_pc_offset = sign_extend(instr & 0x7FF, 11);
                        reg[R_PC] += long_pc_offset; /* JSR */
                    }
                    else
                    {
                        uint16_t r1 = (instr >> 6) & 0x7;
                        reg[R_PC] = reg[r1]; /* JSRR */
                    }
                }
                break;
            case OP_LD:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
                    reg[r0] = mem_read(reg[R_PC] + pc_offset);
                    update_flags(r0);
                }
                break;
            case OP_LDI:
                {
                    /* destination registar (DR) */
                    uint16_t r0 = (instr >> 9) & 0x7;
                    /* PCoffset 9 */
                    uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
                    /* add pc_offset to the current PC, look at that memory location to get the final address */
                    reg[r0] = mem_read(mem_read(reg[R_PC] + pc_offset));
                    update_flags(r0);
                }
                break;
            case OP_LDR:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t r1 = (instr >> 6) & 0x7;
                    uint16_t offset = sign_extend(instr & 0x3F, 6);
                    reg[r0] = mem_read(reg[r1] + offset);
                    update_flags(r0);
                }
                break;
            case OP_LEA:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
                    reg[r0] = reg[R_PC] + pc_offset;
                    update_flags(r0);
                }
                break;
            case OP_ST:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
                    mem_write(reg[R_PC] + pc_offset, reg[r0]);
                }
                break;
            case OP_STI:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
                    mem_write(mem_read(reg[R_PC] + pc_offset), reg[r0]);
                }
                break;
            case OP_STR:
                {
                    uint16_t r0 = (instr >> 9) & 0x7;
                    uint16_t r1 = (instr >> 6) & 0x7;
                    uint16_t offset = sign_extend(instr & 0x3F, 6);
                    mem_write(reg[r1] + offset, reg[r0]);
                }
                break;
            case OP_TRAP:
                reg[R_R7] = reg[R_PC];

                switch (instr & 0xFF)
                {
                    case TRAP_GETC:
                        /* read a single ASCII char */
                        reg[R_R0] = (uint16_t)getchar();
                        update_flags(R_R0);
                        break;
                    case TRAP_OUT:
                        putc((char)reg[R_R0], stdout);
                        fflush(stdout);
                        break;
                    case TRAP_PUTS:
                        {
                            /* one char per word */
                            uint16_t* c = memory + reg[R_R0];
                            while (*c)
                            {
                                putc((char)*c, stdout);
                                ++c;
                            }
                            fflush(stdout);
                        }
                        break;
                    case TRAP_IN:
                        {
                            printf("Enter a character: ");
                            char c = getchar();
                            putc(c, stdout);
                            fflush(stdout);
                            reg[R_R0] = (uint16_t)c;
                            update_flags(R_R0);
                        }
                        break;
                    case TRAP_PUTSP:
                        {
                            /* one char per byte (two bytes per word)
                               here we need to swap back to
                               big endian format */
                            uint16_t* c = memory + reg[R_R0];
                            while (*c)
                            {
                                char char1 = (*c) & 0xFF;
                                putc(char1, stdout);
                                char char2 = (*c) >> 8;
                                if (char2) putc(char2, stdout);
                                ++c;
                            }
                            fflush(stdout);
                        }
                        break;
                    case TRAP_HALT:
                        puts("HALT");
                        fflush(stdout);
                        running = 0;
                        break;
                }
                break;
            case OP_RES:
            case OP_RTI:
            default:
                abort();
                break;
        }

        if (trace_enabled) {
            trace_registers();
            if (running) {
                printf("\n");
            }
        }

        // 追加:step有効時は、1命令実行するたびにEnter入力を待つ:追加するコード
        if (step_enabled && running) {
            wait_for_step();
        }
    }
    restore_input_buffering();

    if (profile_enabled) {
        print_profile();
    }

    return 0;
}

今回できたこと

今回は、LC-3 VMにステップ実行機能を追加しました。

  • --stepオプションを追加しました
  • step_enabledでステップ実行の有効/無効を切り替えました
  • wait_for_step()step>を表示し、Enter入力を待つようにしました
  • --step指定時はtrace表示も自動で出すようにしました
  • Hello Worldを1命令ずつ進め、PC、命令word、レジスタ、条件フラグを確認しました

ステップ実行があると、VMが命令をfetchし、decodeし、executeする流れを、自分のタイミングで止めながら確認できます。

次回はブレークポイントで指定番地で止める

ステップ実行では、すべての命令で止まります。小さいプログラムでは便利ですが、命令数が多いプログラムでは毎回Enterを押すのが大変です。

次回は、指定した番地に来たときだけ止まるブレークポイント機能を追加します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

目次