【问题标题】:How do I leave memory uninitialized in GNU ARM assembly?如何在 GNU ARM 程序集中保留未初始化的内存?
【发布时间】:2019-08-25 16:19:15
【问题描述】:

我在我的 Raspberry Pi 上使用 GCC 来为我正在学习的课程编译一些汇编代码。根据GNU Assembler Reference 中的信息,我了解到我可以在 GNU ARM 程序集中重现以下 C 代码:

int num = 0;

这样写:

        .data
num:    .word 0

太棒了!现在我该怎么写呢?

int num;

我的理解是,像这样保持未初始化的变量意味着我应该将其视为包含之前内存位置中的任何垃圾值。因此,在我以某种方式赋予它一个值之前,我不应该使用它。

但是假设出于某种原因我打算在内存中存储大量数据并且需要为其预留大量空间。在我看来,如果我无论如何都要用一些数据填充它,那么将整个内存区域初始化为某个值将是一种巨大的资源浪费。然而,据我所知,如果不将其初始化为某个值,似乎无法在 GCC ARM 程序集中制作标签。根据我的assembly textbook.word 指令后面可以有零个表达式,但如果以这种方式使用,“那么地址计数器不会提前,也不会保留任何字节。”我的第一个想法是改用“.space”或“.skip”指令,但对于这个指令,甚至official documentation 都说“如果省略了逗号和填充,则假定填充为零。”

我没有办法在 GCC ARM Assembly 中不初始化就保留一块内存吗?

【问题讨论】:

  • 在全局范围内的 C 中,int num;int num = 0; 完全是同义词。 ISO C 保证静态存储是零初始化的,如果没有显式初始化为其他东西。 Are global variables always initialized to zero in C? 一些嵌入式编译器可能会让您在系统启动时通过一些特殊选项避免将部分 BSS 归零。例如。 gcc -mno-clearbss 是 Microblaze 的一个选项。可能对gcc -fno-zero-initialized-in-bss 有用,尽管我认为这会影响int fooint foo=0; 相同。

标签: gcc assembly arm gnu-arm


【解决方案1】:

当你尝试的时候发生了什么?

当我尝试时:

int num = 0;
int mun;

有了 gnu,我得到了

    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "so.c"
    .text
    .comm   mun,4,4
    .global num
    .bss
    .align  2
    .type   num, %object
    .size   num, 4
num:
    .space  4
    .ident  "GCC: (GNU) 8.3.0"

.comm 符号,长度

.comm 声明了一个名为 symbol 的通用符号。 链接时,一个目标文件中的通用符号可以与 在另一个目标文件中定义或通用的同名符号。如果 ld 没有看到符号的定义——只有一个或多个常见的 符号——然后它将分配长度字节的未初始化内存。 长度必须是绝对表达式。如果 ld 看到多个常见的 具有相同名称的符号,并且它们的大小并不相同,它 将使用最大尺寸分配空间。

使用 ELF 时,.comm 指令采用可选的第三个参数。 这是符号的所需对齐方式,指定为字节 边界(例如,对齐 16 表示最少 地址的有效 4 位应为零)。对齐必须 是一个绝对表达式,它必须是二的幂。如果 ld 为公共符号分配未初始化的内存,它将使用 放置符号时对齐。如果未指定对齐方式,则为 将对齐设置为小于或等于 2 的最大幂 到符号的大小,最大为 16。

.comm 的语法在 HPPA 上略有不同。语法是 `符号.comm,长度';符号是可选的。

汇编语言是由汇编器而不是目标定义的。所以答案将是汇编程序(读取和汇编汇编语言程序的工具)特定的,没有理由假设一个汇编程序的答案与另一个相同。以上是gnu汇编器,gas。

您本可以查看您引用的文档或阅读其他 gnu 文档,但回答“在编译程序中执行此操作时会发生什么”的最简单方法是编译它并查看编译器输出。

但不一定假设它没有被初始化:

unsigned int num;
unsigned int fun ( void )
{
    return(num);
}

只需链接即可:

Disassembly of section .text:

00001000 <fun>:
    1000:   e59f3004    ldr r3, [pc, #4]    ; 100c <fun+0xc>
    1004:   e5930000    ldr r0, [r3]
    1008:   e12fff1e    bx  lr
    100c:   00002000    andeq   r2, r0, r0

Disassembly of section .bss:

00002000 <__bss_start>:
    2000:   00000000

它最终以 bss 初始化。

您确实想要对某些内容进行未初始化的访问,然后只需选择一个地址(您知道未初始化的地址 (sram))并访问它:

ldr r0,=0x1234
ldr r0,[r0]

【讨论】:

    【解决方案2】:

    一般来说,不需要初始化的数据应该放在.bss部分。

        .bss
    foobar:
        .skip 99999999
    

    这将在.bss 部分分配99999999 个字节,标签foobar 将是它的地址。它不会使您的目标文件或可执行文件变大 99999999 字节;可执行文件头只是表示需要多少字节的.bss,并且在加载时,系统会分配适当的数量并将其初始化为零。

    您不能跳过加载时间零初始化。系统需要对其进行初始化,因为它可能包含来自内核或其他进程的敏感数据。但是将内存归零非常快,并且内核将使用一种高效的算法,所以我不会担心性能影响。它甚至可能在空闲时间将页面归零,因此当您的程序加载时,已经有归零的内存可用。无论如何,您的程序实际使用内存所花费的时间将会淹没它。

    这意味着您也可以安全地将.bss 用于您确实希望初始化为零的数据(尽管不是任何非零值;如果您想要int foo = 3;,则必须将其放入.data就像你原来的例子一样。)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-12
      • 2011-02-14
      • 2012-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多