汇编程序段指令有什么用?

What are assembler section directives used for?

我正在努力学习 ARM 汇编。

写完这个 Hello World 小程序后:

                .global _start

                .text
_start:         ldr     R1,=msgtxt      
                mov     R2,#13          
                mov     R0,#1           
                mov     R7,#4           
                svc     0               

                mov     R7,#1           
                svc     0               


                .data
msgtxt:         .ascii  "Hello World!\n"

                .end

我注意到我可以删除 .text 和 .data 指令,程序也能正常工作。

因此我很好奇:我读到的所有内容都强调了一个事实,即 .text 部分将用于代码,.data 部分用于数据。可在这里,在我的眼前,他们似乎什么都不做!

因此,如果这些不是用来分别保存代码和数据,那么它们的真正用途是什么?

这些类型的指令取决于您正在构建程序的体系结构,它们会选择将哪个内存部分分配给后面的任何代码或数据。最后,一切都只是一串字节。汇编程序后,symbols/labels 将根据它们所在的部分分配不同的内存地址。

.text一般分配在只读内存段,最适合不希望改变的代码。

.data 通常是内存的可写部分。我相信如果预计不会更改(或者体系结构可能具有类似的只读段),将字符串放在代码数据旁边的 .text 中是很常见的。我会说 .data 部分甚至 避免 大多数时候。为什么?因为 .data 部分需要初始化——在程序启动时从程序二进制文件复制到内存中。您的程序引用的大多数数据都可以是只读的,并且它们操作所需的任何内存通常只分配给 .bss 段,该段分配一段未初始化的内存。

在同一节中混合代码和数据有一些优点,例如可以轻松访问与 PC 寄存器(正在执行的代码的地址)有相对偏移的数据地址。当然也有缺点,如果你试图修改只读内存,你最终至少会忽略你的操作,并且程序可能会触发异常并崩溃。所有这些都是特定于体系结构的,最安全的做法是将代码保存在用于代码的段中,data/allocations 保存在用于数据的段中。

这一切都非常具体地针对您的计划目标。例如,Game Boy Advance 有一个 256KB "slow" 内存区域,一个 32KB "fast" 内存区域,然后是只读 "ROM" 区域(游戏卡带数据),它可以是几个兆字节,汇编程序使用了这些内存部分:

.data or .iwram  -> Internal RAM (32KB)
.bss             -> Internal RAM uninitialized
.ewram           -> External RAM (256KB)
.sbss            -> External RAM uninitialized
.text or .rodata -> Read only ROM (cartridge size)

再举个例子,SPC-700(SNES 声音芯片)有 64KB 的可读写内存,用于所有内容,但它的前 256 个字节访问速度更快("zero page") .在这种理论上的情况下,.data.text 将被分配到相同的内存区域——也就是说,它们不会分配在零页中,并且它们都共享相同的内存。零页将有一个自定义段,.text.data 之间的差异很小 - 只是一种区分汇编程序中哪些符号指向 "data" 的方法哪些符号指向程序代码。

GAS(与大多数 assemblers 一样)默认为 .text 部分,您的只读数据在 .text 中仍然有效

一切都是字节


您可以将 echo 'mov r1, #2' > foo.s 和 assemble+link 转换为 ARM 二进制文件(
gcc -nostdlib -static foo.s 例如)。您可以在 GDB 中单步执行该指令。

(如果没有 sys_exit 系统调用,你的程序会在那之后崩溃,但当然你也可以在没有任何指令的情况下这样做。)

linker 会警告它没有找到 _start 符号(因为你遗漏了标签本身,更不用说告诉 .globl 指令 assembler 使其在目标文件的符号 table.

中可见

但是 GNU binutils ld 的默认设置是使用 .text 部分的开头作为 ELF 入口点。

.text 以外的大多数部分默认情况下不会 link 进入 executable 内存,因此 _start:.data 中通常是一个问题。


只读数据通常应放在 .rodata 部分中,该部分无论如何都会作为 TEXT 段的一部分 link 编辑。因此,就运行时行为而言,将它放在 .text 部分 的末尾(通过省略 .data)几乎完全等同于你应该做的。

What's the difference of section and segment in ELF file format

将其放入 .data 会导致 linker 将其放入不同的段,告诉 OS 的 ELF 程序加载器将其映射为读+写(而不是执行)。

.rodata 部分与 .text 分开的目的是将代码和数据组合在一起。 许多 CPU 已拆分 L1d 和 L1i 缓存,and/or 为数据/指令分离 TLB,因此在拆分缓存中将只读数据与代码浪费进行细粒度混合 space。

在您的情况下,您没有 link 任何其他也有一些代码和一些数据的文件,所以没有区别。