RISC-V ISA Simulator系列之fesvr<1>

深入解析 FESVR(Front-End Server)

url: https://github.com/riscv/riscv-isa-sim.git
commid: fcbdbe7946079650d0e656fa3d353e3f652d471f

代码链接

目录

  1. FESVR 概述
  2. FESVR 代码结构分析
  3. ELF 加载机制

1. FESVR 概述

fesvr(Front-End Server)是 riscv-isa-sim(Spike 模拟器)中的一个核心组件,它负责 模拟器与外部环境之间的交互。主要功能包括:

  • ELF 加载:将 RISC-V 二进制程序加载到模拟器内存。
  • 系统调用处理:拦截并模拟 RISC-V 目标的 ecall,并在宿主机执行相应的系统调用。
  • 与 Spike 交互:通过 HTIF(Host-Target Interface)进行内存访问、I/O 操作等。

在 RISC-V 仿真环境中,fesvr 扮演 仿真程序的桥梁,确保 Spike 能够正确模拟 RISC-V 目标程序的执行。


2. FESVR 代码结构分析

fesvr 的代码主要位于 riscv-isa-sim/fesvr/ 目录,主要文件及作用如下:

fesvr 目录下的源码文件清单,涉及 ELF 加载、设备仿真、HTIF 交互、系统调用 等多个模块。下面是各文件的简要介绍:

1. ELF 相关文件

  • elf.h:ELF 格式的定义,包含 ELF 头结构等信息。
  • elfloader.h / elfloader.cc:解析 ELF 文件,并将程序加载到目标内存。
  • elf2hex.cc:是将 ELF 转换为 HEX 格式的工具。
1.1 elf.h

elf.h文件内容如下:

// See LICENSE for details.

#ifndef _ELF_H
#define _ELF_H

#include <stdint.h>

#define ET_EXEC 2
#define EM_RISCV 243
#define EM_NONE 0
#define EV_CURRENT 1

#define IS_ELF(hdr) \
  ((hdr).e_ident[0] == 0x7f && (hdr).e_ident[1] == 'E' && \
   (hdr).e_ident[2] == 'L'  && (hdr).e_ident[3] == 'F')

#define ELF_SWAP(hdr, val) (IS_ELFLE(hdr)? from_le((val)) : from_be((val)))

#define IS_ELF32(hdr) (IS_ELF(hdr) && (hdr).e_ident[4] == 1)
#define IS_ELF64(hdr) (IS_ELF(hdr) && (hdr).e_ident[4] == 2)
#define IS_ELFLE(hdr) (IS_ELF(hdr) && (hdr).e_ident[5] == 1)
#define IS_ELFBE(hdr) (IS_ELF(hdr) && (hdr).e_ident[5] == 2)
#define IS_ELF_EXEC(hdr) (IS_ELF(hdr) && ELF_SWAP((hdr), (hdr).e_type) == ET_EXEC)
#define IS_ELF_RISCV(hdr) (IS_ELF(hdr) && ELF_SWAP((hdr), (hdr).e_machine) == EM_RISCV)
#define IS_ELF_EM_NONE(hdr) (IS_ELF(hdr) && ELF_SWAP((hdr), (hdr).e_machine) == EM_NONE)
#define IS_ELF_VCURRENT(hdr) (IS_ELF(hdr) && ELF_SWAP((hdr), (hdr).e_version) == EV_CURRENT)

#define PT_LOAD 1

#define SHT_NOBITS 8

typedef struct {
  uint8_t  e_ident[16];
  uint16_t e_type;
  uint16_t e_machine;
  uint32_t e_version;
  uint32_t e_entry;
  uint32_t e_phoff;
  uint32_t e_shoff;
  uint32_t e_flags;
  uint16_t e_ehsize;
  uint16_t e_phentsize;
  uint16_t e_phnum;
  uint16_t e_shentsize;
  uint16_t e_shnum;
  uint16_t e_shstrndx;
} Elf32_Ehdr;

typedef struct {
  uint32_t sh_name;
  uint32_t sh_type;
  uint32_t sh_flags;
  uint32_t sh_addr;
  uint32_t sh_offset;
  uint32_t sh_size;
  uint32_t sh_link;
  uint32_t sh_info;
  uint32_t sh_addralign;
  uint32_t sh_entsize;
} Elf32_Shdr;

typedef struct
{
  uint32_t p_type;
  uint32_t p_offset;
  uint32_t p_vaddr;
  uint32_t p_paddr;
  uint32_t p_filesz;
  uint32_t p_memsz;
  uint32_t p_flags;
  uint32_t p_align;
} Elf32_Phdr;

typedef struct
{
  uint32_t st_name;
  uint32_t st_value;
  uint32_t st_size;
  uint8_t  st_info;
  uint8_t  st_other;
  uint16_t st_shndx;
} Elf32_Sym;

typedef struct {
  uint8_t  e_ident[16];
  uint16_t e_type;
  uint16_t e_machine;
  uint32_t e_version;
  uint64_t e_entry;
  uint64_t e_phoff;
  uint64_t e_shoff;
  uint32_t e_flags;
  uint16_t e_ehsize;
  uint16_t e_phentsize;
  uint16_t e_phnum;
  uint16_t e_shentsize;
  uint16_t e_shnum;
  uint16_t e_shstrndx;
} Elf64_Ehdr;

typedef struct {
  uint32_t sh_name;
  uint32_t sh_type;
  uint64_t sh_flags;
  uint64_t sh_addr;
  uint64_t sh_offset;
  uint64_t sh_size;
  uint32_t sh_link;
  uint32_t sh_info;
  uint64_t sh_addralign;
  uint64_t sh_entsize;
} Elf64_Shdr;

typedef struct {
  uint32_t p_type;
  uint32_t p_flags;
  uint64_t p_offset;
  uint64_t p_vaddr;
  uint64_t p_paddr;
  uint64_t p_filesz;
  uint64_t p_memsz;
  uint64_t p_align;
} Elf64_Phdr;

typedef struct {
  uint32_t st_name;
  uint8_t  st_info;
  uint8_t  st_other;
  uint16_t st_shndx;
  uint64_t st_value;
  uint64_t st_size;
} Elf64_Sym;

#endif

上述代码定义了一系列用于处理 ELF(Executable and Linkable Format)文件的宏和结构体,ELF 是一种在 Unix 和类 Unix 系统中广泛使用的二进制文件格式,用于可执行文件、目标文件、共享库等。下面详细分析各个结构定义的作用:

  1. 宏定义
  • 文件类型和常量
    • ET_EXEC:值为 2,表示该 ELF 文件是一个可执行文件。
    • EM_RISCV:值为 243,代表 ELF 文件的目标架构是 RISC - V。
    • EM_NONE:值为 0,表示无特定的目标架构。
    • EV_CURRENT:值为 1,代表当前的 ELF 版本。
  • ELF 文件检查宏
    • IS_ELF(hdr):用于检查给定的 ELF 头 hdr 是否为有效的 ELF 文件,通过检查 e_ident 数组的前四个字节是否为 0x7f'E''L''F' 来判断。
    • ELF_SWAP(hdr, val):根据 ELF 文件的字节序(大端或小端),调用 from_lefrom_be 函数对值 val 进行字节序转换。
  • ELF 文件特性检查宏
    • IS_ELF32(hdr):检查给定的 ELF 头是否表示一个 32 位的 ELF 文件。
    • IS_ELF64(hdr):检查给定的 ELF 头是否表示一个 64 位的 ELF 文件。
    • IS_ELFLE(hdr):检查给定的 ELF 头是否表示一个小端字节序的 ELF 文件。
    • IS_ELFBE(hdr):检查给定的 ELF 头是否表示一个大端字节序的 ELF 文件。
    • IS_ELF_EXEC(hdr):检查给定的 ELF 头是否表示一个可执行的 ELF 文件。
    • IS_ELF_RISCV(hdr):检查给定的 ELF 头是否表示目标架构为 RISC - V 的 ELF 文件。
    • IS_ELF_EM_NONE(hdr):检查给定的 ELF 头是否表示无特定目标架构的 ELF 文件。
    • IS_ELF_VCURRENT(hdr):检查给定的 ELF 头是否使用当前的 ELF 版本。
  • 程序头表和节头表相关宏
    • PT_LOAD:值为 1,表示程序头表中的一个加载段。
    • SHT_NOBITS:值为 8,表示节头表中的一个不占用文件空间的节。
  1. 结构体定义

2.1 32 位 ELF 头结构体 Elf32_Ehdr

typedef struct {
  uint8_t  e_ident[16];
  uint16_t e_type;
  uint16_t e_machine;
  uint32_t e_version;
  uint32_t e_entry;
  uint32_t e_phoff;
  uint32_t e_shoff;
  uint32_t e_flags;
  uint16_t e_ehsize;
  uint16_t e_phentsize;
  uint16_t e_phnum;
  uint16_t e_shentsize;
  uint16_t e_shnum;
  uint16_t e_shstrndx;
} Elf32_Ehdr;
  • 作用:该结构体用于表示 32 位 ELF 文件的文件头,包含了文件的基本信息,如文件类型、目标架构、版本、入口地址、程序头表和节头表的偏移等。
  • 字段解释
    • e_ident:一个长度为 16 的数组,包含了 ELF 文件的标识信息,如文件类型、字节序、版本等。
    • e_type:表示 ELF 文件的类型,如可执行文件、目标文件等。
    • e_machine:表示目标架构。
    • e_version:表示 ELF 文件的版本。
    • e_entry:表示程序的入口地址。
    • e_phoff:表示程序头表在文件中的偏移。
    • e_shoff:表示节头表在文件中的偏移。
    • e_flags:包含一些与目标架构相关的标志。
    • e_ehsize:表示 ELF 文件头的大小。
    • e_phentsize:表示程序头表中每个条目的大小。
    • e_phnum:表示程序头表中的条目数量。
    • e_shentsize:表示节头表中每个条目的大小。
    • e_shnum:表示节头表中的条目数量。
    • e_shstrndx:表示节名表在节头表中的索引。

2.2 32 位节头结构体 Elf32_Shdr

typedef struct {
  uint32_t sh_name;
  uint32_t sh_type;
  uint32_t sh_flags;
  uint32_t sh_addr;
  uint32_t sh_offset;
  uint32_t sh_size;
  uint32_t sh_link;
  uint32_t sh_info;
  uint32_t sh_addralign;
  uint32_t sh_entsize;
} Elf32_Shdr;
  • 作用:该结构体用于表示 32 位 ELF 文件中的节头,每个节头描述了一个节的信息,如节的名称、类型、大小、在文件中的偏移等。
  • 字段解释
    • sh_name:节名在节名表中的索引。
    • sh_type:节的类型,如代码节、数据节等。
    • sh_flags:节的标志,如是否可执行、是否可写等。
    • sh_addr:节在内存中的地址。
    • sh_offset:节在文件中的偏移。
    • sh_size:节的大小。
    • sh_link:与该节相关的其他节的索引。
    • sh_info:与该节相关的额外信息。
    • sh_addralign:节的地址对齐要求。
    • sh_entsize:如果节中包含固定大小的条目,该字段表示每个条目的大小。

2.3 32 位程序头结构体 Elf32_Phdr

typedef struct
{
  uint32_t p_type;
  uint32_t p_offset;
  uint32_t p_vaddr;
  uint32_t p_paddr;
  uint32_t p_filesz;
  uint32_t p_memsz;
  uint32_t p_flags;
  uint32_t p_align;
} Elf32_Phdr;
  • 作用:该结构体用于表示 32 位 ELF 文件中的程序头,每个程序头描述了一个程序段的信息,如段的类型、在文件中的偏移、在内存中的地址等。
  • 字段解释
    • p_type:段的类型,如加载段、动态链接段等。
    • p_offset:段在文件中的偏移。
    • p_vaddr:段在虚拟内存中的地址。
    • p_paddr:段在物理内存中的地址(在大多数情况下与 p_vaddr 相同)。
    • p_filesz:段在文件中占用的大小。
    • p_memsz:段在内存中占用的大小。
    • p_flags:段的标志,如是否可执行、是否可写等。
    • p_align:段的地址对齐要求。

2.4 32 位符号表条目结构体 Elf32_Sym

typedef struct
{
  uint32_t st_name;
  uint32_t st_value;
  uint32_t st_size;
  uint8_t  st_info;
  uint8_t  st_other;
  uint16_t st_shndx;
} Elf32_Sym;
  • 作用:该结构体用于表示 32 位 ELF 文件中的符号表条目,每个符号表条目描述了一个符号的信息,如符号的名称、值、大小等。
  • 字段解释
    • st_name:符号名在符号名表中的索引。
    • st_value:符号的值,通常是一个地址。
    • st_size:符号所占用的大小。
    • st_info:符号的类型和绑定信息。
    • st_other:保留字段。
    • st_shndx:符号所在节的索引。

2.5 64 位 ELF 头结构体 Elf64_Ehdr

typedef struct {
  uint8_t  e_ident[16];
  uint16_t e_type;
  uint16_t e_machine;
  uint32_t e_version;
  uint64_t e_entry;
  uint64_t e_phoff;
  uint64_t e_shoff;
  uint32_t e_flags;
  uint16_t e_ehsize;
  uint16_t e_phentsize;
  uint16_t e_phnum;
  uint16_t e_shentsize;
  uint16_t e_shnum;
  uint16_t e_shstrndx;
} Elf64_Ehdr;
  • 作用:与 Elf32_Ehdr 类似,用于表示 64 位 ELF 文件的文件头,只是部分字段使用了 64 位整数类型。

2.6 64 位节头结构体 Elf64_Shdr

typedef struct {
  uint32_t sh_name;
  uint32_t sh_type;
  uint64_t sh_flags;
  uint64_t sh_addr;
  uint64_t sh_offset;
  uint64_t sh_size;
  uint32_t sh_link;
  uint32_t sh_info;
  uint64_t sh_addralign;
  uint64_t sh_entsize;
} Elf64_Shdr;
  • 作用:与 Elf32_Shdr 类似,用于表示 64 位 ELF 文件中的节头,部分字段使用了 64 位整数类型。

2.7 64 位程序头结构体 Elf64_Phdr

typedef struct {
  uint32_t p_type;
  uint32_t p_flags;
  uint64_t p_offset;
  uint64_t p_vaddr;
  uint64_t p_paddr;
  uint64_t p_filesz;
  uint64_t p_memsz;
  uint64_t p_align;
} Elf64_Phdr;
  • 作用:与 Elf32_Phdr 类似,用于表示 64 位 ELF 文件中的程序头,部分字段使用了 64 位整数类型。

2.8 64 位符号表条目结构体 Elf64_Sym

typedef struct {
  uint32_t st_name;
  uint8_t  st_info;
  uint8_t  st_other;
  uint16_t st_shndx;
  uint64_t st_value;
  uint64_t st_size;
} Elf64_Sym;
  • 作用:与 Elf32_Sym 类似,用于表示 64 位 ELF 文件中的符号表条目,部分字段使用了 64 位整数类型。

综上所述,这些结构体和宏定义提供了一套完整的接口,用于解析和处理 32 位和 64 位的 ELF 文件。

  • elf.h:ELF 格式的定义,包含 ELF 头结构等信息。
  • elfloader.h / elfloader.cc:解析 ELF 文件,并将程序加载到目标内存。
  • elf2hex.cc:是将 ELF 转换为 HEX 格式的工具。
1.2 elfloader.h/elfloader.cc

elfloader.h

#ifndef _ELFLOADER_H
#define _ELFLOADER_H

#include "elf.h"
#include "memif.h"
#include <map>
#include <string>

class memif_t;
std::map<std::string, uint64_t> load_elf(const char* fn, memif_t* memif, reg_t* entry,
                                         reg_t load_offset, unsigned required_xlen = 0);

#endif

elfloader.cc

load_elf 函数实现

// See LICENSE for license details.

#include "config.h"
#include "elf.h"
#include "memif.h"
#include "byteorder.h"
#include <cstring>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>
#include <unistd.h>
#include <stdexcept>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <cerrno>

std::map<std::string, uint64_t> load_elf(const char* fn, memif_t* memif, reg_t* entry,
                                         reg_t load_offset, unsigned required_xlen = 0)
{
  int fd = open(fn, O_RDONLY);
  struct stat s;
  if (fd == -1)
      throw std::invalid_argument(std::string("Specified ELF can't be opened: ") + strerror(errno));
  if (fstat(fd, &s) < 0)
    abort();
  size_t size = s.st_size;

  char* buf = (char*)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (buf == MAP_FAILED)
      throw std::invalid_argument(std::string("Specified ELF can't be mapped: ") + strerror(errno));
  close(fd);

  assert(size >= sizeof(Elf64_Ehdr));
  const Elf64_Ehdr* eh64 = (const Elf64_Ehdr*)buf;
  assert(IS_ELF32(*eh64) || IS_ELF64(*eh64));
  unsigned xlen = IS_ELF32(*eh64) ? 32 : 64;
  if (required_xlen != 0 && required_xlen != xlen) {
    throw incompat_xlen(required_xlen, xlen);
  }
  assert(IS_ELFLE(*eh64) || IS_ELFBE(*eh64));
  assert(IS_ELF_EXEC(*eh64) || IS_ELF_DYN(*eh64));
  assert(IS_ELF_RISCV(*eh64) || IS_ELF_EM_NONE(*eh64));
  assert(IS_ELF_VCURRENT(*eh64));

  if (IS_ELF_EXEC(*eh64)) {
    load_offset = 0;
  }

  std::vector<uint8_t> zeros;
  std::map<std::string, uint64_t> symbols;

#define LOAD_ELF(ehdr_t, phdr_t, shdr_t, sym_t, bswap)                         \
  do {                                                                         \
    ehdr_t* eh = (ehdr_t*)buf;                                                 \
    phdr_t* ph = (phdr_t*)(buf + bswap(eh->e_phoff));                          \
    *entry = bswap(eh->e_entry) + load_offset;                                 \
    assert(size >= bswap(eh->e_phoff) + bswap(eh->e_phnum) * sizeof(*ph));     \
    for (unsigned i = 0; i < bswap(eh->e_phnum); i++) {                        \
      if (bswap(ph[i].p_type) == PT_LOAD && bswap(ph[i].p_memsz)) {            \
        reg_t load_addr = bswap(ph[i].p_paddr) + load_offset;                  \
        if (bswap(ph[i].p_filesz)) {                                           \
          assert(size >= bswap(ph[i].p_offset) + bswap(ph[i].p_filesz));       \
          memif->write(load_addr, bswap(ph[i].p_filesz),                       \
                       (uint8_t*)buf + bswap(ph[i].p_offset));                 \
        }                                                                      \
        if (size_t pad = bswap(ph[i].p_memsz) - bswap(ph[i].p_filesz)) {       \
          zeros.resize(pad);                                                   \
          memif->write(load_addr + bswap(ph[i].p_filesz), pad, zeros.data());  \
        }                                                                      \
      }                                                                        \
    }                                                                          \
    shdr_t* sh = (shdr_t*)(buf + bswap(eh->e_shoff));                          \
    assert(size >= bswap(eh->e_shoff) + bswap(eh->e_shnum) * sizeof(*sh));     \
    assert(bswap(eh->e_shstrndx) < bswap(eh->e_shnum));                        \
    assert(size >= bswap(sh[bswap(eh->e_shstrndx)].sh_offset) +                \
                       bswap(sh[bswap(eh->e_shstrndx)].sh_size));              \
    char* shstrtab = buf + bswap(sh[bswap(eh->e_shstrndx)].sh_offset);         \
    unsigned strtabidx = 0, symtabidx = 0;                                     \
    for (unsigned i = 0; i < bswap(eh->e_shnum); i++) {                        \
      unsigned max_len =                                                       \
          bswap(sh[bswap(eh->e_shstrndx)].sh_size) - bswap(sh[i].sh_name);     \
      assert(bswap(sh[i].sh_name) < bswap(sh[bswap(eh->e_shstrndx)].sh_size)); \
      assert(strnlen(shstrtab + bswap(sh[i].sh_name), max_len) < max_len);     \
      if (bswap(sh[i].sh_type) & SHT_NOBITS) continue;                         \
      assert(size >= bswap(sh[i].sh_offset) + bswap(sh[i].sh_size));           \
      if (strcmp(shstrtab + bswap(sh[i].sh_name), ".strtab") == 0)             \
        strtabidx = i;                                                         \
      if (strcmp(shstrtab + bswap(sh[i].sh_name), ".symtab") == 0)             \
        symtabidx = i;                                                         \
    }                                                                          \
    if (strtabidx && symtabidx) {                                              \
      char* strtab = buf + bswap(sh[strtabidx].sh_offset);                     \
      sym_t* sym = (sym_t*)(buf + bswap(sh[symtabidx].sh_offset));             \
      for (unsigned i = 0; i < bswap(sh[symtabidx].sh_size) / sizeof(sym_t);   \
           i++) {                                                              \
        unsigned max_len =                                                     \
            bswap(sh[strtabidx].sh_size) - bswap(sym[i].st_name);              \
        assert(bswap(sym[i].st_name) < bswap(sh[strtabidx].sh_size));          \
        assert(strnlen(strtab + bswap(sym[i].st_name), max_len) < max_len);    \
        symbols[strtab + bswap(sym[i].st_name)] = bswap(sym[i].st_value) + load_offset;      \
      }                                                                        \
    }                                                                          \
  } while (0)

  if (IS_ELFLE(*eh64)) {
    if (memif->get_target_endianness() != endianness_little) {
      throw std::invalid_argument("Specified ELF is little endian, but system uses a big-endian memory system. Rerun without --big-endian");
    }
    if (IS_ELF32(*eh64))
      LOAD_ELF(Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Sym, from_le);
    else
      LOAD_ELF(Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Sym, from_le);
  } else {
#ifndef RISCV_ENABLE_DUAL_ENDIAN
    throw std::invalid_argument("Specified ELF is big endian.  Configure with --enable-dual-endian to enable support");
#else
    if (memif->get_target_endianness() != endianness_big) {
      throw std::invalid_argument("Specified ELF is big endian, but system uses a little-endian memory system. Rerun with --big-endian");
    }
    if (IS_ELF32(*eh64))
      LOAD_ELF(Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Sym, from_be);
    else
      LOAD_ELF(Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Sym, from_be);
#endif
  }

  munmap(buf, size);

  return symbols;
}

这个 load_elf 函数的主要功能是解析 ELF(Executable and Linkable Format)文件,并将其加载到目标内存接口 memif_t,同时提取 ELF 文件中的符号表。
该函数广泛用于 RISC-V 仿真器(如 Spike)和其他基于 FESVR(Front-End Server)的系统中,以支持程序加载和符号解析。


1. 函数定义

std::map<std::string, uint64_t> load_elf(const char* fn, memif_t* memif, reg_t* entry,
                                         reg_t load_offset, unsigned required_xlen = 0)

参数解析

  • fn:ELF 文件路径

  • memif:目标内存接口,memif_t 对象用于模拟访存操作

  • entry:ELF 的入口地址指针,存储加载的程序的入口地址

  • load_offset:加载偏移量,决定 ELF 代码和数据在模拟内存中的最终地址

  • required_xlen(可选):指定 ELF 文件的位数(32/64 位),用于检查是否与系统兼容

    返回值

  • std::map<std::string, uint64_t>:返回 ELF 符号表,映射符号名称到其地址(用于调试和符号解析)


2. 打开 ELF 文件

int fd = open(fn, O_RDONLY);
if (fd == -1)
    throw std::invalid_argument(std::string("Specified ELF can't be opened: ") + strerror(errno));
  • 只读方式打开 ELF 文件
  • 如果失败,抛出 std::invalid_argument 异常,并返回错误信息(strerror(errno)

3. 获取 ELF 文件大小

struct stat s;
if (fstat(fd, &s) < 0)
    abort();
size_t size = s.st_size;
  • 使用 fstat 获取 ELF 文件大小,存入 s.st_size
  • 失败时直接 abort() 终止程序

4. 使用 mmap 映射 ELF 文件

char* buf = (char*)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED)
    throw std::invalid_argument(std::string("Specified ELF can't be mapped: ") + strerror(errno));
close(fd);
  • mmap 将 ELF 文件映射到进程地址空间
  • 失败时抛出异常
  • 关闭文件描述符 fd(映射后 fd 不再需要)

5. 解析 ELF 头

assert(size >= sizeof(Elf64_Ehdr));
const Elf64_Ehdr* eh64 = (const Elf64_Ehdr*)buf;
assert(IS_ELF32(*eh64) || IS_ELF64(*eh64));
  • 确保 ELF 文件大小足够大
  • 解析 ELF 头(Elf64_Ehdr 结构体)
  • 确认是 32 位或 64 位 ELFIS_ELF32IS_ELF64 宏)

进一步检查

unsigned xlen = IS_ELF32(*eh64) ? 32 : 64;
if (required_xlen != 0 && required_xlen != xlen) {
    throw incompat_xlen(required_xlen, xlen);
}
  • 如果 required_xlen 设定(非 0),但 ELF 位数不匹配,则抛出异常

6. 解析 ELF 类型

assert(IS_ELFLE(*eh64) || IS_ELFBE(*eh64));
assert(IS_ELF_EXEC(*eh64) || IS_ELF_DYN(*eh64));
assert(IS_ELF_RISCV(*eh64) || IS_ELF_EM_NONE(*eh64));
assert(IS_ELF_VCURRENT(*eh64));
  • 检查 ELF 文件的字节序(小端/大端)
  • 检查 ELF 类型(可执行 EXEC 或动态库 DYN
  • 检查 ELF 目标架构(RISC-V 或未指定)
  • 检查 ELF 版本VCURRENT

7. 处理 load_offset

if (IS_ELF_EXEC(*eh64)) {
    load_offset = 0;
}
  • 如果 ELF 是可执行文件(EXEC),则 load_offset = 0
  • 如果是动态库(DYN),可能需要动态调整 load_offset

8. 解析 ELF
ELF 加载主要依赖于 LOAD_ELF

#define LOAD_ELF(ehdr_t, phdr_t, shdr_t, sym_t, bswap) ...

该宏负责:

  • 读取 ELF 头
  • 解析程序头表(Program Header Table)
  • PT_LOAD 段映射到内存
  • 解析符号表

(1) 加载程序段

for (unsigned i = 0; i < bswap(eh->e_phnum); i++) {
    if (bswap(ph[i].p_type) == PT_LOAD && bswap(ph[i].p_memsz)) {
        reg_t load_addr = bswap(ph[i].p_paddr) + load_offset;
        if (bswap(ph[i].p_filesz)) {
            memif->write(load_addr, bswap(ph[i].p_filesz), (uint8_t*)buf + bswap(ph[i].p_offset));
        }
        if (size_t pad = bswap(ph[i].p_memsz) - bswap(ph[i].p_filesz)) {
            zeros.resize(pad);
            memif->write(load_addr + bswap(ph[i].p_filesz), pad, zeros.data());
        }
    }
}
  • 遍历 程序头表
  • 处理 PT_LOAD
  • 将 ELF 段写入 memif 模拟的内存
  • 填充未初始化的 .bss

(2) 解析符号表

if (strtabidx && symtabidx) {
    char* strtab = buf + bswap(sh[strtabidx].sh_offset);
    sym_t* sym = (sym_t*)(buf + bswap(sh[symtabidx].sh_offset));
    for (unsigned i = 0; i < bswap(sh[symtabidx].sh_size) / sizeof(sym_t); i++) {
        symbols[strtab + bswap(sym[i].st_name)] = bswap(sym[i].st_value) + load_offset;
    }
}
  • 找到 .symtab.strtab
  • 提取符号信息
  • 存入 symbols 映射表

9. 清理

munmap(buf, size);
return symbols;
  • 解除 mmap 映射
  • 返回解析出的符号表

总结

  1. 打开 ELF 文件并映射到内存
  2. 解析 ELF 头部信息
  3. 确定 ELF 类型、架构和字节序
  4. 解析并加载程序段到 memif_t
  5. 解析符号表并返回
  6. 清理资源

代码作用
这个 load_elf 函数广泛用于:

  • Spike 仿真器加载 RISC-V ELF
  • FESVR(Front-End Server)加载 Linux 用户态 ELF
  • 调试工具解析 ELF 符号
1.3 elf2hex.cc
#include <iostream>
#include "htif_hexwriter.h"
#include "memif.h"
#include "elfloader.h"

int main(int argc, char** argv)
{
  if(argc < 4 || argc > 5)
  {
    std::cerr << "Usage: " << argv[0] << " <width> <depth> <elf_file> [base]" << std::endl;
    return 1;
  }

  unsigned width = atoi(argv[1]);
  if(width == 0 || (width & (width-1)))
  {
    std::cerr << "width must be a power of 2" << std::endl;
    return 1;
  }

  unsigned long long int base = 0;
  if(argc==5) {
    base = atoll(argv[4]);
    if(base & (width-1))
    {
      std::cerr << "base must be divisible by width" << std::endl;
      return 1;
    }
  }

  unsigned depth = atoi(argv[2]);
  if(depth == 0 || (depth & (depth-1)))
  {
    std::cerr << "depth must be a power of 2" << std::endl;
    return 1;
  }

  htif_hexwriter_t htif(base, width, depth);
  memif_t memif(&htif);
  reg_t entry;
  load_elf(argv[3], &memif, &entry, 0);
  std::cout << htif;

  return 0;
}

这段 C++ 代码的主要作用是加载 ELF 文件,并将其内容转换成 HEX 格式进行输出。代码涉及 ELF 解析 (load_elf)、内存接口 (memif_t)、以及 HEX 输出 (htif_hexwriter_t)。


1. 解析命令行参数

if(argc < 4 || argc > 5)
{
  std::cerr << "Usage: " << argv[0] << " <width> <depth> <elf_file> [base]" << std::endl;
  return 1;
}
  • 代码要求至少 3 个参数(widthdepthelf_file),可选的第 4 个参数是 base 地址。
  • argc 过少或过多都会报错。

2. 解析 width

unsigned width = atoi(argv[1]);
if(width == 0 || (width & (width-1)))
{
  std::cerr << "width must be a power of 2" << std::endl;
  return 1;
}
  • widthatoi 解析,要求是 2 的幂(如 8、16、32、64 等)。
  • (width & (width-1)) 用于检测 width 是否是 2 的幂。

3. 解析 base(可选参数)

unsigned long long int base = 0;
if(argc==5) {
  base = atoll(argv[4]);
  if(base & (width-1))
  {
    std::cerr << "base must be divisible by width" << std::endl;
    return 1;
  }
}
  • base 是 ELF 加载到的内存基址,默认为 0。
  • 如果提供了 base,需要保证其是 width 的整数倍,否则报错。

4. 解析 depth

unsigned depth = atoi(argv[2]);
if(depth == 0 || (depth & (depth-1)))
{
  std::cerr << "depth must be a power of 2" << std::endl;
  return 1;
}
  • depth 代表存储深度,也必须是 2 的幂。

初始化关键对象

htif_hexwriter_t htif(base, width, depth);
memif_t memif(&htif);
  • htif_hexwriter_t htif(base, width, depth);

    • htif_hexwriter_t 是一个用于 HEX 文件输出的类,表示一个带 HEX 输出能力的 HTIF(Host-Target Interface)。
    • 其参数:
      • base:内存起始地址。
      • width:存储单元宽度(字节)。
      • depth:存储深度。
  • memif_t memif(&htif);

    • memif_t 是一个内存接口封装,用于与 htif_hexwriter_t 进行交互,实现存储操作。

加载 ELF 文件

reg_t entry;
load_elf(argv[3], &memif, &entry, 0);
  • load_elf 解析 ELF 文件,并加载其内容到 memif 代表的存储区域。
  • entry 存储 ELF 文件的入口地址。

内部执行流程:

  1. 解析 ELF 文件头,判断 ELF 格式(32-bit/64-bit、字节序)。
  2. 遍历 ELF 程序头表,将 LOAD 段加载到 memif
  3. 遍历 ELF 符号表,提取符号信息(函数地址、变量地址等)。
  4. 返回符号表,同时 entry 变量获得 ELF 入口地址。

输出 HEX 文件

std::cout << htif;
  • htif_hexwriter_t 内部应该重载了 operator<<,支持直接输出 HEX 格式数据。
  • 这里 std::cout << htif; 的作用是将 ELF 加载的内容以 HEX 格式输出。

总结
1. 代码作用
这段代码的主要功能是:

  • 解析 ELF 文件,并将其内容加载到 memif 代表的虚拟存储区域。
  • 通过 htif_hexwriter_t 将内存中的 ELF 数据转换为 HEX 文件格式,并输出。

2. 关键流程

  1. 解析命令行参数:
    • width(存储单元宽度)
    • depth(存储深度)
    • elf_file(ELF 文件路径)
    • base(可选的内存基址)
  2. 初始化 HEX 文件存储接口 htif_hexwriter_t
  3. 通过 memif_t 作为内存接口,调用 load_elf 加载 ELF 数据。
  4. 解析 ELF 文件,将 LOAD 段写入 memif
  5. 提取 ELF 符号表,返回符号地址信息。
  6. 通过 std::cout 输出 HEX 格式的存储数据。

3. 适用场景

  • 该工具适用于将 RISC-V ELF 可执行文件转换为 HEX 格式,用于 FPGA 加载或嵌入式调试。
  • 适合用作 RISC-V 模拟器、FPGA 代码加载器的一部分。

下一篇:RISC-V ISA Simulator系列之fesvr<2>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值