我正在为没有STM32 IDE的ARM Cortex-M4微控制器编译程序.我将ARM-None-eabi工具链与newlib libc一起使用,这是我为我的特定微控制器改编的一个链接器脚本,以及我从ST获取的一些启动代码.

经过无数个小时的调试,我发现必须对特定的内存区进行零初始化.否则,0x200006E8...0x2000071c将包含随机数据,CPU将在某个时间点访问这些数据,从而导致硬故障. readelf -S显示,这对应于第.got.got.plt节:

  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .isr_vector       PROGBITS        08000000 001000 00018c 00   A  0   0  1
  [ 2] .text             PROGBITS        0800018c 00118c 00c4c4 00  AX  0   0  4
  [ 3] .rodata           PROGBITS        0800c650 00d650 0000a4 00   A  0   0  4
  [ 4] .ARM.extab        PROGBITS        0800c6f4 00d6f4 000000 00   A  0   0  1
  [ 5] .ARM              ARM_EXIDX       0800c6f4 00d6f4 000008 00  AL  2   0  4
  [ 6] .preinit_array    PREINIT_ARRAY   0800c6fc 00e71c 000000 04  WA  0   0  1
  [ 7] .init_array       INIT_ARRAY      0800c6fc 00d6fc 000004 04  WA  0   0  4
  [ 8] .fini_array       FINI_ARRAY      0800c700 00d700 000004 04  WA  0   0  4
  [ 9] .data             PROGBITS        20000000 00e000 0006e8 00  WA  0   0  8
  [10] .got              PROGBITS        200006e8 00e6e8 000028 00  WA  0   0  4
  [11] .got.plt          PROGBITS        20000710 00e710 00000c 04  WA  0   0  4
  [12] .sram2            PROGBITS        2000c000 00e71c 000000 00   W  0   0  1
  [13] .bss              NOBITS          2000071c 00e71c 000500 00  WA  0   0  4

启动代码只对.bss部分进行零初始化,并且没有提到gotplt.链接器脚本也没有引用gotplt. 这意味着没有GET的开始和结束标签(就像.bss一样),启动代码可以使用它们来初始化它,我真的不想硬编码地址.

我使用以下标志与GCC进行编译:

-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -fno-common -ffunction-sections -fdata-sections -Wl,--gc-sections -specs=nano.specs -ffreestanding -Wall -O0 -ggdb

并链接:

-T link.ld -lc -lgcc

据我所知,GET只适用于动态链接. Why is it inserted although the linker script does not specify it? 我试图用objcopy删除.got.got.plt部分,但这对问题没有影响.

我搞不懂为什么GET应该用零来初始化才能工作,而不是用一些地址.Is it possible that during the linking stages the GOT got inserted "into" the 100 somehow, i.e, the GOT section should really be part of 100? GET恰好在.bss段之前(按照预期进行了初始化).

理想情况下,我希望只对.bss部分进行零初始化,并且不对启动代码进行任何修改.GET要么根本不使用,要么静态填充.

任何关于这里可能发生的事情的 idea 都将受到高度赞赏.

推荐答案

也许这是你已经知道的东西,我不能从问题中判断...

unsigned int x;

void fun ( void )
{
    x=5;
}

MEMORY {
    one : ORIGIN = 0x000, LENGTH = 256
    two : ORIGIN = 0x100, LENGTH = 256
}
SECTIONS {
    .text       : { *(.text)       } > one
    .bss        : { *(.bss)        } > two
}

arm-none-eabi-gcc -O2 -c -mcpu=cortex-m4 so.c -o so.o
arm-none-eabi-ld -Tso.ld so.o -o so.elf
arm-none-eabi-objdump -D so.elf


Disassembly of section .text:

00000000 <fun>:
   0:   4b01        ldr r3, [pc, #4]    ; (8 <fun+0x8>)
   2:   2205        movs    r2, #5
   4:   601a        str r2, [r3, #0]
   6:   4770        bx  lr
   8:   00000100    andeq   r0, r0, r0, lsl #2

Disassembly of section .bss:

00000100 <x>:
 100:   00000000    andeq   r0, r0, r0

IMO这是正常的/典型的.(意思是,优化,没有图片/馅饼之类的东西)(稍后将介绍GC部分).您可以看到,编译器在池中为链接器生成一个值来填充x的地址.

注意,我从来不使用这些,因为我做了很多裸金属嵌入的东西,但人GCC和寻找-fPIC-fPIC和派来看区别,这是一个有趣的阅读.在本例中,对于这段代码,四个组合产生了相同的结果.

Disassembly of section .text:

00000000 <fun>:
   0:   4b03        ldr r3, [pc, #12]   ; (10 <fun+0x10>)
   2:   4a04        ldr r2, [pc, #16]   ; (14 <fun+0x14>)
   4:   447b        add r3, pc
   6:   589b        ldr r3, [r3, r2]
   8:   2205        movs    r2, #5
   a:   601a        str r2, [r3, #0]
   c:   4770        bx  lr
   e:   bf00        nop
  10:   00000008    andeq   r0, r0, r8
  14:   00000000    andeq   r0, r0, r0

这是一种双重间接,或者说它增加了一种间接性.现在请注意问题所在

   4:   447b        add r3, pc

池中的值不是GET的地址,而是GOT的相对偏移量.编译器将 for each 函数生成这个函数,这是使用GET的整个过程,首先,你不想在每个函数的池中使用固定地址来寻址数据.

从理论上讲,位置独立也会使二进制代码变得位置独立,是的,就像所有的数据访问一样,这会使代码变得更大.因此,只有在绝对需要的情况下才使用PIC/PIE,特别是对于资源有限的MCU.您保存的每一个字节都是成功的.时钟周期的惩罚通常是可衡量的.

如果您使用的是位置独立性,您可以使用它做两件事,或者同时做两件事:移动代码、移动数据或同 timeshift 动两者.如果您移动代码,那么就像它是如何为这个目标构建的一样,您必须保持GET相对于代码.如果PIC指的是共享库,那么这意味着基于RAM的系统,即将程序加载到RAM中的操作系统.我们不是在一个基于RAM的系统上,除非您为RAM和复制和 skip 构建.如上所述(链接器不会更改/修复这一点),代码到GET地址的关系必须是固定的(对于此编译器、版本、目标、示例等)(如果它发生在一个示例中,那么它可能发生在您身上).但是,如果您想要移动数据,那么您必须更新GET,这意味着它必须在RAM中.所以GET需要在RAM中,但假设二进制文件在闪存中,所以如果你想从不同的位置(闪存或RAM)运行二进制文件,那么你必须将GET移动到一个相对距离之外.然后,如果您想要移动数据(无论是否进行二进制移动),则必须转到GET本身,并将地址的相对更改添加到每个条目.是的,在这两种情况下,你都需要知道GET在哪里,有多大.

如果你没有移动任何东西,那么GET就准备好了……从构建的Angular 来看.如果您在MCU中为ram链接它,那么它将不会被填充,除非您在 bootstrap 中这样做,就像您对.data或.bss所做的那样,这意味着您必须将变量添加到链接器脚本(对于此工具链)或一些其他您可以做的技巧.

它基本上是这个的一些变体:

MEMORY {
    one : ORIGIN = 0x000, LENGTH = 256
    two : ORIGIN = 0x200, LENGTH = 256
}
SECTIONS {
    .text       : { *(.text)       } > one
    __LAB0__ = .;
    .bss        : { 
        __LAB1__ = .;
            *(.bss)        
    __LAB2__ = .;
    } > two AT > one
    __LAB3__ = .;
    .got        : 
    { 
    __LAB4__ = .;
        *(.got)       
    __LAB5__ = .;
    } > two AT > one
}



.align
.word __LAB1__
.word __LAB2__
.word __LAB3__
.word __LAB4__
.word __LAB5__

我将变量放在内部和外部,因为虽然有人会认为它们应该总是相同的,但GNU链接器并非如此.您还需要一些对齐,并调整您的复制循环(在ASM中,不要 bootstrap C中的C)(永远不要使用Memset或Memcpy,只需正确使用它,使用ASM bootstrap C),这样您就可以一次对齐两个或四个寄存器,因此在32位或64位边界上对齐,然后复制32或64的某个倍数.(如果下一个地址不等于结束,则可以使用准确的地址,然后中断循环)

无论如何,同样的方式,你已经隔离了大小和开始的数据,以及如何处理.data,你只需要模仿.data链接器脚本,并复制代码为.got,如果你想要它在ram中.

如果您因为要try 链接的其他已构建内容而以.get结束,则将.get放入您的链接器脚本中的Flash中.(也得到了.plt).当然,在您try 运行它之前,可以使用Readself和objump来确认它们应该在哪里,以及地址是否正确等等.

所以你的问题有一些令人困惑的漏洞.

-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -fno-common -ffunction-sections -fdata-sections -Wl,--gc-sections -specs=nano.specs -ffreestanding -Wall -O0 -ggdb

这看起来像是借来的.段1是为了使您可以在链接器中执行-gc-sections,但随后您指定了单独的链接器命令行

-T link.ld -lc -lgcc

-lc非常非常可怕,让我不寒而栗.但是好吧,你可以问更多的问题,所以我猜…-lgcc不会像从链接器那样工作,你需要从GCC做这件事,或者添加一个路径,只是GNU是如何工作的.

-O0不与-GC-SECTIONS匹配,无论您想要将其变小还是想要使其更大,请 Select 一个.或者是某些行业出于安全原因故意避免优化的-O0.

为了实际删除内容,您可以组合使用链接中的编译和-gc-sections中的-sections选项.

arm-none-eabi-gcc -O2 -c -ffunction-sections -fdata-sections -mcpu=cortex-m4 so.c -o so.o
arm-none-eabi-ld -Tso.ld -gc-sections -print-gc-sections  so.o -o so.elf
arm-none-eabi-objdump -D so.elf

So.self:文件格式elf32-littlearm

你想要GC-Print在那里,这样你就可以看到什么是被删除的,人们可能会认为,嘿,这做得很好,然后可能在砖块或对正在发生的事情感到困惑之后,发现有一个错误.

ENTRY(fun)
MEMORY {
    one : ORIGIN = 0x000, LENGTH = 256
    two : ORIGIN = 0x100, LENGTH = 256
}
SECTIONS {
    .text       : { *(.text)       } > one
    .bss        : { *(.bss)        } > two
}

链接器遵循所有代码路径和数据路径,如果它没有命中,则它会移除一些东西,因此您必须非常小心地处理您想要保留但没有通过名称引用的项.

在任何情况下,都是固定的.

Disassembly of section .text:

00000000 <fun>:
   0:   4b01        ldr r3, [pc, #4]    ; (8 <fun+0x8>)
   2:   2205        movs    r2, #5
   4:   601a        str r2, [r3, #0]
   6:   4770        bx  lr
   8:   00000100    andeq   r0, r0, r0, lsl #2

Disassembly of section .bss:

00000100 <x>:
 100:   00000000    andeq   r0, r0, r0

它删除了我们未使用的数据和函数,节省了程序占用的大量空间.并且经过了优化,节省了更多的空间,运行速度更快.

您没有提供足够的信息来完整回答,但正如我们在 comments 中所述,有一些提示.您的代码可能没有创建.get,但您链接的一些代码可能已经创建了.在这种情况下,让链接器将.get项放入闪存中,您就可以使用它们了.如果您可以在没有图片/饼的情况下进行构建,那么就这样做吧.重新判断您的所有命令行选项-例如,fno-Common似乎是borrow 的和可怕的.没有必要用调试器的东西来inflating 代码,除非你需要,但你仍然想要为发布和调试而构建,因为发布和调试是(可以)不同的二进制文件,可能会有不同的结果,特别是裸机.

根据我们从您的问题中得出的结论.如果需要初始化.get,则需要将其包装在链接器中,就像.Data一样,并执行复制循环,就像.Data一样.使用.Data作为参照,剪切、粘贴并更改名称.如果您必须更改.get运行时.如果没有,那么在闪光灯中放入.get,它就完成了,没有init.如果有可能重新构建创建.get的任何东西,那么这就更好了(如果您不需要位置独立),更小、更快、更容易处理.只有当您计划使用位置独立性并且已经添加了运行时代码(不是由工具生成的,这是您的责任)来完成使用它的工作时,才指定位置独立性.

真正的GET不会全为零,对于这个平台来说也不是.正如您可能已经了解的那样,它充满了地址,并且需要它才能工作.除非,这个库是这样构建的,所以你必须以某种方式神奇地找出所有东西的位置,然后自己填写GET和PLT,这让我头疼,你不是在做动态库,对吗?

C++相关问答推荐

Pure Win32 C(++)-除了替换控件的窗口程序之外,还有其他方法可以在输入时禁用按钮吗?

GCC:try 使用—WError或—pedantic using pragmas

ATTiny1606定时器TCA 0中断未触发

丑陋的三重间接:可扩展的缓冲区管理 struct

模拟shell并运行.sh文件

拥有3x3二维数组并访问数组[1][3]等同于数组[2][0]?

CSAPP微型shell 实验室:卡在sigprocmask

GCC创建应用于移动项的单独位掩码的目的是什么?

在Rust和C之间使用ffi时如何通过 struct 中的[U8;1]成员传递指针

如何用c语言修改shadow文件hash部分(编程)?

在C中创建任意类型的只读指针参数

Caesar密码调试:输出文本末尾的问号和随机字符

我在C程序的Flex/Bison中遇到语法错误

Fprintf正在写入多个 struct 成员,并且数据过剩

S,在 struct 中创建匿名静态缓冲区的最佳方式是什么?

无算术运算符和循环的二进制乘法

可以对两种 struct 类型中的任何一种进行操作的C函数

在我的函数中实现va_arg的问题

尽管将其标记为易失性,但 gcc 是否优化了我的等待代码?

如何用用户输入的多个字符串填充数组?