--- nono/vm/ram.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/ram.cpp 2026/04/29 17:04:32 1.1.1.2 @@ -4,7 +4,14 @@ // #include "ram.h" -#include "vm.h" +#include "aout.h" +#include "elf.h" +#include "iodevstream.h" +#include "mainapp.h" +#include +#include +#include +#include // // メインメモリ @@ -13,9 +20,9 @@ // バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。 // -uint8 *ram; +std::unique_ptr ram; int ram_size; -RAMDevice *gRAM; +std::unique_ptr gRAM; RAMDevice::RAMDevice() { @@ -24,15 +31,10 @@ RAMDevice::RAMDevice() devaddr = 0; ram_size = 0; - ram = NULL; } RAMDevice::~RAMDevice() { - if (ram) { - delete ram; - ram = NULL; - } ram_size = 0; } @@ -40,12 +42,13 @@ bool RAMDevice::Init() { // とりあえず。実際は設定ファイルから - if (gVM->GetType() == VM::TYPE_LUNA) { + if (gMainApp.GetVMType() == VMTYPE_LUNA || + gMainApp.GetVMType() == VMTYPE_LUNA88K) { ram_size = 16 * 1024 * 1024; } else { ram_size = 12 * 1024 * 1024; } - ram = new uint8[ram_size]; + ram.reset(new uint8[ram_size]); return true; } @@ -100,3 +103,310 @@ RAMDevice::Peek8(uint32 addr) { return ram[HLB(addr)]; } + +// filepath で指定されるホストの実行ファイルをゲストにロードする。 +// ファイルのフォーマットは自動で認識する。 +// target_mid は machine id で、実体は a.out の値を流用してはいるが +// ELF であってもこちらで読み替える。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::LoadFile(const char *filepath, uint32 target_mid) +{ + uint8 *file; + struct stat st; + size_t filesize; + uint32 entry; + int fd; + + fd = open(filepath, O_RDONLY); + if (fd == -1) { + warn("LoadFile \"%s\" open failed", filepath); + return 0; + } + + if (fstat(fd, &st) == -1) { + warn("LoadFile \"%s\" fstat failed", filepath); + close(fd); + return 0; + } + + if (!S_ISREG(st.st_mode)) { + warnx("LoadFile \"%s\" not a regular file", filepath); + close(fd); + return 0; + } + + // 今の所マジック判定は最初の4バイトで出来るので + if (st.st_size < 4) { + warnx("LoadFile \"%s\" file too short", filepath); + close(fd); + return 0; + } + filesize = st.st_size; + + file = (uint8 *)mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); + if (file == MAP_FAILED) { + warn("LoadFile \"%s\" mmap failed", filepath); + close(fd); + return 0; + } + + // 先頭3バイトの gzip マジックを調べる + if (file[0] == 0x1f && file[1] == 0x8b && file[2] == 0x08) { + // gzip + entry = LoadGzFile(filepath, file, filesize, target_mid); + } else { + // 通常ファイル + entry = LoadFile(filepath, file, filesize, target_mid); + } + + munmap(file, filesize); + close(fd); + return entry; +} + +// gzip を展開してゲストにロードする。 +uint32 +RAMDevice::LoadGzFile(const char *filepath, + const uint8 *infile, size_t infilesize, uint32 target_mid) +{ + z_stream z {}; + uint32 decompsize; + int rv; + + // gzip なら(?) ファイルの末尾4バイトが (LE で?) 展開後サイズ(?)。 + decompsize = le32toh(*(uint32 *)&infile[infilesize - 4]); + std::unique_ptr dstbuf(new uint8[decompsize]); + + // gzip 展開 + // (uncompress() の方が楽そうに見えるが gzip 形式に対応してないらしい) + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + rv = inflateInit2(&z, 47); + if (rv != Z_OK) { + warnx("LoadGzFile \"%s\" inflateInit2 failed %d", filepath, rv); + return 0; + } + + z.next_in = (Bytef *)infile; + z.avail_in = infilesize - 4; + z.next_out = dstbuf.get(); + z.avail_out = decompsize; + rv = inflate(&z, Z_NO_FLUSH); + if (rv != Z_OK) { + warnx("LoadGzFile \"%s\" inflate failed %d", filepath, rv); + return 0; + } + + inflateEnd(&z); + + // 展開できたのでロード + return LoadFile(filepath, dstbuf.get(), decompsize, target_mid); +} + +// file, filesize で示されるバッファを実行ファイルとみなしてゲストにロードする。 +// ファイルのフォーマットは自動で認識する。 +// target_mid は machine id で、実体は a.out の値を流用してはいるが +// ELF であってもこちらで読み替える。 +// name はここではエラーメッセージとかの表示用でしかない。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::LoadFile(const char *name, const uint8 *file, size_t filesize, + uint32 target_mid) +{ + uint32 entry = 0; + + // フォーマットだけ判定して分岐 + Elf32_Ehdr *ehdr = (Elf32_Ehdr *)file; + aout_header *aout = (aout_header *)file; + if (memcmp(ehdr->e_ident, "\177ELF", 4) == 0) { + entry = Load_elf32(name, file, filesize, target_mid); + } else if ((be32toh(aout->magic) & 0xffff) == AOUT_OMAGIC) { + entry = Load_aout(name, file, filesize, target_mid); + } else { + warnx("LoadFile \"%s\" unknown executable file format", name); + } + + return entry; +} + +// ホストの a.out をゲストの RAM に読み込む。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_aout(const char *name, const uint8 *file, size_t filesize, + uint32 target_mid) +{ + aout_header aout; + int copylen; + + if (filesize < sizeof(aout)) { + warnx("Load_aout \"%s\" file too short", name); + return 0; + } + + // ヘッダを読み込む + // BigEndian しかターゲットにしてないので最初から全部変換しとく + aout.magic = be32toh(((aout_header *)file)->magic); + aout.textsize = be32toh(((aout_header *)file)->textsize); + aout.datasize = be32toh(((aout_header *)file)->datasize); + aout.bsssize = be32toh(((aout_header *)file)->bsssize); + aout.symsize = be32toh(((aout_header *)file)->symsize); + aout.entry = be32toh(((aout_header *)file)->entry); + aout.textrelsize = be32toh(((aout_header *)file)->textrelsize); + aout.datarelsize = be32toh(((aout_header *)file)->datarelsize); + + // マジックをチェック + uint32 mid = AOUT_MID(aout.magic); + if (mid != target_mid) { + warnx("Load_aout \"%s\" machine id mismatch (%03x expected but %03x)", + name, target_mid, mid); + return 0; + } + + putmsg(1, "%s textsize = %08x", __func__, aout.textsize); + putmsg(1, "%s datasize = %08x", __func__, aout.datasize); + putmsg(1, "%s bsssize = %08x", __func__, aout.bsssize); + putmsg(1, "%s symsize = %08x", __func__, aout.symsize); + putmsg(1, "%s entry = %08x", __func__, aout.entry); + putmsg(1, "%s textrelsize = %08x", __func__, aout.textrelsize); + putmsg(1, "%s datarelsize = %08x", __func__, aout.datarelsize); + + // とりあえず再配置は無視 + if (aout.textrelsize != 0 || aout.datarelsize != 0) { + warnx("Load_aout \"%s\" relocation not supported", name); + return 0; + } + + // OpenBSD の boot は text+data が filesize より大きくて何かおかしいが + // OMAGIC の a.out は text+data が連続しているだけなので、とりあえず + // 何も考えずにファイルサイズ分ロードしておく。 + copylen = aout.textsize + aout.datasize; + if (copylen >= filesize) { + copylen = filesize; + warnx("warning: Load_aout \"%s\" coruppted? " + "(text=%u + data=%u, filesize=%d); loading %d bytes", + name, aout.textsize, aout.datasize, (int)filesize, copylen); + /* FALLTHROUGH */ + } + + // 再配置不要なので、text + data を entry に置いて entry に飛ぶだけ + IODeviceStream ds(this, aout.entry); + const uint8 *src = file + sizeof(aout); + for (int i = 0; i < copylen; i++) { + ds.Write8(*src++); + } + + return aout.entry; +} + +// ELF フォーマットのエンディアンからホストエンディアンに変換 +#define elf16toh(x) ((ehdr->e_ident[EI_DATA] == ELFDATA2MSB) ? \ + be16toh(x) : le16toh(x)) +#define elf32toh(x) ((ehdr->e_ident[EI_DATA] == ELFDATA2MSB) ? \ + be32toh(x) : le32toh(x)) + +// ホストの a.out をゲストの RAM に読み込む。 +// target_aout_mid は a.out での machine id (機種情報) で、こっちで ELF の +// machine type に読み替える。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_elf32(const char *name, const uint8 *file, size_t filesize, + uint32 target_aout_mid) +{ + uint16 target_elf_mid; + + // a.out machine id から ELF machine type への読み替え + switch (target_aout_mid) { + case AOUT_MID_M68K: + target_elf_mid = EM_68K; + break; + case AOUT_MID_M88K: + target_elf_mid = EM_88K; + break; + default: + warnx("Load_elf32 unknown target_aout_mid $%x", target_aout_mid); + return 0; + } + + // ヘッダをチェック + const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file; + if (filesize < sizeof(*ehdr)) { + warnx("Load_elf32 \"%s\" file too short", name); + return 0; + } + uint16 e_type = elf16toh(ehdr->e_type); + uint16 e_machine = elf16toh(ehdr->e_machine); + uint32 e_entry = elf32toh(ehdr->e_entry); + putlog(1, "EI_CLASS = $%02x", ehdr->e_ident[EI_CLASS]); + putlog(1, "EI_DATA = $%02x", ehdr->e_ident[EI_DATA]); + putlog(1, "e_type = $%02x", e_type); + putlog(1, "e_machine = $%02x", e_machine); + putlog(1, "e_entry = $%08x", e_entry); + + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) { + warnx("Load_elf32 \"%s\" not ELF32 format", name); + return 0; + } + if (e_type != ET_EXEC) { + warnx("Load_elf32 \"%s\" not executable file", name); + return 0; + } + if (e_machine != target_elf_mid) { + warnx("Load_elf32 \"%s\" machine type mismatch " + "($%02x expected but $%02x)", + name, target_elf_mid, e_machine); + return 0; + } + + // プログラムヘッダに読み込むべき領域が示されている + // phoff がプログラムヘッダのファイル先頭からのオフセット + // phentsize がプログラムヘッダ1つの大きさ + // phnum がプログラムヘッダの個数 + uint32 e_phoff = elf32toh(ehdr->e_phoff); + uint16 e_phentsize = elf16toh(ehdr->e_phentsize); + uint16 e_phnum = elf16toh(ehdr->e_phnum); + putlog(1, "e_phoff = $%08x", e_phoff); + putlog(1, "e_phentsize = $%04x", e_phentsize); + putlog(1, "e_phnum = %d", e_phnum); + if (e_phentsize != sizeof(Elf32_Phdr)) { + warnx("Load_elf32 \"%s\" unknown program header size 0x%x", + name, e_phentsize); + return 0; + } + + IODeviceStream ds(this); + + for (int j = 0; j < e_phnum; j++) { + const Elf32_Phdr *phdr = &((const Elf32_Phdr*)(file + e_phoff))[j]; + // p_type が PT_LOAD なところをロードする。 + // その際 p_memsz > p_filesz なら差が BSS 領域。 + uint32 p_type = elf32toh(phdr->p_type); // タイプ + uint32 p_offset = elf32toh(phdr->p_offset); // ファイル上のオフセット + uint32 p_vaddr = elf32toh(phdr->p_vaddr); // 仮想アドレス + uint32 p_filesz = elf32toh(phdr->p_filesz); // 読み込み元ファイルサイズ + uint32 p_memsz = elf32toh(phdr->p_memsz); // 書き込み先サイズ + putlog(1, "[%d] p_type = $%08x", j, p_type); + putlog(1, "[%d] p_offset = $%08x", j, p_offset); + putlog(1, "[%d] p_vaddr = $%08x", j, p_vaddr); + putlog(1, "[%d] p_filesz = $%08x", j, p_filesz); + putlog(1, "[%d] p_memsz = $%08x", j, p_memsz); + + if (p_type == PT_LOAD) { + ds.SetAddr(p_vaddr); + const uint8 *src = file + p_offset; + int i = 0; + for (; i < p_filesz; i++) { + ds.Write8(*src++); + } + for (; i < p_memsz; i++) { + ds.Write8(0); + } + } else { + warnx("Load_elf32 \"%s\" p_type %d not supported", name, p_type); + return 0; + } + } + + return e_entry; +}