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

深入解析 FESVR(Front-End Server)

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

目录

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

RISC-V ISA Simulator系列之fesvr<1-4>中我们已经完成了

1. FESVR 概述
2. FESVR 代码结构分析
   1. ELF 相关文件
   2. HTIF(Host-Target Interface)
   3. 设备模拟
      3.1. `device.h` / `device.cc`
      3.2. `rfb.h` / `rfb.cc`

内容的,下面我们继续完成下列内容。


2. FESVR 代码结构分析

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

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

3. 设备模拟

  • device.h / device.cc:模拟目标系统中的 I/O 设备,如 UART、存储器等。
  • rfb.h / rfb.cc:是远程帧缓冲(Remote Frame Buffer)相关的仿真代码。
  • memif.h / memif.cc:提供对目标内存的读写访问,供 ELF 加载器和 HTIF 使用。
3.3 memif.h / memif.cc

memif.h

class chunked_memif_t
{
public:
  virtual void read_chunk(addr_t taddr, size_t len, void* dst) = 0;
  virtual void write_chunk(addr_t taddr, size_t len, const void* src) = 0;
  virtual void clear_chunk(addr_t taddr, size_t len) = 0;

  virtual size_t chunk_align() = 0;
  virtual size_t chunk_max_size() = 0;

  virtual endianness_t get_target_endianness() const {
    return endianness_little;
  }

  virtual ~chunked_memif_t() = default;
};

chunked_memif_t 是一个抽象基类,定义了对内存块进行操作的接口。它的成员函数包括:

  • read_chunk(): 用于从内存中读取指定长度的数据到目标地址。
  • write_chunk(): 用于将指定长度的数据写入到内存中。
  • clear_chunk(): 用于清除内存中的指定数据。
  • chunk_align(): 返回数据块的对齐方式。
  • chunk_max_size(): 返回数据块的最大大小。
  • get_target_endianness(): 返回目标平台的字节序,默认返回小端字节序。

这些虚函数提供了操作内存的通用接口,而具体的实现由派生类提供。

class memif_t
{
public:
  memif_t(chunked_memif_t* _cmemif) : cmemif(_cmemif) {}
  virtual ~memif_t(){}

  virtual void read(addr_t addr, size_t len, void* bytes);
  virtual void write(addr_t addr, size_t len, const void* bytes);

  virtual target_endian<uint8_t> read_uint8(addr_t addr);
  virtual target_endian<int8_t> read_int8(addr_t addr);
  virtual void write_uint8(addr_t addr, target_endian<uint8_t> val);
  virtual void write_int8(addr_t addr, target_endian<int8_t> val);

  virtual target_endian<uint16_t> read_uint16(addr_t addr);
  virtual target_endian<int16_t> read_int16(addr_t addr);
  virtual void write_uint16(addr_t addr, target_endian<uint16_t> val);
  virtual void write_int16(addr_t addr, target_endian<int16_t> val);

  virtual target_endian<uint32_t> read_uint32(addr_t addr);
  virtual target_endian<int32_t> read_int32(addr_t addr);
  virtual void write_uint32(addr_t addr, target_endian<uint32_t> val);
  virtual void write_int32(addr_t addr, target_endian<int32_t> val);

  virtual target_endian<uint64_t> read_uint64(addr_t addr);
  virtual target_endian<int64_t> read_int64(addr_t addr);
  virtual void write_uint64(addr_t addr, target_endian<uint64_t> val);
  virtual void write_int64(addr_t addr, target_endian<int64_t> val);

  virtual endianness_t get_target_endianness() const {
    return cmemif->get_target_endianness();
  }

protected:
  chunked_memif_t* cmemif;
};

memif_t 是一个更高级的内存接口类,提供了多种数据类型的内存读写操作,包括字节(uint8_t)、16位(uint16_t)、32位(uint32_t)、64位(uint64_t)等类型。这个类包含的函数通过 chunked_memif_t 提供的接口来实现内存的读写,并支持目标端字节序(大端或小端)。它提供了以下功能:

  • read()write():用于从内存读取和向内存写入指定长度的字节数组。
  • read_uint8()write_uint8() 等:这些函数是为不同的数据类型(如 8 位、16 位、32 位、64 位)提供读写操作。
  • get_target_endianness():返回目标平台的字节序。

该类的设计允许用户在更高层次上进行内存操作,同时确保数据访问的正确性。

class incompat_xlen : public std::exception {
public:
  const unsigned expected_xlen;
  const unsigned actual_xlen;
  incompat_xlen(unsigned _expected_xlen, unsigned _actual_xlen) : expected_xlen(_expected_xlen), actual_xlen(_actual_xlen) {}
};

incompat_xlen 是一个自定义异常类,用于处理数据宽度(XLen)不匹配的情况。该类包含两个成员变量:expected_xlenactual_xlen,它们分别表示期望的数据宽度和实际的数据宽度。如果在处理数据时发生了宽度不匹配的情况,系统将抛出此异常。它继承自 std::exception,使得异常可以与其他标准异常类型兼容。

memif.cc

1. read 函数

void memif_t::read(addr_t addr, size_t len, void* bytes)
{
  size_t align = cmemif->chunk_align();  // 获取内存对齐要求
  • 通过调用 cmemif->chunk_align() 获取内存对齐的要求。align 变量存储了每个内存块需要的对齐方式。对齐方式决定了从内存中读取数据时,每次读取的起始地址必须满足对齐要求。
  if (len && (addr & (align-1)))  // 如果地址不对齐
  {
    size_t this_len = std::min(len, align - size_t(addr & (align-1)));  // 计算需要读取的首块长度
    uint8_t chunk[align];  // 分配一个内存块,用于读取数据
  • 这段代码检查 addr 地址是否对齐。如果地址不是 align 的整数倍(即 addr & (align-1) 非零),说明地址不对齐。
  • 如果不对齐,this_len 计算出从当前位置到对齐边界的长度,确保读取的第一个块长度不会超过剩余的读取长度。
  • chunk 数组用于暂时存储从内存中读取的整块数据。
    cmemif->read_chunk(addr & ~(align-1), align, chunk);  // 从内存中读取一整块数据
    memcpy(bytes, chunk + (addr & (align-1)), this_len);  // 将数据从块中复制到目标地址
  • cmemif->read_chunk 读取的是从 addr & ~(align-1) 开始的对齐内存块。这是通过清除 addr 中的对齐掩码部分来获取对齐地址。
  • 然后将读取的块数据 chunk 中的数据复制到目标内存 bytes 中,从 chunk 中的偏移位置(addr & (align-1))开始,确保读取的部分对齐。
    bytes = (char*)bytes + this_len;  // 更新目标地址
    addr += this_len;  // 更新源地址
    len -= this_len;  // 更新剩余长度
  }
  • 更新目标地址 bytes 和源地址 addr,并减少剩余要读取的数据长度 len
  if (len & (align-1))  // 如果剩余长度未对齐
  {
    size_t this_len = len & (align-1);  // 计算未对齐部分的长度
    size_t start = len - this_len;  // 计算起始位置
    uint8_t chunk[align];  // 分配内存块
  • 这里是处理剩余数据是否对齐。如果剩余的 len 不是对齐的(即 len & (align-1) 非零),则计算出剩余未对齐部分的长度。
    cmemif->read_chunk(addr + start, align, chunk);  // 读取未对齐部分的数据
    memcpy((char*)bytes + start, chunk, this_len);  // 复制数据到目标地址
  • 从内存中读取未对齐的块,并将读取的数据复制到目标内存。
    len -= this_len;  // 更新剩余长度
  }
  • 更新剩余要读取的长度 len
  // 现在对齐了
  for (size_t pos = 0; pos < len; pos += cmemif->chunk_max_size())  // 逐块读取剩余数据
    cmemif->read_chunk(addr + pos, std::min(cmemif->chunk_max_size(), len - pos), (char*)bytes + pos);
}
  • 数据已经对齐,接下来就是按块读取数据。cmemif->chunk_max_size() 返回单个块的最大大小,根据剩余长度 len 逐块读取数据并复制到目标内存。

2. write 函数

void memif_t::write(addr_t addr, size_t len, const void* bytes)
{
  size_t align = cmemif->chunk_align();  // 获取内存对齐要求
  • read 函数一样,首先获取内存块的对齐要求。
  if (len && (addr & (align-1)))  // 如果地址不对齐
  {
    size_t this_len = std::min(len, align - size_t(addr & (align-1)));  // 计算需要写入的首块长度
    uint8_t chunk[align];  // 分配一个内存块,用于写入数据
  • 检查地址是否对齐。如果地址不对齐,计算出需要写入的首块长度。
    cmemif->read_chunk(addr & ~(align-1), align, chunk);  // 从内存中读取一整块数据
    memcpy(chunk + (addr & (align-1)), bytes, this_len);  // 将数据从源地址复制到内存块中
    cmemif->write_chunk(addr & ~(align-1), align, chunk);  // 写入修改后的内存块
  • 读取一整块内存,然后将新数据复制到内存块中,最后写回修改后的内存块。
    bytes = (char*)bytes + this_len;  // 更新源地址
    addr += this_len;  // 更新目标地址
    len -= this_len;  // 更新剩余长度
  }
  • 更新源地址、目标地址和剩余写入的数据长度。
  if (len & (align-1))  // 如果剩余长度未对齐
  {
    size_t this_len = len & (align-1);  // 计算未对齐部分的长度
    size_t start = len - this_len;  // 计算起始位置
    uint8_t chunk[align];  // 分配内存块
  • 处理剩余未对齐部分的数据,首先计算未对齐部分的长度。
    cmemif->read_chunk(addr + start, align, chunk);  // 读取未对齐部分的数据
    memcpy(chunk, (char*)bytes + start, this_len);  // 将数据复制到内存块中
    cmemif->write_chunk(addr + start, align, chunk);  // 将修改后的数据写回内存
  • 读取并写入未对齐部分的数据。
    len -= this_len;  // 更新剩余长度
  }
  • 更新剩余的写入长度。
  // 现在对齐了
  bool all_zero = len != 0;  // 检查数据是否全为零
  for (size_t i = 0; i < len; i++)  // 检查每个字节是否为零
    all_zero &= ((const char*)bytes)[i] == 0;
  • 检查数据是否全部为零。如果是,直接清除内存区域。
  if (all_zero) {  // 如果数据全为零
    cmemif->clear_chunk(addr, len);  // 清除内存块
  } else {  // 否则按块写入数据
    size_t max_chunk = cmemif->chunk_max_size();
    for (size_t pos = 0; pos < len; pos += max_chunk)
      cmemif->write_chunk(addr + pos, std::min(max_chunk, len - pos), (char*)bytes + pos);
  }
}
  • 如果数据全部为零,则通过 cmemif->clear_chunk 清除指定的内存区域。
  • 否则,逐块写入内存,直到所有数据写入完毕。

3. MEMIF_READ_FUNCMEMIF_WRITE_FUNC

#define MEMIF_READ_FUNC \
  if(addr & (sizeof(val)-1)) \
    throw std::runtime_error("misaligned address"); \
  this->read(addr, sizeof(val), &val); \
  return val
  • 该宏在进行读取操作时,首先检查地址是否对齐。如果地址不对齐,则抛出 misaligned address 异常。如果对齐,调用 read 函数读取数据并返回。
#define MEMIF_WRITE_FUNC \
  if(addr & (sizeof(val)-1)) \
    throw std::runtime_error("misaligned address"); \
  this->write(addr, sizeof(val), &val)
  • 该宏在进行写入操作时,首先检查地址是否对齐。如果不对齐,则抛出异常。如果对齐,调用 write 函数写入数据。

4. 类型特定的读写函数

这些函数都使用了 MEMIF_READ_FUNCMEMIF_WRITE_FUNC 宏来读取和写入特定类型的数据,例如 uint8_t, int8_t, uint16_t, int16_t 等。

target_endian<uint8_t> memif_t::read_uint8(addr_t addr)
{
  target_endian<uint8_t> val;
  MEMIF_READ_FUNC;
}
  • 该函数读取 uint8_t 类型的数据。如果地址不对齐,抛出异常。如果对齐,调用 read 函数进行数据读取。

类似的处理也应用于其他类型(如 int8_t, uint16_t, int16_t, 等)。

总结:

  • readwrite 函数的核心逻辑是处理内存对齐,确保数据按对齐要求读写。对于不对齐的数据,进行块读写,并处理剩余未对齐部分的数据。
  • 使用宏 MEMIF_READ_FUNCMEMIF_WRITE_FUNC 来简化对不同数据类型的读写操作,并保证地址对齐。
  • 在写操作中,若数据全为零,会优化为清除内存,而不是逐字节写入。

4. 系统调用

  • syscall.h / syscall.cc:处理 ecall 指令,将 RISC-V 系统调用映射到宿主机的系统调用。

syscall.h

这是一个名为 syscall_t 的 C++ 类定义,主要用于处理系统调用,特别是与文件系统、进程控制和 I/O 操作相关的系统调用。

类结构和成员变量

class syscall_t : public device_t
{
 public:
  syscall_t(htif_t*);
  ~syscall_t();
  
  void set_chroot(const char* where);
  
 private:
  const char* identity() { return "syscall_proxy"; }
  
  htif_t* htif;         // 高级接口,通常用来与主机或外部设备通信
  memif_t* memif;       // 内存接口,可能用于直接操作内存(例如读取、写入)
  std::vector<syscall_func_t> table;  // 系统调用表,存储不同系统调用的处理函数
  fds_t fds;            // 文件描述符管理
  std::vector<reg_t> fds_index;  // 文件描述符索引,用于跟踪打开的文件描述符
  
  void handle_syscall(command_t cmd);  // 处理系统调用
  void dispatch(addr_t mm);            // 调度系统调用,可能涉及内存操作
  
  std::string chroot;  // 存储当前的 chroot 路径
  std::string do_chroot(const char* fn);  // 执行 chroot 操作
  std::string undo_chroot(const char* fn);  // 恢复 chroot 操作
  
  // 各种系统调用函数声明,处理不同的系统调用
  reg_t sys_exit(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_openat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_read(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_pread(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_write(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_pwrite(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_close(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_lseek(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_fstat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_lstat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_statx(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_fstatat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_faccessat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_fcntl(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_ftruncate(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_renameat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_linkat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_unlinkat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_mkdirat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_getcwd(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_getmainvars(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_chdir(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
  reg_t sys_readlinkat(reg_t, reg_t, reg_t, reg_t, reg_t, reg_t, reg_t);
};

成员变量

  • htif_t* htif;:指向 htif_t 类型的指针,可能用于处理主机或外部接口。htif 可能代表一种 “host-target interface”(主机-目标接口)用于与外部设备进行通信。
  • memif_t* memif;:指向 memif_t 类型的指针,可能用于进行内存操作。它提供了对内存的直接访问,如读写内存。
  • std::vector<syscall_func_t> table;:存储了系统调用函数的列表。syscall_func_t 是一个函数指针类型,指向每个系统调用的处理函数。
  • fds_t fds;:文件描述符管理类,负责分配和释放文件描述符。
  • std::vector<reg_t> fds_index;:文件描述符索引,可能用于跟踪哪些文件描述符已被分配。

成员函数

  • set_chroot(const char* where);:设置当前进程的 chroot 路径。chroot 是一个将进程限制在指定目录中的机制。
  • identity():返回设备的身份字符串,这里是 “syscall_proxy”。
  • handle_syscall(command_t cmd);:处理一个系统调用的函数,接受一个 command_t 类型的命令。
  • dispatch(addr_t mm);:根据内存地址调度系统调用。
  • do_chroot(const char* fn);undo_chroot(const char* fn);:执行和撤销 chroot 操作。

系统调用处理函数

  • sys_exit:处理 exit 系统调用,退出程序。
  • sys_openat:处理 openat 系统调用,用于打开一个文件。
  • sys_read:处理 read 系统调用,用于读取文件。
  • sys_write:处理 write 系统调用,用于写入文件。
  • sys_close:处理 close 系统调用,关闭文件描述符。
  • sys_lseek:处理 lseek 系统调用,定位文件指针。
  • 其他类似的函数处理各类文件系统操作和 I/O 系统调用。

fds_t

class fds_t
{
 public:
  reg_t alloc(int fd);  // 分配文件描述符
  void dealloc(reg_t fd);  // 释放文件描述符
  int lookup(reg_t fd);  // 查找文件描述符的状态
  
 private:
  std::vector<int> fds;  // 存储文件描述符
};
  • alloc():分配一个文件描述符。
  • dealloc():释放指定的文件描述符。
  • lookup():查找文件描述符的状态。

系统调用表
table 是一个存储系统调用函数指针的向量。系统调用的每个函数(如 sys_exitsys_openat 等)都定义了不同的功能,它们接收不同的参数并返回不同类型的结果。每个系统调用都会按照表中的索引调用相应的函数。

其他成员函数

  • set_chrootdo_chroot/undo_chroot:提供了对 chroot 操作的封装,使得进程可以在受限的目录树中运行。
  • handle_syscall:处理具体的系统调用命令,可能涉及根据命令选择不同的系统调用函数。
  • dispatch:根据内存地址或命令调度对应的系统调用处理函数。

总结
syscall_t 类定义了一个系统调用处理代理,主要包括处理与文件操作、进程管理相关的系统调用。它通过函数指针表管理和调用不同的系统调用函数,且提供了对文件描述符的分配和管理。chroot 操作的封装也使得进程可以在特定的目录环境中执行。

syscall.cc

  1. 结构体构造函数

riscv_stat::riscv_stat(const struct stat& s, htif_t* htif)

  riscv_stat(const struct stat& s, htif_t* htif)
    : dev(htif->to_target<uint64_t>(s.st_dev)),
      ino(htif->to_target<uint64_t>(s.st_ino)),
      mode(htif->to_target<uint32_t>(s.st_mode)),
      nlink(htif->to_target<uint32_t>(s.st_nlink)),
      uid(htif->to_target<uint32_t>(s.st_uid)),
      gid(htif->to_target<uint32_t>(s.st_gid)),
      rdev(htif->to_target<uint64_t>(s.st_rdev)), __pad1(),
      size(htif->to_target<uint64_t>(s.st_size)),
      blksize(htif->to_target<uint32_t>(s.st_blksize)), __pad2(),
      blocks(htif->to_target<uint64_t>(s.st_blocks)),
      atime(htif->to_target<uint64_t>(s.st_atime)), __pad3(),
      mtime(htif->to_target<uint64_t>(s.st_mtime)), __pad4(),
      ctime(htif->to_target<uint64_t>(s.st_ctime)), __pad5(),
      __unused4(), __unused5() {}
};

  • 功能:将标准 struct stat 结构体的数据转换为 riscv_stat 结构体,并且将数据转换为目标端格式。
  • 参数
    • s:标准 struct stat 结构体,包含文件的状态信息。
    • htifhtif_t 类型的指针,用于进行端序转换。
  • 实现:将 s 中的每个成员通过 htif->to_target 方法转换为目标端格式,并赋值给 riscv_stat 的对应成员。

riscv_statx::riscv_statx(const struct statx& s, htif_t* htif)(仅在 HAVE_STATX 定义时有效)

  riscv_statx(const struct statx& s, htif_t* htif)
    : mask(htif->to_target<uint32_t>(s.stx_mask)),
      blksize(htif->to_target<uint32_t>(s.stx_blksize)),
      attributes(htif->to_target<uint64_t>(s.stx_attributes)),
      nlink(htif->to_target<uint32_t>(s.stx_nlink)),
      uid(htif->to_target<uint32_t>(s.stx_uid)),
      gid(htif->to_target<uint32_t>(s.stx_gid)),
      mode(htif->to_target<uint16_t>(s.stx_mode)), __spare0(),
      ino(htif->to_target<uint64_t>(s.stx_ino)),
      size(htif->to_target<uint64_t>(s.stx_size)),
      blocks(htif->to_target<uint64_t>(s.stx_blocks)),
      attributes_mask(htif->to_target<uint64_t>(s.stx_attributes_mask)),
      atime {
        htif->to_target<int64_t>(s.stx_atime.tv_sec),
        htif->to_target<uint32_t>(s.stx_atime.tv_nsec)
      },
      btime {
        htif->to_target<int64_t>(s.stx_btime.tv_sec),
        htif->to_target<uint32_t>(s.stx_btime.tv_nsec)
      },
      ctime {
        htif->to_target<int64_t>(s.stx_ctime.tv_sec),
        htif->to_target<uint32_t>(s.stx_ctime.tv_nsec)
      },
      mtime {
        htif->to_target<int64_t>(s.stx_mtime.tv_sec),
        htif->to_target<uint32_t>(s.stx_mtime.tv_nsec)
      },
      rdev_major(htif->to_target<uint32_t>(s.stx_rdev_major)),
      rdev_minor(htif->to_target<uint32_t>(s.stx_rdev_minor)),
      dev_major(htif->to_target<uint32_t>(s.stx_dev_major)),
      dev_minor(htif->to_target<uint32_t>(s.stx_dev_minor)),
#ifdef HAVE_STATX_MNT_ID
      mnt_id(htif->to_target<uint64_t>(s.stx_mnt_id)),
      __spare2(), __spare3()
#else
      __spare2()
#endif
      {}
};
  • 功能:将标准 struct statx 结构体的数据转换为 riscv_statx 结构体,并且将数据转换为目标端格式。
  • 参数
    • s:标准 struct statx 结构体,包含文件的扩展状态信息。
    • htifhtif_t 类型的指针,用于进行端序转换。
  • 实现:将 s 中的每个成员通过 htif->to_target 方法转换为目标端格式,并赋值给 riscv_statx 的对应成员。对于时间戳成员,分别处理秒和纳秒部分。
  1. syscall_t 类的成员函数

syscall_t::syscall_t(htif_t* htif)

syscall_t::syscall_t(htif_t* htif)
  : htif(htif), memif(&htif->memif()), table(2048)
{
  table[17] = &syscall_t::sys_getcwd;
  table[25] = &syscall_t::sys_fcntl;
  table[34] = &syscall_t::sys_mkdirat;
  table[35] = &syscall_t::sys_unlinkat;
  table[37] = &syscall_t::sys_linkat;
  table[38] = &syscall_t::sys_renameat;
  table[46] = &syscall_t::sys_ftruncate;
  table[48] = &syscall_t::sys_faccessat;
  table[49] = &syscall_t::sys_chdir;
  table[56] = &syscall_t::sys_openat;
  table[57] = &syscall_t::sys_close;
  table[62] = &syscall_t::sys_lseek;
  table[63] = &syscall_t::sys_read;
  table[64] = &syscall_t::sys_write;
  table[67] = &syscall_t::sys_pread;
  table[68] = &syscall_t::sys_pwrite;
  table[78] = &syscall_t::sys_readlinkat;
  table[79] = &syscall_t::sys_fstatat;
  table[80] = &syscall_t::sys_fstat;
  table[93] = &syscall_t::sys_exit;
  table[291] = &syscall_t::sys_statx;
  table[1039] = &syscall_t::sys_lstat;
  table[2011] = &syscall_t::sys_getmainvars;

  register_command(0, std::bind(&syscall_t::handle_syscall, this, _1), "syscall");

  int stdin_fd = dup(0), stdout_fd0 = dup(1), stdout_fd1 = dup(1);
  if (stdin_fd < 0 || stdout_fd0 < 0 || stdout_fd1 < 0)
    throw std::runtime_error("could not dup stdin/stdout");

  fds_index.push_back(fds.alloc(stdin_fd)); // stdin -> stdin
  fds_index.push_back(fds.alloc(stdout_fd0)); // stdout -> stdout
  fds_index.push_back(fds.alloc(stdout_fd1)); // stderr -> stdout
}
  • 功能syscall_t 类的构造函数,初始化系统调用表,注册系统调用处理命令,复制标准输入输出文件描述符。
  • 参数
    • htifhtif_t 类型的指针,代表硬件接口。
  • 实现
    • 初始化 htifmemiftable 成员变量。
    • 将系统调用号映射到对应的成员函数,填充 table 数组。
    • 注册系统调用处理命令,使用 std::bind 绑定 handle_syscall 方法。
    • 复制标准输入输出文件描述符,并将其分配到 fds 中。

syscall_t::~syscall_t()

syscall_t::~syscall_t() {
  for (auto i: fds_index) {
    close(fds.lookup(i));
    fds.dealloc(i);
  }
}
  • 功能syscall_t 类的析构函数,关闭所有打开的文件描述符并释放资源。
  • 实现:遍历 fds_index,关闭对应的文件描述符,并从 fds 中释放索引。

std::string syscall_t::do_chroot(const char* fn)

std::string syscall_t::do_chroot(const char* fn)
{
  if (!chroot.empty() && *fn == '/')
    return chroot + fn;
  return fn;
}
  • 功能:如果设置了根目录,将绝对路径转换为包含根目录的路径。
  • 参数
    • fn:文件路径。
  • 实现:如果 chroot 不为空且 fn 是绝对路径,则返回 chrootfn 拼接后的路径;否则返回原路径。

std::string syscall_t::undo_chroot(const char* fn)

std::string syscall_t::undo_chroot(const char* fn)
{
  if (chroot.empty())
    return fn;
  if (strncmp(fn, chroot.c_str(), chroot.size()) == 0
      && (chroot.back() == '/' || fn[chroot.size()] == '/'))
    return fn + chroot.size() - (chroot.back() == '/');
  return "/";
}
  • 功能:如果设置了根目录,将包含根目录的路径转换为相对路径。
  • 参数
    • fn:文件路径。
  • 实现:如果 chroot 为空,返回原路径;否则,检查 fn 是否以 chroot 开头,如果是,则返回去除 chroot 后的路径;否则返回根路径 /

void syscall_t::handle_syscall(command_t cmd)


void syscall_t::handle_syscall(command_t cmd)
{
  if (cmd.payload() & 1) // test pass/fail
  {
    htif->exitcode = cmd.payload();
    if (htif->exit_code())
      std::cerr << "*** FAILED *** (tohost = " << htif->exit_code() << ")" << std::endl;
    return;
  }
  else // proxied system call
    dispatch(cmd.payload());

  cmd.respond(1);
}
  • 功能:处理系统调用命令,根据命令负载判断是测试结果还是代理系统调用。
  • 参数
    • cmdcommand_t 类型的命令对象。
  • 实现
    • 如果 cmd.payload() 的最低位为 1,表示测试结果,设置 htif->exitcode 并输出错误信息(如果测试失败)。
    • 否则,调用 dispatch 方法处理代理系统调用,并回复命令。

reg_t syscall_t::sys_exit(reg_t code, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_exit(reg_t code, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  htif->htif_exit(code << 1 | 1);
  return 0;
}

  • 功能:实现 exit 系统调用,设置退出码。
  • 参数
    • code:退出码。
    • a1 - a6:其他参数(未使用)。
  • 实现:设置 htif->exitcodecode << 1 | 1,并返回 0。

static reg_t sysret_errno(sreg_t ret)

static reg_t sysret_errno(sreg_t ret)
{
  return ret == -1 ? -errno : ret;
}
  • 功能:将系统调用的返回值转换为包含错误码的返回值。
  • 参数
    • ret:系统调用的原始返回值。
  • 实现:如果 ret 为 -1,表示系统调用失败,返回 -errno;否则返回 ret

reg_t syscall_t::sys_read(reg_t fd, reg_t pbuf, reg_t len, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_read(reg_t fd, reg_t pbuf, reg_t len, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> buf(len);
  ssize_t ret = read(fds.lookup(fd), buf.data(), len);
  reg_t ret_errno = sysret_errno(ret);
  if (ret > 0)
    memif->write(pbuf, ret, buf.data());
  return ret_errno;
}
  • 功能:实现 read 系统调用,从文件描述符读取数据。
  • 参数
    • fd:文件描述符。
    • pbuf:缓冲区地址。
    • len:读取的字节数。
    • a3 - a6:其他参数(未使用)。
  • 实现
    • 创建一个长度为 len 的缓冲区。
    • 调用 read 函数从文件描述符读取数据。
    • 如果读取成功,将数据写入 pbuf 指向的内存。
    • 返回处理后的返回值。

reg_t syscall_t::sys_pread(reg_t fd, reg_t pbuf, reg_t len, reg_t off, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_pread(reg_t fd, reg_t pbuf, reg_t len, reg_t off, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> buf(len);
  ssize_t ret = pread(fds.lookup(fd), buf.data(), len, off);
  reg_t ret_errno = sysret_errno(ret);
  if (ret > 0)
    memif->write(pbuf, ret, buf.data());
  return ret_errno;
}
  • 功能:实现 pread 系统调用,从文件描述符指定偏移量处读取数据。
  • 参数
    • fd:文件描述符。
    • pbuf:缓冲区地址。
    • len:读取的字节数。
    • off:偏移量。
    • a4 - a6:其他参数(未使用)。
  • 实现:与 sys_read 类似,只是调用 pread 函数并指定偏移量。

reg_t syscall_t::sys_write(reg_t fd, reg_t pbuf, reg_t len, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_write(reg_t fd, reg_t pbuf, reg_t len, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> buf(len);
  memif->read(pbuf, len, buf.data());
  reg_t ret = sysret_errno(write(fds.lookup(fd), buf.data(), len));
  return ret;
}

  • 功能:实现 write 系统调用,向文件描述符写入数据。
  • 参数
    • fd:文件描述符。
    • pbuf:缓冲区地址。
    • len:写入的字节数。
    • a3 - a6:其他参数(未使用)。
  • 实现
    • 创建一个长度为 len 的缓冲区。
    • pbuf 指向的内存读取数据到缓冲区。
    • 调用 write 函数将数据写入文件描述符。
    • 返回处理后的返回值。

reg_t syscall_t::sys_pwrite(reg_t fd, reg_t pbuf, reg_t len, reg_t off, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_pwrite(reg_t fd, reg_t pbuf, reg_t len, reg_t off, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> buf(len);
  memif->read(pbuf, len, buf.data());
  reg_t ret = sysret_errno(pwrite(fds.lookup(fd), buf.data(), len, off));
  return ret;
}

  • 功能:实现 pwrite 系统调用,向文件描述符指定偏移量处写入数据。
  • 参数
    • fd:文件描述符。
    • pbuf:缓冲区地址。
    • len:写入的字节数。
    • off:偏移量。
    • a4 - a6:其他参数(未使用)。
  • 实现:与 sys_write 类似,只是调用 pwrite 函数并指定偏移量。

reg_t syscall_t::sys_close(reg_t fd, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_close(reg_t fd, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  if (close(fds.lookup(fd)) < 0)
    return sysret_errno(-1);
  fds.dealloc(fd);
  return 0;
}
  • 功能:实现 close 系统调用,关闭文件描述符。
  • 参数
    • fd:文件描述符。
    • a1 - a6:其他参数(未使用)。
  • 实现
    • 调用 close 函数关闭文件描述符。
    • 如果关闭失败,返回处理后的错误码;否则,从 fds 中释放索引并返回 0。

reg_t syscall_t::sys_lseek(reg_t fd, reg_t ptr, reg_t dir, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_lseek(reg_t fd, reg_t ptr, reg_t dir, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  return sysret_errno(lseek(fds.lookup(fd), ptr, dir));
}

  • 功能:实现 lseek 系统调用,设置文件偏移量。
  • 参数
    • fd:文件描述符。
    • ptr:偏移量。
    • dir:偏移基准。
    • a3 - a6:其他参数(未使用)。
  • 实现:调用 lseek 函数并返回处理后的返回值。

reg_t syscall_t::sys_fstat(reg_t fd, reg_t pbuf, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_fstat(reg_t fd, reg_t pbuf, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  struct stat buf;
  reg_t ret = sysret_errno(fstat(fds.lookup(fd), &buf));
  if (ret != (reg_t)-1)
  {
    riscv_stat rbuf(buf, htif);
    memif->write(pbuf, sizeof(rbuf), &rbuf);
  }
  return ret;
}
  • 功能:实现 fstat 系统调用,获取文件状态信息。
  • 参数
    • fd:文件描述符。
    • pbuf:缓冲区地址。
    • a2 - a6:其他参数(未使用)。
  • 实现
    • 调用 fstat 函数获取文件状态信息。
    • 如果获取成功,将状态信息转换为 riscv_stat 结构体并写入 pbuf 指向的内存。
    • 返回处理后的返回值。

reg_t syscall_t::sys_fcntl(reg_t fd, reg_t cmd, reg_t arg, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_fcntl(reg_t fd, reg_t cmd, reg_t arg, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  return sysret_errno(fcntl(fds.lookup(fd), cmd, arg));
}
  • 功能:实现 fcntl 系统调用,对文件描述符进行控制操作。
  • 参数
    • fd:文件描述符。
    • cmd:操作命令。
    • arg:操作参数。
    • a3 - a6:其他参数(未使用)。
  • 实现:调用 fcntl 函数并返回处理后的返回值。

reg_t syscall_t::sys_ftruncate(reg_t fd, reg_t len, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_ftruncate(reg_t fd, reg_t len, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  return sysret_errno(ftruncate(fds.lookup(fd), len));
}
  • 功能:实现 ftruncate 系统调用,截断文件到指定长度。
  • 参数
    • fd:文件描述符。
    • len:截断长度。
    • a2 - a6:其他参数(未使用)。
  • 实现:调用 ftruncate 函数并返回处理后的返回值。

reg_t syscall_t::sys_lstat(reg_t pname, reg_t len, reg_t pbuf, reg_t a3, reg_t a4, reg_t a5, reg_t a6)


reg_t syscall_t::sys_lstat(reg_t pname, reg_t len, reg_t pbuf, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> name(len);
  memif->read(pname, len, name.data());

  struct stat buf;
  reg_t ret = sysret_errno(lstat(do_chroot(name.data()).c_str(), &buf));
  if (ret != (reg_t)-1)
  {
    riscv_stat rbuf(buf, htif);
    memif->write(pbuf, sizeof(rbuf), &rbuf);
  }
  return ret;
}
  • 功能:实现 lstat 系统调用,获取文件或符号链接的状态信息。
  • 参数
    • pname:文件名地址。
    • len:文件名长度。
    • pbuf:缓冲区地址。
    • a3 - a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取文件名。
    • 调用 lstat 函数获取文件状态信息。
    • 如果获取成功,将状态信息转换为 riscv_stat 结构体并写入 pbuf 指向的内存。
    • 返回处理后的返回值。

reg_t syscall_t::sys_statx(reg_t fd, reg_t pname, reg_t len, reg_t flags, reg_t mask, reg_t pbuf, reg_t a6)(仅在 HAVE_STATX 定义时有效)

reg_t syscall_t::sys_statx(reg_t fd, reg_t pname, reg_t len, reg_t flags, reg_t mask, reg_t pbuf, reg_t a6)
{
#ifndef HAVE_STATX
  return -ENOSYS;
#else
  std::vector<char> name(len);
  memif->read(pname, len, name.data());

  struct statx buf;
  reg_t ret = sysret_errno(statx(fds.lookup(fd), do_chroot(name.data()).c_str(), flags, mask, &buf));
  if (ret != (reg_t)-1)
  {
    riscv_statx rbuf(buf, htif);
    memif->write(pbuf, sizeof(rbuf), &rbuf);
  }
  return ret;
#endif
}
  • 功能:实现 statx 系统调用,获取文件的扩展状态信息。
  • 参数
    • fd:文件描述符。
    • pname:文件名地址。
    • len:文件名长度。
    • flags:标志位。
    • mask:掩码。
    • pbuf:缓冲区地址。
    • a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取文件名。
    • 调用 statx 函数获取文件状态信息。
    • 如果获取成功,将状态信息转换为 riscv_statx 结构体并写入 pbuf 指向的内存。
    • 返回处理后的返回值。

reg_t syscall_t::sys_openat(reg_t dirfd, reg_t pname, reg_t len, reg_t flags, reg_t mode, reg_t a5, reg_t a6)

#define AT_SYSCALL(syscall, fd, name, ...) \
  (syscall(fds.lookup(fd), int(fd) == RISCV_AT_FDCWD ? do_chroot(name).c_str() : (name), __VA_ARGS__))

reg_t syscall_t::sys_openat(reg_t dirfd, reg_t pname, reg_t len, reg_t flags, reg_t mode, reg_t a5, reg_t a6)
{
  std::vector<char> name(len);
  memif->read(pname, len, name.data());
  int fd = sysret_errno(AT_SYSCALL(openat, dirfd, name.data(), flags, mode));
  if (fd < 0)
    return sysret_errno(-1);
  return fds.alloc(fd);
}
  • 功能:实现 openat 系统调用,打开文件。
  • 参数
    • dirfd:目录文件描述符。
    • pname:文件名地址。
    • len:文件名长度。
    • flags:打开标志。
    • mode:文件权限。
    • a5 - a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取文件名。
    • 调用 openat 函数打开文件。
    • 如果打开成功,分配一个新的文件描述符索引并返回;否则返回处理后的错误码。

reg_t syscall_t::sys_fstatat(reg_t dirfd, reg_t pname, reg_t len, reg_t pbuf, reg_t flags, reg_t a5, reg_t a6)

reg_t syscall_t::sys_fstatat(reg_t dirfd, reg_t pname, reg_t len, reg_t pbuf, reg_t flags, reg_t a5, reg_t a6)
{
  std::vector<char> name(len);
  memif->read(pname, len, name.data());

  struct stat buf;
  reg_t ret = sysret_errno(AT_SYSCALL(fstatat, dirfd, name.data(), &buf, flags));
  if (ret != (reg_t)-1)
  {
    riscv_stat rbuf(buf, htif);
    memif->write(pbuf, sizeof(rbuf), &rbuf);
  }
  return ret;
}
  • 功能:实现 fstatat 系统调用,获取文件状态信息。
  • 参数
    • dirfd:目录文件描述符。
    • pname:文件名地址。
    • len:文件名长度。
    • pbuf:缓冲区地址。
    • flags:标志位。
    • a5 - a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取文件名。
    • 调用 fstatat 函数获取文件状态信息。
    • 如果获取成功,将状态信息转换为 riscv_stat 结构体并写入 pbuf 指向的内存。
    • 返回处理后的返回值。

reg_t syscall_t::sys_faccessat(reg_t dirfd, reg_t pname, reg_t len, reg_t mode, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_faccessat(reg_t dirfd, reg_t pname, reg_t len, reg_t mode, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> name(len);
  memif->read(pname, len, name.data());
  return sysret_errno(AT_SYSCALL(faccessat, dirfd, name.data(), mode, 0));
}
  • 功能:实现 faccessat 系统调用,检查文件的访问权限。
  • 参数
    • dirfd:目录文件描述符。
    • pname:文件名地址。
    • len:文件名长度。
    • mode:访问模式。
    • a4 - a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取文件名。
    • 调用 faccessat 函数检查文件访问权限。
    • 返回处理后的返回值。

reg_t syscall_t::sys_renameat(reg_t odirfd, reg_t popath, reg_t olen, reg_t ndirfd, reg_t pnpath, reg_t nlen, reg_t a6)

reg_t syscall_t::sys_renameat(reg_t odirfd, reg_t popath, reg_t olen, reg_t ndirfd, reg_t pnpath, reg_t nlen, reg_t a6)
{
  std::vector<char> opath(olen), npath(nlen);
  memif->read(popath, olen, opath.data());
  memif->read(pnpath, nlen, npath.data());
  return sysret_errno(renameat(fds.lookup(odirfd), int(odirfd) == RISCV_AT_FDCWD ? do_chroot(opath.data()).c_str() : opath.data(),
                             fds.lookup(ndirfd), int(ndirfd) == RISCV_AT_FDCWD ? do_chroot(npath.data()).c_str() : npath.data()));
}
  • 功能:实现 renameat 系统调用,重命名文件或目录。
  • 参数
    • odirfd:旧目录文件描述符。
    • popath:旧文件名地址。
    • olen:旧文件名长度。
    • ndirfd:新目录文件描述符。
    • pnpath:新文件名地址。
    • nlen:新文件名长度。
    • a6:其他参数(未使用)。
  • 实现
    • popathpnpath 指向的内存读取旧文件名和新文件名。
    • 调用 renameat 函数重命名文件或目录。
    • 返回处理后的返回值。

reg_t syscall_t::sys_linkat(reg_t odirfd, reg_t poname, reg_t olen, reg_t ndirfd, reg_t pnname, reg_t nlen, reg_t flags)

reg_t syscall_t::sys_linkat(reg_t odirfd, reg_t poname, reg_t olen, reg_t ndirfd, reg_t pnname, reg_t nlen, reg_t flags)
{
  std::vector<char> oname(olen), nname(nlen);
  memif->read(poname, olen, oname.data());
  memif->read(pnname, nlen, nname.data());
  return sysret_errno(linkat(fds.lookup(odirfd), int(odirfd) == RISCV_AT_FDCWD ? do_chroot(oname.data()).c_str() : oname.data(),
                             fds.lookup(ndirfd), int(ndirfd) == RISCV_AT_FDCWD ? do_chroot(nname.data()).c_str() : nname.data(),
                             flags));
}
  • 功能:实现 linkat 系统调用,创建硬链接。
  • 参数
    • odirfd:旧目录文件描述符。
    • poname:旧文件名地址。
    • olen:旧文件名长度。
    • ndirfd:新目录文件描述符。
    • pnname:新文件名地址。
    • nlen:新文件名长度。
    • flags:标志位。
  • 实现
    • ponamepnname 指向的内存读取旧文件名和新文件名。
    • 调用 linkat 函数创建硬链接。
    • 返回处理后的返回值。

reg_t syscall_t::sys_unlinkat(reg_t dirfd, reg_t pname, reg_t len, reg_t flags, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_unlinkat(reg_t dirfd, reg_t pname, reg_t len, reg_t flags, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> name(len);
  memif->read(pname, len, name.data());
  return sysret_errno(AT_SYSCALL(unlinkat, dirfd, name.data(), flags));
}

  • 功能:实现 unlinkat 系统调用,删除文件或目录。
  • 参数
    • dirfd:目录文件描述符。
    • pname:文件名地址。
    • len:文件名长度。
    • flags:标志位。
    • a4 - a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取文件名。
    • 调用 unlinkat 函数删除文件或目录。
    • 返回处理后的返回值。

reg_t syscall_t::sys_mkdirat(reg_t dirfd, reg_t pname, reg_t len, reg_t mode, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_mkdirat(reg_t dirfd, reg_t pname, reg_t len, reg_t mode, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> name(len);
  memif->read(pname, len, name.data());
  return sysret_errno(AT_SYSCALL(mkdirat, dirfd, name.data(), mode));
}
  • 功能:实现 mkdirat 系统调用,创建目录。
  • 参数
    • dirfd:目录文件描述符。
    • pname:目录名地址。
    • len:目录名长度。
    • mode:目录权限。
    • a4 - a6:其他参数(未使用)。
  • 实现
    • pname 指向的内存读取目录名。
    • 调用 mkdirat 函数创建目录。
    • 返回处理后的返回值。

reg_t syscall_t::sys_getcwd(reg_t pbuf, reg_t size, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_getcwd(reg_t pbuf, reg_t size, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<char> buf(size);
  char* ret = getcwd(buf.data(), size);
  if (ret == NULL)
    return sysret_errno(-1);
  std::string tmp = undo_chroot(buf.data());
  if (size <= tmp.size())
    return -ENOMEM;
  memif->write(pbuf, tmp.size() + 1, tmp.data());
  return tmp.size() + 1;
}
  • 功能:实现 getcwd 系统调用,获取当前工作目录。
  • 参数
    • pbuf:缓冲区地址。
    • size:缓冲区大小。
    • a2 - a6:其他参数(未使用)。
  • 实现
    • 调用 getcwd 函数获取当前工作目录。
    • 如果获取成功,将路径转换为相对路径并写入 pbuf 指向的内存。
    • 返回路径长度。

reg_t syscall_t::sys_getmainvars(reg_t pbuf, reg_t limit, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)


reg_t syscall_t::sys_getmainvars(reg_t pbuf, reg_t limit, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  std::vector<std::string> args = htif->target_args();
  std::vector<target_endian<uint64_t>> words(args.size() + 3);
  words[0] = htif->to_target<uint64_t>(args.size());
  words[args.size()+1] = target_endian<uint64_t>::zero; // argv[argc] = NULL
  words[args.size()+2] = target_endian<uint64_t>::zero; // envp[0] = NULL

  size_t sz = (args.size() + 3) * sizeof(words[0]);
  for (size_t i = 0; i < args.size(); i++)
  {
    words[i+1] = htif->to_target<uint64_t>(sz + pbuf);
    sz += args[i].length() + 1;
  }

  std::vector<char> bytes(sz);
  memcpy(bytes.data(), words.data(), sizeof(words[0]) * words.size());
  for (size_t i = 0; i < args.size(); i++)
    strcpy(&bytes[htif->from_target(words[i+1]) - pbuf], args[i].c_str());

  if (bytes.size() > limit)
    return -ENOMEM;

  memif->write(pbuf, bytes.size(), bytes.data());
  return 0;
}
  • 功能:获取目标参数并写入内存。
  • 参数
    • pbuf:缓冲区地址。
    • limit:缓冲区大小限制。
    • a2 - a6:其他参数(未使用)。
  • 实现
    • 获取目标参数。
    • 构造一个包含参数信息的数组。
    • 将参数信息和参数值写入缓冲区。
    • 如果缓冲区大小超过限制,返回 -ENOMEM;否则返回 0。

reg_t syscall_t::sys_chdir(reg_t path, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)

reg_t syscall_t::sys_chdir(reg_t path, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6)
{
  size_t size = 0;
  while (memif->read_uint8(path + size++))
    ;
  std::vector<char> buf(size);
  for (size_t offset = 0;; offset++)
  {
    buf[offset] = memif->read_uint8(path + offset);
    if (!buf[offset])
      break;
  }
  return sysret_errno(chdir(buf.data()));
}
  • 功能:实现 chdir 系统调用,更改当前工作目录。
  • 参数
    • path:目录路径地址。
    • a1 - a6:其他参数(未使用)。
  • 实现
    • path 指向的内存读取目录路径。
    • 调用 chdir 函数更改当前工作目录。
    • 返回处理后的返回值。

void syscall_t::dispatch(reg_t mm)

void syscall_t::dispatch(reg_t mm)
{
  target_endian<reg_t> magicmem[8];
  memif->read(mm, sizeof(magicmem), magicmem);

  reg_t n = htif->from_target(magicmem[0]);
  if (n >= table.size() || !table[n])
    throw std::runtime_error("bad syscall #" + std::to_string(n));

  magicmem[0] = htif->to_target((this->*table[n])(htif->from_target(magicmem[1]), htif->from_target(magicmem[2]), htif->from_target(magicmem[3]), htif->from_target(magicmem[4]), htif->from_target(magicmem[5]), htif->from_target(magicmem[6]), htif->from_target(magicmem[7])));

  memif->write(mm, sizeof(magicmem), magicmem);
}
  • 功能:根据系统调用号调用对应的成员函数。
  • 参数
    • mm:系统调用信息的内存地址。
  • 实现
    • mm 指向的内存读取系统调用号和参数。
    • 根据系统调用号查找对应的成员函数。
    • 调用成员函数并将结果写回 mm 指向的内存。
  1. fds_t 类的成员函数

reg_t fds_t::alloc(int fd)

reg_t fds_t::alloc(int fd)
{
  reg_t i;
  for (i = 0; i < fds.size(); i++)
    if (fds[i] == -1)
      break;

  if (i == fds.size())
    fds.resize(i+1);

  fds[i] = fd;
  return i;
}
  • 功能:分配一个新的文件描述符索引。
  • 参数
    • fd:实际的文件描述符。
  • 实现
    • 查找 fds 数组中第一个空闲的索引。
    • 如果没有空闲索引,扩展 fds 数组。
    • fd 存储到该索引位置,并返回索引。

void fds_t::dealloc(reg_t fd)

void fds_t::dealloc(reg_t fd)
{
  fds[fd] = -1;
}

  • 功能:释放文件描述符索引。
  • 参数
    • fd:文件描述符索引。
  • 实现:将 fds[fd] 设置为 -1,表示该索引空闲。

int fds_t::lookup(reg_t fd)

int fds_t::lookup(reg_t fd)
{
  if (int(fd) == RISCV_AT_FDCWD)
    return AT_FDCWD;
  return fd >= fds.size() ? -1 : fds[fd];
}

  • 功能:根据索引查找对应的文件描述符。
  • 参数
    • fd:文件描述符索引。
  • 实现:如果 fd 等于 RISCV_AT_FDCWD,返回 AT_FDCWD;否则,如果 fdfds 数组范围内,返回 fds[fd];否则返回 -1。
  1. syscall_t::set_chroot(const char* where)
void syscall_t::set_chroot(const char* where)
{
  char buf1[PATH_MAX], buf2[PATH_MAX];

  if (getcwd(buf1, sizeof(buf1)) == NULL
      || chdir(where) != 0
      || getcwd(buf2, sizeof(buf2)) == NULL
      || chdir(buf1) != 0)
  {
    fprintf(stderr, "could not chroot to %s\n", where);
    exit(-1);
  }

  chroot = buf2;
}
  • 功能:设置根目录,用于文件路径的转换。
  • 参数
    • where:根目录路径。
  • 实现
    • 保存当前工作目录。
    • 切换到指定的根目录并获取新的工作目录。
    • 切换回原来的工作目录。
    • 如果任何步骤失败,输出错误信息并退出程序;否则,将新的工作目录设置为 chroot

这段代码实现了 syscall_t 类中的 sys_readlinkat 成员函数,其主要功能是模拟 readlinkat 系统调用,用于读取符号链接的内容。

  1. reg_t syscall_t::sys_readlinkat(reg_t dirfd, reg_t ppathname, reg_t ppathname_size, reg_t pbuf, reg_t bufsiz, reg_t a5, reg_t a6)
reg_t syscall_t::sys_readlinkat(reg_t dirfd, reg_t ppathname, reg_t ppathname_size,
                                reg_t pbuf, reg_t bufsiz, reg_t a5, reg_t a6)
  • 参数说明
    • dirfd:目录文件描述符,用于指定相对路径的基准目录。
    • ppathname:指向符号链接路径名的内存地址。
    • ppathname_size:符号链接路径名的大小。
    • pbuf:用于存储符号链接内容的缓冲区的内存地址。
    • bufsiz:缓冲区的大小。
    • a5a6:未使用的额外参数。
  1. 读取符号链接路径名
std::vector<char> pathname(ppathname_size);
memif->read(ppathname, ppathname_size, pathname.data());
  • 创建一个大小为 ppathname_sizestd::vector<char> 类型的 pathname 容器,用于存储符号链接的路径名。
  • 调用 memif 对象的 read 方法,从 ppathname 指向的内存地址读取 ppathname_size 字节的数据到 pathname 容器中。
  1. 创建存储符号链接内容的缓冲区
std::vector<char> buf(bufsiz);
  • 创建一个大小为 bufsizstd::vector<char> 类型的 buf 容器,用于存储符号链接的内容。
  1. 调用 readlinkat 系统调用
ssize_t ret = sysret_errno(AT_SYSCALL(readlinkat, dirfd, pathname.data(), buf.data(), bufsiz));
  • 使用 AT_SYSCALL 宏调用 readlinkat 系统调用,尝试读取符号链接的内容到 buf 容器中。
  • sysret_errno 函数用于处理系统调用的返回值,如果系统调用失败(返回 -1),则将错误码转换为合适的返回值。
  1. 将读取的内容写入指定缓冲区
if (ret > 0)
    memif->write(pbuf, ret, buf.data());
  • 如果 readlinkat 系统调用成功,且返回的字节数大于 0,则调用 memif 对象的 write 方法,将 buf 容器中的内容写入到 pbuf 指向的内存地址。
  1. 返回结果
return ret;
  • 返回 readlinkat 系统调用的结果,可能是读取的字节数或者错误码。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值