OS作成 - elfローダ編
Linkers & Loadersを斜め読みしました。例によって理解度は3%くらいなのですが、自分には知らない事が盛りだくさん(ライブラリのメジャー、マイナーバージョン規則やダイナミックリンクの手法等)あったので収穫は大きいです。ただ特定のフォーマット(elf等)のローダを書くときにこの本が資料として十分かというとちょっと足りない気がします。IBMのOS/360のオブジェクト構造とか歴史的な読み物としてはおもしろいのですが実践的には使えません(汗。
再配置が発生しないコードならほぼ今やってるCOM形式のようなノリで書けると思いますが、そのまえにちょっとmonaのコードを探索してみます。
mona/core/elf_server/main.cpp
static void MessageLoop() { for (MessageInfo msg;;) { if (Message::receive(&msg) != 0) continue; switch (msg.header) { case MSG_DISPOSE_HANDLE: MemoryMap::unmap(msg.arg1); Message::reply(&msg); break; case MSG_PROCESS_CREATE_IMAGE: { monapi_cmemoryinfo* mi = NULL; uint32_t entryPoint = 0; int result = CreateImage(&mi, &entryPoint, msg.str, msg.arg1 == MONAPI_TRUE); if (result == 0) { char buf[16]; sprintf(buf, "%d", mi->Size); Message::reply(&msg, mi->Handle, entryPoint, buf); monapi_cmemoryinfo_delete(mi); } else { Message::reply(&msg, 0, result); } break; } default: break; } }
Monaのメッセージング機構はソース読んで無いので知らないのですが、ファイルの拡張子が".EL"だった場合はelf形式のファイルと判断し、このelf serverにメッセージが送られてきます。MessageLoop()がそれを受け取りprocessを作成する時にはCreateImage()関数が呼ばれています。そこで作成した結果を共有メモリ用monapi_cmemoryinfo構造体に格納しエントリポイントと共にreplyしてます。
では、CreateImage()の処理。
CreateImageは同じ関数名の関数が二つ存在し、まずはファイルが圧縮されているかどうかを調べ、圧縮されていれば
mi = monapi_call_file_decompress_bz2_file(path, prompt ? MONAPI_TRUE : MONAPI_FALSE);
のように解凍し共有メモリに読み込み、通常のファイルであれば
mi = monapi_file_read_all(path);
のようにそのまま共有メモリに読み込みます。
上記のように処理されたデータはメインのCreateImage関数に渡されます。
static int CreateImage(monapi_cmemoryinfo** dest, uint32_t* entryPoint, monapi_cmemoryinfo* mi, bool prompt) { ELFParser parser; if (!parser.set(mi->Data, mi->Size)) { if (prompt) _printf("%s: file type is not ELF!\n", SVR); return 3; } int type = parser.getType(); if (type != ELFParser::TYPE_RELOCATABLE && type != ELFParser::TYPE_EXECUTAB LE) { if (prompt) _printf("%s: file type is not supported!\n", SVR); return 3; } int result = parser.parse(); if (result != 0) { if (prompt) _printf("%s: can not parse!\n", SVR); return 3; } monapi_cmemoryinfo* dst = monapi_cmemoryinfo_new(); if (!monapi_cmemoryinfo_create(dst, parser.getImageSize(), prompt ? MONAPI_ TRUE : MONAPI_FALSE)) { monapi_cmemoryinfo_delete(dst); return 3; } if (!parser.load(dst->Data)) { if (prompt) _printf("%s: load failed!\n", SVR); monapi_cmemoryinfo_delete(dst); return 3; } *dest = dst; *entryPoint = parser.getEntryPoint(); return 0; }
miに格納されているデータをELFParserクラスで解析し、最終的にparser.load(dst->Data)でプロセス用のメモリ領域にロードしています。
mona/core/elf_server/elfparser.h
class ELFParser { (snip) private: uint8_t* elf; ELFHeader* header; ELFProgramHeader* pheader; ELFSectionHeader* sheader; ELFSymbolEntry* symbols; uint32_t sectionNames, symbolNames; uint32_t startAddr, endAddr, imageSize; (snip) } ** mona/core/elf_server/elfparser.cpp >|cpp| bool ELFParser::set(uint8_t* elf, uint32_t size) { /* ELF Header */ if (size < sizeof(ELFHeader)) return false; this->header = (ELFHeader*)elf; int type = this->getType(); if (type == TYPE_NOT_ELF) return false; /* Program Header */ uint32_t phdrend = this->header->phdrpos + sizeof(ELFProgramHeader) * this- >header->phdrcnt; if (size < phdrend) return false; this->pheader = (ELFProgramHeader*)&elf[this->header->phdrpos]; /* Section Header */ uint32_t shdrend = this->header->shdrpos + sizeof(ELFSectionHeader) * this- >header->shdrcnt; if (size < shdrend) return false; this->sheader = (ELFSectionHeader*)&elf[this->header->shdrpos]; (snip) }
取得したファイルの共有メモリ上のデータmi->DataよりELFヘッダ、ELFプログラムヘッダ、ELFセクションヘッダを取得している。
(snip) /* find min of start address, and max of end address */ this->startAddr = 0xFFFFFFFF; this->endAddr = 0; for (int i = 0; i < this->header->phdrcnt; i++) { ELFProgramHeader p = this->pheader[i]; if (p.type != PT_LOAD) continue; if (p.virtualaddr < this->startAddr) this->startAddr = p.virtualaddr; if ((p.virtualaddr + p.memorysize) > this->endAddr) this->endAddr = p.v irtualaddr + p.memorysize; } for (int i = 0; i < this->header->shdrcnt; i++) { ELFSectionHeader s = this->sheader[i]; if (!(s.flags & ALLOC)) continue; if (s.address < this->startAddr) this->startAddr = s.address; if ((s.address + s.size) > this->endAddr) this->endAddr = s.address + s .size; } (snip)
ここでは各セクションのベースアドレスとサイズの値からこのプロセスのstart addressとend addressを算出している。
CreateImageのparser.getType()ではこのファイルが再配置可能か実行可能かをELFヘッダーより取得。parser.parse()ではおそらく再配置用の解析を行う予定なのだろうが、実質何もしていなかった。どうもMonaはPEの方がメイン(PEではDLL用の再配置処理もやっているようだった)でelfはさほど力を入れていない様子(開発環境がcygwinメインでelfの扱いが難しいという理由なのかな?)。
bool ELFParser::load(uint8_t* image) { switch (this->getType()) { case TYPE_EXECUTABLE: memset(image, 0, this->imageSize); for (int i = 0; i < this->header->phdrcnt; i++) { ELFProgramHeader p = this->pheader[i]; if (p.type != PT_LOAD) continue; memcpy(&image[p.virtualaddr - this->startAddr], &this->elf[p.of fset], p.filesize); } return true; case TYPE_RELOCATABLE: break; } return false; }
parser.load()ではELFプログラムヘッダより配置先の仮想アドレスに対して読み込みファイル内のセクションoffsetからのデータをmemcpyでコピーしているだけ。
実行可能なファイルを読み込むだけであればやはりかなり簡単に出来そうですね。