【问题标题】:Is there a way to use gcc to convert C to MIPS?有没有办法使用 gcc 将 C 转换为 MIPS?
【发布时间】:2011-05-09 16:40:03
【问题描述】:

我为一个类完成了 C 到 MIPS 的转换,我想对照程序集检查它。我听说有一种配置 gcc 的方法,它可以将 C 代码转换为 MIPS 架构而不是 x86 架构(我的计算机使用 Intel i5 处理器)并打印输出。

在 Ubuntu(gcc 附带)中运行终端,我使用什么命令来配置 gcc 以转换为 MIPS?还有什么我需要安装的吗?

编辑: 让我澄清一下。请阅读这个。 我不是在寻找要使用的编译器,或者人们说“你可以交叉编译,但是你应该使用没有关于如何设置的说明的其他东西。”

如果您要发布该内容,至少请参考我的说明。 GCC 随 Ubuntu 一起提供。我没有安装编译器的经验,而且很难找到除 GCC 之外的任何东西的在线教程。然后是我也需要了解的交叉编译的情况。谢谢。

【问题讨论】:

  • 您可能只想“编译”而不是“转换”。编译器总是将一种语言“转换”为另一种语言。如果您在 x86 boxen 上运行,它通常会将您的源语言转换为适合您的操作系统的 x86 可执行文件,但编译器不仅限于针对其主机操作系统和架构...
  • 是的,这就是我要问的。感谢您纠正我对术语的使用,但您能回答实际问题吗?

标签: c gcc compilation mips cross-compiling


【解决方案1】:

您应该从 Ubuntu 存储库安装交叉编译器。存储库中提供了 GCC MIPS C 交叉编译器。根据您的需要选择:

  • gcc-mips-linux-gnu - 32 位大端。
  • gcc-mipsel-linux-gnu - 32 位小端序。
  • gcc-mips64-linux-gnuabi64 - 64 位大端。
  • gcc-mips64el-linux-gnuabi64 - 64 位小端。
  • 等

(对于 Ubuntu 20.10 (Groovy Gorilla) 或更高版本的用户以及 Debian 用户的注意事项:如果您通常喜欢使用 build-essential 软件包安装常规编译器,那么您会很想知道 crossbuild-essential-mips 的存在、crossbuild-essential-mipsel、crossbuild-essential-mips64el等)

在以下示例中,我将假设您选择了 32 位 little-endian 版本 (sudo apt-get install gcc-mipsel-linux-gnu)。其他 MIPS 版本的命令类似。

要处理 MIPS 而不是系统的本机架构,请使用 mipsel-linux-gnu-gcc 命令而不是 gcc。例如,mipsel-linux-gnu-gcc -fverbose-asm -S myprog.c 生成一个包含 MIPS 程序集的文件 myprog.s。

查看 MIPS 程序集的另一种方法:运行 mipsel-linux-gnu-gcc -g -c myprog.c 以生成包含调试信息的目标文件 myprog.o。然后使用mipsel-linux-gnu-objdump -d -S myprog.o查看目标文件的反汇编。例如,如果myprog.c 是这样的:

#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    printf("The answer is: %d\n", a + b);
    return 0;
}

如果它是使用mipsel-linux-gnu-gcc -g -c myprog.c 编译的,那么mipsel-linux-gnu-objdump -d -S myprog.o 将显示如下内容:

myprog.o:     file format elf32-tradlittlemips


Disassembly of section .text:

00000000 <main>:
#include <stdio.h>

int main() {
   0:   27bdffd8    addiu   sp,sp,-40
   4:   afbf0024    sw  ra,36(sp)
   8:   afbe0020    sw  s8,32(sp)
   c:   03a0f025    move    s8,sp
  10:   3c1c0000    lui gp,0x0
  14:   279c0000    addiu   gp,gp,0
  18:   afbc0010    sw  gp,16(sp)
    int a = 1;
  1c:   24020001    li  v0,1
  20:   afc20018    sw  v0,24(s8)
    int b = 2;
  24:   24020002    li  v0,2
  28:   afc2001c    sw  v0,28(s8)
    printf("The answer is: %d\n", a + b);
  2c:   8fc30018    lw  v1,24(s8)
  30:   8fc2001c    lw  v0,28(s8)
  34:   00621021    addu    v0,v1,v0
  38:   00402825    move    a1,v0
  3c:   3c020000    lui v0,0x0
  40:   24440000    addiu   a0,v0,0
  44:   8f820000    lw  v0,0(gp)
  48:   0040c825    move    t9,v0
  4c:   0320f809    jalr    t9
  50:   00000000    nop
  54:   8fdc0010    lw  gp,16(s8)
    return 0;
  58:   00001025    move    v0,zero
}
  5c:   03c0e825    move    sp,s8
  60:   8fbf0024    lw  ra,36(sp)
  64:   8fbe0020    lw  s8,32(sp)
  68:   27bd0028    addiu   sp,sp,40
  6c:   03e00008    jr  ra
  70:   00000000    nop
    ...

【讨论】:

    【解决方案2】:

    对于一个小程序或几个功能的一次性使用,你不需要在本地安装任何东西。

    使用 Matt Godbolt 的编译器资源管理器站点 https://godbolt.org/,该站点具有适用于各种 ISA 的 GCC 和 clang,包括 MIPS 和 x86-64 以及一些其他编译器。

    请注意,默认情况下编译器资源管理器会过滤指令,因此您可以仅查看指令,而忽略对齐、节、.globl 等内容。 (对于没有全局/静态数据的函数,这实际上很好,尤其是当您只想使用编译器为您制作示例时。无论如何,如果您不使用任何指令,默认部分是.text。 )


    大多数想要 MIPS asm 做家庭作业的人都在使用 SPIM 或 MARS,通常没有分支延迟槽。 (与真正的 MIPS 不同,因此您需要调整编译器以不在无条件运行的分支之后利用下一条指令,即使它已被占用。)对于 GCC,选项是 -fno-delayed-branch - 那将用 NOP 填充每个延迟槽,因此代码仍将在真正的 MIPS 上运行。您可以手动删除所有 NOP。

    可能需要进行其他调整,例如 MARS 可能要求您使用 jr $31 而不是 j $31、Tweak mips-gcc output to work with MARS。当然,I/O 代码必须使用MARS's toy system calls 来实现,而不是jal 调用标准库函数,如printf 或std::ostream::operator&lt;&lt;。不过,您可以有效地编译(和手动调整)asm 以处理数据,例如乘以整数或求和或反转数组。

    不幸的是,GCC 没有选择使用$a0 等寄存器名称来代替$r。对于 PowerPC,有 -mregnames 使用 r1 而不是 1,但 MIPS 没有类似的选项来使用“更具符号性”的 reg 名称。

    int maybe_square(int num) {
        if (num>0)
            return num;
        return num * num;
    }
    

    On Godbolt 与 GCC 5.4 -xc -O3 -march=mips32r2 -Wall -fverbose-asm -fno-delayed-branch

    -xc 编译为 C,而不是 C++,因为我发现这比在下拉列表中切换 C 和 C++ 语言并让网站删除我的源代码更方便。

    -fverbose-asm 用 C 变量名称为目标和源创建 asm。 (在优化的代码中,通常是临时发明的,但并非总是如此。)

    -O3 启用完全优化,因为默认的-O0 调试模式对于人类来说是一团糟。如果您想手动查看代码并了解它如何实现源代码,请始终至少使用-Og。 How to remove "noise" from GCC/clang assembly output?。如果为带有 SIMD 指令的 ISA 进行编译,您还可以使用 -fno-unroll-loops 和 -fno-tree-vectorize。

    这使用 mul 而不是经典的 MIPS mult + mflo,这要归功于 -march= 选项告诉 GCC 我们正在为以后的 MIPS ISA 进行编译,而不是默认基线是什么。 (也许是 MIPS I aka R2000,-march=mips1)

    另请参阅MIPS target options 上的 GCC 手册部分。

    # gcc 5.4 -O3
    square:
            blez    $4,$L5
            nop
            move    $2,$4    # D.1492, num         # retval = num
            j       $31                            # jr $ra  = return
            nop
    
    $L5:
            mul     $2,$4,$4   # D.1492, num, num   # retval = num * num
            j       $31                             # jr $ra  = return
            nop
    

    或者使用 clang,使用 -target mips 告诉它为 MIPS 编译。 您可以在桌面上执行此操作;与 GCC 不同,clang 通常在构建时启用了多个后端。

    来自同一个 Godbolt 链接,clang 10.1 -xc -O3 -target mips -Wall -fverbose-asm -fomit-frame-pointer。默认目标显然是 MIPS32 或类似于 clang 的目标。此外,clang 默认为 MIPS 启用帧指针,从而使 asm 嘈杂。

    请注意,它选择了无分支汇编,将 if 转换为条件移动以在原始输入和 mul 结果之间进行选择。不幸的是,clang 不支持-fno-delayed-branch;也许它有相同选项的另一个名称,或者可能没有希望。

    maybe_square:
            slti    $1, $4, 1
            addiu   $2, $zero, 1
            movn    $2, $4, $1            # conditional move based on $1
            jr      $ra
            mul     $2, $2, $4            # in the branch delay slot
    

    在这种情况下,我们可以简单地将mul 放在jr 之前,但在其他情况下,转换为无分支延迟 asm 并不是一件容易的事。例如在循环计数器 before 上进行分支,它不能通过将减量放在首位来撤消;那会改变意思。


    注册名称:

    编译器使用寄存器编号,而不是名称。对于人类使用,您通常需要翻译回来。网上很多地方都有 MIPS 注册表,显示 $4..$7 是 $a0..$a3,$8..$15 是 $t0..$t7 等。例如this one。

    【讨论】:

      【解决方案3】:

      GCC 可以为大量架构生成汇编代码,包括 MIPS。但是,当 GCC 本身被编译时,就决定了给定 GCC 实例的目标是什么架构。您将在 Ubuntu 系统中找到的预编译二进制文件了解 x86(可能包括 32 位和 64 位模式),但不了解 MIPS。

      使用与运行 GCC 本身的架构不同的目标架构编译 GCC 被称为准备交叉编译工具链。这是可行的,但需要相当多的文档阅读和耐心;您通常需要先构建一个交叉汇编器和交叉链接器(GNU binutils),然后再构建 cross-GCC 本身。

      我建议使用buildroot。这是一组脚本和生成文件,旨在帮助生成完整的交叉编译工具链和实用程序。最终,您将获得针对目标系统的完整操作系统和开发工具。这包括您所追求的交叉编译器。

      另一个完全不同的解决方案是使用QEMU。这是一个适用于各种处理器和系统的仿真器,包括 MIPS 系统。您可以使用它来运行具有 MIPS 处理器的虚拟机,并在该机器中安装 MIPS 操作系统,例如Debian,一个 Linux 发行版。这样,您将获得一个 native GCC(在 MIPS 系统上运行并为 MIPS 生成代码的 GCC)。

      QEMU 方式可能更简单一些;使用交叉编译需要对一些毛茸茸的细节有所了解。无论哪种方式,您都需要大约 1 GB 的可用磁盘空间。

      【讨论】:

      • 如果可以,先生,我会为这个答案投票两次,干杯
      • 如果您只需要交叉编译器,请从 Ubuntu 存储库安装它。见:stackoverflow.com/a/58040917/5916915
      【解决方案4】:

      您可以交叉编译 GCC,以便它生成 MIPS 代码而不是 x86。这是一次很好的学习经历。

      如果您想要快速获得结果,您还可以获得一个支持 MIPS 的预构建 GCC。一种是 CodeSourcery Lite 工具链。它是免费的,适用于许多架构(包括 MIPS),并且它们已准备好使用适用于 Linux 和 Windows 的二进制文件。

      http://www.codesourcery.com/sgpp/lite/mips/portal/subscription?@template=lite

      【讨论】:

        【解决方案5】:

        你应该编译你自己的 gcc 版本,它可以交叉编译。当然这并不容易,因此您可以寻找不同的方法.. 例如this SDK。

        【讨论】:

          【解决方案6】:

          这不是配置问题,您需要一个可以交叉编译到 MIPS 的 GCC 版本。这需要一个特殊的 GCC 构建,并且设置起来相当麻烦(构建 GCC 不适合胆小的人)。

          我建议为此使用LCC。与 GCC 相比,使用 LCC 进行交叉编译要容易得多,而且在当前机器上构建 LCC 只需几秒钟。

          【讨论】:

          • 感谢您的快速回复,但它绕过了我的问题。您是在告诉我我应该做什么(交叉编译?),然后将我引导到不同的编译器。我在详细询问如何获得该版本的 GCC 以及要使用哪些命令,而不是我是否应该使用 GCC 或其他编译器。然后是设置这个编译器的例子,在 LCC 网站上没有任何类型的说明。
          • 我假设您的主要兴趣是将 C 编译为 MIPS 程序集,而不是使用 GCC。如果您坚持为此使用 GCC,请继续,但请考虑到自己已受到警告 - 只编译一个程序是不值得的(特别是如果您难以构建 LCC)。关于构建跨 GCC 的一些(相对过时,但我没有发现任何更新的)指令在这里:gcc.gnu.org/wiki/Building_Cross_Toolchains_with_gcc
          • 我的错误。我想我误解了你的回答。是的,我的兴趣是将 C 转换为 MIPS。但是,如果您要告诉我使用不同的编译器,您能否将我介绍给解释如何设置它以及如何与它交叉编译到 MIPS 的某个地方?我找不到这两样东西。
          • 在 doc/install.html 的 LCC 发行版中有详细的设置说明。具体做什么取决于您在哪个主机平台上进行编译——Win32、Linux 等等?无论如何,要获得 MIPS 编译器,您在构建 lcc 时将“mips/irix”指定为 TARGET。
          【解决方案7】:

          您需要将源代码下载到 binutils 和 gcc-core 并使用 ../configure --target=mips ... 之类的东西进行编译。您可能需要选择特定的 MIPS 目标。然后你可以使用mips-gcc -S。

          【讨论】:

          • 你能解释一下吗?我还需要从 C 转换为 MIPSR3000。
          • @ShankhadeepMukerji 看看gcc.gnu.org/install。在第 3 步下查看“目标规范”。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-06
          • 2017-03-25
          • 2021-09-01
          • 2021-05-15
          • 1970-01-01
          相关资源
          最近更新 更多