gcc 为什么我需要“.global main”来编译这个程序?

iyfamqjs  于 2022-11-24  发布在  其他
关注(0)|答案(2)|浏览(202)

我有ARM的汇编代码。

.text
.global main

fib:
    
    push { r4}
    MOV r1, #1 
    MOV r2, #0  
    MOV r3, #1
    
    loop:
    CMP r3, r0
    BGE exit
    mov r4,r1
    ADD r1, r1,r2
    mov r2, r4
   
    add r3, r3, #1
    B loop
    
    exit:
    pop {r4}
    mov r0, r1
    MOV PC,  lr

main:
  
    mov r0, #13
    push {lr}
    BL fib
    pop {lr}
    mov r1, r0
    ldr r0, =output_string
    push {lr}
    bl printf
    pop {lr}
    MOV PC, lr



@ The 'data' section contains static data for our program
.data
output_string:
    .asciz "%d\n"

但是我想知道,为什么我需要“.global main”来编译它?我在一个答案here上读到,这告诉编译器它将对链接器可见,因为其他目标文件将使用它。但是我这里不是只有一个目标文件吗?
它是否也告诉我们程序应该从那里开始,因此没有它就不能工作?

cu6pst1q

cu6pst1q1#

关于“但是我这里不是只有一个对象文件吗?",您仍然需要给予一个入口点。.text使以下行属于“code”部分(不可修改),而.data部分包含静态定义的变量。
在.text中,应该有一个入口点。在您的例子中,它是“main”程序。在其他情况下,例如,当您使用汇编程序而不是GCC时,_start是入口点,您应该使用

.global _start

_start:
o8x7eapl

o8x7eapl2#

下面是简短的回答...
你使用了compile而不是assemble这个词,这就把你正在做的事情联系起来了。你还使用了main而没有任何引导程序,这也意味着你希望别人提供入口点和引导程序。

main:
    b .

arm-none-eabi-as so.s -o so.o
arm-none-eabi-objdump -d so.o

so.o:     file format elf32-littlearm

Disassembly of section .text:

00000000 <main>:
   0:   eafffffe    b   0 <main>

这就是你如何把它汇编成一个对象。gcc程序本身不是一个编译器,它是一个调用其他程序的shell程序一个C预处理器(例如扩展包含和定义)一个C编译器,一个汇编器和链接器。除非你告诉它不要这样做。
所以

arm-none-eabi-gcc so.s -o so
/storage4/gnu/arm/bin/../lib/gcc/arm-none-eabi/12.2.0/../../../../arm-none-eabi/bin/ld: cannot find crt0.o: No such file or directory
/storage4/gnu/arm/bin/../lib/gcc/arm-none-eabi/12.2.0/../../../../arm-none-eabi/bin/ld: cannot find -lc: No such file or directory
collect2: error: ld returned 1 exit status

在我的情况下,这说明了更多的故事。
我猜这就是你的故事吧。

arm-linux-gnueabi-gcc so.s -o so
/usr/lib/gcc-cross/arm-linux-gnueabi/9/../../../../arm-linux-gnueabi/bin/ld: /usr/lib/gcc-cross/arm-linux-gnueabi/9/../../../../arm-linux-gnueabi/lib/../lib/crt1.o: in function `_start':
(.text+0x34): undefined reference to `main'
collect2: error: ld returned 1 exit status

您基本上是在尝试“编译”一个C程序,但使用了一些汇编和名称main(),这是C语言特有的。
大多数人不会构建自己的gnu工具链,他们只是下载一个,即使他们至少为一个本地(而不是交叉)编译器构建了工具链,也会尝试检测主机并为其构建,包括操作系统特定的项目,也许还有一个C库。
不幸的是,C库(printf,memcpy等)倾向于在引导程序上加载东西,特别是链接器脚本。因此,链接器脚本和引导程序(这种结合通常必须存在,才能有一个正确的C引导程序)以及C库(不太多)之间有这种亲密的结合。
有人早在不管出于什么原因(不要问“为什么”的问题,假设没有答案)时就显然用了这个标签开头:然后变成了_start。至少对于GNU相关的C库引导程序来说,它已经卡住了。你也可以免费或作为行李(旁观者的眼睛)得到的东西之一是默认的链接器脚本,如果你没有指定一个,那么(至少有一个)默认的。
该默认值包含以下行

ENTRY(_start)

您可以使用grep查找这些链接器脚本。
然后是引导程序,至少在gnu相关的世界里通常被称为crt0.S,也许还有其他的,因为有太多的人不知道。
That _start最终会导致对main()的调用。这取决于工具链或库或两者的组合,以及需要多少步骤才能到达那里。
我可以把上面的链接,变成一个无聊的程序。

arm-none-eabi-ld so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
arm-none-eabi-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .text:

00008000 <main>:
    8000:   eafffffe    b   8000 <main>

它使用默认链接器脚本,并且在任何对象中都没有找到_start入口点(全局标签),因此它发出警告,指出它将只使用. text的开头。
我们可以用一个同样无用的引导程序继续这段旅程,但它将把各个点联系起来。

_start:
    bl main
    b .

arm-none-eabi-as bs.s -o bs.o
arm-none-eabi-ld bs.o so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
arm-none-eabi-ld: bs.o: in function `_start':
(.text+0x0): undefined reference to `main'

所以我们需要做两件事
英国标准

.globl _start
_start:
    bl main
    b .

所以

.globl main
main:
    b .

arm-none-eabi-as bs.s -o bs.o
arm-none-eabi-as so.s -o so.o
arm-none-eabi-ld bs.o so.o -o so.elf
arm-none-eabi-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .text:

00008000 <_start>:
    8000:   eb000000    bl  8008 <main>
    8004:   eafffffe    b   8004 <_start+0x4>

00008008 <main>:
    8008:   eafffffe    b   8008 <main>

现在,工具很高兴,它正在构建一个基于链接器脚本的程序,我们没有控制,但我们已经控制了所有的代码。
如果我们回到这里,让gcc用默认值调用所有的程序。

arm-linux-gnueabi-gcc so.s -o so.elf
arm-linux-gnueabi-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .init:

00010294 <_init>:
   10294:   e92d4008    push    {r3, lr}
   10298:   eb00001d    bl  10314 <call_weak_fn>
   1029c:   e8bd8008    pop {r3, pc}

有一吨的东西在那里...你可以看到你自己,你的小主要和其他功能是埋在这一切
如果使用readelf

Entry point address:               0x102d8

操作系统需要知道。

000102d8 <_start>:
   102d8:   e3a0b000    mov fp, #0
   102dc:   e3a0e000    mov lr, #0
   102e0:   e49d1004    pop {r1}        ; (ldr r1, [sp], #4)
   102e4:   e1a0200d    mov r2, sp
   102e8:   e52d2004    push    {r2}        ; (str r2, [sp, #-4]!)
   102ec:   e52d0004    push    {r0}        ; (str r0, [sp, #-4]!)
   102f0:   e59fc010    ldr ip, [pc, #16]   ; 10308 <_start+0x30>
   102f4:   e52dc004    push    {ip}        ; (str ip, [sp, #-4]!)
   102f8:   e59f000c    ldr r0, [pc, #12]   ; 1030c <_start+0x34>
   102fc:   e59f300c    ldr r3, [pc, #12]   ; 10310 <_start+0x38>
   10300:   ebffffeb    bl  102b4 <__libc_start_main@plt>
   10304:   ebfffff0    bl  102cc <abort@plt>
   10308:   0001042c    .word   0x0001042c
   1030c:   000103c8    .word   0x000103c8

并且像这样的系统/目标/工具链所期望的那样存在_start。
main在这里(就在它从所有被链接的东西中着陆的地方)

000103c8 <main>:
   103c8:   eafffffe    b   103c8 <main>

不管是什么原因。

000102d8 <_start>:
   102d8:   e3a0b000    mov fp, #0
   102dc:   e3a0e000    mov lr, #0
   102e0:   e49d1004    pop {r1}        ; (ldr r1, [sp], #4)
   102e4:   e1a0200d    mov r2, sp
   102e8:   e52d2004    push    {r2}        ; (str r2, [sp, #-4]!)
   102ec:   e52d0004    push    {r0}        ; (str r0, [sp, #-4]!)
   102f0:   e59fc010    ldr ip, [pc, #16]   ; 10308 <_start+0x30>
   102f4:   e52dc004    push    {ip}        ; (str ip, [sp, #-4]!)
   102f8:   e59f000c    ldr r0, [pc, #12]   ; 1030c <_start+0x34>  <-------
   102fc:   e59f300c    ldr r3, [pc, #12]   ; 10310 <_start+0x38>
   10300:   ebffffeb    bl  102b4 <__libc_start_main@plt>
   10304:   ebfffff0    bl  102cc <abort@plt>
   10308:   0001042c    .word   0x0001042c
   1030c:   000103c8    .word   0x000103c8 <--------
   10310:   000103cc    .word   0x000103cc

r 0包含到main的地址,我将让您解开其余部分,这只是多年/数十年积累的痛苦。
如果我们选择这样做:
so.ld

MEMORY
{
    one : ORIGIN = 0x00001000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > one
}

arm-linux-gnueabi-gcc -Wl,-Tso.ld bs.o so.o -o so.elf
/usr/lib/gcc-cross/arm-linux-gnueabi/9/../../../../arm-linux-gnueabi/bin/ld: bs.o: in function `_start':
(.text+0x0): multiple definition of `_start'; /usr/lib/gcc-cross/arm-linux-gnueabi/9/../../../../arm-linux-gnueabi/lib/../lib/crt1.o:(.text+0x0): first defined here
/usr/lib/gcc-cross/arm-linux-gnueabi/9/../../../../arm-linux-gnueabi/bin/ld: error: no memory region specified for loadable section `.note.gnu.build-id'
collect2: error: ld returned 1 exit status

因为gcc想吸收它所知道的默认自举,因此彼得的评论。

arm-linux-gnueabi-gcc -nostartfiles -nostdlib -Wl,-Tso.ld,--build-id=none bs.o so.o -o so.elf
arm-linux-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .text:

00001000 <_start>:
    1000:   eb000000    bl  1008 <main>
    1004:   eafffffe    b   1004 <_start+0x4>

00001008 <main>:
    1008:   eafffffe    b   1008 <main>

现在我们已经使用了一个编译器,它与一个目标有很强的关联,可以用作汇编器和链接器。

ENTRY(hello)
MEMORY
{
    one : ORIGIN = 0x00001000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > one
}

/usr/lib/gcc-cross/arm-linux-gnueabi/9/../../../../arm-linux-gnueabi/bin/ld: warning: cannot find entry symbol hello; defaulting to 0000000000001000

它在这种情况下是有效的,但要解决这个问题,我需要连接所有的点。
英国标准

.globl hello
hello:
    bl main
    b .

它又固定同样:

.globl hello
hello:
    bl world
    b .


破碎的

(.text+0x0): undefined reference to `world'

所以

.globl world
world:
    b .

已修复。
您已经创建了两个标注(小心,因为你可能会陷入其他麻烦,见下文)(它们不是“函数”)。其中一个主要暗示你想用一些汇编来开发C工具。你也推动lr来允许嵌套函数,但在你的代码中没有任何地方有引导程序(至少设置堆栈指针并调用main(),还要准备目标所需的.bss和.data,再加上printf后面堆积如山的东西)。所以你想要所有的C库开销,并期望在我们假设的操作系统上运行它(从你不完整的问题中)。
所以你想从引导程序中调用main,所以你必须使它全局化,这样被调用者才能看到它。
这里你会遇到的另一个问题是你的汇编语言是32位arm还是thumb取决于命令行。
啊手臂不是拇指:

MOV PC, lr

存储器
但举个例子

unsigned int more_fun ( unsigned int );
unsigned int fun ( unsigned int x )
{
    return(more_fun(x)+1);
}

armv 5 t或更高版本,但不是armv7

Disassembly of section .text:

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   ebfffffe    bl  0 <more_fun>
   8:   e2800001    add r0, r0, #1
   c:   e8bd8010    pop {r4, pc}

以ARMv 7s开始(以及以它们结束,因为ARMv 8-a是64位,具有可能的ARMv7模式)

arm-linux-gnueabi-gcc -march=armv7 -O2 -c fun.c -o fun.o
arm-linux-gnueabi-objdump -d fun.o

fun.o:     file format elf32-littlearm

Disassembly of section .text:

00000000 <fun>:
   0:   b508        push    {r3, lr}
   2:   f7ff fffe   bl  0 <more_fun>
   6:   3001        adds    r0, #1
   8:   bd08        pop {r3, pc}
   a:   bf00        nop

所以

.globl world
world:
    bl fun
    b .

.global more_fun
more_fun:
    bx lr

这将崩溃

Disassembly of section .text:

00001000 <hello>:
    1000:   eb000000    bl  1008 <world>
    1004:   eafffffe    b   1004 <hello+0x4>

00001008 <world>:
    1008:   fa000001    blx 1014 <fun>
    100c:   eafffffe    b   100c <world+0x4>

00001010 <more_fun>:
    1010:   e12fff1e    bx  lr

00001014 <fun>:
    1014:   b508        push    {r3, lr}
    1016:   f7ff fffb   bl  1010 <more_fun>
    101a:   3001        adds    r0, #1
    101c:   bd08        pop {r3, pc}
    101e:   bf00        nop

它是在手臂和拇指之间弹跳,没有适当的蹦床(单板)。它没有很好地混合手臂和拇指模式。
所以

.globl world
.global more_fun
.type more_fun,%function
.type world,%function

world:
    bl fun
    b .

more_fun:
    bx lr

它不需要蹦床,因为blx很好,但是我们可以看到,它确实正确地看到了对fun()的调用,就像在gcc生成的程序集中一样

.thumb_func
    .type   fun, %function
fun:

两种解决方案都被使用(对于thumb模式,您可以在标签之前使用.thumb_func(它看到的下一个标签),对于arm,没有快捷方式,但正如您所看到的,.globl或.global和.type不像.thumb_func那样是位置特定的),并且该工具知道如何处理fun,但直到我们指定了more_fun才知道。
要查看蹦床下降到armv6或更早版本

arm-none-eabi-gcc -O2 -c  -mthumb fun.c -o fun.o
arm-linux-gnueabi-ld -Tso.ld bs.o so.o fun.o -o so.elf
arm-linux-gnueabi-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .text:

00001000 <hello>:
    1000:   eb000000    bl  1008 <world>
    1004:   eafffffe    b   1004 <hello+0x4>

00001008 <world>:
    1008:   eb000006    bl  1028 <__fun_from_arm>
    100c:   eafffffe    b   100c <world+0x4>

00001010 <more_fun>:
    1010:   e12fff1e    bx  lr

00001014 <fun>:
    1014:   b510        push    {r4, lr}
    1016:   f000 f80d   bl  1034 <__more_fun_from_thumb>
    101a:   3001        adds    r0, #1
    101c:   bc10        pop {r4}
    101e:   bc02        pop {r1}
    1020:   4708        bx  r1
    1022:   46c0        nop         ; (mov r8, r8)
    1024:   0000        movs    r0, r0
    ...

00001028 <__fun_from_arm>:
    1028:   e59fc000    ldr ip, [pc]    ; 1030 <__fun_from_arm+0x8>
    102c:   e12fff1c    bx  ip
    1030:   00001015    .word   0x00001015

00001034 <__more_fun_from_thumb>:
    1034:   4778        bx  pc
    1036:   e7fd        b.n 1034 <__more_fun_from_thumb>
    1038:   eafffff4    b   1010 <more_fun>
    103c:   00000000    andeq   r0, r0, r0


因此您可能需要添加

.type main, %function

您的代码以及.globl或.global(您的首选项)。不是为了使工具链满意,而是为了使代码可能执行。
使用bx lr而不是mov pc,lr。手臂只有armv4和更老的(?)日子早已过去。
或者你可以

pop {pc}

显示器
而不是这两个

pop {lr}
MOV PC, lr

pop {lr}
bx lr

你也许可以摆脱与mov pc,lr今天,但很长一段时间,这是一个问题。将不得不查找互通的核心架构,你正在使用,随着时间的推移,越来越多的指令可以使用。
为什么我需要“.global main”来编译这个程序?
使用“compile”一词意味着使用的是gcc而不是汇编程序(和连接器)(您也有一个main())。Gcc使用默认链接器脚本和默认引导程序调用链接器(crt0.o),这取决于为该系统构建gcc的方式。crt0.o调用main().您正在将对象与调用它得程序链接,但尚未提供名为main得外部标签,因此链接器将找不到main()在任何地方建造并因此抱怨。
您并不是只有一个对象,您有crt0.o和C库。printf()本身就吸收了大量的C库。C库很可能会作为一个库提供给链接器(something.a)对象文件的列表并不长,但它就像拥有了一吨多的对象文件。如果你有C库作为对象,我们需要一个非常非常长的目标文件列表来让链接器满意。
你的几行代码就像上面所示的那样吸收了大量的东西。它不仅仅是你的一个对象文件。

相关问题