--- nono/vm/ram.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/ram.cpp 2026/04/29 17:05:14 1.1.1.12 @@ -1,102 +1,696 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "ram.h" -#include "vm.h" - // // メインメモリ // -// RAM はロングワード単位でホストバイトオーダ配置なので、 -// バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。 -// -uint8 *ram; -int ram_size; +#include "ram.h" +#include "aout.h" +#include "autofd.h" +#include "config.h" +#include "elf.h" +#include "iodevstream.h" +#include "mainapp.h" +#include "mpu.h" +#include +#include +#include +#include + +// グローバル参照用 RAMDevice *gRAM; +// RAM はロングワード単位でホストバイトオーダ配置なので、 +// バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。 +std::unique_ptr mainram; + +// コンストラクタ RAMDevice::RAMDevice() + : inherited("RAM") { - logname = "ram"; - devname = "MainRAM"; devaddr = 0; - ram_size = 0; - ram = NULL; + // アクセスウェイト + switch (gMainApp.GetVMType()) { + case VMType::LUNA88K: + // 取扱説明書p.21 に、CMMU からのアクセスタイムが + // リード(4byte)で 200ns、バーストリード(16byte)で 320ns + // ライト(4byte)で 160ns、バーストライト(16byte)で 280ns とある。 + // 25MHz は 40ns/clockで、この数値は CMMU からのアクセス全体の時間 + // らしいので、ここで加算するウェイトはそれから 2クロック引いたもの。 + read_wait = 3; + write_wait = 2; + break; + case VMType::LUNA1: + // LUNA-I は1ウェイト。 + read_wait = 1; + write_wait = 1; + break; + + case VMType::X68030: + default: + // X68030 ではシステムポートが設定するのでここでは不要。 + break; + } } +// デストラクタ RAMDevice::~RAMDevice() { - if (ram) { - delete ram; - ram = NULL; - } - ram_size = 0; + gRAM = NULL; } bool RAMDevice::Init() { - // とりあえず。実際は設定ファイルから - if (gVM->GetType() == VM::TYPE_LUNA) { - ram_size = 16 * 1024 * 1024; - } else { - ram_size = 12 * 1024 * 1024; + int ram_size_MB; + + const ConfigItem& item = gConfig->Find("ram-size"); + ram_size_MB = item.AsInt(); + if (ram_size_MB < 1) { + item.Err(); + return false; } - ram = new uint8[ram_size]; + // 下限以外の制約は機種による + switch (gMainApp.GetVMType()) { + case VMType::X68030: + // メインメモリは 12MB まで。 + // 内部は 8KB 単位で処理できるが設定が 1MB 単位。 + if (ram_size_MB > 12) { + item.Err("configurable maximum main memory size is 12 [MB]"); + return false; + } + break; + + case VMType::LUNA1: + // 16MB までは、実機に合わせて 4MB 単位とする。 + // see http://wiki.netbsd.org/ports/luna68k/luna68k_info/#hardware + // + // 16MB を超える増設 (or 魔改造) 部分は 1MB 単位で 255MB までとする。 + // 実 ROM のメモリチェックは 32KB だかそのくらいずつ行われ、 + // 最後に番兵として Null 領域 (BusError ではなく) が必要。 + // 現行の構成をあまり変えない範囲なら厳密には (256MB - 32KB) が + // 上限だが、設定ファイルからの指定が 1MB 単位なので 255MB。 + if (ram_size_MB <= 16) { + if (ram_size_MB % 4 != 0) { + item.Err("for 16MB or less, must be specified in 4 [MB] unit"); + return false; + } + } else if (ram_size_MB > 255) { + item.Err("configurable maximum RAM size is 255 [MB]"); + return false; + } + break; + + case VMType::LUNA88K: + // 64MB までは、実機に合わせて 16MB 単位とする。 + // + // 64MB を超える増設 (or 魔改造) 部分は未確認。 + if (ram_size_MB <= 64) { + if (ram_size_MB % 16 != 0) { + item.Err("for 64MB or less, must be specified in 16 [MB] unit"); + return false; + } + } else if (ram_size_MB > 255) { + item.Err("configurable maximum RAM size is 255 [MB]"); + return false; + } + break; + + default: + __unreachable(); + } + + ram_size = ram_size_MB * 1024 * 1024; + mainram.reset(new uint8[ram_size]); + + // devtable[] への RAM デバイスの配置はこの後 VM::Init 側で行っている return true; } +// 電源オン/リセット +void +RAMDevice::ResetHard(bool poweron) +{ + if (poweron) { + // XXX ノイズを用意する + } + + // X68030 ではリセットで戻るはず。(LUNA は固定なので関係ない) + if (gMainApp.GetVMType() == VMType::X68030) { + read_wait = 0; + write_wait = 0; + } +} + uint64 RAMDevice::Read8(uint32 addr) { + gMPU->AddCycle(read_wait); uint32 data; - data = ram[HLB(addr)]; + data = mainram[HLB(addr)]; + putlog(4, "$%08x.B -> $%02x", addr, data); return data; } uint64 RAMDevice::Read16(uint32 addr) { + gMPU->AddCycle(read_wait); uint32 data; - data = *(uint16 *)&ram[HLW(addr)]; + data = *(uint16 *)&mainram[HLW(addr)]; + putlog(4, "$%08x.W -> $%04x", addr, data); return data; } uint64 RAMDevice::Read32(uint32 addr) { + gMPU->AddCycle(read_wait); uint32 data; - data = *(uint32 *)&ram[addr]; + data = *(uint32 *)&mainram[addr]; + putlog(4, "$%08x.L -> $%08x", addr, data); return data; } uint64 RAMDevice::Write8(uint32 addr, uint32 data) { - ram[HLB(addr)] = data; + gMPU->AddCycle(write_wait); + mainram[HLB(addr)] = data; + putlog(3, "$%08x.B <- $%02x", addr, data); return 0; } uint64 RAMDevice::Write16(uint32 addr, uint32 data) { - *(uint16 *)&ram[HLW(addr)] = data; + gMPU->AddCycle(write_wait); + *(uint16 *)&mainram[HLW(addr)] = data; + putlog(3, "$%08x.W <- $%04x", addr, data); return 0; } uint64 RAMDevice::Write32(uint32 addr, uint32 data) { - *(uint32 *)&ram[addr] = data; + gMPU->AddCycle(write_wait); + *(uint32 *)&mainram[addr] = data; + putlog(3, "$%08x.L <- $%08x", addr, data); return 0; } uint64 RAMDevice::Peek8(uint32 addr) { - return ram[HLB(addr)]; + return mainram[HLB(addr)]; +} + +// アクセスウェイトを設定 (X68030 でシステムポートから呼ばれる) +void +RAMDevice::SetWait(uint32 wait) +{ + read_wait = wait; + write_wait = wait; +} + +// filepath で指定されるホストの実行ファイルをゲストにロードする。 +// ファイルのフォーマットは自動で認識する。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::LoadFile(const char *filepath) +{ + uint8 *file; + struct stat st; + size_t filesize; + uint32 entry; + autofd fd; + + fd = open(filepath, O_RDONLY); + if (fd == -1) { + warn("%s \"%s\" open failed", __func__, filepath); + return 0; + } + + if (fstat(fd, &st) == -1) { + warn("%s \"%s\" fstat failed", __func__, filepath); + return 0; + } + + if (!S_ISREG(st.st_mode)) { + warnx("%s \"%s\" not a regular file", __func__, filepath); + return 0; + } + + // 今の所マジック判定は最初の4バイトで出来るので + if (st.st_size < 4) { + warnx("%s \"%s\" file too short", __func__, filepath); + return 0; + } + filesize = st.st_size; + + file = (uint8 *)mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); + if (file == MAP_FAILED) { + warn("%s \"%s\" mmap failed", __func__, filepath); + return 0; + } + + // 先頭3バイトの gzip マジックを調べる + if (file[0] == 0x1f && file[1] == 0x8b && file[2] == 0x08) { + // gzip + entry = LoadGzFile(filepath, file, filesize); + } else { + // 通常ファイル + entry = LoadFile(filepath, file, filesize); + } + + munmap(file, filesize); + return entry; +} + +// gzip を展開してゲストにロードする。 +uint32 +RAMDevice::LoadGzFile(const char *filepath, const uint8 *infile, + size_t infilesize) +{ + z_stream z {}; + uint32 decompsize; + int rv; + + // gzip なら(?) ファイルの末尾4バイトが (LE で?) 展開後サイズ(?)。 + decompsize = le32toh(*(const 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("%s \"%s\" inflateInit2 failed %d", __func__, filepath, rv); + return 0; + } + + z.next_in = const_cast(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("%s \"%s\" inflate failed %d", __func__, filepath, rv); + return 0; + } + + inflateEnd(&z); + + // 展開できたのでロード + return LoadFile(filepath, dstbuf.get(), decompsize); +} + +// file, filesize で示されるバッファを実行ファイルとみなしてゲストにロードする。 +// ファイルのフォーマットは自動で認識する。 +// name はここではエラーメッセージとかの表示用でしかない。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::LoadFile(const char *name, const uint8 *file, size_t filesize) +{ + uint32 entry = 0; + + // フォーマットだけ判定して分岐 + const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file; + const aout_header *aout = (const aout_header *)file; + if (memcmp(ehdr->e_ident, "\177ELF", 4) == 0) { + entry = Load_elf32(name, file, filesize); + } else if (AOUT_MAGIC(be32toh(aout->magic)) == AOUT_OMAGIC) { + entry = Load_aout(name, file, filesize); + } else { + warnx("%s \"%s\" unknown executable file format", __func__, name); + } + + return entry; +} + +// ホストの a.out をゲストの RAM に読み込む。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_aout(const char *name, const uint8 *file, size_t filesize) +{ + aout_header aout; + int copylen; + + if (filesize < sizeof(aout)) { + warnx("%s \"%s\" file too short", __func__, name); + return 0; + } + + // ヘッダを読み込む + // BigEndian しかターゲットにしてないので最初から全部変換しとく + aout.magic = be32toh(((const aout_header *)file)->magic); + aout.textsize = be32toh(((const aout_header *)file)->textsize); + aout.datasize = be32toh(((const aout_header *)file)->datasize); + aout.bsssize = be32toh(((const aout_header *)file)->bsssize); + aout.symsize = be32toh(((const aout_header *)file)->symsize); + aout.entry = be32toh(((const aout_header *)file)->entry); + aout.textrelsize = be32toh(((const aout_header *)file)->textrelsize); + aout.datarelsize = be32toh(((const aout_header *)file)->datarelsize); + + if (loglevel >= 1) { + const char *mstr; + switch (AOUT_MAGIC(aout.magic)) { + case AOUT_OMAGIC: mstr = "OMAGIC"; break; + case AOUT_NMAGIC: mstr = "NMAGIC"; break; + case AOUT_ZMAGIC: mstr = "ZMAGIC"; break; + default: + mstr = "unsupported magic"; + break; + } + putmsgn("%s magic = %08x (MID=0x%03x, %s)", __func__, + aout.magic, AOUT_MID(aout.magic), mstr); + putmsgn("%s textsize = %08x", __func__, aout.textsize); + putmsgn("%s datasize = %08x", __func__, aout.datasize); + putmsgn("%s bsssize = %08x", __func__, aout.bsssize); + putmsgn("%s symsize = %08x", __func__, aout.symsize); + putmsgn("%s entry = %08x", __func__, aout.entry); + putmsgn("%s textrelsize = %08x", __func__, aout.textrelsize); + putmsgn("%s datarelsize = %08x", __func__, aout.datarelsize); + } + + // MID はチェックしない。 + // CPU アーキテクチャが一致するかくらいはチェックしたいところだが、 + // MID は CPU ごとではなく OS(?) ごとに異なっているのと、MID の + // 一元管理された一次情報が存在しないので、正解リストを作るのが困難。 + + // とりあえず再配置は無視 + if (aout.textrelsize != 0 || aout.datarelsize != 0) { + warnx("%s \"%s\" relocation not supported", __func__, 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: %s \"%s\" corrupted? " + "(text=%u + data=%u, filesize=%d); loading %d bytes", + __func__, + name, aout.textsize, aout.datasize, (int)filesize, copylen); + /* FALLTHROUGH */ + } + + if (aout.entry + copylen > GetSize()) { + warnx("%s \"%s\" out of memory in VM", __func__, name); + return 0; + } + + // 再配置不要なので、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)) + +// XXX このへん、そのうちきれいにする +#define GetElfStr1(array, v) GetElfStr(array, countof(array), v) +static const char * +GetElfStr(const std::pair *map, uint mapcount, uint v) +{ + for (int i = 0; i < mapcount; i++) { + if (map[i].first == v) { + return map[i].second; + } + } + return "?"; +} + +static std::pair class_str[] = { + { ELFCLASSNONE, "NONE" }, + { ELFCLASS32, "32bit" }, + { ELFCLASS64, "64bit" }, +}; +#define GetElfClassStr(x) GetElfStr1(class_str, (x)) + +static std::pair data_str[] = { + { ELFDATANONE, "NONE" }, + { ELFDATA2LSB, "LE" }, + { ELFDATA2MSB, "BE" }, +}; +#define GetElfDataStr(x) GetElfStr1(data_str, (x)) + +static std::pair etype_str[] = { + { ET_NONE, "ET_NONE" }, + { ET_REL, "ET_REL" }, + { ET_EXEC, "ET_EXEC" }, + { ET_DYN, "ET_DYN" }, + { ET_CORE, "ET_CORE" }, +}; +#define GetElfETypeStr(x) GetElfStr1(etype_str, (x)) + +static std::pair machine_str[] = { + // 多いので関係分のみ + { EM_NONE, "NONE" }, + { EM_68K, "Motorola 68000" }, + { EM_88K, "Motorola 88000" }, +}; +#define GetElfMachineStr(x) GetElfStr1(machine_str, (x)) + +static std::pair ptype_str[] = { + { PT_NULL, "PT_NULL" }, + { PT_LOAD, "PT_LOAD" }, + { PT_DYNAMIC, "PT_DYNAMIC" }, + { PT_INTERP, "PT_INTERP" }, + { PT_NOTE, "PT_NOTE" }, + { PT_SHLIB, "PT_SHLIB" }, + { PT_PHDR, "PT_PHDR" }, + { PT_GNU_RELRO, "PT_GNU_RELRO" }, // GNU specific + { PT_OPENBSD_RANDOMIZE, "PT_OPENBSD_RANDOMIZE" }, + { PT_OPENBSD_WXNEEDED, "PT_OPENBSD_WXNEEDED" }, + { PT_OPENBSD_BOOTDATA, "PT_OPENBSD_BOOTDATA" }, +}; +#define GetElfPTypeStr(x) GetElfStr1(ptype_str, (x)) + +static std::pair shtype_str[] = { + { SHT_NULL, "SHT_NULL" }, + { SHT_PROGBITS, "SHT_PROGBITS" }, + { SHT_RELA, "SHT_RELA" }, + { SHT_NOBITS, "SHT_NOBITS" }, + { SHT_REL, "SHT_REL" }, +}; +#define GetElfShTypeStr(x) GetElfStr1(shtype_str, (x)) + +// ホストの ELF をゲストの RAM に読み込む。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_elf32(const char *name, const uint8 *file, size_t filesize) +{ + uint16 target_elf_mid; + + if (gMainApp.Has(VMCap::M68K)) { + target_elf_mid = EM_68K; + } else if (gMainApp.Has(VMCap::M88K)) { + target_elf_mid = EM_88K; + } else { + PANIC("Unknown CPU?"); + } + + // ヘッダをチェック + const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file; + if (filesize < sizeof(*ehdr)) { + warnx("%s \"%s\" file too short", __func__, name); + return 0; + } + uint16 e_type = elf16toh(ehdr->e_type); + uint16 e_machine = elf16toh(ehdr->e_machine); + uint32 e_entry = elf32toh(ehdr->e_entry); + if (loglevel >= 1) { + uint n; + n = ehdr->e_ident[EI_CLASS]; + putlogn("EI_CLASS = $%02x (%s)", n, GetElfClassStr(n)); + n = ehdr->e_ident[EI_DATA]; + putlogn("EI_DATA = $%02x (%s)", n, GetElfDataStr(n)); + + putlogn("e_type = $%02x (%s)", e_type, GetElfETypeStr(e_type)); + putlogn("e_machine = $%02x (%s)", e_machine, + GetElfMachineStr(e_machine)); + putlogn("e_entry = $%08x", e_entry); + } + + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) { + warnx("%s \"%s\" not ELF32 format", __func__, name); + return 0; + } + if (e_machine != target_elf_mid) { + warnx("%s \"%s\" machine type mismatch " + "($%02x expected but $%02x)", __func__, + name, target_elf_mid, e_machine); + return 0; + } + + switch (e_type) { + case ET_EXEC: + if (Load_elf32_exec(name, file, filesize)) { + return e_entry; + } + break; + case ET_REL: + return Load_elf32_rel(name, file, filesize); + default: + warnx("%s \"%s\" unsupported file type", __func__, name); + break; + } + return 0; +} + +// ホストの ELF 実行形式ファイルをゲストの RAM に読み込む。 +// 読み込めたら true、エラーなら false を返す。 +// エントリポイントは呼び出し元が知っているのでこっちでは関与しない。 +bool +RAMDevice::Load_elf32_exec(const char *name, const uint8 *file, size_t filesize) +{ + const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file; + + // プログラムヘッダに読み込むべき領域が示されている + // 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); + if (loglevel >= 1) { + putlogn("e_phoff = $%08x", e_phoff); + putlogn("e_phentsize = $%04x", e_phentsize); + putlogn("e_phnum = %d", e_phnum); + } + if (e_phentsize != sizeof(Elf32_Phdr)) { + warnx("%s \"%s\" unknown program header size 0x%x", __func__, + name, e_phentsize); + return false; + } + + 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); // 書き込み先サイズ + if (loglevel >= 1) { + putlogn("[%d] p_type = $%08x (%s)", j, + p_type, GetElfPTypeStr(p_type)); + putlogn(" p_offset = $%08x", p_offset); + putlogn(" p_vaddr = $%08x", p_vaddr); + putlogn(" p_filesz = $%08x", p_filesz); + putlogn(" p_memsz = $%08x", p_memsz); + } + + if (p_type == PT_LOAD) { + if (p_vaddr + p_memsz > GetSize()) { + warnx("%s \"%s\" out of memory in VM", __func__, name); + return false; + } + + 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 if (p_type == PT_NOTE) { + // ベンダー文字列みたいなのらしいので無視してよい + continue; + } else if (p_type == PT_OPENBSD_RANDOMIZE) { + // XXX どうする? + continue; + + } else { + warnx("%s \"%s\" p_type 0x%x(%s) not supported", __func__, + name, p_type, GetElfPTypeStr(p_type)); + return false; + } + } + + return true; +} + +// ホストの ELF オブジェクトの text セクションをゲストの RAM に読み込む。 +// .o(オブジェクトファイル) 用。 +// 読み込めたら true、エラーなら false を返す。 +// 戻り値はエントリポイントアドレス。エラーなら 0 を返す。 +uint32 +RAMDevice::Load_elf32_rel(const char *name, const uint8 *file, size_t filesize) +{ + const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)file; + uint32 entry = 0x20000; + + uint32 e_shoff = elf32toh(ehdr->e_shoff); + uint16 e_shentsize = elf16toh(ehdr->e_shentsize); + uint16 e_shnum = elf16toh(ehdr->e_shnum); + if (loglevel >= 1) { + putlogn("e_shoff = $%08x", e_shoff); + putlogn("e_shentsize = $%04x", e_shentsize); + putlogn("e_shnum = %d", e_shnum); + } + if (e_shentsize != sizeof(Elf32_Shdr)) { + warnx("%s \"%s\" unknown section header size 0x%x", __func__, + name, e_shentsize); + return 0; + } + + IODeviceStream ds(this); + ds.SetAddr(entry); + + for (int j = 0; j < e_shnum; j++) { + const Elf32_Shdr *shdr = &((const Elf32_Shdr*)(file + e_shoff))[j]; + // sh_type + uint32 sh_type = elf32toh(shdr->sh_type); // タイプ + uint32 sh_addr = elf32toh(shdr->sh_addr); // アドレス + uint32 sh_offset = elf32toh(shdr->sh_offset); // ファイル先頭から + uint32 sh_size = elf32toh(shdr->sh_size); // サイズ + if (loglevel >= 1) { + putlogn("[%d] sh_type = $%08x (%s)", j, + sh_type, GetElfShTypeStr(sh_type)); + putlogn(" sh_addr = $%08x", sh_addr); + putlogn(" sh_offset = $%08x", sh_offset); + putlogn(" sh_size = $%08x", sh_size); + } + + if (sh_type == SHT_PROGBITS) { + const uint8 *src = file + sh_offset; + int i = 0; + for (; i < sh_size; i++) { + ds.Write8(*src++); + } + } else if (sh_type == SHT_RELA || sh_type == SHT_REL) { + // 再配置情報があるのは .R 形式ではない + warnx("%s \"%s\" has relocation section", __func__, name); + return 0; + } else { + // ? + } + } + + return entry; }