【反汇编玩耍2】论指针和数组

在反汇编观察后,你会发现:指针变量就是块内存区域,里面存放的是地址,你可以通过这个地址访问其它内存。

                                               数组就是块连续的内存区域,里面连续排列着同样size的内存,多维数组也是一样的。

上述很简单,就不贴代码赘述了。

 

但人们一般纠结这样一个问题:数组名 是不是 一种指针?

也就是说arr[]的这个arr是不是一种指针?

 

这个问题之前csdn论坛上讨论的热火朝天:

http://bbs.csdn.net/topics/380226723

http://blog.csdn.net/yby4769250/article/details/7294718#reply

 

一种看法就是把这个数组名当作一种特殊的指针来看待,特殊之处在于是常量,不能改变其指向的位置。

一种是透过C语言,从C语言编译之后的汇编,视角来看,认为数组名和指针在内存中完全不是一回事。

 

太认真考虑其定义和概念就接近于一种“玄学”,我这里就单纯讲反汇编角度来分析 数组名 和 指针在内存中的差别吧。

随便写的代码:

#include <stdio.h>

void func(int *p) {
    *(p+1) = 2;
}

void main() {
        int v = 1;
        int *p = &v;
        int v2 = *p + v;
        int arr[100];
        int v3 = arr[0] + v;
        int v4 = *(arr+2) + v2;
        func(arr);
}

 

0000000000001149 <func>:
#include <stdio.h>

void func(int *p) {
    1149:    f3 0f 1e fa              endbr64 
    114d:    55                       push   %rbp
    114e:    48 89 e5                 mov    %rsp,%rbp
    1151:    48 89 7d f8              mov    %rdi,-0x8(%rbp)
    *(p+1) = 2;
    1155:    48 8b 45 f8              mov    -0x8(%rbp),%rax
    1159:    48 83 c0 04              add    $0x4,%rax
    115d:    c7 00 02 00 00 00        movl   $0x2,(%rax)
}
    1163:    90                       nop
    1164:    5d                       pop    %rbp
    1165:    c3                       retq   

0000000000001166 <main>:

void main() {
    1166:    f3 0f 1e fa              endbr64 
    116a:    55                       push   %rbp
    116b:    48 89 e5                 mov    %rsp,%rbp
    116e:    48 81 ec c0 01 00 00     sub    $0x1c0,%rsp
    1175:    64 48 8b 04 25 28 00     mov    %fs:0x28,%rax
    117c:    00 00 
    117e:    48 89 45 f8              mov    %rax,-0x8(%rbp)
    1182:    31 c0                    xor    %eax,%eax
        int v = 1;
    1184:    c7 85 48 fe ff ff 01     movl   $0x1,-0x1b8(%rbp)
    118b:    00 00 00 
        int *p = &v;
    118e:    48 8d 85 48 fe ff ff     lea    -0x1b8(%rbp),%rax
    1195:    48 89 85 58 fe ff ff     mov    %rax,-0x1a8(%rbp)
        int v2 = *p + v;
    119c:    48 8b 85 58 fe ff ff     mov    -0x1a8(%rbp),%rax
    11a3:    8b 10                    mov    (%rax),%edx
    11a5:    8b 85 48 fe ff ff        mov    -0x1b8(%rbp),%eax
    11ab:    01 d0                    add    %edx,%eax
    11ad:    89 85 4c fe ff ff        mov    %eax,-0x1b4(%rbp)
        int arr[100];
        int v3 = arr[0] + v;
    11b3:    8b 95 60 fe ff ff        mov    -0x1a0(%rbp),%edx
    11b9:    8b 85 48 fe ff ff        mov    -0x1b8(%rbp),%eax
    11bf:    01 d0                    add    %edx,%eax
    11c1:    89 85 50 fe ff ff        mov    %eax,-0x1b0(%rbp)
        int v4 = *(arr+2) + v2;
    11c7:    8b 95 68 fe ff ff        mov    -0x198(%rbp),%edx
    11cd:    8b 85 4c fe ff ff        mov    -0x1b4(%rbp),%eax
    11d3:    01 d0                    add    %edx,%eax
    11d5:    89 85 54 fe ff ff        mov    %eax,-0x1ac(%rbp)
        func(arr);
    11db:    48 8d 85 60 fe ff ff     lea    -0x1a0(%rbp),%rax
    11e2:    48 89 c7                 mov    %rax,%rdi
    11e5:    e8 5f ff ff ff           callq  1149 <func>
}
    11ea:    90                       nop
    11eb:    48 8b 45 f8              mov    -0x8(%rbp),%rax
    11ef:    64 48 33 04 25 28 00     xor    %fs:0x28,%rax
    11f6:    00 00 
    11f8:    74 05                    je     11ff <main+0x99>
    11fa:    e8 51 fe ff ff           callq  1050 <__stack_chk_fail@plt>
    11ff:    c9                       leaveq 
    1200:    c3                       retq   
    1201:    66 2e 0f 1f 84 00 00     nopw   %cs:0x0(%rax,%rax,1)
    1208:    00 00 00 
    120b:    0f 1f 44 00 00           nopl   0x0(%rax,%rax,1)

 

在main函数里,

局部变量v,*p,v2,arr[100],v3,v4在栈中分别为:

 -0x1b8(%rbp)、-0x1a8(%rbp)、-0x1b4(%rbp)、-0x1a0(%rbp)、-0x1b0(%rbp)、-0x1ac(%rbp)

 

int v = 1;

int *p = &v;

int v2 = *p + v;

对应为:
 movl $0x1,-0x1b8(%rbp)       
 lea -0x1b8(%rbp),%rax             

                                                 //将局部变量v的地址,存在%rax,其实-0x1b8(%rbp)就是寻址写法,相当于%rbp的值-0x1b8,

                                                //如果是mov就是按-0x1b8(%rbp)的值去内存取对应的内容,

                                                //如果是lea,则直接取%rbp的值-0x1b8这个值,也就是局部变量的地址
 mov %rax,-0x1a8(%rbp)        //此时%rax存的是v的地址值,这里mov %rax是直接取这个值,

                                                //如果写成mov (%rax),就是去寻址,也就是v的值,而非v的地址

 mov -0x1a8(%rbp),%rax
 mov (%rax),%edx
 mov -0x1b8(%rbp),%eax          //这里用的是mov而非lea,也就是取*p(寻址后取内存值),而非p(地址值)
 add %edx,%eax
 mov %eax,-0x1b4(%rbp)

 

 

int arr[100];
int v3 = arr[0] + v;

int v4 = *(arr+2) + v2;

对应为
 mov -0x1a0(%rbp),%edx                            //将arr[0]存入%edx
 mov -0x1b8(%rbp),%eax                            //将v存入%eax
 add %edx,%eax                                         //%eax存arr[100]+v
  mov %eax,-0x1b0(%rbp)                           //存入v3

mov -0x198(%rbp),%edx                             //将arr[2]的值存入%edx
 mov -0x1b4(%rbp),%eax                            //。。。
 add %edx,%eax                                         //。。。
 mov %eax,-0x1ac(%rbp)                           //。。。

 

可以看出,在汇编的概念里,只有寻址的概念,具体说也只有取地址,或依据地址去取内存值的概念,也无所谓数组或指针的区别……

当然数组是一块连续的地址区域,比如上面的代码,arr[0]就是-0x1a0(%rbp),arr[2]就是-0x198(%rbp),跳了8个字节,也就是两个int的大小(2023年12月补充,修改,说实话之前基本没搞懂)

 

上述是讲数组名 和 指针的差别?

下面要讲,

而当数组作为函数参数时,其实就是转化为指针来玩的。

 

先讲调用函数时的反汇编代码(这里取数组地址,然后压入栈再call func):

        func(arr);
00811537  lea         eax,[arr]  
0081153D  push        eax  
0081153E  call        func (0811226h)  
00811543  add         esp,4 

 

 

再讲func函数里面对数组操作的反汇编代码:

 

上面的代码的函数func:

void func(int *p) {
    *(p+1) = 2;
}

反汇编代码为:

void func(int *p) {
    1149:    f3 0f 1e fa              endbr64 
    114d:    55                       push   %rbp
    114e:    48 89 e5                 mov    %rsp,%rbp
    1151:    48 89 7d f8              mov    %rdi,-0x8(%rbp)
    *(p+1) = 2;
    1155:    48 8b 45 f8              mov    -0x8(%rbp),%rax
    1159:    48 83 c0 04              add    $0x4,%rax
    115d:    c7 00 02 00 00 00        movl   $0x2,(%rax)
}
    1163:    90                       nop
    1164:    5d                       pop    %rbp
    1165:    c3                       retq   

这里形参为指针,没什么异议。

 

改写一下,形参改为,arr[]的话:

void func(int arr[]) {
    arr[1] = 2;
}

 

void func(int arr[]) {
    1149:    f3 0f 1e fa              endbr64 
    114d:    55                       push   %rbp
    114e:    48 89 e5                 mov    %rsp,%rbp
    1151:    48 89 7d f8              mov    %rdi,-0x8(%rbp)
    arr[1] = 2;
    1155:    48 8b 45 f8              mov    -0x8(%rbp),%rax
    1159:    48 83 c0 04              add    $0x4,%rax
    115d:    c7 00 02 00 00 00        movl   $0x2,(%rax)
}
    1163:    90                       nop
    1164:    5d                       pop    %rbp
    1165:    c3                       retq   

 

 

可以看出二者(不管形参写成*p还是arr[])是没差别的。

数组作为函数参数,都是会被转化为指针来操作。

为何?

 

当然没有差别!因为入参是函数调用前处理好的,为这几句:

lea -0x1a0(%rbp),%rax
mov %rax,%rdi
callq 1149 <func>

先取数组地址,放在%rax,再存在被调用函数需保存的寄存器,%rdi中,也就是通过%rdi传参,传的就是数组地址,也可以理解为C语言的指针(2023年12月补充,修改,说实话之前基本没搞懂)

 

调用函数,开辟新的栈空间以后,都是通过这个%rdi获取数组地址(指针),所以是没有任何差别的

 

至於各個寄存器的作用,哪些是被調用者保存寄存器,參考此圖,取自《深入理解計算機系統》

 

posted on 2017-07-05 15:54  J·Marcus  阅读(795)  评论(4编辑  收藏  举报

导航