流程部分:声明是否也向 .text 添加了一些内容?如果是,它添加了什么?

Process sections: does a declaration add also something to .text? If yes, what does it add?

我有一个像这样的 C 代码,它可能会被编译到 ARM 的 ELF 文件中:

int a;
int b=1;

int foo(int x) {
    int c=2;
    static float d=1.5;
    // .......
}

我知道所有可执行代码都进入 .text 部分,而 .data.bss.rodata 将包含各种 variables/constants。 我的问题是:像 int b=1; 这样的行是否也向 .text 部分添加了一些内容,或者它只是告诉编译器将一个初始化为 1 的新变量放入 .data (然后可能部署在最终硬件上时映射到 RAM 内存中)?

此外,在尝试反编译类似的代码时,我注意到函数 foo() 中的一行 int c=2;stack 添加了一些内容,但也添加了一些内容.text 行,其中实际存储了值“2”。

那么,一般来说,声明是否总是暗示在程序集级别添加到 .text 的内容?如果是,是否取决于上下文(即变量是否在函数内部,是否是局部全局变量,...)以及实际添加的内容?

非常感谢。

正如@goodvibration 正确指出的那样,只有全局或静态变量进入段。这是因为它们的生命周期就是程序的整个执行时间。

局部变量有不同的生命周期。它们仅在它们定义的块(例如函数)执行期间存在。如果调用一个函数,所有不适合寄存器的参数都会被压入堆栈,并且 return 地址被写入 link 寄存器。* 该函数可能会保存 link 寄存器和堆栈中的其他寄存器 并在堆栈中为局部 变量添加一些 space (这是您观察到的代码)。在函数结束时,弹出保存的寄存器并重新调整堆栈指针。通过这种方式,您可以获得局部变量的自动垃圾收集

*:请注意,这仅适用于 ARM(的某些调用约定)。这是不同的,例如适用于英特尔处理器。

does a line like int b=1; here add also something to the .text section, or does it only tell the compiler to place a new variable initialized to 1 in .data (then probably mapped in RAM memory when deployed on the final hardware)?

您了解这可能是特定于实现的,但很可能您只会在数据部分获得初始化数据。如果它是一个常量,它可能会进入文本部分。

Moreover, trying to decompile a similar code, I noticed that a line such as int c=2;, inside the function foo(), was adding something to the stack, but also some lines of .text where the value '2' was actually memorized there.

初始化的自动变量,每次进入函数的范围时都必须初始化。 c 的 space 保留在堆栈上(或在寄存器中,取决于 ABI),但程序必须记住初始化它的常量,最好将其放在文本中的某个位置段,作为常量值或作为 "move immediate" 指令。

So, in general, does a declaration always imply also something added to .text at an assembly level?

没有。如果静态变量被初始化为零或 null 或根本没有初始化,通常只需要在 bss 中保留 space 即可。如果静态非常量变量被初始化为非零值,它只会被放入数据段。

这是其中之一。

int a;
int b=1;
int foo(int x) {
    int c=2;
    static float d=1.5;
    int e;
    e=x+2;
    return(e);
}

第一件事没有优化。

arm-none-eabi-gcc -c so.c -o so.o
arm-none-eabi-objdump -D so.o
arm-none-eabi-ld -Ttext=0x1000 -Tdata=0x2000 so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000
arm-none-eabi-objdump -D so.elf > so.list

不要担心警告,需要 link 才能看到一切都找到了归宿

Disassembly of section .text:

00001000 <foo>:
    1000:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
    1004:   e28db000    add r11, sp, #0
    1008:   e24dd014    sub sp, sp, #20
    100c:   e50b0010    str r0, [r11, #-16]
    1010:   e3a03002    mov r3, #2
    1014:   e50b3008    str r3, [r11, #-8]
    1018:   e51b3010    ldr r3, [r11, #-16]
    101c:   e2833002    add r3, r3, #2
    1020:   e50b300c    str r3, [r11, #-12]
    1024:   e51b300c    ldr r3, [r11, #-12]
    1028:   e1a00003    mov r0, r3
    102c:   e28bd000    add sp, r11, #0
    1030:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
    1034:   e12fff1e    bx  lr

Disassembly of section .data:

00002000 <b>:
    2000:   00000001    andeq   r0, r0, r1

00002004 <d.4102>:
    2004:   3fc00000    svccc   0x00c00000

Disassembly of section .bss:

00002008 <a>:
    2008:   00000000    andeq   r0, r0, r0

作为反汇编,它会尝试反汇编数据,因此忽略它(例如 0x2008 旁边的 andeq)。

a 变量是全局的且未初始化的,因此它位于 .bss 中(通常......编译器可以选择做任何它想做的事情,只要它正确地实现了语言,就不必有一个叫做 .bss 的东西例如,但 gnu 和许多其他人都这样做)。

b 是全局的并已初始化,因此它位于 .data 中,如果它被声明为 const,它可能会位于 .rodata 中,具体取决于编译器及其提供的内容。

c 是一个被初始化的本地非静态变量,因为 C 提供递归这需要在堆栈上(或使用寄存器或其他易失性资源管理),并初始化每个 运行。我们需要在没有优化的情况下编译才能看到这个

1010:   e3a03002    mov r3, #2
1014:   e50b3008    str r3, [r11, #-8]

d 是我所说的局部全局变量,它是一个静态局部变量,因此它存在于函数之外,而不是在堆栈中,与全局变量一起存在,但只能进行局部访问。

我在你的示例中添加了e,这是一个本地未初始化,但随后使用的。如果我没有使用它并且没有优化,可能会为它分配 space 但没有初始化。

将 x 保存在堆栈中(根据此调用约定 x 进入 r0)

100c:   e50b0010    str r0, [r11, #-16]

然后从栈中加载x,将两个相加,保存为栈中的e。从 此调用约定的堆栈和位置 return 位置是 r0.

1018:   e51b3010    ldr r3, [r11, #-16]
101c:   e2833002    add r3, r3, #2
1020:   e50b300c    str r3, [r11, #-12]
1024:   e51b300c    ldr r3, [r11, #-12]
1028:   e1a00003    mov r0, r3

对于所有架构,未优化这有点典型,总是从堆栈中读取变量并快速将它们放回原处。其他架构对于传入参数和传出 return 值所在的位置有不同的调用约定。

如果我优化(gcc 行上的-O2)

Disassembly of section .text:

00001000 <foo>:
    1000:   e2800002    add r0, r0, #2
    1004:   e12fff1e    bx  lr

Disassembly of section .data:

00002000 <b>:
    2000:   00000001    andeq   r0, r0, r1

Disassembly of section .bss:

00002004 <a>:
    2004:   00000000    andeq   r0, r0, r0

b 是全局的,所以在对象级别必须为它保留全局 space,它是 .data,优化不会改变它。

a 也是全局的并且仍然是 .bss,因为在对象级别它被声明为这样分配以防另一个对象需要它。 linker 没有删除这些。

现在 c 和 d 是死代码,它们什么都不做,不需要存储,所以 c 不再分配 space 在堆栈上, d 也不再分配任何 .data space.

对于此代码的此调用约定,我们有大量用于此体系结构的寄存器,因此 e 不需要在 堆栈,它进入 r0 数学可以用 r0 完成,然后它是 returned in r0.

我知道我没有告诉 linker 将 .bss 放在哪里,而是告诉它 .data 它毫无怨言地将 .bss 放在同一个 space 中。例如,我可以放 -Tbss=0x3000 来给它自己的 space 或者只是完成一个 linker 脚本。链接器脚本会对典型结果造成严重破坏,因此请当心。

典型,但可能有例外的编译器:

非常量全局变量进入 .data 或 .bss 取决于它们是否在声明期间被初始化。 如果 const 那么也许 .rodata 或 .text 取决于(或者 .data 或 .bss 在技术上可行)

非静态局部变量根据需要进入通用寄存器或堆栈(如果未完全优化)。

静态局部变量(如果没有优化掉)与全局变量一起生活但不能全局访问它们只是像全局变量一样在 .data 或 .bss 中分配 space。

参数完全由该编译器为该目标使用的调用约定控制。仅仅因为 arm 或 mips 或其他可能已经写下约定并不意味着编译器必须使用它,只有当他们声称支持某些约定或标准时他们才应该尝试遵守。为了使编译器有用,它需要一个约定并坚持它,无论它是什么,以便函数的调用者和被调用者都知道从哪里获取参数和 return 一个值。具有足够寄存器的架构通常会有一个约定,其中少数几个寄存器用于前这么多参数(不一定是一对一),然后堆栈用于所有其他参数。同样,如果可能,可以使用寄存器来获取 return 值。由于缺少 gprs 或其他原因,某些体系结构在两个方向上都使用堆栈。或一个堆栈和另一个寄存器。欢迎您找出约定并尝试阅读它们,但归根结底,您使用的编译器(如果没有被破坏)会遵循约定,并且通过设置像上面那样的实验,您可以看到约定在起作用。

加上在这种情况下的优化。

void more_fun ( unsigned long long );
unsigned fun ( unsigned int x, unsigned long long y )
{
    more_fun(y);
    return(x+1);
}

如果我告诉你 arm 约定通常使用 r0-r3 作为前几个参数,你可能会假设 x 在 r0 中,r1 和 r2 用于 y,我们可以在需要堆栈之前有另一个小参数,出色地 也许是旧的手臂,但现在它希望 64 位变量使用偶数然后使用奇数。

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   e1a04000    mov r4, r0
   8:   e1a01003    mov r1, r3
   c:   e1a00002    mov r0, r2
  10:   ebfffffe    bl  0 <more_fun>
  14:   e2840001    add r0, r4, #1
  18:   e8bd4010    pop {r4, lr}
  1c:   e12fff1e    bx  lr

所以 r0 包含 x,r2/r3 包含 y 并且 r1 被忽略了。

测试被精心设计为没有 y 作为死代码并将其传递给另一个函数,我们可以看到 y 在进入 fun 和离开 more_fun 的过程中的存储位置。 r2/r3在来的路上,需要在r0/r1才能叫的更开心

我们需要为 return 保留 x 的乐趣。人们可能期望 x 会落在堆栈上,但它会未经优化,而是保存一个寄存器,约定已声明将由函数 (r4) 保留,并在整个函数中或至少在该函数中使用 r4 来存储 x。性能优化,如果 x 需要被多次访问,则进入堆栈的内存周期比寄存器访问成本更高。

然后它计算 return 并清理堆栈、寄存器。

IMO 重要的是要看到这一点,调用约定对某些变量起作用,而其他变量可能会根据优化而有所不同,没有优化它们是大多数人会立即声明的,.bss,.data( .text/.rodata),然后进行优化,这取决于变量是否存在。