代码之家  ›  专栏  ›  技术社区  ›  es483

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

  •  3
  • es483  · 技术社区  · 6 年前

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

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

    我知道所有可执行代码都会进入 .text 节,而 .data , .bss .rodata 将包含各种变量/常量。 我的问题是:一条线 int b=1; 这里还添加了一些内容到 .文本 节,还是只告诉编译器在 .数据 (当部署到最终硬件上时,可能映射到RAM内存中)?

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

    那么,一般来说,声明是否总是意味着 .文本 在装配级别?如果是,是否取决于上下文(即,如果变量位于函数内部,如果是局部全局变量,…)实际上添加了什么?

    提前非常感谢。

    3 回复  |  直到 6 年前
        1
  •  1
  •   JeremyP    6 年前

    线条像 int b=1; 这里还向添加了一些内容。或者它只告诉编译器在中放置一个初始化为1的新变量。数据(当部署到最终硬件上时,可能映射到RAM内存中)?

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

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

    每次输入函数的作用域时,必须初始化已初始化的自动变量。空间 c 保留在堆栈上(或在寄存器中,具体取决于ABI),但程序必须记住初始化它的常量,最好将其放在文本段的某个位置,作为常量值或“立即移动”指令。

    所以,一般来说,声明是否总是意味着添加了一些内容。程序集级别的文本?

    不可以。如果静态变量被初始化为零或null,或者根本没有初始化,那么它通常只足以在bss中保留空间。如果一个静态非常量变量被初始化为非零值,它将被放入数据段中。

        2
  •  1
  •   Matthias    6 年前

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

    局部变量具有不同的生存期。它们仅在其定义的块(如函数)执行期间存在。如果调用某个函数,所有不适合寄存器a的参数将被推送到堆栈,返回地址将写入链接寄存器。*该函数可能会在堆栈中保存链接寄存器和其他寄存器 并在堆栈中为本地 变量(这是您观察到的代码)。函数结束时,弹出保存的寄存器,并重新调整堆栈指针。这样,您就可以 自动垃圾收集 对于局部变量。

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

        3
  •  1
  •   old_timer    6 年前

    这是一个尝试一下的东西。

    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
    

    不要担心警告,需要链接才能看到所有东西都找到了家

    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是全局的,并已初始化,因此它将进入。数据,如果它被声明为const,它可能会降落在其中。rodata取决于编译器及其提供的内容。

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

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

    d是我所说的局部全局,它是一个静态的局部,所以它位于函数之外,不在堆栈上,与全局一起,但只具有局部访问权限。

    我在您的示例中添加了e,这是一个未初始化但随后使用的本地。如果我没有使用它,也没有进行优化,可能会为它分配空间,但没有初始化。

    在堆栈上保存x(根据x在r0中输入的调用约定)

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

    然后从堆栈中加载x,添加两个,在堆栈上另存为e。从读取e 此调用约定的堆栈和放置在返回位置,即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
    

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

    如果我选择大小(-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是全局的,因此在对象级别必须为其保留一个全局空间。数据、优化并没有改变这一点。

    a也是全球性的,也是静止的。bss,因为在对象级别,它被声明为这样分配的,以防另一个对象需要它。链接器不会删除这些。

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

    对于这个代码的调用约定,我们为这个体系结构提供了大量的寄存器,因此e不需要在 堆栈,它在r0中出现。可以用r0进行数学运算,然后在r0中返回。

    我知道我没有告诉链接器放在哪里。bss通过告诉它。它输入的数据。bss位于同一空间,无投诉。例如,我可以把-Tbss=0x3000作为自己的空间,或者只是编写一个链接器脚本。链接器脚本可能会破坏典型的结果,所以要小心。

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

    非常量全局变量进入。数据或。bss取决于它们是否在声明期间初始化。 如果是常量,那么可能是。rodata或。取决于文本(或。数据或。bss技术上可行)

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

    静态局部变量(如果没有优化掉的话)与全局变量共存,但不能全局访问,它们只会得到分配的空间。数据或。bss和globals一样。

    参数完全由编译器为该目标使用的调用约定控制。仅仅因为arm或mips或其他可能已经写下了一个约定,并不意味着编译器必须使用它,只有当他们声称支持某个约定或标准时,才应该尝试遵守。为了使编译器有用,它需要一个约定并坚持它,无论它是什么,以便函数的调用者和被调用者都知道从哪里获取参数和返回值。具有足够寄存器的体系结构通常会有一个约定,即为前这么多参数(不一定是一对一)使用少量寄存器,然后堆栈用于所有其他参数。同样,如果可能,可以使用寄存器作为返回值。一些架构由于缺少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,我们可以在需要堆栈之前使用另一个小参数,好吧 可能是旧的arm,但现在它希望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\u fun的过程中存储在哪里。r2/r3即将进入,需要在r0/r1中调用更多乐趣。

    我们需要保留x,以便从乐趣中回归。有人可能会认为x会落在堆栈上,而堆栈没有进行优化,但会保存一个寄存器,按照约定,该寄存器将由函数(r4)保存,并在整个函数中使用r4,或者至少在该函数中使用r4来存储x。如果x需要多次接触,则性能优化会比寄存器访问成本更高。

    然后计算返回值并清理堆栈、寄存器。

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