【问题标题】:What are assembler section directives used for?汇编器部分指令用于什么?
【发布时间】:2019-08-02 02:31:42
【问题描述】:

我正在努力学习 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 用于数据的事实。但在这里,在我的眼前,他们似乎什么都不做!

因此,如果它们不分别用于保存代码和数据,它们的真正目的是什么?

【问题讨论】:

    标签: assembly arm gnu-assembler


    【解决方案1】:

    这些类型的指令取决于您构建程序的架构,它们会选择将哪个内存部分分配给随后的任何代码或数据。最后,一切都只是一串字节。程序编译完成后,符号/标签将根据它们所在的部分分配不同的内存地址。

    .text 通常分配在只读内存段中,最适用于预计不会更改的代码。

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

    在同一段中混合代码和数据有一些优点,例如可以通过与 PC 寄存器的相对偏移量(正在执行的代码的地址)轻松访问数据的地址。当然也有缺点,如果你试图修改只读内存,你最终会被忽略,并且程序可能会触发异常并崩溃。所有这些都非常特定于架构,最安全的选择是将代码保留在用于代码的段中,并将数据/分配保留在用于数据的段中。

    这完全取决于您的计划所针对的目标。例如,Game Boy Advance 有一个 256KB 的“慢速”内存区域,一个 32KB 的“快速”内存区域,然后是只读“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 字节访问速度更快(“零页”)。在这个理论案例中,.data.text 将被分配到相同的内存区域——也就是说,它们不会被分配到零页中,并且它们都共享相同的内存。零页会有一个自定义段,.text.data 之间的区别会很小——只是一种区分汇编程序中哪些符号指向“数据”以及哪些符号指向程序的方法代码。

    【讨论】:

    • 老兄,你的帖子让我在回来的路上。 :) 应该明确的是,修改 ROM 内存会使程序崩溃,所以指令很重要。此外,由于您提到的速度原因,这也很重要。在现代操作系统上,虽然执行权限受到严格控制,但将代码放在数据部分会导致安全错误。
    • 我是任天堂自制软件的狂热爱好者。我想念所有这些东西。 :(
    • 这很有趣,谢谢。如果没有使用指令,你能告诉我什么是“默认”行为吗?所有代码都放在可写内存中了吗?
    • 这并不是真正明确定义的东西,并且取决于您的汇编程序。我猜它通常默认为.text 部分,因为通常您正在组装代码而不是数据。
    • @mukunda - 我仍然是一个狂热者。我现在实际上是 Nintendo 编译器团队的负责人 :)
    【解决方案2】:

    GAS(与大多数汇编程序一样)默认为 .text 部分,您的只读数据仍可在 .text 中使用

    一切都只是字节


    您可以执行echo 'mov r1, #2' > foo.s 并将其组装+链接到 ARM 二进制文件中(例如,使用
    gcc -nostdlib -static foo.s)。您可以在 GDB 中单步执行该指令。

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

    链接器将警告它没有找到_start 符号(因为您遗漏了标签本身,更不用说告诉汇编器使其在目标文件的符号表中可见的.globl 指令。

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

    默认情况下,除了.text 之外的大多数部分都不会链接到可执行内存,因此在.data 中包含_start: 通常会是个问题。


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

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

    将其放入 .data 会导致链接器将其放入不同的段中,从而告诉操作系统的 ELF 程序加载器将其映射为读+写(而不是执行)。

    .rodata 部分与.text 分开的目的是将代码和数据组合在一起。 许多 CPU 具有分离的 L1d 和 L1i 缓存,和/或单独的 TLB 用于数据 /指令,因此只读数据与代码的细粒度混合会浪费拆分缓存中的空间。

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

    【讨论】:

    • 感谢您提供的这些精确度,但您能否详细说明“许多 CPU 已拆分 L1d 和 L1i 缓存,和/或单独的 TLB 用于数据/指令,因此对只读数据进行了细粒度混合使用代码会浪费拆分缓存中的空间。”这对我来说并不是很清楚(尽管看起来很有趣)。
    • @YkonO'Clast:如果您在两个 64 字节 RAM 块中混合了代码和数据,则它们都必须同时加载到 L1i 和 L1d 缓存中。但是如果其中一个是纯代码,另一个是纯数据,那么仅数据缓存行将仅在 L1d 中,而仅代码行将在 L1i 中。所以拆分缓存会使细粒度混合浪费空间。
    猜你喜欢
    • 2011-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    • 2015-05-15
    • 2011-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多