汇编:静态变量与RIP

您所在的位置:网站首页 rsi寄存器 汇编:静态变量与RIP

汇编:静态变量与RIP

2024-07-13 03:50| 来源: 网络整理| 查看: 265

 

在线看汇编

在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):

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

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

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

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

 

然后设置成员变量的值

被编译为:

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

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

参数传递规则:

一个参数用rdi(edi)传两个参数用rdi、rsi(edi、rsi)传三个参数用rdi、rsi、rdx(edi、esi、edx)传四个参数用rdi、rsi、rdx、rcx(edi、esi、edx、ecx)传五个参数用rdi、rsi、rdx、rcx、r8(edi、esi、edx、ecx、r8)传六个参数用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

总结:

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

 

Reference:

从机器码理解RIP 相对寻址

汇编语言入门教程

 

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3