libc是如何工作的?

4
我正在编写一个MIPS32模拟器,并希望能够在使用gcc编译C程序时使用整个标准C库(可能带有GNU扩展)。据我所知,在MIPS32架构上,I/O是通过系统调用处理的。为了成功运行使用libc/glibc的程序,我该如何确定需要模拟哪些系统调用?(避免试错)。
编辑:请参见此处以获取有关系统调用的示例。
(如果您感兴趣,可以在此处查看该项目,欢迎提供任何反馈。请注意,它处于非常早期的阶段。)

我理解你的问题是想将系统调用传递到宿主机的libc吗? - Blrfl
不完全是这样。在某种程度上,当程序向仿真器的标准输出输出字符时,最终将执行主机的系统调用。但我不关心管理这个过程,只要仿真器能够执行libc使用的系统调用即可。 - Tamás Szelei
换句话说,我会编写C++代码来在模拟器中模拟系统调用。 - Tamás Szelei
4个回答

7

简短回答

阅读更长的回答。

简短回答

如果你打算提供一个自定义的 libc,使用你的仿真器的某些特性来让主机操作系统执行你的系统调用,那么你必须实现所有这些系统调用。

更长回答

先停一下,看看在一个真实(非模拟)系统中通常是如何分层的:

  1. 外围设备有一些 I/O 接口(例如编号端口或内存映射),CPU 可以操纵它们来做他们所要做的事情。
  2. CPU 运行可以操作硬件的软件。这可以是单用途程序或运行其他程序的操作系统。由于 libc 在画面上,我们假设有一个类 Unix 的操作系统。
  3. 由操作系统运行的用户空间程序使用一种定义良好的接口,在其与操作系统之间进行通信,来询问执行某些“系统”功能。

你试图实现的工作发生在第 3 层和第 2 层之间,即 libc 或用户代码中的函数执行任何 OS 定义为触发系统调用的操作。这会引起许多问题:

  • 操作系统定义触发系统调用的方式因操作系统而异,甚至同一操作系统的不同版本也有所不同(但很少)。在“真实”的系统上,提供一个动态链接的libc可以处理这些细节。除此之外,如果您想要运行一个MIPS32二进制文件,它是否使用了您的模拟器支持的系统调用约定?

  • 您需要提供一个自定义的libc,使其执行特定的系统调用并让您的模拟器识别。您希望运行的任何程序都必须交叉编译为MIPS32,并与之静态链接,就像程序需要的任何其他库一样(例如libm)。或者,您的模拟器软件包将需要提供模拟动态链接器以及所有所需库的动态链接副本,因为在主机上打开这些库是行不通的。如果您有足够的源代码来从头重新编译该程序,则移植可能比模拟更好。

  • 任何假设路径到特定系统上的文件或其他假设关于它们在某些设备(它们本身就是文件)中找到什么的代码都无法正确运行。

  • 如果您提供第二层,那么您就要签署提供整个操作系统某个特定版本行为的完整、正确模拟。一些调用,如read()write(),很容易处理;而其他调用,如fork()uselib()ioctl()则要困难得多。此外,您的程序使用的调用和行为与主机操作系统提供的调用之间并不一定有一对一的映射关系。所有这些都假设目标程序是Unix,并且主机也是Unix。如果目标编译为其他环境,则情况就未知了。

那就是为什么大多数模拟器只提供CPU和某些目标系统的硬件行为(即第一层中的所有内容)。有了这些,您可以运行原始系统的引导ROM、操作系统和用户程序,所有这些都没有改变。已经存在许多MIPS32模拟器就是这样做的,并且可以运行在它们所模拟的硬件上运行的未经修改的操作系统版本。祝你的项目顺利。

@Downvoter,您能解释一下为什么您认为这是一个不好的答案吗? - Tamás Szelei
+1 抱歉你被踩了...我不是那个踩你的人,但当有人这样对一个好的回答时,我感到很难过。 - Jason
感谢您提供深入的答案。 - Tamás Szelei

0

大多数ISO标准C库都可以用纯C编写。只有少部分需要访问较低级别的操作系统功能。

至少,您需要在块或字符级别模拟基本的I/O,以便使用fopenfreadfwrite。您可以采用Unix方法,在较低级别的openreadwrite调用之上实现这些函数。

您还需要管理mallocfree的动态内存分配。

以及需要访问执行堆栈的setjmplongjmp

还有timesignal.h函数。


谢谢你的回答,但这并没有太大帮助。我知道我想要模拟什么。gcc编译的代码可能会使用系统调用来请求处理器执行实际的操作,例如文件I/O。我想知道这些系统调用代码是什么以及它们预期要做什么。例如,SPIM提供了一些基本功能的系统调用。 - Tamás Szelei
我的错;从你的问题中并不清楚你需要什么级别的仿真。我假设你是在库级别上模拟LIBC。 - David R Tribble

0

我不确定MIPS是如何工作的,但在Win32上,操作系统调用必须通过DLL/EXE导入表明确地导入到进程中。MIPS系统使用的可执行文件格式可能存在类似的情况。


它比那个低一到两个级别。它是我决定使用的任何格式(可能会编写一个ELF加载器)。 - Tamás Szelei

0
通常的方法是模拟CPU和一组标准外设。然后在模拟器中启动一个带有libc和硬件驱动程序的操作系统。Libc将调用操作系统的驱动程序,驱动程序将调用模拟器中的虚拟硬件。一个流行的例子是DosBox。
你问题的另一个解释是,你不想编写完整的模拟器,而是想要一个二进制兼容层,使你能够在非mips32系统上执行mips32二进制文件。一个流行的例子是MacOsX(Intel),它也可以执行PowerPC应用程序。
在后一种情况下,你需要模拟操作系统的ABI(应用程序二进制接口),或者也许你可以使用libc的ABI。在两种情况下,你都需要实现运行在模拟器上的存根代码和运行在主机上的代理代码:
  • 存根将函数调用参数序列化
  • ...并使用一些特殊的虚拟指令将它们从仿真器内存传输到主机内存
  • 代理需要修补参数(字节序、整数长度、地址空间等)
  • ...并在主机系统上执行函数调用
  • 代理然后修补和序列化传出的函数参数
  • ...并将它们传回存根
  • ...存根将数据返回给调用者

大多数调用无法使用通用存根/代理工作,而需要特定的解决方案。

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接