エミュレータ開発日記 in Rust~ELFローダ編①~
ここ半年ほど、RustでCPUエミュレータを書いています。 今更ですが、これも記録に残していこうと思います。
誰かの役に立つかもしれないですし。
今は、ELFローダを作っています。
これまでは、objcopy
コマンドでraw binaryを作成し、特定アドレスにマッピングする、という実装で動いていました。
エミュレータのテストで、ELFファイルを直接動かした方が楽な状況になったので、ELFローダを作ることにしました。
ELFを解析するcrateは既に存在しますが、ELFを解析するプログラムを作ったことがないため、自作します。 何かを理解したい時には、フルスクラッチで作る、そして、文書を書く、これが一番です。
当面の目標
readelf
で得られるヘッダ情報をエミュレータ内で扱えるようにバイナリをパースしていきます。
特にエントリポイントと、プログラムヘッダ情報を読み出すあたりが重要です。
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: RISC-V Version: 0x1 Entry point address: 0x80000000 Start of program headers: 52 (bytes into file) Start of section headers: 8692 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) Number of section headers: 6 Section header string table index: 5
はじめの一歩
ELF Identification
ELF形式のファイルの先頭には、ELF Identificationという16バイトのデータが存在します。 先ほど示したヘッダ内の先頭にある部分です。
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0
OS/ABIがUNIX - System V
となっています。OS/ABIのフィールドが0x00
の場合、ELFOSABI_NONEまたはELFOSABI_SYSVです。
今回は、ベアメタルで動作するバイナリを作成しているため、ELFOSABI_NONE
と解釈すると良さそうです。
ということで、とりあえず使用する定数を定義しておきます。
/// 0x7f 'E' 'L' 'F' const HEADER_MAGIC: [u8; 4] = [0x7f, 0x45, 0x4c, 0x46]; const SIZE_ELF_IDENT: usize = 16; const ELF_CLASS_32: u8 = 1; const ELF_DATA_LSB: u8 = 1; const EV_CURRENT: u8 = 1; const ELF_OS_ABI_NONE: u8 = 0;
ELF magic
ELF形式のファイルは、`0x7f454c46'というマジックナンバーから始まります。 適当なELFバイナリ(test-elfとしています)を用意して、マジックナンバーが読み込めるテストからスタートします。
とりあえず、ファイル名を引数に、ElfLoader
のオブジェクトを作成し、ELF形式のファイルかどうか、を確認できるような作りで考えてみます。
#[cfg(test)] mod test { use super::*; #[test] fn is_elf() { let loader = ElfLoader::try_new("test-elf").unwrap(); assert!(loader.is_elf(), "target file is not elf binary"); } }
ElfLoader
は次のようにしました。
try_new
では、ファイルを開いて、mmapしています。
new
ではなく、try_new
としているのは、clippyさんに怒られるからです。
clippyさん的には、newでResultを返すのはお気に召さないようです。
warning: methods called `new` usually return `Self`
is_elf
では、mmapした領域の先頭4バイトのデータが、0x7f454c45
になっているか、を確認します。
use std::fs::File; use memmap::Mmap; // 0x7f 'E' 'L' 'F' const HEADER_MAGIC: [u8; 4] = [0x7f, 0x45, 0x4c, 0x46]; pub struct ElfLoader { mapped_file: Mmap, } impl ElfLoader { pub fn try_new(file_path: &str) -> std::io::Result<ElfLoader> { let file = File::open(&file_path)?; Ok(ElfLoader { mapped_file: unsafe { Mmap::map(&file)? }, }) } fn is_elf(&self) -> bool { self.mapped_file[0..4] == elf::HEADER_MAGIC } }
memmap
crateのMmap::map()
はunsafeな関数です。
リテラシの高いRustプログラマを目指す私としては、unsafe
がなぜunsafeなのか、はたまたsafeなのか、をきちんと書きたいところです。
unsafeな理由は、下記のsafety
に記述されています。
/// ## Safety /// /// All file-backed memory map constructors are marked `unsafe` because of the potential for /// *Undefined Behavior* (UB) using the map if the underlying file is subsequently modified, in or /// out of process. Applications must consider the risk and take appropriate precautions when /// using file-backed maps. Solutions such as file permissions, locks or process-private (e.g. /// unlinked) files exist but are platform specific and limited.
プロセス内外でファイルを書き換えられると未定義動作となる、とあります。 しっかりと作るのであれば、ファイルをロックするオプションを指定して、mmapする必要がありそうですね。
後、適当なELF形式以外のファイルを用意して、同じテストが失敗することを確認しておきました。 これで、ELF magicを読みだすところまで作ることができました。
ElfIdentification struct
次に、ELF Identificationをstructで管理するようにします。 愚直に行きます。
/// File identification in elf header. struct ElfIdentification { magic: [u8; 4], class: u8, endianess: u8, version: u8, os_abi: u8, os_abi_version: u8, reserved: [u8; 7], // zero filled. } impl ElfIdentification { // assumption: `binary` has enough length to read elf identification. fn new(binary: &[u8]) -> ElfIdentification { let mut magic: [u8; 4] = [0; 4]; for (i, b) in binary[0..4].iter().enumerate() { magic[i] = *b; } ElfIdentification { magic, class: binary[4], endianess: binary[5], version: binary[6], os_abi: binary[7], os_abi_version: binary[8], reserved: [0; 7], } } }
ちゃんと読み込めているかどうか、テストしておきましょう。
// Check the ELF identification is as bellow: // Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 // Class: ELF32 // Data: 2's complement, little endian // Version: 1 (current) // OS/ABI: UNIX - System V #[test] fn elf_identification() { let file = File::open("test-elf").unwrap(); let mapped_file = unsafe { Mmap::map(&file).unwrap() }; let identification = ElfIdentification::new(&mapped_file); assert_eq!(ELF_CLASS_32, identification.class); assert_eq!(ELF_DATA_LSB, identification.endianess); assert_eq!(EV_CURRENT, identification.version); assert_eq!(ELF_OS_ABI_NONE, identification.os_abi); }
このテストは通りました。これで、ELF Identificationの読み込みが完成です!
参考
リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY)
- 作者: 坂井弘亮
- 出版社/メーカー: CQ出版
- 発売日: 2010/08/01
- メディア: 単行本
- 購入: 6人 クリック: 55回
- この商品を含むブログ (16件) を見る