本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。
C言語で作るLC-3仮想マシン 第12回: 自作LC-3 VMをGitHubで公開できる形に整える | UNIX Cafe

自作LC-3 VMをGitHubで公開できる形に整える
前回は、第10回までに作った観察機能を使って、小さいLC-3プログラムの動きを読みました。
--traceで命令の流れを見て、--profileで命令ごとの実行回数を数え、--breakや--dump-memoryでループ中の状態やメモリ配置を確認しました。
今回は最終回として、ここまで作ってきたLC-3 VMをGitHubで読める形、試せる形に整えます。新しい命令やデバッグ機能は追加しません。README、Makefile、サンプル、実行例をそろえ、読者が手元にコピーして同じように動かせる状態にします。
GitHubではlc3-vm-cという名前で公開しています。この記事では、GitHub上のファイル置き場を見ながら、何を置いておくと初めて読む人が試しやすいかを見ていきます。
今回はGitHubで試しやすくする
第12回では、VM本体のコードには触りません。今回は、GitHubで見たときに分かりやすく、手元でも試しやすい環境を整えます。
これまでの記事では、毎回lc3.cに機能を追加したり、LC-3の小さいプログラムを書いたりしてきました。今回は、作ったものを他の人が見つけやすく、動かしやすくするために、説明とファイルの置き方を整えます。
README.md: GitHubで最初に読む説明ページですMakefile:makeでVMを作れるようにするファイルです.gitignore: 自動でできるファイルや外から取ってくるファイルを、Gitに入れないようにしますprograms/: LC-3の.objファイルを置く場所ですscripts/: サンプル取得用の補助スクリプトを置きますlessons/: 記事ごとのコードを置きますLICENSEとATTRIBUTION.md: ライセンスと出典をまとめます
今回の整理は地味な作業ですが、読者が「どのファイルを見ればよいのか」「どのコマンドを実行すればよいのか」で迷わないために、必要な作業だと思うので整理しておきます。
ファイルの置き方を見る
現在のGitHub上のファイルは、次のような構成にしています。
lc3-vm-c/
lc3.c
Makefile
README.md
LICENSE
ATTRIBUTION.md
lc3as-lab/
lessons/
programs/
scripts/lc3.cは、連載で作ってきたLC-3 VM本体です。Makefileは、VMをlc3という名前の実行ファイルにするためのファイルです。README.mdは、GitHubで最初に表示される説明です。
lc3as-lab/は、第4回と第5回で扱ったHello World用のアセンブリと小さいアセンブラの場所です。lessons/には、記事ごとのコードを置いています。ルートのlc3.cは連載の進行に合わせて変わりますが、lessons/を見ると各回の状態に戻れます。
programs/は、外部から取得したLC-3の.objファイルを置く場所です。scripts/には、そのサンプルを取得するためのスクリプトを置いています。
READMEに入口を作る
GitHubで公開する場合、最初に読まれるのはほとんどの場合README.mdです。ここに、このファイル一式が何なのか、何が必要なのか、どう動かすのかをまとめます。
このGitHubのページでは、READMEの先頭に短い説明とバッジを置いています。
# LC-3 VM in C
[](https://en.wikipedia.org/wiki/C_(programming_language))



LC-3 educational computer virtual machine written in C.その下には、ブログ連載へのリンクを並べています。記事からGitHubへ来た読者も、GitHubから記事へ来た読者も、対応する回へ戻れるようにするためです。
READMEには、長い説明を全部入れる必要はありません。まずは、次の内容があれば十分です。
- このGitHubの置き場所が何をするものか
- どの記事と対応しているか
- 何が必要か
- どうやって
lc3を作るか - どう実行するか
- 詰まったときに何を確認するか
最初から完璧な説明を目指すより、読者が最初のmakeと最初の実行まで進めることを優先します。
Makefileで作り方をそろえる
次に、Makefileです。連載中は、記事によって実行ファイル名がmainだったりlc3だったりしました。GitHubで試すときは読者が迷わないように、実行ファイル名をlc3にそろえます。
今回置いているMakefileは次のようにしています。
CC ?= cc
CFLAGS ?= -Wall -Wextra -pedantic -std=c99
lc3: lc3.c
$(CC) $(CFLAGS) lc3.c -o lc3
.PHONY: clean
clean:
rm -f lc3これで、読者はGitHubから取ってきたフォルダの中でmakeを実行するだけで、VMを作れます。
makeCC ?= ccとしているので、必要なら使うCコンパイラを変えられます。たとえばclangを明示したい場合は、次のように実行できます。
make CC=clang作った実行ファイルを消したい場合は、cleanを使います。
make clean自動でできるファイルをGitに入れない
makeを実行すると、lc3という実行ファイルができます。サンプルを取得すると、programs/に.objファイルが入ります。これらは、後から作ったり取得したりできるファイルなので、基本的にはGitに入れません。
そのため、.gitignoreに次のような設定を入れています。
lc3
main
*.o
*.dSYM/
programs/*.obj
lc3as-lab/build/
lc3as-lab/src/*.obj
lc3as-lab/src/*.sym
articles/
.DS_Storeここで大事なのは、自分で書いたファイルと、あとから作られるファイルを分けることです。lc3.cやMakefileはGitに入れます。一方、makeでできるlc3や、外から取ってくるprograms/*.objは入れません。
ただし、第11回で使った小さいloop.objのように、記事を同じように試すために必要なサンプルはlessons/11-debug-example/examples/に置いています。外から取ってくるサンプルと、記事用の小さいサンプルは扱いを分けています。
外部サンプルを取得できるようにする
第3回では、元チュートリアルの2048.objやrogue.objを動かしました。これらは便利な確認用プログラムですが、このGitHubには直接入れず、必要な人があとから取得する形にしています。
取得用のスクリプトはscripts/download-programs.shです。
#!/bin/sh
set -eu
mkdir -p programs
curl -L -o programs/2048.obj https://www.jmeiners.com/lc3-vm/supplies/2048.obj
curl -L -o programs/rogue.obj https://www.jmeiners.com/lc3-vm/supplies/rogue.obj
echo "Downloaded programs/2048.obj and programs/rogue.obj"使うときは、GitHubから取ってきたフォルダの中で次のように実行します。
./scripts/download-programs.sh取得後は、次のようなファイルができます。
programs/
2048.obj
rogue.obj外から取ってくるファイルは、どこから取ってきたものかを分かるようにしておきます。自分で書いたコードと、外部のサンプルを分けておくと、あとから見返したときにも分かりやすくなります。
lessonsで記事ごとの状態を残す
この連載では、記事が進むたびにlc3.cが変わります。最新のlc3.cだけを置いておくと、過去記事を読んでいる読者が、その回のコードを確認しづらくなります。
そこで、lessons/に記事ごとのコードを置いています。
lessons/
01-skeleton/
02-instruction-set/
03-trap-terminal-games/
06-trace/
07-profile/
08-step/
09-breakpoint/
10-dump/
11-debug-example/
12-release/たとえば、第7回の記事を読んでいる場合はlessons/07-profile/を見ます。第11回の小さいループプログラムを確認したい場合は、lessons/11-debug-example/を見ます。
第4回と第5回で扱ったアセンブリと小さいアセンブラは、lc3as-lab/にまとめています。ここはVM本体というより、LC-3の.objを作る側を見るための実験場所です。
最小プログラムで動作を確認する
整理したら、まずは最小のプログラムでVMが動くことを確認します。ここでは、停止するだけのhalt.objを使います。
programs/*.objはGitに入れないようにしているので、手元にコピーした直後にはprograms/halt.objは入っていません。確認用に、次のコマンドで最小の.objファイルを作ります。
mkdir -p programs
printf '\x30\x00\xf0\x25' > programs/halt.objこの4byteは、開始番地x3000とTRAP HALT命令を並べたものです。
make
./lc3 programs/halt.obj出力は次のようになります。
HALTこれで、VMが.objを読み込み、TRAP HALTを実行して停止できることを確認できます。大きいプログラムを動かす前に、まずこのような小さい確認を通しておくと、問題の切り分けがしやすくなります。
.objファイルはバイナリファイルです。中身を見たい場合は、xxdを使えます。
xxd programs/halt.objLC-3の.objでは、先頭の16bitが配置先の開始番地です。たとえば3000なら、プログラムはLC-3メモリのx3000から読み込まれます。
loop.objで観察機能を確認する
次に、第11回で使ったloop.objを使って、公開版のVMでも観察機能が動くことを確認します。
./lc3 lessons/11-debug-example/examples/loop.obj通常実行では、Hiが3回表示されてから停止します。
Hi
Hi
Hi
HALT--profileを付けると、命令ごとの実行回数を確認できます。
./lc3 --profile lessons/11-debug-example/examples/loop.objHi
Hi
Hi
HALT
profile:
total instructions: 15
BR 3
ADD 4
AND 1
LEA 3
TRAP 4--dump-memoryを使うと、命令と文字列がメモリ上にどう並んでいるかを確認できます。
./lc3 --dump-memory x3000 12 lessons/11-debug-example/examples/loop.objHi
Hi
Hi
HALT
memory x3000..x300B:
x3000: 5260 1263 E004 F022 127F 03FC F025 0048
x3008: 0069 000A 0000 0000この出力は、第11回で読んだ内容と同じです。GitHubに置いているルートのlc3.cでも、trace、profile、break、dumpの機能を組み合わせて使えることを確認できます。
2048やRogueを動かす
小さいサンプルで確認できたら、元チュートリアル由来の大きいLC-3プログラムも動かせます。
./scripts/download-programs.sh
./lc3 programs/2048.obj2048では、最初にANSI端末かどうかを聞かれます。
Are you on an ANSI terminal (y/n)?通常のターミナルであれば、yと答えて進めます。操作はW、A、S、Dです。
W up
A left
S down
D rightRogueも同じように実行できます。
./lc3 programs/rogue.objここでは、ゲームの中身を詳しく説明するより、VMがLC-3の命令、TRAP、端末入力、画面出力を使って大きめのプログラムも実行できることを確認します。
ライセンスと出典を明記する
公開するときは、コード本体だけでなく、ライセンスと出典も置いておきます。
LICENSE: このコードのライセンスですATTRIBUTION.md: 元にしたチュートリアルや参考資料への出典です
このGitHubの置き場所では、コード本体にはMIT Licenseを置き、元チュートリアルへの出典はATTRIBUTION.mdにまとめています。
今回のVMは、Justin Meiners氏らによる「Write your Own Virtual Machine」を参考にしています。外部サンプルである2048.objやrogue.objも、そのチュートリアルのsuppliesから取得します。そのため、READMEやATTRIBUTION.mdに参照先を明記しておきます。
トラブルシューティングをREADMEに入れる
初心者向けに公開する場合は、うまく動かないときの確認場所もREADMEに入れておくと親切です。
このVMでは、キーボード入力を扱うためにターミナルの設定を一時的に変えます。途中で止めた場合、入力した文字が見えないなど、ターミナルの表示がいつもと違う状態になることがあります。その場合は、次のコマンドで戻せます。
resetほかにも、READMEには次のような確認ポイントを書いています。
.objファイルのパスが正しいか.objを読むときのbyte順を正しく変換しているか- 実行しようとしている命令をVMに実装しているか
LEAやBRの移動先を、命令を読んだあとのPCから計算しているか
全部の問題をREADMEだけで解決する必要はありません。ただ、最初に詰まりやすい場所を書いておくと、読者が自分で確認しやすくなります。
連載全体を振り返る
最後に、ここまでの流れを振り返ります。
- 第1回から第3回では、LC-3 VM本体を作りました
- 第4回から第5回では、LC-3アセンブリを書き、
.objを作る側を見ました - 第6回から第10回では、VMの中を観察する道具を作りました
- 第11回では、小さいLC-3プログラムを実際に読みました
- 第12回では、GitHubで試せる形に整えました
最初は、メモリ配列、レジスタ配列、PC、命令を1つ読む処理だけの小さいプログラムでした。そこに、計算や分岐の命令、文字表示、キーボード入力、アセンブリ、簡単なアセンブラ、trace、profile、step、break、dumpを少しずつ足してきました。
Cの配列、bit演算、switch、ファイル読み込み、ターミナルの入出力だけでも、CPUが命令を読んで実行する流れをかなり近くで見ることができます。
この連載で作ったVMは、高速に動かすための実用VMではありません。目的は、LC-3の命令、PC、レジスタ、メモリ、条件フラグ、TRAPがどのようにつながっているかを、自分で動かしながら確認することです。
今回のまとめ
第12回では、新しいVM機能ではなく、GitHubで見つけやすく、手元で試しやすくするための準備をしました。
README.mdに最初に読む説明を置きましたMakefileでmakeだけでVMを作れるようにしました.gitignoreで自動でできるファイルと外部サンプルをGitに入れないようにしましたscripts/download-programs.shで2048やRogueを取得できるようにしましたlessons/で記事ごとのコードを残しましたLICENSEとATTRIBUTION.mdでライセンスと出典を明記しました
これで、LC-3 VMを作り、アセンブリを書き、動きを確認し、GitHubから試せる形にするところまでを一通り見てきました。
小さいVMを自分で作ると、普段は見えない「命令を読み、レジスタを更新し、メモリを読み書きし、条件によって分岐する」という流れが見えるようになります。LC-3は小さい教材用コンピューターですが、その小ささのおかげで、コンピューターの基本的な動きを手元で追える題材になります。
第1回から読み直す
LC-3 VMを最初から作る流れを確認したい場合は、こちらから読み直してください。第1回では、メモリ、レジスタ、PC、.objローダーを用意し、TRAP HALTで停止する最小のVMを作ります。








