|
|
nono 1.7.0
//
// nono
// Copyright (C) 2022 nono project
// Licensed under nono-license.txt
//
//
// ローダ
//
// BootLoader オブジェクト (ログ表示名 "BootLoader") は
// Device リストにつなぐ常設インスタンスではないため、
// そのままではコマンドラインオプション等でログレベルを指定する方法がないが
// その代わり、RAM デバイスのログレベルを引き継いである。
// よって、使い方としては従来通り -C -Lram で指定できる。
//
// Object 派生で putlog() のオーバーライドは用意していないので、
// ログ出力には putmsg() を使うこと。
#include "bootloader.h"
#include "aout.h"
#include "autofd.h"
#include "elf.h"
#include "iodevstream.h"
#include "mainapp.h"
#include "mainram.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <zlib.h>
#include <vector>
// info で指定された実行ファイルを mainram にロードする。
/*static*/ bool
BootLoader::LoadExec(MainRAMDevice *mainram_, LoadInfo *info)
{
BootLoader loader(mainram_);
return loader.LoadExec(info);
}
// info で指定されたデータファイルを mainram にロードする。
/*static*/ bool
BootLoader::LoadData(MainRAMDevice *mainram_, LoadInfo *info)
{
BootLoader loader(mainram_);
return loader.LoadData(info);
}
// コンストラクタ
BootLoader::BootLoader(MainRAMDevice *mainram_)
: inherited(OBJ_BOOTLOADER)
{
mainram = mainram_;
// RAM デバイスのログレベルを踏襲する。
loglevel = mainram->loglevel;
}
// デストラクタ
BootLoader::~BootLoader()
{
}
// info で指定されたデータファイルをゲストにロードする。
// info->data, size が指定されていればバッファから読み込む (現状こちらのみ)。
// info->start には読み込み先アドレスを指定する (戻り値ではない)。
// info->end にはロードした最終アドレス(の次のアドレス) が返る (ここは同じ)。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::LoadData(LoadInfo *info)
{
assert(info->data != NULL);
assert(info->size != 0);
// ゲストにコピー。
if (mainram->WriteMem(info->start, info->data, info->size) == false) {
return false;
}
info->end = info->start + info->size;
return true;
}
// info で指定された実行ファイルをゲストにロードする。
// info->data, size が指定されていればバッファから読み込む。
// 指定されてなければホストの info->path から読み込む (その際 data, size は
// 内部で使用する)。
// info->path はいずれにしてもエラー表示の際に使用する。
// 実行ファイルのフォーマットは自動で認識する。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::LoadExec(LoadInfo *info)
{
if (info->data == NULL || info->size == 0) {
return LoadExecFromFile(info);
} else {
return LoadExecFromBuf(info);
}
}
// info->path で指定された実行ファイルをゲストにロードする。
// ファイルのフォーマットは自動で認識する。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::LoadExecFromFile(LoadInfo *info)
{
uint8 *filedata;
size_t filesize;
struct stat st;
autofd fd;
bool rv;
fd = open(info->path, O_RDONLY);
if (fd == -1) {
warn("%s \"%s\"", __method__, info->path);
return false;
}
if (fstat(fd, &st) == -1) {
warn("%s \"%s\" fstat failed", __method__, info->path);
return false;
}
if (!S_ISREG(st.st_mode)) {
warnx("%s \"%s\" not a regular file", __method__, info->path);
return false;
}
// 今の所マジック判定は最初の4バイトで出来るので
if (st.st_size < 4) {
warnx("%s \"%s\" file too short", __method__, info->path);
return false;
}
filesize = st.st_size;
filedata = (uint8 *)mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
if (filedata == MAP_FAILED) {
warn("%s \"%s\" mmap failed", __method__, info->path);
return false;
}
info->data = filedata;
info->size = filesize;
// 先頭3バイトの gzip マジックを調べる
if (filedata[0] == 0x1f && filedata[1] == 0x8b && filedata[2] == 0x08) {
// gzip
rv = LoadGzFile(info);
} else {
// 通常ファイル
rv = LoadExecFromBuf(info);
}
munmap(filedata, filesize);
return rv;
}
// gzip を展開してゲストにロードする。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::LoadGzFile(LoadInfo *info)
{
z_stream z {};
uint32 decompsize;
int rv;
// この時点の data, size は展開前のもの
const uint8 *infile = info->data;
size_t infilesize = info->size;
// gzip なら(?) ファイルの末尾4バイトが (LE で?) 展開後サイズ(?)。
decompsize = le32toh(*(const uint32 *)&infile[infilesize - 4]);
std::vector<uint8> dstbuf(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__, info->path, rv);
return false;
}
z.next_in = const_cast<Bytef *>(infile);
z.avail_in = infilesize - 4;
z.next_out = dstbuf.data();
z.avail_out = decompsize;
rv = inflate(&z, Z_NO_FLUSH);
if (rv != Z_OK) {
warnx("%s \"%s\" inflate failed %d", __func__, info->path, rv);
return false;
}
inflateEnd(&z);
// 展開できたのでロード
info->data = dstbuf.data();
info->size = decompsize;
return LoadExecFromBuf(info);
}
// info->data, size で指定されたバッファを実行ファイルとみなしてゲストに
// ロードする。ファイルのフォーマットは自動で認識する。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::LoadExecFromBuf(LoadInfo *info)
{
bool rv;
// フォーマットだけ判定して分岐
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)info->data;
const aout_header *aout = (const aout_header *)info->data;
if (memcmp(ehdr->e_ident, "\177ELF", 4) == 0) {
rv = Load_elf32(info);
} else if (AOUT_MAGIC(be32toh(aout->magic)) == AOUT_OMAGIC) {
rv = Load_aout(info);
} else {
warnx("%s \"%s\" unknown executable file format", __func__, info->path);
return false;
}
return rv;
}
// ホストの a.out をゲストの RAM に読み込む。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::Load_aout(LoadInfo *info)
{
aout_header aout;
int copylen;
if (info->size < sizeof(aout)) {
warnx("%s \"%s\" file too short", __func__, info->path);
return false;
}
// ヘッダを読み込む
// BigEndian しかターゲットにしてないので最初から全部変換しとく
const aout_header *ah = (const aout_header *)info->data;
aout.magic = be32toh(ah->magic);
aout.textsize = be32toh(ah->textsize);
aout.datasize = be32toh(ah->datasize);
aout.bsssize = be32toh(ah->bsssize);
aout.symsize = be32toh(ah->symsize);
aout.entry = be32toh(ah->entry);
aout.textrelsize = be32toh(ah->textrelsize);
aout.datarelsize = be32toh(ah->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__, info->path);
return false;
}
// OpenBSD の boot は text+data が filesize より大きくて何かおかしいが
// OMAGIC の a.out は text+data が連続しているだけなので、とりあえず
// 何も考えずにファイルサイズ分ロードしておく。
copylen = aout.textsize + aout.datasize;
if (copylen >= info->size) {
copylen = info->size;
warnx("warning: %s \"%s\" corrupted? "
"(text=%u + data=%u, filesize=%d); loading %d bytes",
__func__,
info->path, aout.textsize, aout.datasize, (int)info->size, copylen);
// FALLTHROUGH
}
if (aout.entry + copylen > mainram->GetSize()) {
warnx("%s \"%s\" out of memory in VM", __func__, info->path);
return false;
}
// 再配置不要なので、text + data を entry に置いて entry に飛ぶだけ
bool ok = mainram->WriteMem(aout.entry, info->data + sizeof(aout), copylen);
if (ok == false) {
warnx("%s \"%s\" WriteMem($%x, $%x) failed",
__func__, info->path, aout.entry, copylen);
return false;
}
// a.out は entry だけでよい
info->entry = aout.entry;
return true;
}
// 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))
#define htoelf16(x) ((ehdr->e_ident[EI_DATA] == ELFDATA2MSB) ? \
htobe16(x) : htole16(x))
#define htoelf32(x) ((ehdr->e_ident[EI_DATA] == ELFDATA2MSB) ? \
htobe32(x) : htole32(x))
// XXX このへん、そのうちきれいにする
#define GetElfStr1(array, v) GetElfStr(array, countof(array), v)
static const char *
GetElfStr(const std::pair<uint, const char *> *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<uint, const char *> class_str[] = {
{ ELFCLASSNONE, "NONE" },
{ ELFCLASS32, "32bit" },
{ ELFCLASS64, "64bit" },
};
#define GetElfClassStr(x) GetElfStr1(class_str, (x))
static std::pair<uint, const char *> data_str[] = {
{ ELFDATANONE, "NONE" },
{ ELFDATA2LSB, "LE" },
{ ELFDATA2MSB, "BE" },
};
#define GetElfDataStr(x) GetElfStr1(data_str, (x))
static std::pair<uint, const char *> 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<uint, const char *> machine_str[] = {
// 多いので関係分のみ
{ EM_NONE, "NONE" },
{ EM_68K, "Motorola 68000" },
{ EM_88K, "Motorola 88000" },
};
#define GetElfMachineStr(x) GetElfStr1(machine_str, (x))
static std::pair<uint, const char *> 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<uint, const char *> shtype_str[] = {
{ SHT_NULL, "SHT_NULL" },
{ SHT_PROGBITS, "SHT_PROGBITS" },
{ SHT_SYMTAB, "SHT_SYMTAB" },
{ SHT_STRTAB, "SHT_STRTAB" },
{ SHT_RELA, "SHT_RELA" },
{ SHT_NOTE, "SHT_NOTE" },
{ SHT_NOBITS, "SHT_NOBITS" },
{ SHT_REL, "SHT_REL" },
};
#define GetElfShTypeStr(x) GetElfStr1(shtype_str, (x))
// ホストの ELF をゲストの RAM に読み込む。
// 成功すれば true、失敗すれば false を返す。
bool
BootLoader::Load_elf32(LoadInfo *info)
{
uint16 target_elf_mid;
bool rv;
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 *)info->data;
if (info->size < sizeof(*ehdr)) {
warnx("%s \"%s\" file too short", __func__, info->path);
return false;
}
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];
putmsgn("EI_CLASS = $%02x (%s)", n, GetElfClassStr(n));
n = ehdr->e_ident[EI_DATA];
putmsgn("EI_DATA = $%02x (%s)", n, GetElfDataStr(n));
putmsgn("e_type = $%02x (%s)", e_type, GetElfETypeStr(e_type));
putmsgn("e_machine = $%02x (%s)", e_machine,
GetElfMachineStr(e_machine));
putmsgn("e_entry = $%08x", e_entry);
}
if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) {
warnx("%s \"%s\" not ELF32 format", __func__, info->path);
return false;
}
if (e_machine != target_elf_mid) {
warnx("%s \"%s\" machine type mismatch "
"($%02x expected but $%02x)", __func__,
info->path, target_elf_mid, e_machine);
return false;
}
switch (e_type) {
case ET_EXEC:
info->entry = e_entry;
rv = Load_elf32_exec(info);
break;
case ET_REL:
rv = Load_elf32_rel(info);
break;
default:
warnx("%s \"%s\" unsupported file type", __func__, info->path);
return false;
}
if (loglevel >= 1) {
putmsgn("start = $%08x", info->start);
putmsgn("sym = $%08x", info->sym);
putmsgn("end = $%08x", info->end);
}
return rv;
}
// ホストの ELF 実行形式ファイルをゲストの RAM に読み込む。
// 読み込めたら true、エラーなら false を返す。
// エントリポイントは呼び出し元が知っているのでこっちでは関与しない。
bool
BootLoader::Load_elf32_exec(LoadInfo *info)
{
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)info->data;
std::string bootmsg;
// プログラムヘッダに読み込むべき領域が示されている。
// - e_phoff がプログラムヘッダのファイル先頭からのオフセット
// - e_phentsize がプログラムヘッダ1つの大きさ
// - e_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) {
putmsgn("e_phoff = $%08x", e_phoff);
putmsgn("e_phentsize = $%04x", e_phentsize);
putmsgn("e_phnum = %d", e_phnum);
}
if (e_phentsize != sizeof(Elf32_Phdr)) {
warnx("%s \"%s\" unknown program header size 0x%x", __func__,
info->path, e_phentsize);
return false;
}
IODeviceStream ds(mainram);
for (int j = 0; j < e_phnum; j++) {
const Elf32_Phdr *phdr =
&((const Elf32_Phdr*)(info->data + 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) {
putmsgn("[%d] p_type = $%08x (%s)", j,
p_type, GetElfPTypeStr(p_type));
putmsgn(" p_offset = $%08x", p_offset);
putmsgn(" p_vaddr = $%08x", p_vaddr);
putmsgn(" p_filesz = $%08x", p_filesz);
putmsgn(" p_memsz = $%08x", p_memsz);
}
if (p_type == PT_LOAD) {
if (p_vaddr + p_memsz > mainram->GetSize()) {
warnx("%s \"%s\" out of memory in VM", __func__, info->path);
return false;
}
// 読み込み開始位置を一応覚えておく。
// 隙間のある2つ以上のヘッダが来られるとあまり意味はなくなるが。
if (j == 0) {
info->start = p_vaddr;
} else {
info->start = std::min(info->start, p_vaddr);
}
ds.SetAddr(p_vaddr);
ds.WriteMem(info->data + p_offset, p_filesz);
for (int i = p_filesz; i < p_memsz; i++) {
ds.Write1(0);
}
bootmsg += string_format("%d+%d", p_filesz, p_memsz - p_filesz);
} 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__,
info->path, p_type, GetElfPTypeStr(p_type));
return false;
}
}
info->end = ds.GetAddr();
info->sym = info->end;
// シンボルテーブルをロード。出来なくても構わないので続行する。
// 本当は NetBSD カーネルの時に限るのだが、とりあえず。
if (info->loadsym) {
Load_elf32_sym(info, bootmsg);
}
// boot がカーネルを読んだ時と同じ形式のログを出しておく。(検証用)
putmsg(1, "%s=0x%x", bootmsg.c_str(), info->end - info->start);
return true;
}
// NetBSD カーネルのシンボルテーブルを読み込む。
// NetBSD のブートローダはカーネルを読み込む際 (LOAD_HDR | LOAD_SYM オプション
// が指定されていればだが、デフォルトでは (LOAD_ALL として) 指定されているので)
// カーネル本体 (プログラム領域 + BSS) の後ろに ELF ヘッダ、セクションヘッダ、
// シンボルセクションを配置する。当然配置しただけでは中に書いてあるアドレスが
// 異なるので、アドレスの再配置も行う。まじか。
//
// 他 OS は未調査。本来 NetBSD カーネルのみで行うべきだが、ELF 実行ファイル
// からは判断しづらいので、とりあえず常に行う。
// 他 ELF からは、範囲外アクセスすると見えないはずの便利なものが見えてしまう
// という不具合があることになる。
//
// メモリマップ (上が0)
// : :
// : ここまでが読み込み済みの :
// : カーネルの内容(たぶんBSS末尾):
// +- - - - - - - - - - - - - - - + <-- 呼び出し時点の info->end
// | 4バイト境界までパディング |
// +------------------------------+ <-- elfp (ここが起点)
// | ELF ヘッダ (52バイト) |
// | .e_shoff (セクションヘッダ位置) = (shpp - elfp)
// | |
// +------------------------------+ <-- shpp
// | セクションヘッダ(e_shnum個) |
// : :
// | |
// +- - - - - - - - - - - - - - - +
// | 4バイト境界までパディング |
// +------------------------------+ ここからシンボル情報
// | セクション名テーブル | 一つ目はセクション名テーブル
// +- - - - - - - - - - - - - - - +
// | 4バイト境界までパディング |
// +------------------------------+
// | SYMTAB or STRTAB #n |
// +- - - - - - - - - - - - - - - +
// | 4バイト境界までパディング |
// +------------------------------+
// | SYMTAB or STRTAB #m |
// +- - - - - - - - - - - - - - - +
// | 4バイト境界までパディング |
// +------------------------------+
//
// SYMTAB、STRTAB はあるだけ全部並べるが、おそらく最小セットは次の3つ。
//
// 1. セクション名テーブル
// セクションタイプ STRTAB のうちセクション番号が ELF ヘッダの e_shstrndx
// で指定されているもの。セクション名 (".text" とか) が入っている。
// 実行には不要だが、セクション名比較が必要なケースがあるので置いてある。
//
// 2. シンボルテーブル
// セクションタイプが SYMTAB のもの。
//
// 3. 文字列テーブル
// セクションタイプ STRTAB のうち、前述の SYMTAB セクションの sh_link が
// このセクション番号を指しているもの。
//
// 各セクション内の情報はセクション内で閉じておりリロケータブル。
// ELF ヘッダとセクションヘッダは元の ELF バイナリから抜き出したため、
// オフセットを持っている箇所 (のうち NetBSD がシンボル解決のために参照する
// もの) は、ここで書き換える必要がある。その時の起点は ELF ヘッダの先頭
// (elfp)。具体的には、ELF ヘッダにあるセクションヘッダの位置 (e_shoff) と
// 各セクションヘッダにある自セクションの位置 (sh_offset)。のはず。
//
bool
BootLoader::Load_elf32_sym(LoadInfo *info, std::string& bootmsg)
{
// アラインメントが必要な時は32ビット(4バイト)。
// これは ELF の制約ではなく NetBSD のローダの話。
const uint ELFROUND = 4;
Elf32_Ehdr elf;
std::vector<char> shstr;
uint32 maxp;
// ELF ヘッダをローカルに読み込む。
// ここは後で書き換えて RAM に書き出すので、中身は BE のまま。
// それとマクロのためどのみち *ehdr は必要。
memcpy(&elf, info->data, sizeof(elf));
const Elf32_Ehdr *ehdr = &elf;
uint16 e_machine = elf16toh(elf.e_machine);
uint32 e_shoff = elf32toh(elf.e_shoff);
uint16 e_shnum = elf16toh(elf.e_shnum);
uint16 e_shstrndx = elf16toh(elf.e_shstrndx);
if (loglevel >= 1) {
putmsgn("e_shoff = $%08x", e_shoff);
putmsgn("e_shnum = %d", e_shnum);
putmsgn("e_shstrndx = %d", e_shstrndx);
}
maxp = info->end;
maxp = roundup(maxp, ELFROUND);
/*
* Load the ELF HEADER, SECTION HEADERS and possibly the SYMBOL
* SECTIONS.
*/
// ここに ELF ヘッダだが、書き出す内容は後で決まるので、
// ここではサイズ分空けておくだけ。
uint32 elfp = maxp;
maxp += sizeof(Elf32_Ehdr);
// セクションヘッダをワークの shp に読み込む。
uint32 sz = sizeof(Elf32_Shdr) * e_shnum;
std::vector<Elf32_Shdr> shp(e_shnum);
memcpy(&shp[0], info->data + e_shoff, sz);
// ここを shpp にして、maxp はセクションヘッダ分進める。
uint32 shpp = maxp;
maxp += roundup(sz, ELFROUND);
// section names セクションをローカルの shstr に読み込む。
uint32 shstroff = 0;
uint32 shstrsz = 0;
if (e_shstrndx != SHN_UNDEF) {
shstroff = elf32toh(shp[e_shstrndx].sh_offset);
shstrsz = elf32toh(shp[e_shstrndx].sh_size);
shstr.resize(shstrsz);
memcpy(&shstr[0], info->data + shstroff, shstrsz);
}
// ここでやっと、この ELF バイナリが NetBSD のものかどうか調べる。
// 本当は NetBSD カーネルに限定したいけど、特定方法が分からないので
// とりあえず NetBSD 製バイナリというところまで。NetBSD 製バイナリは
// ".note.netbsd.ident" という名前の NOTE セクションを持ってるようだ。
// 知らんけど。
bool is_netbsd = false;
for (int i = 0; i < e_shnum; i++) {
uint32 sh_name = elf32toh(shp[i].sh_name); // セクション名
uint32 sh_type = elf32toh(shp[i].sh_type); // タイプ
if (sh_type == SHT_NOTE &&
strcmp(&shstr[sh_name], ".note.netbsd.ident") == 0)
{
is_netbsd = true;
break;
}
}
// 案の定というか非公式 NetBSD/luna88k はベースが古すぎるせいか
// ".note.netbsd.ident" セクションを持っていない。
// 仕方ないので M88k の ELF で OpenBSD 用セクションを持ってなければ
// NetBSD ということにする…。
if (e_machine == EM_88K) {
// プログラムヘッダから PT_OPENBSD_* を探すのでもよいが、
// この関数内はもうセクションヘッダを触っているので、ここでも
// セクションヘッダから名前を引くことにする。
bool is_openbsd = false;
for (int i = 0; i < e_shnum; i++) {
uint32 sh_name = elf32toh(shp[i].sh_name); // セクション名
uint32 sh_type = elf32toh(shp[i].sh_type); // タイプ
if (sh_type == SHT_PROGBITS &&
strcmp(&shstr[sh_name], ".openbsd.randomdata") == 0)
{
is_openbsd = true;
break;
}
}
if (is_openbsd == false) {
is_netbsd = true;
}
}
if (is_netbsd == false) {
putmsg(1, "symbol not loaded (no NetBSD binary)");
return true;
}
/*
* First load the section names section. Only useful for CTF.
*/
// まず、さっき読んだ section names セクションを RAM にコピー。
if (e_shstrndx != SHN_UNDEF) {
mainram->WriteMem(maxp, info->data + shstroff, shstrsz);
shp[e_shstrndx].sh_offset = htoelf32(maxp - elfp);
maxp += roundup(shstrsz, ELFROUND);
}
/*
* Now load the symbol sections themselves. Make sure the sections are
* ELFROUND-aligned. Update sh_offset to be relative to elfp. Set it to
* zero when we don't want the sections to be taken care of, the kernel
* will properly skip them.
*/
// シンボルセクションを読み込む。セクションは ELFROUND 境界に整列。
bool first = true;
for (int i = 1; i < e_shnum; i++) {
uint32 sh_name = elf32toh(shp[i].sh_name); // セクション名
uint32 sh_type = elf32toh(shp[i].sh_type); // タイプ
uint32 sh_addr = elf32toh(shp[i].sh_addr); // アドレス
uint32 sh_offset = elf32toh(shp[i].sh_offset); // ファイル先頭から
uint32 sh_size = elf32toh(shp[i].sh_size); // サイズ
if (loglevel >= 1) {
putmsgn("[%2u] sh_name = %s", i, &shstr[sh_name]);
putmsgn(" sh_type = $%08x (%s)",
sh_type, GetElfShTypeStr(sh_type));
putmsgn(" sh_addr = $%08x", sh_addr);
putmsgn(" sh_offset = $%08x", sh_offset);
putmsgn(" sh_size = $%08x", sh_size);
}
if (i == e_shstrndx) {
// このセクションはすでに読んでいる
continue;
}
switch (sh_type) {
case SHT_PROGBITS:
// PROGBITS は通常シンボルではないが、
// ".SUNW_ctf" セクションにはシンボルが入ってるようだ。
if (shstr.empty() == false) {
/* got a CTF section? */
if (strcmp(&shstr[sh_name], ".SUNW_ctf") == 0) {
goto havesym;
}
}
shp[i].sh_offset = htoelf32(0);
break;
case SHT_STRTAB:
// 文字列テーブルセクションはたぶん2つあって、
// 片方はセクション名テーブルで、これはカーネルには必要ない。
// もう一方の文字列テーブルはシンボルテーブルに対応する文字列
// テーブルなので、こっちが実行時のシンボルとして必要。
// これは SYMTAB の link フィールドから差されているほう。
//
// Sec# Name Type Link
// [15] .symtab SYMTAB 16 <- シンボル情報
// [16] .strtab STRTAB 0 <- [15] に対応するシンボルテーブル
// [17] .shstrtab STRTAB 0 <- セクション名テーブル
for (int j = 1; j < e_shnum; j++) {
uint32 shj_type = elf32toh(shp[j].sh_type);
uint32 shj_link = elf32toh(shp[j].sh_link);
if (shj_type == SHT_SYMTAB && shj_link == (uint)i) {
goto havesym;
}
}
/*
* Don't bother with any string table that isn't
* referenced by a symbol table.
*/
shp[i].sh_offset = htoelf32(0);
break;
case SHT_SYMTAB:
havesym:
bootmsg += string_format("%s%d", (first ? " [" : "+"), sh_size);
mainram->WriteMem(maxp, info->data + sh_offset, sh_size);
shp[i].sh_offset = htoelf32(maxp - elfp);
maxp += roundup(sh_size, ELFROUND);
first = false;
break;
case SHT_NOTE:
{
struct __packed {
Elf32_Nhdr nh;
uint8_t name[ELF_NOTE_NETBSD_NAMESZ + 1];
uint8_t desc[ELF_NOTE_NETBSD_DESCSZ];
} note;
if (sh_size < sizeof(note)) {
shp[i].sh_offset = htoelf32(0);
break;
}
// 今の所 NOTE に使いみちはないけどとりあえず真似ておく
memcpy(¬e, info->data + sh_offset, sizeof(note));
uint32 n_namesz = elf32toh(note.nh.n_namesz);
uint32 n_descsz = elf32toh(note.nh.n_descsz);
uint32 n_type = elf32toh(note.nh.n_type);
if (n_namesz == ELF_NOTE_NETBSD_NAMESZ &&
n_descsz == ELF_NOTE_NETBSD_DESCSZ &&
n_type == ELF_NOTE_TYPE_NETBSD_TAG &&
memcmp(note.name, ELF_NOTE_NETBSD_NAME, sizeof(note.name)) == 0)
{
uint32 netbsd_version;
memcpy(&netbsd_version, ¬e.desc, sizeof(netbsd_version));
putmsg(1, "netbsd_version=%d", elf32toh(netbsd_version));
}
shp[i].sh_offset = htoelf32(0);
break;
}
default:
shp[i].sh_offset = htoelf32(0);
break;
}
}
if (first == false) {
bootmsg += ']';
}
if (loglevel >= 1) {
putmsgn("Modified Section Header:");
for (int k = 0; k < elf16toh(elf.e_shnum); k++) {
auto& s = shp[k];
putmsgn("[%2u] %-8s offset=%08x size=%08x",
k,
GetElfShTypeStr(elf32toh(s.sh_type)) + 4,
elf32toh(s.sh_offset),
elf32toh(s.sh_size));
}
}
// ローカルの shp を shpp の位置に書き出す。
// このために shp はターゲットエンディアンになっている。
mainram->WriteMem(shpp, &shp[0], sz);
/*
* Update the ELF HEADER to give information relative to elfp.
*/
elf.e_phoff = htoelf32(0);
elf.e_shoff = htoelf32(sizeof(Elf32_Ehdr));
elf.e_phentsize = htoelf16(0);
elf.e_phnum = htoelf16(0);
if (loglevel >= 1) {
putmsgn("Modified ELF Header:");
putmsgn(" e_type = $%04x", elf16toh(elf.e_type));
putmsgn(" e_entry = $%08x", elf32toh(elf.e_entry));
putmsgn(" e_phoff = $%08x", elf32toh(elf.e_phoff));
putmsgn(" e_shoff = $%08x", elf32toh(elf.e_shoff));
putmsgn(" e_phentsize = $%04x x %d",
elf16toh(elf.e_phentsize), elf16toh(elf.e_phnum));
putmsgn(" e_shentsize = $%04x x %d",
elf16toh(elf.e_shentsize), elf16toh(elf.e_shnum));
}
mainram->WriteMem(elfp, &elf, sizeof(elf));
info->sym = elfp;
info->end = maxp;
return true;
}
// ホストの ELF オブジェクトの text セクションをゲストの RAM に読み込む。
// .o(オブジェクトファイル) 用。
// 読み込めたら true、エラーなら false を返す。
bool
BootLoader::Load_elf32_rel(LoadInfo *info)
{
const Elf32_Ehdr *ehdr = (const Elf32_Ehdr *)info->data;
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) {
putmsgn("e_shoff = $%08x", e_shoff);
putmsgn("e_shentsize = $%04x", e_shentsize);
putmsgn("e_shnum = %d", e_shnum);
}
if (e_shentsize != sizeof(Elf32_Shdr)) {
warnx("%s \"%s\" unknown section header size 0x%x", __func__,
info->path, e_shentsize);
return false;
}
IODeviceStream ds(mainram, entry);
for (int j = 0; j < e_shnum; j++) {
const Elf32_Shdr *shdr =
&((const Elf32_Shdr*)(info->data + 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) {
putmsgn("[%d] sh_type = $%08x (%s)", j,
sh_type, GetElfShTypeStr(sh_type));
putmsgn(" sh_addr = $%08x", sh_addr);
putmsgn(" sh_offset = $%08x", sh_offset);
putmsgn(" sh_size = $%08x", sh_size);
}
if (sh_type == SHT_PROGBITS) {
ds.WriteMem(info->data + sh_offset, sh_size);
} else if (sh_type == SHT_RELA || sh_type == SHT_REL) {
// 再配置情報があるのは .R 形式ではない
warnx("%s \"%s\" has relocation section", __func__, info->path);
return false;
} else {
// ?
}
}
info->start = entry;
info->entry = entry;
info->end = ds.GetAddr();
return true;
}
//
// LoadInfo クラス
//
LoadInfo::LoadInfo()
{
loadsym = true;
entry = -1;
}
LoadInfo::LoadInfo(const char *path_)
: LoadInfo()
{
path = path_;
}
LoadInfo::LoadInfo(const char *path_, const uint8 *data_, size_t size_)
: LoadInfo(path_)
{
data = data_;
size = size_;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.