|
|
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 = ®buf;
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;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.