我正在编写一个 MIPS32 仿真器,并希望在使用 gcc 编译 C 程序时能够使用整个标准 C 库(可能带有 GNU 扩展)。
据我所知,I/O 由 MIPS32 架构上的系统调用处理。要使用 libc/glibc 成功运行程序,我如何知道我需要模拟哪些系统调用?(无需反复试验)
编辑:有关我所说的系统调用的示例,请参阅this 。
(如果您有兴趣,可以在这里查看该项目,欢迎任何反馈。请记住,它处于非常早期的阶段)
非常简短的回答
阅读更长的答案。
简答
如果您打算提供一个自定义 libc,它使用您的模拟器的某些功能让主机操作系统执行您的系统调用,您必须实现所有这些。
更长的答案
退后一步,看看事物在真实(非仿真)系统中的典型分层方式:
您要完成的工作发生在第 3 层和第 2 层之间,其中 libc 或用户代码中的函数执行操作系统定义为触发系统调用的任何操作。这会打开许多蠕虫罐:
操作系统定义为触发系统调用的内容因操作系统而异,并且(很少)在同一操作系统的版本之间有所不同。通过提供一个负责隐藏这些细节的可动态链接的 libc,在“真实”系统上缓解了这个问题。除此之外,如果您要运行 MIPS32 二进制文件,它是否使用您的模拟器支持的系统调用约定?
您需要提供一个自定义 libc,它可以执行您的模拟器可以识别为进行特定系统调用并执行它的操作。您希望运行的任何程序都必须交叉编译为 MIPS32 并与其静态链接,程序所需的任何其他库也是如此(想到 libm)。或者,您的仿真器包将需要提供动态链接器的模拟以及所有必需库的动态可链接副本,因为在主机上打开这些将不起作用。如果您有足够的源代码从头开始重新编译程序,那么移植可能比仿真更好。
任何对特定系统上文件的路径做出假设或对它们在某些设备(它们本身就是文件)中会找到什么做出假设的任何代码都不会正确运行。
如果您提供第 2 层,则您自己注册以提供对整个操作系统的一个特定版本的行为的完整、正确的模拟。有些电话喜欢read()
并且write()
很容易处理;其他人喜欢fork()
,uselib()
并且ioctl()
会更加困难。您的程序使用的调用和行为与主机操作系统提供的调用和行为也不一定是一对一的映射。所有这些都假设主机是 Unix,目标程序也是。如果目标是为其他环境编译的,那么所有的赌注都没有了。
最后一点就是为什么大多数仿真器只提供一个 CPU 和某些目标系统的硬件行为(即,第 1 层中的所有内容)。有了这些,您就可以运行原始系统的引导 ROM、操作系统和用户程序,所有这些都不会改变。有许多现有的 MIPS32 仿真器可以做到这一点,并且可以运行在它们仿真的硬件上运行的操作系统的未更改版本。
HTH 并祝您项目好运。
通常的方法是不仅模拟 CPU,而且模拟一组具有代表性的标准外设。然后你在你的模拟器中启动一个操作系统,其中包含一个 libc 和硬件驱动程序。Libc 将调用操作系统驱动程序,这些驱动程序调用仿真器中的虚拟硬件。有关流行的示例,请参阅 DosBox。
您的问题的另一种解释是您不想编写一个完整的模拟器,而是一个允许您在非 mips32 系统上执行 mips32 二进制文件的二进制兼容层。一个流行的例子是 MacOsX (Intel),它也可以执行 PowerPC 应用程序。
在后一种情况下,您需要模拟 OSes ABI(应用程序二进制接口),或者您可以摆脱 libc 的 ABI。在这两种情况下,您都需要实现在模拟器上运行的存根代码和在主机上运行的代理代码:
大多数调用将无法使用通用存根/代理,但需要特定的解决方案。
祝你好运!
我不确切知道 MIPS 是如何工作的,但是在 Win32 上,操作系统调用必须通过 DLL/EXE 导入表显式导入到进程中。MIPS 系统使用的可执行格式可能有类似的东西。
大多数 ISO 标准 C 库可以直接用 C 编写。只有少数部分需要访问较低级别的操作系统功能。
至少,您需要在块级或字符级模拟fopen
、fread
和fwrite
. open
不过,您可以采用 Unix 方法,并在较低级别的read
、 和write
调用之上实现这些方法。
而且您必须管理malloc
和的动态内存分配free
。
和setjmp
,longjmp
它需要访问执行堆栈。
还有time
和signal.h
功能。