本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。
C言語で作るLC-3仮想マシン 第9回: ブレークポイントで指定番地で止める | UNIX Cafe

ブレークポイントで指定番地で止める
前回は、VMに--stepオプションを追加し、1命令実行するたびに停止してEnterで次へ進められるようにしました。
--stepは、Hello Worldのような短いLC-3プログラムを1命令ずつ確認するには便利です。一方で、少し命令数が増えると、見たい場所へたどり着くまでに何度もEnterを押す必要があります。
第9回では、VMに--breakオプションを追加します。--break x3002のようにLC-3の番地を指定し、その番地に到達したときだけ停止できるようにします。
GitHubではlc3-vm-cという名前で公開しています。
この記事の完成コードは、GitHubのlessons/09-breakpointに置いています。リポジトリ直下のlc3.cは、連載の進行に合わせて最新版へ更新していきます。
今回作るブレークポイント機能の全体像
今回追加する--breakは、指定したLC-3番地にPCが到達したときだけVMを止める機能です。
たとえば、Hello WorldのHALT命令がx3002に置かれている場合、次のように実行します。
./lc3 --break x3002 examples/hello.obj出力は次のような形を目指します。
breakpoint hit at x3002
PC=3002 INSTR=F025 OP=TRAP HALT
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=3002 PC=3002 COND=P
break>x3000のLEAとx3001のPUTSは普通に実行され、x3002のHALTに入る直前で止まります。
今回追加する主な機能は、以下の通りです。
break_enabled: ブレークポイントを有効にするフラグbreakpoint: 停止したいLC-3番地を保存する変数parse_address():x3002のような文字列を16bitアドレスへ変換する関数wait_for_break(): ブレークポイント到達時にEnter入力を待つ関数--break xADDRオプション: コマンドラインから停止番地を指定する引数
第9回で作業する場所は、VM本体の中では次の範囲です。
lc3.c
├─ 補助関数
│ ├─ parse_address() を追加
│ └─ wait_for_break() を追加
│
└─ main()
├─ break_enabled を追加
├─ breakpoint を追加
├─ --break オプションを追加
└─ fetch前にPCとbreakpointを比較するブレークポイントはPCと比較する
LC-3 VMでは、命令をfetchするときにPCを進めます。
uint16_t pc_before = reg[R_PC];
uint16_t instr = mem_read(reg[R_PC]++);
uint16_t op = instr >> 12;mem_read(reg[R_PC]++)を実行した後のreg[R_PC]は、すでに次の命令を指しています。
ブレークポイントで止めたいのは、「これから実行しようとしている命令の番地」です。そのため、判定はfetch前のreg[R_PC]で行うのが分かりやすいです。
while (running)
↓
PCがbreakpointと一致するか確認する
↓
一致したら状態を表示して待つ
↓
fetch
↓
executeこの順番にすると、--break x3002を指定したとき、x3002の命令を実行する直前で止まります。
アドレス文字列を数値に変換する
まず、x3002のような文字列をuint16_tのアドレスへ変換する関数を追加します。
LC-3の資料では、16進数の番地をx3000のように書くことが多いです。今回もこの表記を基本にします。
追加するコード: 表示用の補助関数opcode_name()より前に追加します。(補助関数)
// 追加: x3002 や 0x3002 のような文字列をLC-3アドレスへ変換する
static int parse_address(const char *s, uint16_t *out)
{
char *end;
unsigned long value;
if (s == NULL || *s == '\0') {
return 0;
}
if (s[0] == 'x' || s[0] == 'X') {
++s;
}
value = strtoul(s, &end, 16);
if (*end != '\0' || value > 0xFFFF) {
return 0;
}
*out = (uint16_t)value;
return 1;
}xまたはXで始まっている場合は、その1文字を飛ばしてから16進数として読みます。
strtoul()は、文字列を数値へ変換する標準ライブラリ関数です。第9回では複雑なCLIライブラリは使わず、この程度の小さい関数で十分です。
LC-3のアドレスは16bitなので、0xFFFFを超える値はエラーにします。
–breakオプションを受け取る
次に、main()の引数処理へ--breakを追加します。
第8回の時点では、--trace、--profile、--stepを受け取れるようになっていました。
変更後のコード: main()の引数処理を、次の形に変更します。(main関数)
// 変更:メイン関数にbreakオプションを追加
int trace_enabled = 0;
int profile_enabled = 0;
int step_enabled = 0;
int break_enabled = 0; // 追加: main()の引数処理
uint16_t breakpoint = 0; // 追加: main()の引数処理
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;
} else if (strcmp(argv[i], "--step") == 0) {
step_enabled = 1;
} else if (strcmp(argv[i], "--break") == 0) {
if (i + 1 >= argc || !parse_address(argv[i + 1], &breakpoint)) {
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
}
break_enabled = 1;
++i;
} else if (argv[i][0] == '-') {
fprintf(stderr, "unknown option: %s\n", argv[i]);
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
} else if (image_path == NULL) {
image_path = argv[i];
} else {
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
}
}--breakは、次の引数として停止したい番地を受け取ります。
./lc3 --break x3002 examples/hello.obj--breakの後に番地がない場合や、番地として読めない文字列が渡された場合は、usageを表示して終了します。
break用の入力待ちを追加する
ブレークポイントに到達したら、その場で状態を表示し、Enter入力を待つようにします。
第8回のwait_for_step()と似た形で、wait_for_break()を追加します。
追加するコード: wait_for_step()の次、main()関数の前に追加します。(補助関数)
// 追加: breakpoint到達時に、続行するまで待つ
static void wait_for_break(void)
{
int c;
printf("break> ");
fflush(stdout);
do {
c = getchar();
} while (c != '\n' && c != EOF);
printf("\n");
}今回は、Enterを押したら続行するだけにします。
将来的には、ここにcで続行、qで終了、rでレジスタ表示のような操作を追加できます。ただし、第9回では対話式デバッガには広げず、指定番地で止まることに絞ります。
ブレークポイントに到達したら止める
最後に、実行ループの先頭でreg[R_PC]とbreakpointを比較します。
追加するコード: while (running)の中で、命令をfetchする前に追加します。(main関数-while)
// 追加: 指定番地に到達したら、命令を実行する前に状態を表示して止める
if (break_enabled && reg[R_PC] == breakpoint) {
uint16_t pc = reg[R_PC];
uint16_t instr = mem_read(pc);
uint16_t op = instr >> 12;
printf("breakpoint hit at x%04X\n", pc);
trace_instruction(pc, instr, op);
trace_registers();
printf("\n");
wait_for_break();
}ここでは、命令を実行する前にmem_read(pc)で命令wordを読みます。
reg[R_PC]++は使いません。ブレークポイント表示のために命令を読むだけで、PCは進めないようにします。
表示には、第6回で作ったtrace_instruction()とtrace_registers()を再利用します。新しい表示関数を増やさず、これまで作った観察用の道具を組み合わせます。
Hello WorldのHALT直前で止める
ビルドして、Hello WorldのHALT直前で止めてみます。
cd lessons/09-break
make
./lc3 --break x3002 examples/hello.obj出力は次のようになります。
Hello, World!
breakpoint hit at x3002
PC=3002 INSTR=F025 OP=TRAP HALT
R0=3003 R1=0000 R2=0000 R3=0000 R4=0000 R5=0000 R6=0000 R7=3002 PC=3002 COND=P
break>この時点では、x3002のTRAP HALTはまだ実行されていません。Enterを押すと、次にHALTが実行されます。
HALTこの出力を見ると、Hello Worldは次の順番で動いていることが分かります。
x3000のLEAで、文字列の先頭番地x3003をR0へ入れるx3001のTRAP PUTSで、R0が指す文字列を表示するx3002のTRAP HALTに入る直前で、ブレークポイントに到達する- Enterを押すと、
TRAP HALTが実行されてVMが停止する
–trace、–profile、–breakを組み合わせる
第8回までに作ったオプションと、今回の--breakは組み合わせて使えます。
./lc3 --break x3002 examples/hello.obj
./lc3 --trace --break x3002 examples/hello.obj
./lc3 --profile --break x3002 examples/hello.obj--stepと--breakの使い分けは、次のように考えると分かりやすいです。
--step: 最初から1命令ずつ見たいとき--break: 見たい番地まで一気に進めたいとき--trace --break: そこまでの流れも表示しながら、指定番地で止めたいとき
--profileも同時に指定すると、ブレークポイントで途中停止しながら、最後に命令数の集計も表示できます。
2048やRogueで使うときの注意
--breakは、--stepよりも大きいLC-3プログラムで扱いやすい機能です。ただし、2048やRogueのようなゲームで使うには、停止したい番地を先に知っておく必要があります。
Hello Worldでは、命令がx3000、x3001、x3002に並んでいるため、止める番地を決めやすいです。一方で、ゲームのような大きいプログラムでは、どの番地にどの処理があるかを調べる必要があります。
そのため、第9回ではHello Worldを中心に確認します。第11回では、もう少し小さいループ付きのLC-3プログラムを用意し、--trace、--profile、--step、--breakを組み合わせてデバッグする流れを扱います。
今回の完成コード
ここまでの変更をまとめると、次の形になります。
完成コード: lc3.c の全文です。第9回で追加した主な場所には、// 追加:のコメントを付けています。実行確認は、このコードを入れ終わってから行います。
#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];
}
// 追加: x3002 や 0x3002 のような文字列をLC-3アドレスへ変換する
static int parse_address(const char *s, uint16_t *out)
{
char *end;
unsigned long value;
if (s == NULL || *s == '\0') {
return 0;
}
if (s[0] == 'x' || s[0] == 'X') {
++s;
}
if (*s == '\0') {
return 0;
}
value = strtoul(s, &end, 16);
if (*end != '\0' || value > 0xFFFF) {
return 0;
}
*out = (uint16_t)value;
return 1;
}
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]);
}
}
static void wait_for_step(void)
{
int c;
printf("step> ");
fflush(stdout);
do {
c = getchar();
} while (c != '\n' && c != EOF);
printf("\n");
}
// 追加: breakpoint到達時に、続行するまで待つ
static void wait_for_break(void)
{
int c;
printf("break> ");
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;
int break_enabled = 0; // 追加: --breakオプションが指定されたか
uint16_t breakpoint = 0; // 追加: 停止するLC-3アドレス
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;
} else if (strcmp(argv[i], "--step") == 0) {
step_enabled = 1;
// 追加: --breakの次の引数をブレークポイント番地として読む
} else if (strcmp(argv[i], "--break") == 0) {
if (i + 1 >= argc || !parse_address(argv[i + 1], &breakpoint)) {
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
}
break_enabled = 1;
++i;
} else if (argv[i][0] == '-') {
fprintf(stderr, "unknown option: %s\n", argv[i]);
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
} else if (image_path == NULL) {
image_path = argv[i];
} else {
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
}
}
if (image_path == NULL) {
fprintf(stderr, "usage: %s [--trace] [--profile] [--step] [--break xADDR] image.obj\n", argv[0]);
return 2;
}
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 */
// 追加: 指定番地に到達したら、命令を実行する前に状態を表示して止める
if (break_enabled && reg[R_PC] == breakpoint) {
uint16_t pc = reg[R_PC];
uint16_t instr = mem_read(pc);
uint16_t op = instr >> 12;
printf("breakpoint hit at x%04X\n", pc);
trace_instruction(pc, instr, op);
trace_registers();
printf("\n");
wait_for_break();
}
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");
}
}
if (step_enabled && running) {
wait_for_step();
}
}
restore_input_buffering();
if (profile_enabled) {
print_profile();
}
return 0;
}
まとめ
今回は、VMに--breakオプションを追加し、指定したLC-3番地で止まれるようにしました。
--break x3002で、指定した番地に到達したときだけ停止できるようになりました。- ブレークポイント判定はfetch前の
PCで行うと、停止位置を理解しやすくなります。 --stepより少ない操作で、見たい場所のレジスタやPCを確認できます。
--trace、--profile、--step、--breakがそろうと、VMの動きをいろいろな粒度で観察できるようになります。
次回はレジスタとメモリをダンプする
今回は、VMに--breakオプションを追加し、指定したLC-3番地に到達したときだけ停止できるようにしました。
次回は、プログラム終了後にレジスタ一覧や指定範囲のメモリを表示する--dump-registersと --dump-memoryを追加し、VM内部の状態をまとめて確認できるようにします。









