何时需要使用 GAS ELF 指令 .type、.thumb、.size 和 .section?

23

我正在为基于ARM Cortex-M3的微控制器编写汇编程序(使用Thumb 2指令集),使用GNU as。

在一些示例代码中,我发现了像 .size.section.type 这样的指令,我明白它们是ELF指令。举个例子:

    .section    .text.Reset_Handler
    .weak       Reset_Handler
    .type       Reset_Handler, %function  
Reset_Handler:
    bl      main
    b       Infinite_Loop    
    .size   Reset_Handler, .-Reset_Handler


.type指令被认为是设置符号类型的指令,通常为%object(表示数据?)或%function。我不知道它会有什么区别。它并不总是包含在内,因此我不确定何时需要使用它。

与此相关的还有.thumb_func指令。根据我的阅读,它似乎可能相当于:

.thumb 
.type Symbol_Name, %function

还是其他问题吗?



.size 指定了与符号相关联的大小。但我不知道什么时候需要这样做。这是否默认计算,但可通过此指令覆盖?如果是这样——什么情况下需要覆盖?



.section 更容易找到文档,我也比较确定它的作用,但对于使用方法仍有一些疑虑。我理解它会在 ELF 节(text 用于代码,data 用于可写数据,bss 用于未初始化的数据,rodata 用于常量等)之间切换,并在需要时定义新节。我想你可能会根据是否定义代码、数据、未初始化数据等来切换它们。但为什么要为函数创建子节,就像上面的示例一样呢?


非常感谢您的任何帮助。如果您能找到教程或文档的链接,以更深入地解释这个问题——最好是容易理解的——我将不胜感激。

目前,《Using as》手册已经给了我一些帮助——也许您可以从中获取比我更多的知识。


4
我已经为这个问题添加了赏金,希望能得到更详细的答案,特别是关于.type和.size指令的。 - user1481860
建议将其拆分为每个指令一个问题。建议:先了解ELF格式,然后制作带有和不带有每个指令的最小示例,编译并在其上运行“readelf -a”。 - Ciro Santilli OurBigBook.com
3个回答

17
我已经编写了很多ARM/Thumb汇编语言程序,并且只需要使用其中少量的许多指令。正如另一位回答者所指出的,.thumb_func非常重要。 例如: .globl _start _start: b reset
reset:
.arm
.globl one one: add r0,r0,#1 bx lr .thumb
.globl two two: add r0,r0,#2 bx lr .thumb_func .globl three three: add r0,r0,#3 bx lr
.word two .word three .arm或曾经是.code32.code 32告诉编译器这是ARM代码而不是Thumb代码,在您的Cortex-M3中不需要使用它。
类似地,.thumb曾经是.code 16或者可能仍然有效,它使得接下来的代码是Thumb而不是ARM。
如果您正在使用的标签不是全局标签,并且您不需要从其他文件或间接地转移到该标签,则不需要使用.thumb_func。但是为了正确计算到这些全局标签之一的分支的地址(lsbit对于Thumb为1,对于ARM为0),您需要将其标记为Thumb或Arm标签,.thumb_func可以实现这一点,否则您必须在分支之前设置该位并添加更多代码,而且该标签无法从C中调用。
00000000 <_start>: 0: eaffffff b 4 00000004 : 4: e2800001 add r0, r0, #1 8: e12fff1e bx lr 0000000c : c: 3002 adds r0, #2 e: 4770 bx lr 00000010 : 10: 3003 adds r0, #3 12: 4770 bx lr 14: 0000000c andeq r0, r0, ip 18: 00000011 andeq r0, r0, r1, lsl r0
.thumb之前,汇编器是ARM代码,正如所需的那样。
两个函数的标签(two和three)都是Thumb代码,但two标签具有偶数地址,而three则具有正确的奇数地址。
上面的示例使用了最新的CodeSourcery工具进行汇编、链接和转储。现在在 cortex-m3 中,所有指令都是 Thumb(/Thumb2) .thumb_func,可能不那么重要,可以使用命令行开关(很容易进行实验以找出结果)。但是养成这个习惯还是很好的,以防从 Thumb-only 处理器转移到普通的 ARM/Thumb 核心。
汇编器通常喜欢添加所有这些指令和其他使事物看起来/感觉更像高级语言的方式。我只是说您不必使用它们,我为 ARM 切换了汇编器,并且对许多不同的处理器使用许多不同的汇编器,并倾向于更少即更好的方法,即专注于汇编本身并尽可能少地使用与工具相关的项目。我通常是例外而不是规则,因此您可以通过查看编译器输出生成的指令(并通过文档进行验证)来确定更常用的指令。
unsigned int one ( unsigned int x )
{
    return(x+1);
}
.arch armv5te .fpu softvfp .eabi_attribute 20, 1 .eabi_attribute 21, 1 .eabi_attribute 23, 3 .eabi_attribute 24, 1 .eabi_attribute 25, 1 .eabi_attribute 26, 2 .eabi_attribute 30, 2 .eabi_attribute 18, 4 .file "bob.c" .text .align 2 .global one .type one, %function one: .fnstart .LFB0: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. add r0, r0, #1 bx lr .fnend .size one, .-one .ident "GCC: (Sourcery G++ Lite 2010.09-50) 4.5.1" .section .note.GNU-stack,"",%progbits
当在 ARM 和 Thumb 汇编中混合代码或数据时,我会使用 .align。您应该期望此类平台的汇编器知道 Thumb 指令位于半字边界上,ARM 指令位于字边界上。但是工具并不总是那么聪明。随处添加 .align 不会有害。 .text 是默认值,因此有点冗余,但不会有害。如果将目标编译为 ROM 和 RAM 的组合,则 .text.data 是标准属性(不特定于 ARM),则您可能要考虑(取决于您如何使用链接器脚本),否则 .text 将适用于所有内容。

.size 显然是这个函数从开始到该指令的大小。汇编器无法自行确定,因此如果该函数的大小对您的代码、链接脚本、调试器、加载程序等很重要,则需要正确设置它,否则您就不必费心了。无论如何,函数都是一个高级概念,汇编器实际上没有函数,更不需要声明它们的大小。而且C编译器肯定不关心,它只在寻找一个标签以便能够跳转,而对于ARM体系结构来说,它是跳转到Thumb指令集还是ARM指令集。

如果你在长代码段上懒得使用你的立即数(ldr rx,=0x12345678),那么你可能会发现.pool指令(有一个更新的等效指令)很有用。在这种情况下,工具并不总是聪明地将这些数据放在无条件分支之后,有时您需要告诉它们。我半开玩笑地说“懒”,因为总是要做label:.word 的事情,而且我相信ARM和GCC工具都允许这种快捷方式,所以我像其他人一样经常使用它。

另外请注意,LLVM输出了一个额外的.eabi_attribute或两个,在Code Sourcery版本/修改的binutils中得到了支持,但在GNU发布的binutils中不受支持(也许还没有)。两种解决方案有效,一是修改LLVM的asm打印函数,以便不写eabi_attributes,或者至少用注释(@)写它们;二是从Code Sourcery获取binutils源代码/修改,并以这种方式构建binutils。Code Sourcery往往领先于GNU(例如支持Thumb2),或者会将新功能回溯,因此我认为这些LLVM属性很快就会出现在主线binutils中。从LLVM编译的代码中删除eabi_attribute对我没有产生任何不良影响。

以下是相同函数的LLVM输出,显然这是我修改过的llc,以注释掉eabi_attribute

    .syntax unified
@   .eabi_attribute 20, 1
@   .eabi_attribute 21, 1
@   .eabi_attribute 23, 3
@   .eabi_attribute 24, 1
@   .eabi_attribute 25, 1
@   .eabi_attribute 44, 1
    .file   "bob.bc"
    .text
    .globl  one
    .align  2
    .type   one,%function
one:                                    @ @one
@ BB#0:                                 @ %entry
    add r0, r0, #1
    bx  lr
.Ltmp0:
    .size   one, .Ltmp0-one
elf文件格式有很好的文档记录,如果你想真正了解elf特定指令(如果有的话)是在做什么,那么很容易解析。其中许多指令都是为了帮助链接器而不是其他任何东西。例如,.thumb_func.text.data

谢谢,那确实有帮助。所以除非我们在谈论特殊情况,否则既不需要 .size 也不需要 .type? - user1481860
1
我从未使用过 .size 或 .type,我的大部分工作都在 ARM 上,并且总是涉及汇编语言。我假设如果您的链接器脚本或加载器/elf解析器想要查找这些内容,则需要将它们放在那里,我使用非常简单的链接器脚本,实际上只关心 .text vs .data,基本上是 ROM vs RAM。 - old_timer
关于 .size 指令:在 Android git 树中的 bionic 中,提交 fb723c87490b76d1d2fe521886f7cb6c96ed40b7 说:使用 BEGIN(x) 和 END(x) 宏更新 ARM 系统调用,以提供系统调用代码的大小信息。对于 valgrind 很有用。 - misiu_mp

7

程序的各个部分与ELF格式紧密相关,大多数系统(如Linux、BSD等)都使用这种格式存储它们的目标文件和可执行文件。 本文 可以让您深入了解ELF的工作原理,从而帮助您理解各个部分的原因。

简单来说,各个部分让您将程序组织成具有不同属性的不同内存区域,包括地址、执行和写入权限等。在最终链接阶段,链接器使用特定的 链接脚本,通常将所有同名部分(例如所有编译单元的所有代码等)分组在一起,并为它们分配内存中的最终地址。

对于嵌入式系统,它们的用途尤其明显:首先,启动代码(通常包含在.text部分中)必须加载到固定地址才能执行。然后,只读数据可以分组到专用的只读部分,该部分将映射到设备的ROM区域中。最后一个例子:操作系统具有仅调用一次然后不再使用的初始化函数,浪费了宝贵的内存空间。如果将所有这些初始化函数分组到一个名为.initcode的专用部分中,并且如果将该部分设置为程序的最后一个部分,则操作系统可以通过降低其自己的内存上限在初始化完成后轻松回收此内存。例如,Linux就以此著称,而GCC允许您通过在变量或方法后缀中加入__attribute__ ((section ("MYSECTION")))来将变量或方法放置在特定部分中。

.type.size对我来说仍然不太清楚。我认为它们是链接器的辅助工具,并且从未在汇编生成的代码之外看到过它们。

.thumb_func似乎只需要用于旧的OABI接口,以便与Arm代码进行交互。除非您使用的是旧工具链,否则您可能不必担心它。


至少这样清楚了部分内容,我想。你定义自己的章节有特殊的原因吗? - user1481860
6
如果您要获取函数的地址,.thumb_func 就是必需的。对于 Thumb 函数,它的地址必须设置第 0 位(与数据项地址不同)。该指令使汇编器将符号标记为这种类型,因此链接器知道何时设置第 0 位。 - Igor Skochinsky

6

我在研究为什么最近的 binutils(经过 2.21.53 (MacPorts) 和 2.22 (Yagarto 4.7.1) 验证)会导致 ARM 和 Thumb 的相互操作中断时,发现了这个问题。

根据我的经验,.thumb_func 在早期的 binutils 中可以很好地生成正确的交互式 veneers。但是,在更近期的版本中,需要使用 .type *name*, %function 指令来确保正确的 veneer 生成。

binutils 邮件列表帖子

我懒得挖掘旧版本的 binutils,以检查在早期的 binutils 中是否可以在 .type 指令的位置上使用 .thumb_func 指令来代替。我想在您的代码中包含这两个指令也没什么坏处。

编辑:更新了有关在代码中使用 .thumb_func 的评论,显然它可以用于标记 Thumb 例程以生成 veneers,但除非使用 .type 指令来标记 ARM 函数,否则 Thumb->ARM 交互会失败。


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