【问题标题】:How does libc work?libc 是如何工作的?
【发布时间】:2011-05-04 10:57:48
【问题描述】:

我正在编写一个 MIPS32 仿真器,并希望在使用 gcc 编译 C 程序时能够使用整个标准 C 库(可能带有 GNU 扩展)。

据我所知,I/O 由 MIPS32 架构上的系统调用处理。要使用 libc/glibc 成功运行程序,我如何知道我需要模拟哪些系统调用?(无需反复试验)

编辑:请参阅this 了解我所说的系统调用的示例。

(如果您有兴趣,可以查看项目here,欢迎任何反馈。请记住,它处于非常早期阶段)

【问题讨论】:

  • 我理解您的问题是否意味着您想将系统调用传递给主机的 libc?
  • 不完全是。好吧,在某种程度上,最终将执行主机的系统调用,也就是说,程序将一个字符输出到模拟器的标准输出。但我不关心管理这个,只关心模拟器可以执行 libc 使用的系统调用。
  • 换句话说,我会编写 C++ 代码来模拟模拟器中的系统调用。

标签: c++ c emulation libc mips32


【解决方案1】:

非常简短的回答

阅读更长的答案。

简答

如果您打算提供一个自定义 libc,使用您的模拟器的某些功能让主机操作系统执行您的系统调用,您必须实现所有这些。

更长的答案

退后一步,看看事物在真实(非模拟)系统中的典型分层方式:

  1. 外设具有一些 I/O 接口(例如,编号端口或内存映射),CPU 可以通过这些接口让它们执行任何操作。
  2. CPU 运行了解如何操作硬件的软件。这可以是单一用途的程序或运行其他程序的操作系统。由于 libc 在图片中,我们假设有一个操作系统并且它是 Unix-y 的东西。
  3. 操作系统运行的用户空间程序使用它们与操作系统之间定义的接口来请求执行某些“系统”功能。

您要完成的工作发生在第 3 层和第 2 层之间,其中 libc 或用户代码中的函数执行操作系统定义为触发系统调用的任何操作。这会打开许多​​蠕虫罐:

  • 操作系统定义为触发系统调用的内容因操作系统而异,并且(很少)在同一操作系统的版本之间有所不同。通过提供一个负责隐藏这些细节的可动态链接的 libc,在“真实”系统上缓解了这个问题。除此之外,如果您要运行 MIPS32 二进制文件,它是否使用您的模拟器支持的系统调用约定?

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

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

  • 如果您提供第 2 层,则您自己注册以提供对整个操作系统的一个特定版本的行为的完整、正确的模拟。像read()write() 这样的电话很容易处理;像fork()uselib()ioctl() 这样的其他人会更困难。您的程序使用的调用和行为与主机操作系统提供的调用和行为也不一定是一对一的映射。所有这些都假设主机是 Unix,目标程序也是。如果目标是为其他环境编译的,那么所有的赌注都没有。

最后一点是为什么大多数模拟器只提供一个 CPU 和某些目标系统的硬件行为(即第 1 层中的所有内容)。有了这些,您就可以运行原始系统的引导 ROM、操作系统和用户程序,所有这些都不会改变。有许多 existing MIPS32 emulators 就是这样做的,并且可以运行在它们模拟的硬件上运行的操作系统的未更改版本。

HTH 并祝您的项目好运。

【讨论】:

  • @Downvoter,你能解释一下为什么你认为这是一个不好的答案吗?
  • +1 很抱歉你被否决了……我不是投反对票的人,但是当有人这样做时我会感到难过
  • 感谢您的深入解答。
【解决方案2】:

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

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

您必须管理mallocfree 的动态内存分配。

还有setjmplongjmp,它们需要访问执行堆栈。

还有timesignal.h 函数。

【讨论】:

  • 感谢您的回答,但这并没有多大帮助。我知道我想模仿什么。 gcc 编译的代码可能会使用系统调用来要求处理器实际执行文件 I/O 之类的操作。我想知道这些系统调用代码是什么以及它们应该做什么。例如SPIM 为基本功能提供了一些系统调用。
  • 过失;从您的问题中不清楚您的仿真需要在什么级别。我假设您在库级别模拟 LIBC。
【解决方案3】:

我不知道 MIPS 是如何工作的,但在 Win32 上,操作系统调用必须通过 DLL/EXE 导入表显式导入到进程中。 MIPS 系统使用的可执行格式可能有类似的东西。

【讨论】:

  • 比那个低一两级。这是我决定使用的任何格式(可能会编写一个 ELF 加载器)。
【解决方案4】:

通常的方法是不仅模拟 CPU,还模拟一组具有代表性的标准外设。然后你在你的模拟器中启动一个操作系统,其中包含一个 libc 和硬件驱动程序。 Libc 将调用操作系统驱动程序,这些驱动程序调用仿真器中的虚拟硬件。有关流行的示例,请参阅 DosBox。

您的问题的另一种解释是您不想编写一个完整的模拟器,而是一个允许您在非 mips32 系统上执行 mips32 二进制文件的二进制兼容层。一个流行的例子是 MacOsX (Intel),它也可以执行 PowerPC 应用程序。

在后一种情况下,您需要模拟操作系统 ABI(应用程序二进制接口),或者您可以摆脱 libc 的 ABI。在这两种情况下,您都需要实现在模拟器上运行的存根代码和在主机上运行的代理代码:

  • 存根序列化函数调用参数
  • ...并使用一些特殊的虚拟指令将它们从模拟器内存​​传输到主机内存
  • 代理需要修补参数(字节序、整数长度、地址空间...)
  • ...并在主机系统上执行函数调用
  • 代理然后对传出的函数参数进行打包和序列化
  • ...并将它们传输回存根
  • ...将数据返回给调用者

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

祝你好运!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-25
    • 2010-10-06
    • 1970-01-01
    • 2022-01-16
    • 2017-07-24
    • 2016-11-13
    相关资源
    最近更新 更多