FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RustでELFファイルを開く方法を調査する (1. ELFファイルをRustで開く方法)

非常に久しぶりにRustを触っている。RISC-Vシミュレータ以外にもRustでやってみたいことがあって色々調べているのだが、RustでELFファイルなどのバイナリを扱う方法を少し調べていた。シミュレータの用途でもあるし、別の用途でもELFファイルを扱えるといろいろ便利だ。

以下のブログを参考にした。なるほど、mmapというのを使えばELFをメモリにマッピングしているような状態で扱うことができるらしい。

tomo-wait-for-it-yuki.hatenablog.com

  • elf_loader/src/main.rs
use std::fs::File;
use memmap::Mmap;
use std::env;

// 0x7f 'E' 'L' 'F'
const HEADER_MAGIC: [u8; 4] = [0x7f, 0x45, 0x4c, 0x46];

const EM_RISCV: u16 = 243;

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)? },
        })
    }
    ...

main()内で以下のようにELFのファイル名を指定してファイルをオープンする。

fn main() {
    let args: Vec<String> = env::args().collect();

    let filename = &args[1];
    let loader = match ElfLoader::try_new(filename) {
        Ok(loader) => loader,
        Err(error) => {
            panic!("There was a problem opening the file: {:?}", error)
        },
    };
...

ロードに成功すれば、次にELFのビットフィールドを読んでいきいろんな情報を収集する。

    if loader.is_elf() {
        println!("OK!");
        println!("EI_CLASS   = {}", loader.get_ei_class());
        println!("EI_DATA    = {}", loader.get_ei_data());
        println!("EI_VERSION = {}", loader.get_ei_version());

        println!("EI_MACHINE = {}", loader.get_ei_machine_string());

    } else {
        println!("Not ELF file!");
    }

これで実行してみる。

$ cargo run ${HOME}/riscv64/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-p-simple
OK!
EI_CLASS   = 2
EI_DATA    = 1
EI_VERSION = 1
EI_MACHINE = RISCV
$ cargo run target/debug/elf_loader
OK!
EI_CLASS   = 2
EI_DATA    = 1
EI_VERSION = 1
EI_MACHINE = X86_64

とりあえずまずは最初の段階までたどり着いた。

もうすこしELFの読み取りを進めていく。少なくと各セクションのデータを読み込んで、逆アセンブリが出力できる程度にはしておきたいな。

ELFヘッダの位置は、/usr/include/elf.hを確認すると、以下のような順番で格納されている。以下は64ビットの例:

  • /usr/include/elf.h
typedef struct
{
  unsigned char   e_ident[EI_NIDENT]; /* Magic number and other info */
  Elf64_Half    e_type;         /* Object file type */
  Elf64_Half    e_machine;      /* Architecture */
  Elf64_Word    e_version;      /* Object file version */
  Elf64_Addr    e_entry;        /* Entry point virtual address */
  Elf64_Off e_phoff;        /* Program header table file offset */
  Elf64_Off e_shoff;        /* Section header table file offset */
  Elf64_Word    e_flags;        /* Processor-specific flags */
  Elf64_Half    e_ehsize;       /* ELF header size in bytes */
  Elf64_Half    e_phentsize;        /* Program header table entry size */
  Elf64_Half    e_phnum;        /* Program header table entry count */
  Elf64_Half    e_shentsize;        /* Section header table entry size */
  Elf64_Half    e_shnum;        /* Section header table entry count */
  Elf64_Half    e_shstrndx;     /* Section header string table index */
} Elf64_Ehdr;

この通りにフィールドを切り取っていくことにした。まずは定数を定義して簡単に切り取れるようにする。

  • elf_loader/src/main.rs
/* 64bit architectures */
const ELF64_ADDR_SIZE   :usize = mem::size_of::<u64>();
const ELF64_OFF_SIZE    :usize = mem::size_of::<u64>();
const ELF64_SWORD_SIZE  :usize = mem::size_of::<i32>();
const ELF64_WORD_SIZE   :usize = mem::size_of::<u32>();
const ELF64_HALF_SIZE   :usize = mem::size_of::<u16>();

const E_TYPE_START_BYTE      :usize = 16;
const E_TYPE_SIZE_BYTE       :usize = ELF64_HALF_SIZE;
const E_MACHINE_START_BYTE   :usize = E_TYPE_START_BYTE + E_TYPE_SIZE_BYTE;
const E_MACHINE_SIZE_BYTE    :usize = ELF64_HALF_SIZE;
const E_VERSION_START_BYTE   :usize = E_MACHINE_START_BYTE + E_MACHINE_SIZE_BYTE;
const E_VERSION_SIZE_BYTE    :usize = ELF64_WORD_SIZE;
const E_ENTRY_START_BYTE     :usize = E_VERSION_START_BYTE + E_VERSION_SIZE_BYTE;
const E_ENTRY_SIZE_BYTE      :usize = ELF64_ADDR_SIZE;
...

これに基づいてELFの情報を切り取っていく。ダサいが以下のような実装にしている。もうちょっとビットフィールド切り取りを綺麗に作りたいが、まずは正確動作最優先。

    fn get_e_version(&self) -> u64 {
        (self.mapped_file[E_VERSION_START_BYTE + 3] as u64) << 24 |
        (self.mapped_file[E_VERSION_START_BYTE + 2] as u64) << 16 |
        (self.mapped_file[E_VERSION_START_BYTE + 1] as u64) <<  8 |
        (self.mapped_file[E_VERSION_START_BYTE + 0] as u64)
    }

    fn get_e_entry(&self) -> u64 {
        (self.mapped_file[E_ENTRY_START_BYTE + 3] as u64) << 24 |
        (self.mapped_file[E_ENTRY_START_BYTE + 2] as u64) << 16 |
        (self.mapped_file[E_ENTRY_START_BYTE + 1] as u64) <<  8 |
        (self.mapped_file[E_ENTRY_START_BYTE + 0] as u64)
    }

    fn get_e_phoff(&self) -> u64 {
        (self.mapped_file[E_PHOFF_START_BYTE + 3] as u64) << 24 |
        (self.mapped_file[E_PHOFF_START_BYTE + 2] as u64) << 16 |
        (self.mapped_file[E_PHOFF_START_BYTE + 1] as u64) <<  8 |
        (self.mapped_file[E_PHOFF_START_BYTE + 0] as u64)
    }

    fn get_e_shoff(&self) -> u64 {
        (self.mapped_file[E_SHOFF_START_BYTE + 3] as u64) << 24 |
        (self.mapped_file[E_SHOFF_START_BYTE + 2] as u64) << 16 |
        (self.mapped_file[E_SHOFF_START_BYTE + 1] as u64) <<  8 |
        (self.mapped_file[E_SHOFF_START_BYTE + 0] as u64)
    }

    /* Processor-specific flags */
    fn get_e_flags(&self) -> u32 {
        (self.mapped_file[E_FLAGS_START_BYTE + 3] as u32) << 24 |
        (self.mapped_file[E_FLAGS_START_BYTE + 2] as u32) << 16 |
        (self.mapped_file[E_FLAGS_START_BYTE + 1] as u32) <<  8 |
        (self.mapped_file[E_FLAGS_START_BYTE + 0] as u32)
    }
    /* ELF header size in bytes */
    fn get_e_ehsize(&self) -> u32 {
        (self.mapped_file[E_EHSIZE_START_BYTE + 1] as u32) <<  8 |
        (self.mapped_file[E_EHSIZE_START_BYTE + 0] as u32)
    }
    /* Program header table entry size */
    fn get_e_phentsize(&self) -> u32 {
        (self.mapped_file[E_PHENTSIZE_START_BYTE + 1] as u32) <<  8 |
...

main()println!で以下のようにダンプする。

    if loader.is_elf() {
        println!("OK!");
        println!("EI_CLASS   = {}", loader.get_ei_class());
        println!("EI_DATA    = {}", loader.get_ei_data());
        println!("EI_VERSION = {}",   loader.get_ei_version());

        println!("\n");
        println!("E_TYPE      = {}",   loader.get_e_type_string());
        println!("E_MACHINE   = {}",   loader.get_e_machine_string());
        println!("E_VERSION   = {}",   loader.get_e_version());
        println!("E_ENTRY     = {:x}", loader.get_e_entry());
        println!("E_PHOFF     = {:x}", loader.get_e_phoff());
        println!("E_SHOFF     = {:x}", loader.get_e_shoff());
        println!("E_FLAGS     = {}",   loader.get_e_flags());
        println!("E_EHSIZE    = {}",   loader.get_e_ehsize());
        println!("E_PHENTSIZE = {}",   loader.get_e_phentsize());
        println!("E_PHNUM     = {}",   loader.get_e_phnum());
        println!("E_SHENTSIZE = {}",   loader.get_e_shentsize());
        println!("E_SHNUM     = {}",   loader.get_e_shnum());
        println!("E_SHSTRNDX  = {}",   loader.get_e_shstrndx());
...
$ cargo run ${HOME}/riscv64/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-p-simple
OK!
EI_CLASS   = 2
EI_DATA    = 1
EI_VERSION = 1


E_TYPE      = ET_EXEC
E_MACHINE   = RISCV
E_VERSION   = 1
E_ENTRY     = 80000000
E_PHOFF     = 40
E_SHOFF     = 2270
E_FLAGS     = 0
E_EHSIZE    = 64
E_PHENTSIZE = 56
E_PHNUM     = 2
E_SHENTSIZE = 64
E_SHNUM     = 6
E_SHSTRNDX  = 5

無事に切り取ることができた。