File:  [Isaki's NoNo m68k/m88k emulator] / nono / util / runx / runx.cpp
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:36 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, v022, HEAD
nono 1.2.0

// vi:set ts=4:

//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//

//
// Human68k の実行ファイルを NetBSD/m68k 上で実行する。
//

// メモリマップ
// 0000'0000 ベクタ
// 0000'1000 (4KB/ページの時の mmap 開始アドレス)
// 0000'2000 (8KB/ページの時の mmap 開始アドレス)
// 0000'2000 初期 A7 保存 (func_t から戻るため)
// 0000'2200 エントリポイント
// 0000'c000 コマンドライン引数
// 0001'ff00 SSP 初期値
// 0001'ff00 PSP (プロセスエントリ)
// 0002'0000 ロードアドレス

#include "runx.h"
#include <fcntl.h>
#include <inttypes.h>
#include <libgen.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>

// ぽかよけ
#if !defined(__m68k__)
#error "This file must be compiled on m68k!"
#endif

#define LOAD_ADDR	(0x20000)

typedef int (*func_t)();

// .X ファイルのヘッダ構造
struct XFileHeader {
	uint8  magic[2];		// +00 識別子 'HU'
	uint8  unused1;
	uint8  loadmode;		// +03 ロードモード
	uint32 base_addr;		// +04 ベースアドレス
	uint32 exec_addr;		// +08 実行開始アドレス
	uint32 text_size;		// +0c テキストサイズ
	uint32 data_size;		// +10 データサイズ
	uint32 bss_size;		// +14 BSS サイズ
	uint32 reloc_size;		// +18 再配置テーブルサイズ
	uint32 symbol_size;		// +1c シンボルサイズ
	uint32 scdline_size;	// +20 SCD 行番号テーブルサイズ
	uint32 scdsym_size;		// +24 SCD シンボルテーブルサイズ
	uint32 scdstr_size;		// +28 SCD 文字列テーブルサイズ
	uint32 unused2[4];
	uint32 module_offset;	// +3c モジュールリストの位置
} __packed;

static void usage();
static void init_mem();
static uint32 loadfile();
static uint32 loadfileX();
static uint32 loadfileR();
static void dumpwait(int);
static void dumpreg(const struct reg *);
static int  run(uint32);
static int  run_parent(int, int);

int opt_debug;
int opt_trace;
struct reg_emul emul;

static const char *filename;
static uint32 last_addr;
static uint8 *mem;
static uint8 *page0;
static uint32 pgsize;
static uint32 ret_addr;
static char human68k_arg[255];

int
main(int ac, char *av[])
{
	int c;

	while ((c = getopt(ac, av, "dt")) != -1) {
		switch (c) {
		 case 'd':
			opt_debug++;
			break;
		 case 't':
			opt_trace = 1;
			break;
		 default:
			usage();
		}
	}
	ac -= optind;
	av += optind;

	if (ac < 1) {
		usage();
	}

	// Human68k コマンド名と引数。
	filename = av[0];
	human68k_arg[0] = '\0';
	for (int i = 1; i < ac; i++) {
		if (i != 1) {
			strlcat(human68k_arg, " ", sizeof(human68k_arg));
		}
		strlcat(human68k_arg, av[i], sizeof(human68k_arg));
	}
	DEBUG(1, "human68k_arg=\"%s\"", human68k_arg);

	// 0番地を含む1ページは mmap(2) とかでも割り当て出来ないようなので
	// 仕方ないので自前で用意する。
	pgsize = getpgsize();
	page0 = (uint8 *)calloc(1, pgsize);
	if (page0 == NULL) {
		err(1, "calloc failed");
	}

	// mem は pgsize 番地以降 12MB までを確保しておく。
	// このためにこの実行ファイル自身は 16MB 以降にロードしてある。
	mem = (uint8 *)mmap((void *)pgsize, 12*1024*1024 - pgsize,
		PROT_READ | PROT_WRITE,
		MAP_FIXED | MAP_ANON | MAP_SHARED | MAP_INHERIT, -1, 0);
	if (mem == MAP_FAILED) {
		err(1, "mmap");
	}
	DEBUG(2, "mem=%p", mem);
	init_mem();
	init_doscall();

	// ロード。
	uint32 exec_addr = loadfile();
	if (exec_addr == (uint32)-1) {
		exit(1);
	}

	// 実行。
	if (run(exec_addr) < 0) {
		exit(1);
	}

	return 0;
}

static void
usage()
{
	fprintf(stderr, "%s <Human68k executable> [<argument...>]\n",
		getprogname());
	exit(1);
}

// メモリの初期化。何をどこまでやるか。
static void
init_mem()
{
	writemem1(0xcbc, 3);	// とりあえず
	writemem1(0xcbd, 0xff);
}

// 実行ファイルをロードする。
static uint32
loadfile()
{
	const char *e;

	// Support only .X and .R executable for now.
	e = strrchr(filename, '.');
	if (e != NULL && strcasecmp(e, ".x") == 0) {
		return loadfileX();
	} else {
		return loadfileR();
	}
}

// .X ファイルを読み込んで、リロケートまで行う。
static uint32
loadfileX()
{
	struct stat st;
	struct XFileHeader *xhdr;
	uint8 *file;
	int fd;

	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		err(1, "%s", filename);
	}

	if (fstat(fd, &st) < 0) {
		err(1, "%s: stat failed", filename);
	}
	DEBUG(1, "loading %s %u bytes", filename, (uint)st.st_size);

	file = (uint8 *)mmap(NULL, st.st_size, PROT_READ, MAP_FILE | MAP_SHARED,
		fd, 0);
	if (file == MAP_FAILED) {
		err(1, "%s: mmap failed", filename);
	}

	xhdr = (struct XFileHeader *)file;
	if (xhdr->magic[0] != 'H' || xhdr->magic[1] != 'U') {
		errx(1, "%s: is not .X file", filename);
	}

	uint32 base_addr = be32toh(xhdr->base_addr);
	uint32 exec_addr = be32toh(xhdr->exec_addr);
	uint32 text_size = be32toh(xhdr->text_size);
	uint32 data_size = be32toh(xhdr->data_size);
	uint32 bss_size  = be32toh(xhdr->bss_size);
	DEBUG(1, "base=$%x exec=$%x (text=$%x data=$%x bss=$%x)",
		base_addr, exec_addr, text_size, data_size, bss_size);

	uint32 reloc_size = be32toh(xhdr->reloc_size);

	uint32 load_addr = LOAD_ADDR;
	uint32 load_size = text_size + data_size;
	last_addr = load_addr + load_size + bss_size;

	if (load_addr < pgsize) {
		for (int i = 0; i < load_size; i++) {
			writemem1(load_addr + i, file[sizeof(XFileHeader) + i]);
		}
	} else {
		memcpy((void *)load_addr, &file[sizeof(XFileHeader)], load_size);
	}

	// 再配置テーブルの file での位置。
	uint32 reloc_pos = sizeof(XFileHeader) + load_size;

	// 再配置。
	uint32 offset = load_addr - base_addr;
	uint32 reloc_end = reloc_pos + reloc_size;
	uint32 A = load_addr;
	uint32 B = base_addr;
	uint32 C = A - B;
	while (reloc_pos < reloc_end) {
		uint32 D;
		D = be16toh(*(const uint16 *)&file[reloc_pos]);
		DEBUG(3, "%s: D=%x", __func__, D);
		reloc_pos += 2;
		if (D == 1) {
			D = be32toh(*(const uint32 *)&file[reloc_pos]);
			DEBUG(3, "%s: odd, D=%x", __func__, D);
			reloc_pos += 4;
		}
		if ((D & 1) == 0) {
			A += D;
			uint32 old = readmem4(A);
			writemem4(A, old + C);
			DEBUG(3, "%s: Write_L A=%x, old=%x new=%x", __func__,
				A, old, old + C);
		} else {
			A += D - 1;
			uint32 old = readmem2(A);
			writemem2(A, old + C);
			DEBUG(3, "%s: Write_W A=%x, old=%x new=%x", __func__,
				A, old, old + C);
		}
	}
	munmap(file, st.st_size);
	close(fd);

	exec_addr += offset;
	DEBUG(1, ".x format, base_addr=%08x, exec_addr=%08x", base_addr, exec_addr);
	return exec_addr;
}

// .R ファイルを読み込む。
static uint32
loadfileR()
{
	struct stat st;
	int fd;

	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		err(1, "%s", filename);
	}

	if (fstat(fd, &st) < 0) {
		err(1, "%s: stat failed", filename);
	}
	DEBUG(1, "loading %s %u bytes", filename, (uint)st.st_size);

	// XXX TODO
	uint32 load_addr = LOAD_ADDR;
	int n = read(fd, mem + load_addr, st.st_size);
	if (n < 0) {
		err(1, "%s", filename);
	}
	if (n < st.st_size) {
		errx(1, "%s: read too short", filename);
	}
	close(fd);

	DEBUG(1, ".r format, exec_addr=%08x", load_addr);
	return load_addr;
}

uint32
readmem1(uint32 addr)
{
	if (addr < pgsize) {
		return page0[addr];
	} else {
		return mem[addr - pgsize];
	}
}

uint32
readmem2(uint32 addr)
{
	uint32 data = (readmem1(addr) << 8) | readmem1(addr + 1);
	return data;
}

uint32
readmem4(uint32 addr)
{
	uint32 data = (readmem2(addr) << 16) | readmem2(addr + 2);
	return data;
}

void
writemem1(uint32 addr, uint32 data)
{
	if (addr < pgsize) {
		page0[addr] = data;
	} else {
		mem[addr - pgsize] = data;
	}
}

void
writemem2(uint32 addr, uint32 data)
{
	writemem1(addr,     data >> 8);
	writemem1(addr + 1, data);
}

void
writemem4(uint32 addr, uint32 data)
{
	writemem2(addr,     data >> 16);
	writemem2(addr + 2, data);
}

// wait(2) status のダンプ表示。
static void
dumpwait(int status)
{
	printf("wait status=0x%x", status);
	if (WIFEXITED(status)) {
		printf(" EXITED status=0x%x\n", WEXITSTATUS(status));
	}
	if (WIFSIGNALED(status)) {
		printf(" SIGNAL %d", WTERMSIG(status));
		if (WCOREDUMP(status)) {
			printf(" coredump");
		}
		printf("\n");
	}
	if (WIFSTOPPED(status)) {
		int signo = WSTOPSIG(status);
		printf(" STOPPED by %s\n", signame(signo));
	}
}

static void
dumpreg(const struct reg *reg)
{
	printf("D0:%08x D4:%08x  A0:%08x A4:%08x  SR:%04x\n",
		RegD(0), RegD(4), RegA(0), RegA(4), GetSR);
	printf("D1:%08x D5:%08x  A1:%08x A5:%08x\n",
		RegD(1), RegD(5), RegA(1), RegA(5));
	printf("D2:%08x D6:%08x  A2:%08x A6:%08x\n",
		RegD(2), RegD(6), RegA(2), RegA(6));
	printf("D3:%08x D7:%08x  A3:%08x A7:%08x\n",
		RegD(3), RegD(7), RegA(3), RegA(7));
}

static int
run(uint32 exec_addr)
{
	int fd[2];
	int r;
	pid_t pid;
	uint32 addr;

#define Write2(d)	do {	\
	writemem2(addr, (d));	\
	addr += 2;	\
} while (0)
#define Write4(d)	do {	\
	writemem4(addr, (d));	\
	addr += 4;	\
} while (0)

	// コマンドライン文字列を書き込む。LASCII 形式。
	addr = 0xc001;
	for (int i = 0; i < 255; i++) {
		uint8 c = (uint8)human68k_arg[i];
		writemem1(addr++, c);
		if (c == '\0') {
			writemem1(0xc000, i);
			break;
		}
	}

	// PSP (Process Entry) を書き込む。
	// PSP はロードアドレス-256 の位置でなければならないようだ。
	uint32 psp_base = LOAD_ADDR - 0x100;
	uint32 ram_size = 10 * 1024 * 1024;
	uint32 sp_backup = 0x2000;
	uint32 boot_addr = 0x2200;
	addr = psp_base;
	Write4(-1);	// 1つ前のメモリ管理ポインタ
	Write4(-1);	// このメモリを確保したプロセスのメモリ管理ポインタ
	Write4(ram_size - 4 + 1);	// このメモリブロックの終わり+1のアドレス
	Write4(-1);	// 次のメモリ管理ポインタ

	addr = psp_base + 0x20;
	Write4(0xc000);	// コマンドライン

	addr = boot_addr;
	Write2(0x23cf);		// move.l	a7,sp_backup	// スタックを保存
	Write4(sp_backup);
	Write2(0x2e7c);		// move.l	psp_base,a7
	Write4(psp_base);
	Write2(0x207c);		// move.l	psp_base,a0
	Write4(psp_base);
	Write2(0x227c);		// move.l	last_addr,a1
	Write4(last_addr);
	Write2(0x247c);		// move.l	#$c000,a2
	Write4(0xc000);
	Write2(0x267c);		// move.l	#$e000,a3
	Write4(0xe000);
	Write2(0x287c);		// move.l	exec_addr,a4
	Write4(exec_addr);
	Write2(0x2c49);		// move.l	a1,a6
	Write2(0x4eb9);		// jsr.l	exec_addr
	Write4(exec_addr);
	ret_addr = addr;
	Write2(0x2e79);		// movea.l	sp_backup,a7	// スタックを戻して
	Write4(sp_backup);
	Write2(0x4e75);		// rts

	// READY 通知用パイプ。
	if (pipe2(fd, O_NOSIGPIPE) < 0) {
		err(1, "pipe");
	}

	pid = fork();
	if (pid < 0) {
		err(1, "fork");
	}
	if (pid == 0) {
		/* child */

		// 親の準備が出来るのを待つ。
		char dummy[1];
		if (read(fd[0], dummy, 1) < 0) {
			err(1, "child: read failed");
		}

		func_t p = (func_t)boot_addr;
		r = p();
	} else {
		/* parent */
		r = run_parent(pid, fd[1]);

		kill(pid, SIGKILL);
	}
	exit(r);
}

// 親プロセス側。
static int
run_parent(int pid, int wfd)
{
	int status;
	uint32 nextpc;

	DEBUG(1, "child pid=%u", pid);

	if (ptrace(PT_ATTACH, pid, NULL, 0) < 0) {
		warn("ptrace(PT_ATTACH)");
		return 1;
	}

	// nextpc==1 は STOP した直後から再開、の意。
	nextpc = 1;

	for (;;) {
		if (wait(&status) < 0) {
			warn("wait");
			return 1;
		}
		if (opt_debug >= 2) {
			dumpwait(status);
		}
		if (WIFEXITED(status)) {
			break;
		}

		if (WIFSTOPPED(status)) {
			struct reg regbuf;
			struct reg *reg = &regbuf;

			if (ptrace(PT_GETREGS, pid, reg, 0) < 0) {
				warn("ptrace(PT_GETREGS)");
				return 1;
			}

			int signo = WSTOPSIG(status);

			// PT_ATTACH が効くと SIGSTOP が来る。
			if (signo == SIGSTOP) {
				// 準備が出来たので通知 (同期)。
				write(wfd, "", 1);
				DEBUG(1, "STOP -> Continue");
			} else if (signo == SIGILL || signo == SIGSEGV) {
				// 特権違反命令や未実装命令は SIGILL、
				// メモリアクセス違反は SIGSEGV が飛んでくるが、
				// どちらも命令コードを調べて再実行するところは同じ。
				uint32 inst32;
				errno = 0;
				inst32 = ptrace(PT_READ_I, pid, (void *)RegPC, 0);
				if (errno != 0) {
					warn("ptrace(PT_READ_I)");
					return 1;
				}
				DEBUG(2, "pc=%08x inst32=%08x", RegPC, inst32);
				if (opt_debug >= 2) {
					dumpreg(reg);
				}
				uint16 inst = inst32 >> 16;

				if ((inst & 0xff00) == 0xff00) {
					if (doscall(pid, (inst & 0xff), reg) == 1) {
						nextpc = ret_addr;
						goto next;
					}
					nextpc = RegPC + 2;
				} else {
					nextpc = exec_op(pid, reg, signo, inst);
					if (nextpc == (uint32)-1) {
						break;
					}
				}

			} else if (signo == SIGTRAP || signo == SIGFPE) {
				// TRAP15 は SIGTRAP、CHK* 命令やゼロ除算は SIGFPE、と
				// 本来別のシグナルが飛んで来るが
				// どちらも同じ形式で処理できるのでまとめてある。
				struct ptrace_siginfo psi;
				if (ptrace(PT_GET_SIGINFO, pid, &psi, sizeof(psi)) < 0) {
					warn("ptrace(PT_GET_SIGINFO)");
					return 1;
				}
				if (signo == SIGTRAP && psi.psi_siginfo.si_code == TRAP_CHLD) {
					// 子プロセス起動時にこちらが来る場合がある。
					// see kern_sig.c eventswitch()
					DEBUG(1, "TRAP_CHLD");
					goto next;
				}
				switch (psi.psi_siginfo.si_trap) {
				 case T_ZERODIV:
					TRACE(RegPC, "previous op issued T_ZERODIV");
					nextpc = exception_format2(reg, 5);
					break;
				 case T_CHKINST:
					TRACE(RegPC, "previous op issued T_CHKINST");
					nextpc = exception_format2(reg, 6);
					break;
				 case T_TRAPVINST:
					TRACE(RegPC, "previous op issued T_TRAPVINST");
					nextpc = exception_format2(reg, 7);
					break;
				 case T_TRAP15:
					iocscall(pid, reg);
					// nextpc は更新されている。
					break;
				 case T_FPEMULD:
					// FPE の Unsupported Data Type でこれが飛んでくるっぽいが
					// とりあえず F ライン例外に落としておく。
					TRACE(RegPC, "previous op issued T_FPEMULD");
					nextpc = exception_format2(reg, 11);
					break;
				 default:
					warnx("%06x: Not implemented si_trap %d",
						RegPC, psi.psi_siginfo.si_trap);
					return 1;
				}

			} else {
				warnx("%06x: Unknown signal %d", RegPC, signo);
				return 1;
			}

 next:
			if (ptrace(PT_SETREGS, pid, reg, 0) < 0) {
				warn("ptrace(PT_SETREGS)");
				return 1;
			}

			// 実行を再開し、次に何か起きるまで待つ。
			if (ptrace(PT_CONTINUE, pid, (void *)nextpc, 0) < 0) {
				err(1, "PT_CONTINUE");
			}
			nextpc = 1;
		}
	}

	return 0;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.