内存布局黑客

Ahm*_*otb 11 c gcc operating-system memory-management

我一直在youtube上学习门课程,它正在讨论一些程序员如何利用那些知识来记忆如何做聪明的东西......讲座中的一个例子是那样的

#include <stdio.h>
void makeArray();
void printArray();
int main(){
        makeArray();
        printArray();
        return 0;
}
void makeArray(){
    int array[10];
    int i;
    for(i=0;i<10;i++)
        array[i]=i;
}
void printArray(){
    int array[10];
    int i;  
    for(i=0;i<10;i++)
        printf("%d\n",array[i]);
}
Run Code Online (Sandbox Code Playgroud)

这个想法只要两个函数在堆栈段上具有相同的激活记录大小就可以工作并打印0到9之间的数字......但实际上它打印的内容类似于

134520820
-1079626712
0
1
2
3
4
5
6
7
Run Code Online (Sandbox Code Playgroud)

乞讨总会有那两个值...任何人都可以解释一下??? 我在linux中使用gcc

准确的讲座网址从5:15开始

pax*_*blo 23

对不起,但对于那段代码并没有什么聪明,使用它的人非常愚蠢.


附录:

或者,有时候,有时候,非常聪明.观看了在问题更新中链接的视频,这不是一些违反规则的流氓代码猴子.这家伙明白他做得很好.

它需要深入了解生成的底层代码,并且如果您的环境发生变化(如编译器,体系结构等),可能很容易中断(如此处所述和所见).

但是,只要你掌握了这些知识,你就可以逃脱它.这不是我建议除了退伍军人以外的任何人,但我可以看到它在非常有限的情况下占有一席之地,说实话我毫无疑问偶尔会有点......务实......比我应该有的在我自己的职业生涯中:-)

现在回到你的常规节目......


它在架构,编译器,编译器版本之间是不可移植的,甚至可能在编译器的同一版本中的优化级别,以及未定义的行为(读取未初始化的变量).

如果您想了解它,最好的办法是检查编译器输出的汇编代码.

但总的来说,最好的办法就是忘记它并编写标准代码.


例如,此脚本显示gcc如何在不同的优化级别具有不同的行为:

pax> gcc -o qq qq.c ; ./qq
0
1
2
3
4
5
6
7
8
9

pax> gcc -O3 -o qq qq.c ; ./qq
1628373048
1629343944
1629097166
2280872
2281480
0
0
0
1629542238
1629542245
Run Code Online (Sandbox Code Playgroud)

在gcc的高优化级别(我称之为疯狂的优化级别),这就是makeArray功能.基本上可以看出阵列没有被使用,因此优化了它的初始化.

_makeArray:
        pushl   %ebp            ; stack frame setup
        movl    %esp, %ebp

                                ; heavily optimised function

        popl    %ebp            ; stack frame tear-down

        ret                     ; and return
Run Code Online (Sandbox Code Playgroud)

我实际上有点惊讶g​​cc甚至在那里留下了函数存根.

更新:正如Nicholas Knight在评论中指出的那样,该函数仍然存在,因为它必须对链接器可见 - 使得函数静态导致gcc也删除存根.

如果你在下面的优化级别0检查汇编代码,它会给出一个线索(这不是实际原因 - 见下文).检查下面的代码,你会发现两个函数的堆栈框架设置是不同的,尽管它们传入的参数完全相同,并且相同的局部变量:

subl    $48, %esp     ; in makeArray
subl    $56, %esp     ; in printArray
Run Code Online (Sandbox Code Playgroud)

这是因为printArray分配了一些额外的空间来存储printf格式字符串的地址和数组元素的地址,每个字节有四个字节,这占据了8个字节(两个32位值)的差异.

这是你的阵列printArray()被两个值关闭的最可能的解释.

这是优化级别0的两个功能,供您享受:-)

_makeArray:
        pushl   %ebp                     ; stack fram setup
        movl    %esp, %ebp
        subl    $48, %esp
        movl    $0, -4(%ebp)             ; i = 0
        jmp     L4                       ; start loop
L5:
        movl    -4(%ebp), %edx
        movl    -4(%ebp), %eax
        movl    %eax, -44(%ebp,%edx,4)   ; array[i] = i
        addl    $1, -4(%ebp)             ; i++
L4:
        cmpl    $9, -4(%ebp)             ; for all i up to and including 9
        jle     L5                       ; continue loop
        leave
        ret
        .section .rdata,"dr"
LC0:
        .ascii "%d\12\0"                 ; format string for printf
        .text

_printArray:
        pushl   %ebp                     ; stack frame setup
        movl    %esp, %ebp
        subl    $56, %esp
        movl    $0, -4(%ebp)             ; i = 0
        jmp     L8                       ; start loop
L9:
        movl    -4(%ebp), %eax           ; get i
        movl    -44(%ebp,%eax,4), %eax   ; get array[i]
        movl    %eax, 4(%esp)            ; store array[i] for printf
        movl    $LC0, (%esp)             ; store format string
        call    _printf                  ; make the call
        addl    $1, -4(%ebp)             ; i++
L8:
        cmpl    $9, -4(%ebp)             ; for all i up to and including 9
        jle     L9                       ; continue loop
        leave
        ret
Run Code Online (Sandbox Code Playgroud)

更新:罗迪在评论中指出.这不是您的具体问题的原因,因为在这种情况下,数组实际上是在内存中的同一位置(%ebp-44%ebp被跨越两个调用相同).我试图指出的是,具有相同参数列表和相同本地参数的两个函数不一定最终具有相同的堆栈帧布局.

所需要的只是printArray交换其局部变量的位置(包括开发人员未明确创建的任何临时变量),你会遇到这个问题.

  • @paxdiablo:存在的存根很容易解释,消除存根的唯一安全的地方是链接器,我怀疑它受到GCC的-On标志的影响. (5认同)
  • 它可能在架构之间可移植,但绝不保证可以工作,因为它依赖于C编译器的未定义行为和常见习惯.在某些情况下,像这样的疯狂事情很难知道. (2认同)
  • @Ahmed,我并没有专门针对你,但是,当你在这个行业工作了30年并不得不清理这种问题这么多时间,我可能会变得愤世嫉俗,年轻的Padawan :-)看到更新特定的可能原因,在其中一个函数中使用更多堆栈.你可以通过检查汇编程序文件(例如gcc -S)来学习很多东西. (2认同)