GCC在幕后调用各种东西,不仅仅是
as
,还有
ld
。如果您想证明它,这很容易实现(将正确的
as
、
ld
和其他二进制文件替换为打印出其命令行的二进制文件,然后运行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 );
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
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
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()并不是什么特殊的函数名。在某些编译器中可能是,但我可以制作不需要该函数名称的程序。
gcc
本身代表“GNU 编译器集合”(注意它没有以任何方式提到 C,这是正确的,C 编译器隐藏在cc
下面,而 C++ 在g++
下面,还有许多编译器在gcc
中)。因此,它实际上是一种“包装器”,用于调用所有其他工具来生成最终的二进制可执行文件,是一个比较聪明的包装器,知道为所有工具使用哪些选项(如目标平台),以及哪些属于编译器或链接器。 - Ped7g