avatar

汇编相关要点

关于c语言在下的调用过程描述(IA32)

  • 关于函数调用指令call指令
    该指令将PC压栈,然后跳转,也就是图中的返回地址,此时sp指向返回地址
  • 入栈 sp先减,后写入数据;
  • 准备阶段
    • pushl %ebp,此时esp指向函数栈帧底部(存放上一个函数的栈帧底部位置)
    • movl %esp,%ebp,此时esp 和ebp指向了函数栈帧底部
    • subl 分配栈空间
  • 设置局部变量
    • 准备入口参数(X64架构下,参数优先放在寄存器中)
  • 处理返回参数
  • 结束
    • movl %ebp,%esp 将栈顶置为被调用函数栈底
    • pop %ebp 将bp置为原调用函数栈底,并且将sp还原为返回地址处

关于c++ 类在反汇编中的体现

classTest.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class T{
int a;
public:
int b;
void test(){
a=3;
}
};
int main(){
T a;
a.test();
return 0;
}

通过objdump -d 查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
00000000004005ad <main>:
4005ad: 55 push %rbp
4005ae: 48 89 e5 mov %rsp,%rbp
4005b1: 48 83 ec 10 sub $0x10,%rsp
4005b5: 48 8d 45 f0 lea -0x10(%rbp),%rax
4005b9: 48 89 c7 mov %rax,%rdi
4005bc: e8 07 00 00 00 callq 4005c8 <_ZN1T4testEv>
4005c1: b8 00 00 00 00 mov $0x0,%eax
4005c6: c9 leaveq
4005c7: c3 retq

00000000004005c8 <_ZN1T4testEv>:
4005c8: 55 push %rbp
4005c9: 48 89 e5 mov %rsp,%rbp
4005cc: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4005d0: 48 8b 45 f8 mov -0x8(%rbp),%rax
4005d4: c7 00 03 00 00 00 movl $0x3,(%rax)
4005da: 5d pop %rbp
4005db: c3 retq
4005dc: 0f 1f 40 00 nopl 0x0(%rax)
  • 目前可以推断出来的信息

  • 4005b5: 48 8d 45 f0 lea -0x10(%rbp),%rax //
    4005b9: 48 89 c7 mov %rax,%rdi
    4005bc: e8 07 00 00 00 callq 4005c8 <_ZN1T4testEv> //对于类的函数来说,g++编译将其看作一个全局符号,其具有绝对的地址

  • 这个例子中很明确的可以看出

  • 1.main函数中对于栈类进行空间分配的方式,由于没有显示的调用某个特定的构造函数,所以此处直接分配了16个空间其中位于底地址的4字节存放了该实例化 a.a的数据

  • 2.调用实例化a.test的过程

    • %rdi 存放了实例化a的地址,实际上该地址也就是a.a的地址
    • 4005d4: c7 00 03 00 00 00 movl $0x3,(%rax) 此时rax就是rdi 也就是a.a的栈地址,执行a=3;
    • 如果执行b=3 那么就是movl $0x3,0x04(%rax)
  • 结论:

    1. 对于类/结构体来说,其内存的布局方式都是相同的,变量占用空间,类中的函数当作全局符号进行解析
    2. 类并没有什么难以理解的,全局变量对于c++来说其作用域就是文件单元;类的作用域就是类,两者只是一种组织方式
    3. 对于类而言,其内部的变量基本上都被处理为local var,分配在栈或者堆上,并不是全局变量存放在data中
    • 如本例直接分配的类实例
    • 作用在局部作用域中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class T{
int a;
public:
int b;
void test(){
a=3;
}
};
int main(){
for(int index=0;index++;index<10){
T a;
a.test();
}
return 0;
}

汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
00000000004005ad <main>:
4005ad: 55 push %rbp
4005ae: 48 89 e5 mov %rsp,%rbp
4005b1: 48 83 ec 10 sub $0x10,%rsp
4005b5: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
4005bc: eb 0c jmp 4005ca <main+0x1d>
4005be: 48 8d 45 f0 lea -0x10(%rbp),%rax //循环内部代码
4005c2: 48 89 c7 mov %rax,%rdi
4005c5: e8 1a 00 00 00 callq 4005e4 <_ZN1T4testEv>
4005ca: 8b 45 fc mov -0x4(%rbp),%eax //循环判断代码
4005cd: 8d 50 01 lea 0x1(%rax),%edx
4005d0: 89 55 fc mov %edx,-0x4(%rbp)
4005d3: 85 c0 test %eax,%eax
4005d5: 0f 95 c0 setne %al
4005d8: 84 c0 test %al,%al
4005da: 75 e2 jne 4005be <main+0x11>
4005dc: b8 00 00 00 00 mov $0x0,%eax
4005e1: c9 leaveq
4005e2: c3 retq
4005e3: 90 nop

对于循环内部生成空间会被本身循环利用,如反复产生的a对象,而类的析构函数不是用来处理栈分配的空间,只有在堆中创建析构才是重要的
对于循环体结束后,栈分配的空间即使没有回收也不能进行调用了,此处可以使用指针访问到,数据是否正确这和编译器优化有关,比如说此处编译器将内存回收,填上其他值;并且在c++的语法中也不推荐去调用此处,但是如果通过指针应该还是可以访问到的.

文章作者: fancylight
文章链接: https://www.fancylight.top/2018/09/05/%E6%B1%87%E7%BC%96%E7%9B%B8%E5%85%B3%E8%A6%81%E7%82%B9/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 博客
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论