4

与另一个问题有关我正在尝试在 gem5 中运行这个简单的 C 程序:

int main() {
    int a=1, b=2;
    int c=a+b;
    return c;
}

它失败了,因为 gem5 没有实现一些系统调用。

我的问题是,为什么像这样的简单程序需要系统调用?这应该可以毫无问题地运行裸机。有没有办法编译它以避免系统调用?我正在使用 arm-linux-gnueabi-gcc -static -DUNIX 来编译它。

4

4 回答 4

13

没有系统调用,程序无法退出。它的工作方式通常是这样的:

// Not how it's actually implemented... just a sketch.
void _start() {
    char **argv = ...;
    int argc = ...;
    // ... other initialization code ...
    int retcode = main(argc, argv);
    exit(retcode);
}

确切的细节取决于操作系统,但是exit()终止进程的,通常必须是系统调用或通过系统调用实现。

请注意,这适用于“托管”C 实现,而不适用于“独立”C 实现,并且是高度特定于操作系统的。有独立的 C 实现可以在裸机上运行,​​但托管的 C 实现通常需要操作系统。

您可以在没有标准库和运行时的情况下进行编译,但您的入口点无法返回……没有运行时,没有什么可返回的。

创建一个裸机程序

通常可以编译能够运行裸机的程序。

  • 使用-ffreestanding. 这使得 GCC 生成的代码不假定标准库可用(并具有其他效果)。

  • 使用-nostdlib. 这将阻止 GCC 与标准库链接。请注意,memcmp无论如何都可能会生成memsetmemcpy、 和memmove调用,因此您可能必须自己提供这些。

此时您可以编写程序,但您通常必须使用_start而不是main

void _start(void) {
    while (1) { }
}

请注意,您不能从_start! 想一想……无处可归。当你像这样编译一个程序时,你可以看到它没有使用任何系统调用,也没有加载器。

$ gcc -ffreestanding -nostdlib test.c

我们可以验证它没有加载任何库:

$ ldd a.out                              
    静态链接
$ readelf -d a.out

偏移 0xf30 处的动态部分包含 8 个条目:
  标签类型名称/值
 0x000000006ffffef5 (GNU_HASH) 0x278
 0x0000000000000005 (STRTAB) 0x2b0
 0x0000000000000006 (SYMTAB) 0x298
 0x000000000000000a (STRSZ) 1 (字节)
 0x000000000000000b (SYMENT) 24 (字节)
 0x0000000000000015(调试)0x0
 0x000000006ffffffb (FLAGS_1) 标志:PIE
 0x0000000000000000 (NULL) 0x0

我们还可以看到它不包含任何进行系统调用的代码:

$ objdump -d a.out

a.out:文件格式elf64-x86-64


部分.text的反汇编:

00000000000002c0 <_start>:
 2c0: eb fe jmp 2c0 <_start>
于 2018-07-10T16:48:49.727 回答
4

我的问题是,为什么像这样的简单程序需要系统调用?

运行时加载程序ld.so执行系统调用。C 运行时执行系统调用。做strace <application>看看。

于 2018-07-10T16:47:37.563 回答
2

您可能想要检查 gcc 的一些参数。其中:

  • -f独立式
  • -nostdlib
  • -nodefaultlibs

我的问题是,为什么像这样的简单程序需要系统调用?

因为进入 main 和退出程序是基于系统调用的。

于 2018-07-10T16:51:02.753 回答
0

用 arm-unknown-linux-uclibcgnueabi 编译解决了这个问题。显然 uclibc 实现不使用 gem5 没有实现的系统调用。

于 2018-07-11T20:17:16.673 回答