非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

アセンブラ(2)

X68000 用のプログラムでスーパーバイザーモードの中にコンパイラが出力するアセンブリ言語のプログラムを書くのが目的なので、少しやり方を変えます。スタックの状態がよくわからないのが問題の一つと考えられます。まずそれを調べるために以下のようなことを ChatGPT で調べてみました。

GCC の 68000 向けの C++ コンパイラが出力する 68000 のアセンブリ言語を解釈して、スタックの状態を確認するための簡易的なインタープリターを作ってください

と入力すると以下のような結果が返ってきました。

GCC が 68000 向けに出力するアセンブリコードを扱うための「簡易インタープリター(スタックの状態を追跡する)」を C++ で作るサンプルを以下に示します。

🎯 目的

  • `move`, `add`, `sub`, `link`, `unlk`, `jsr`, `rts` などの命令を解釈
  • スタックポインタ (`A7`) とスタック領域をシミュレート
  • 命令の実行ごとにスタックの状態を出力

📘 制約と前提

  • 実行可能な機械語ではなく、アセンブリテキストを読み取る
  • データとアドレスレジスタは D0–D7, A0–A7
  • スタックは A7 のみを使う(他の A レジスタは省略)
  • スタックは上位アドレスから下位へ伸びる(68000 仕様)

🧪 例に対応するアセンブリコード例

link    a6,#-8
move.l  d0,-(a7)
add.l   #4,d0
move.l  (a7)+,d1
unlk    a6
rts

🛠️ 簡易インタープリターの C++ コード

#include <iostream>
#include <sstream>
#include <map>
#include <vector>
#include <iomanip>

using namespace std;

using u32 = uint32_t;

struct CPU {
    map<string, u32> D;   // D0-D7
    map<string, u32> A;   // A0-A7
    vector<u32> stack;    // スタック領域(32ビット値)
    u32 baseSP = 0x1000;  // スタックベースアドレス

    CPU() {
        for (int i = 0; i < 8; ++i) {
            D["d" + to_string(i)] = 0;
            A["a" + to_string(i)] = 0;
        }
        A["a7"] = baseSP; // スタックポインタの初期化
        stack.resize(1024, 0); // スタックは 4KB 分確保
    }

    // SP に基づくアドレスインデックス
    int spIndex() {
        return (baseSP - A["a7"]) / 4;
    }

    void push(u32 val) {
        A["a7"] -= 4;
        stack[spIndex()] = val;
    }

    u32 pop() {
        u32 val = stack[spIndex()];
        A["a7"] += 4;
        return val;
    }

    void dumpStack() {
        cout << "SP = $" << hex << A["a7"] << dec << endl;
        for (int i = spIndex(); i < (int)stack.size(); ++i) {
            cout << "  [$" << hex << (baseSP - i * 4) << "] = $" << stack[i] << dec << endl;
        }
    }
};

void interpret(const vector<string>& lines) {
    CPU cpu;

    for (const string& line : lines) {
        cout << "> " << line << endl;

        istringstream iss(line);
        string op;
        iss >> op;

        if (op == "link") {
            string reg, offset;
            iss >> reg >> offset;
            reg.pop_back(); // remove comma
            int disp = stoi(offset);
            cpu.push(cpu.A[reg]); // save old frame pointer
            cpu.A[reg] = cpu.A["a7"];
            cpu.A["a7"] += disp; // allocate space
        }
        else if (op == "unlk") {
            string reg;
            iss >> reg;
            cpu.A["a7"] = cpu.A[reg];
            cpu.A[reg] = cpu.pop();
        }
        else if (op == "move.l") {
            string src, dst;
            iss >> src >> dst;
            if (src == "d0" && dst == "-(a7)") {
                cpu.push(cpu.D["d0"]);
            }
            else if (src == "(a7)+" && dst == "d1") {
                cpu.D["d1"] = cpu.pop();
            }
        }
        else if (op == "add.l") {
            string imm, dst;
            iss >> imm >> dst;
            if (imm[0] == '#') {
                int val = stoi(imm.substr(1));
                cpu.D[dst] += val;
            }
        }
        else if (op == "rts") {
            cout << "Return from subroutine" << endl;
        }
        else if (op == "jsr") {
            cout << "Jump to subroutine (not simulated)" << endl;
        }

        cpu.dumpStack();
        cout << endl;
    }
}

🧪 使用例

int main() {
    vector<string> asmCode = {
        "link    a6,#-8",
        "move.l  d0,-(a7)",
        "add.l   #4,d0",
        "move.l  (a7)+,d1",
        "unlk    a6",
        "rts"
    };
    interpret(asmCode);
    return 0;
}

✅ 実行結果の一例(概略)

> link    a6,#-8
SP = $0ffc
  [$0ffc] = $1000

> move.l  d0,-(a7)
SP = $0ff8
  [$0ffc] = $1000
  [$0ff8] = $0

> add.l   #4,d0
SP = $0ff8
  [$0ffc] = $1000
  [$0ff8] = $0

> move.l  (a7)+,d1
SP = $0ffc
  [$0ffc] = $1000

> unlk    a6
SP = $1000

> rts
Return from subroutine

🔧 今後の拡張

  • `pea`, `jsr`, `movem`, `lea` などへの対応
  • メモリ領域のエミュレート
  • レジスタのダンプ出力
  • シンボル付きデバッグ出力(ラベル・関数名対応)

ご希望であれば、具体的な `.s` ファイルの解析や命令セットの追加にも対応できます。