使用as + gcc和仅使用gcc处理ARM汇编的区别

3

我正在使用 asgcc 来汇编和创建ARM汇编程序的可执行文件,正如这篇教程所推荐的那样,具体步骤如下:

给定一个汇编源代码文件 program.s,我运行以下命令:

as -o program.o program.s

然后:

gcc -o program program.o

然而,直接在汇编源代码上运行gcc,如下所示:

gcc -o program program.s

产生相同结果。

在幕后,gcc是否会调用as?是否有任何理由同时使用asgcc,考虑到gcc单独就可以从源代码生成可执行文件?

我在树莓派3上运行这个命令,使用的是Raspbian Jessie(一种Debian衍生版),gcc 4.9.2,as 2.25。


gcc 本身代表“GNU 编译器集合”(注意它没有以任何方式提到 C,这是正确的,C 编译器隐藏在 cc 下面,而 C++ 在 g++ 下面,还有许多编译器在 gcc 中)。因此,它实际上是一种“包装器”,用于调用所有其他工具来生成最终的二进制可执行文件,是一个比较聪明的包装器,知道为所有工具使用哪些选项(如目标平台),以及哪些属于编译器或链接器。 - Ped7g
1个回答

8
GCC在幕后调用各种东西,不仅仅是as,还有ld。如果您想证明它,这很容易实现(将正确的asld和其他二进制文件替换为打印出其命令行的二进制文件,然后运行GCC并查看调用的二进制文件)。
当您将GCC用作汇编器时,它会经过C预处理器,因此您可以执行一些相当恶心的操作,例如:
start.s
//this is a comment
@this is a comment
#define FOO BAR

.globl _start
_start:
    mov sp,#0x80000
    bl hello
    b .

.globl world
world:
    bx lr

为了更好地了解正在发生的事情,这里还有其他文件:

so.h

unsigned int world ( unsigned int, unsigned int );

#define FIVE 5
#define SIX 6

so.c

#include "so.h"

unsigned int hello ( void )
{
    unsigned int a,b,c;
    a=FIVE;
    b=SIX;
    c=world(a,b);
    return(c+1);
}

构建
arm-none-eabi-gcc -save-temps -nostdlib -nostartfiles -ffreestanding -O2 start.s so.c -o so.elf
arm-none-eabi-objdump -D so.elf

生成

00008000 <_start>:
    8000:   e3a0d702    mov sp, #524288 ; 0x80000
    8004:   eb000001    bl  8010 <hello>
    8008:   eafffffe    b   8008 <_start+0x8>

0000800c <world>:
    800c:   e12fff1e    bx  lr

00008010 <hello>:
    8010:   e92d4010    push    {r4, lr}
    8014:   e3a01006    mov r1, #6
    8018:   e3a00005    mov r0, #5
    801c:   ebfffffa    bl  800c <world>
    8020:   e8bd4010    pop {r4, lr}
    8024:   e2800001    add r0, r0, #1
    8028:   e12fff1e    bx  lr

这是一个非常简单的项目。这里是经过预处理器处理后的so.i文件,它会获取包含文件并替换定义:

# 1 "so.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "so.c"


# 1 "so.h" 1

unsigned int world ( unsigned int, unsigned int );
# 4 "so.c" 2

unsigned int hello ( void )
{
    unsigned int a,b,c;
    a=5;
    b=6;
    c=world(a,b);
    return(c+1);
}

然后GCC调用实际的编译器(其程序名称不是GCC)。

这将生成so.s文件:

    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "so.c"
    .text
    .align  2
    .global hello
    .syntax unified
    .arm
    .fpu softvfp
    .type   hello, %function
hello:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    push    {r4, lr}
    mov r1, #6
    mov r0, #5
    bl  world
    pop {r4, lr}
    add r0, r0, #1
    bx  lr
    .size   hello, .-hello
    .ident  "GCC: (GNU) 6.3.0"

然后将其输入汇编器生成so.o文件。然后调用链接器将它们转换为so.elf文件。

现在,您可以直接进行大多数调用。这并不意味着这些程序没有调用其他程序。GCC仍然调用一个或多个程序来实际执行编译。

arm-none-eabi-as start.s -o start.o
arm-none-eabi-gcc -O2 -S so.c 
arm-none-eabi-as so.s -o so.o
arm-none-eabi-ld start.o so.o -o so.elf
arm-none-eabi-objdump -D so.elf

产生相同的结果:
00008000 <_start>:
    8000:   e3a0d702    mov sp, #524288 ; 0x80000
    8004:   eb000001    bl  8010 <hello>
    8008:   eafffffe    b   8008 <_start+0x8>

0000800c <world>:
    800c:   e12fff1e    bx  lr

00008010 <hello>:
    8010:   e92d4010    push    {r4, lr}
    8014:   e3a01006    mov r1, #6
    8018:   e3a00005    mov r0, #5
    801c:   ebfffffa    bl  800c <world>
    8020:   e8bd4010    pop {r4, lr}
    8024:   e2800001    add r0, r0, #1
    8028:   e12fff1e    bx  lr

使用GCC时使用-S感觉有些不对。改为这种方式使用会更自然:

arm-none-eabi-gcc -O2 -c so.c -o so.o

现在有一个链接脚本,我们没有提供,但工具链有默认值。我们可以控制它,根据这个的目标,也许我们应该这样做。
我不高兴看到新/当前版本的as容忍C注释等等......以前不是这样的,一定是最新版本的新东西。
因此,“工具链”这个术语是指将多个工具链接在一起,一个链接到下一个。
并非所有编译器都需要汇编语言步骤。有些编译成中间代码,然后有另一个工具将特定于编译器的中间代码转换为汇编语言。然后调用一些汇编程序(GCC的中间代码在编译步骤中的表格中,而clang / llvm则可以要求它编译到这个代码,然后从那里进入其中一个目标的汇编语言)。
有些编译器直接生成机器码,而不停留在汇编语言上。这可能是那些“只是因为它在那里就爬山”的事情,与“绕路”相反。就像纯粹使用汇编语言编写操作系统一样。
对于任何体量较大的项目和支持它的工具,您都需要一个链接器,并且是第一个支持新目标的工具。处理器(芯片或ip或两者)供应商将拥有汇编程序,以及其他可用的工具。
尝试手动使用汇编语言编译甚至以上简单的C程序。然后尝试再次进行不使用汇编语言,手动只使用机器代码。您会发现,对于编译器开发人员来说,使用汇编语言作为中间步骤要明智得多,除此之外,这种方式已经被使用了很长时间,这也是继续使用它的一个好理由。
如果您在使用的GNU工具链目录中漫游,您可能会找到像cc1之类的程序。
./libexec/gcc/arm-none-eabi/6.3.0/cc1 --help
The following options are specific to just the language Ada:
 None found.  Use --help=Ada to show *all* the options supported by the Ada front-end.

The following options are specific to just the language AdaSCIL:
 None found.  Use --help=AdaSCIL to show *all* the options supported by the AdaSCIL front-end.

The following options are specific to just the language AdaWhy:
 None found.  Use --help=AdaWhy to show *all* the options supported by the AdaWhy front-end.

The following options are specific to just the language C:
 None found.  Use --help=C to show *all* the options supported by the C front-end.

The following options are specific to just the language C++:
  -Wplacement-new                   
  -Wplacement-new=                  0xffffffff

The following options are specific to just the language Fortran:

现在,如果你运行cc1程序来处理使用-save-temps选项保存的so.i文件,就会得到汇编语言文件so.s。
你可以继续深入查找目录或gnu工具源代码以找到更多好东西。
请注意,在Stack Overflow上这个问题以各种方式反复提出过很多次。
还要注意,如我所示,main()并不是什么特殊的函数名。在某些编译器中可能是,但我可以制作不需要该函数名称的程序。

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