url: https://github.com/riscv/riscv-isa-sim.git
commid: fcbdbe7946079650d0e656fa3d353e3f652d471f
在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`
内容的,下面我们继续完成下列内容。
fesvr
的代码主要位于 riscv-isa-sim/fesvr/
目录,主要文件及作用如下:
fesvr
目录下的源码文件清单,涉及 ELF 加载、设备仿真、HTIF 交互、系统调用 等多个模块。下面是各文件的简要介绍:
device.h
/ device.cc
:模拟目标系统中的 I/O 设备,如 UART、存储器等。rfb.h
/ rfb.cc
:是远程帧缓冲(Remote Frame Buffer)相关的仿真代码。memif.h
/ memif.cc
:提供对目标内存的读写访问,供 ELF 加载器和 HTIF 使用。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_xlen
和 actual_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_FUNC
和 MEMIF_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_FUNC
和 MEMIF_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
, 等)。
总结:
read
和 write
函数的核心逻辑是处理内存对齐,确保数据按对齐要求读写。对于不对齐的数据,进行块读写,并处理剩余未对齐部分的数据。MEMIF_READ_FUNC
和 MEMIF_WRITE_FUNC
来简化对不同数据类型的读写操作,并保证地址对齐。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 table;
:存储了系统调用函数的列表。syscall_func_t
是一个函数指针类型,指向每个系统调用的处理函数。fds_t fds;
:文件描述符管理类,负责分配和释放文件描述符。std::vector 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
系统调用,定位文件指针。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_exit
、sys_openat
等)都定义了不同的功能,它们接收不同的参数并返回不同类型的结果。每个系统调用都会按照表中的索引调用相应的函数。
其他成员函数
set_chroot
和 do_chroot
/undo_chroot
:提供了对 chroot
操作的封装,使得进程可以在受限的目录树中运行。handle_syscall
:处理具体的系统调用命令,可能涉及根据命令选择不同的系统调用函数。dispatch
:根据内存地址或命令调度对应的系统调用处理函数。总结
syscall_t
类定义了一个系统调用处理代理,主要包括处理与文件操作、进程管理相关的系统调用。它通过函数指针表管理和调用不同的系统调用函数,且提供了对文件描述符的分配和管理。chroot
操作的封装也使得进程可以在特定的目录环境中执行。
syscall.cc
- 结构体构造函数
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
结构体,包含文件的状态信息。htif
:htif_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
结构体,包含文件的扩展状态信息。htif
:htif_t
类型的指针,用于进行端序转换。s
中的每个成员通过 htif->to_target
方法转换为目标端格式,并赋值给 riscv_statx
的对应成员。对于时间戳成员,分别处理秒和纳秒部分。
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
类的构造函数,初始化系统调用表,注册系统调用处理命令,复制标准输入输出文件描述符。htif
:htif_t
类型的指针,代表硬件接口。htif
、memif
和 table
成员变量。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
是绝对路径,则返回 chroot
与 fn
拼接后的路径;否则返回原路径。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);
}
cmd
:command_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->exitcode
为 code << 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
:其他参数(未使用)。popath
和 pnpath
指向的内存读取旧文件名和新文件名。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
:标志位。poname
和 pnname
指向的内存读取旧文件名和新文件名。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
指向的内存。
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
;否则,如果 fd
在 fds
数组范围内,返回 fds[fd]
;否则返回 -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
系统调用,用于读取符号链接的内容。
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
:缓冲区的大小。a5
和 a6
:未使用的额外参数。std::vector<char> pathname(ppathname_size);
memif->read(ppathname, ppathname_size, pathname.data());
ppathname_size
的 std::vector
类型的 pathname
容器,用于存储符号链接的路径名。memif
对象的 read
方法,从 ppathname
指向的内存地址读取 ppathname_size
字节的数据到 pathname
容器中。std::vector<char> buf(bufsiz);
bufsiz
的 std::vector
类型的 buf
容器,用于存储符号链接的内容。readlinkat
系统调用ssize_t ret = sysret_errno(AT_SYSCALL(readlinkat, dirfd, pathname.data(), buf.data(), bufsiz));
AT_SYSCALL
宏调用 readlinkat
系统调用,尝试读取符号链接的内容到 buf
容器中。sysret_errno
函数用于处理系统调用的返回值,如果系统调用失败(返回 -1),则将错误码转换为合适的返回值。if (ret > 0)
memif->write(pbuf, ret, buf.data());
readlinkat
系统调用成功,且返回的字节数大于 0,则调用 memif
对象的 write
方法,将 buf
容器中的内容写入到 pbuf
指向的内存地址。return ret;
readlinkat
系统调用的结果,可能是读取的字节数或者错误码。下一篇:RISC-V ISA Simulator系列之fesvr<6>