汇编:静态变量与RIP

 

在线看汇编

在class A中定义一个静态变量,然后对其进行操作,我们看看这个静态变量的是怎么被操作的。

 #include "stdlib.h"

class A{
    public:
    static int test0;
    static int test1;
};

int A::test0 = 1;
int A::test1 = 1;

int main(){

    A::test0 = 4;
    A::test1 = 5;
}

 对应的汇编

其中对静态变量的操作,是通过RIP+相对地址实现的,因为静态变量不是在stack或者heap阶段,而是在bss或者data段,因此可以直接通过RIP+相对地址索引到。

实际上也就是这样

在main函数中,将A的静态成员设置为4,对应的汇编是

mov     DWORD PTR A::record[rip], 4
dword   双字 就是四个字节
ptr     pointer缩写 即指针

而之所以使用RIP,是因为RIP是保存了当前的程序的指针,加上偏移,就可以索引到静态变量的空间。

本质上就是当前RIP肯定指向的是text,在编译器编译时,静态成员变量的地址在bss或者data段,是可以计算出RIP和静态成员变量的相对地址的,因此这样就可以索引到了。

 

 

则被编译成了以下内容,需要注意的是,只调用了一次的new[],但是调用了10次的构造函数

如果只生成一个对象

那么会被编译为(此时A只有成员变量int a):

  1. mov edi, 4  ----------- edi 是默认的传参寄存器,而4是当前的class的大小。  有意思的是,如果成员变量是一个int,那么传入的是4;如果一个int,一个char* ,那么 int占4个字节, 64bit环境中,char* 占据8个字节,因此int会补齐到8个字节+char*的8个字节,一共16个字节
  2. 调用构造函数
  3. 构造函数的结果默认是存在rax中的,将rax的结果存到rbx中
  4. 将rbx的存取rdi,实际上就是new出来的地址,传递给构造函数做参数
  5. 调用A::A() 构造函数

定义三个类A的对象,因为此时A不是new出来的,所以不会call new函数分配在堆上,而是在栈上分配

  1.  lea rax,[rbp-0x1c] ----------- 将rbp-0x1c的计算结果存入rax中,rbp是栈指针寄存器,指明了当前帧的基地址,而减去0x1c就是在减去了开头的传入的参数空间之后,为a0分配的栈内的起始地址;
  2. 将rax的结果传递给rdi,以作为实参传递给构造函数
  3. 调用构造函数

可以看出,地址分别是rbp-0x1c, rbp-0x20,rbp-0x24, 这就是为是三个对象分配的栈内的空间,间隔4字节,因为A的成员只有一个整型。

 

然后设置成员变量的值

被编译为:

最后一行,就会调用A的setA成员函数,而这个函数,会被编译为:

这里传入的rdi,应该就是this指针,即当前a的地址,而esi就是int类型形参的值了。我们可以看到rdi的值最后传到了rbp-8这个地址的空间上,后面我们如果对这个地址对应的空间操作,实际上就是操作this所指向的对象的空间了。

参数传递规则:

  1. 一个参数用rdi(edi)传
  2. 两个参数用rdi、rsi(edi、rsi)传
  3. 三个参数用rdi、rsi、rdx(edi、esi、edx)传
  4. 四个参数用rdi、rsi、rdx、rcx(edi、esi、edx、ecx)传
  5. 五个参数用rdi、rsi、rdx、rcx、r8(edi、esi、edx、ecx、r8)传
  6. 六个参数用rdi、rsi、rdx、rcx、r8、r9(edi、esi、edx、ecx、r8、r9)传

整体的代码:

 #include "stdlib.h"

class A{
    public:
    static int record;
    public:
        int a;
    public:
        A(){
            a = 1;
        }

        ~A(){}

        void setA(int aTmp){
            a+=aTmp;
        }
};

int A::record = 1;

int main(){

    A::record = 4;

    A *a = new A();

    delete a;

    A::record = 5;

    A a0,a1,a2;
    a0.setA(0);
    a1.setA(1);
    a2.setA(2);
    return 0;
}

 

比较复杂的情况:类A中存在指针,并且调用时,声明了A对象数组。

#include "stdlib.h"

class A{
    public:
    static int record;
    public:
        int a;
        char *ptr=nullptr;
    public:
        A(){
            a = 1;
            ptr = (char *)malloc(100);
        }

        ~A(){
            free(ptr);
        }

        void setA(int aTmp){
            a = aTmp;
        }
};

int A::record = 1;

int main(){

    A::record = 4;

    A *a = new A[10];
    delete []a;

    A::record = 5;
    //A b,c;
    //b.setA(1);
    //c.setA(2);
    return 0;
}
.Ltext0:
A::A() [base object constructor]:
.LFB15:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB2:
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax+8], 0
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], 1
        mov     edi, 100
        call    malloc
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax+8], rdx
.LBE2:
        nop
        leave
        ret
.LFE15:
A::~A() [base object destructor]:
.LFB18:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB3:
        mov     rax, QWORD PTR [rbp-8]
        mov     rax, QWORD PTR [rax+8]
        mov     rdi, rax
        call    free
.LBE3:
        nop
        leave
        ret
.LFE18:
A::record:
        .long   1
main:
.LFB21:
        push    rbp
        mov     rbp, rsp
        push    r13
        push    r12
        push    rbx
        sub     rsp, 24
        mov     DWORD PTR A::record[rip], 4
        mov     edi, 168
        call    operator new[](unsigned long)
        mov     rbx, rax
        mov     QWORD PTR [rbx], 10
        lea     rax, [rbx+8]
        mov     r12d, 9
        mov     r13, rax
.L5:
        test    r12, r12
        js      .L4
        mov     rdi, r13
        call    A::A() [complete object constructor]
        add     r13, 16
        sub     r12, 1
        jmp     .L5
.L4:
        lea     rax, [rbx+8]
        mov     QWORD PTR [rbp-40], rax
        mov     rax, QWORD PTR [rbp-40]
        mov     eax, DWORD PTR [rax]
        lea     edx, [rax+1]
        mov     rax, QWORD PTR [rbp-40]
        mov     DWORD PTR [rax], edx
        cmp     QWORD PTR [rbp-40], 0
        je      .L6
        mov     rax, QWORD PTR [rbp-40]
        sub     rax, 8
        mov     rax, QWORD PTR [rax]
        sal     rax, 4
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-40]
        lea     rbx, [rdx+rax]
.L8:
        cmp     rbx, QWORD PTR [rbp-40]
        je      .L7
        sub     rbx, 16
        mov     rdi, rbx
        call    A::~A() [complete object destructor]
        jmp     .L8
.L7:
        mov     rax, QWORD PTR [rbp-40]
        sub     rax, 8
        mov     rax, QWORD PTR [rax]
        sal     rax, 4
        lea     rdx, [rax+8]
        mov     rax, QWORD PTR [rbp-40]
        sub     rax, 8
        mov     rsi, rdx
        mov     rdi, rax
        call    operator delete[](void*, unsigned long)
.L6:
        mov     DWORD PTR A::record[rip], 5
        mov     eax, 0
        add     rsp, 24
        pop     rbx
        pop     r12
        pop     r13
        pop     rbp
        ret

总结:

  1.  寄存器:rbp 栈指针, rsp 栈顶指针,rax函数返回值,rdi,rsi,函数默认传参寄存器;
  2. 全局变量,静态变量通过RIP相对索引进行访问更改;
  3. new() 的本质上是先调用operator new函数,然后将new出的指针传递给构造函数,进行构造;new[] 则是new一次,但是调用构造函数N次
  4. 老生常谈,new出的类对象是在heap上;临时类对象是在stack上,使用ebp索引;
  5. char* 类型,不是一个字节,而是8个字节(64bit环境)
  6. push ebp;保存调用函数的帧基址;mov ebp esp;生成被调用函数的新帧基址;

 

Reference:

从机器码理解RIP 相对寻址

汇编语言入门教程

 

欢迎关注我的公众号《处理器与AI芯片》

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值