当前位置: 首页 > 知识库问答 >
问题:

从rsp拆解sub和不同的函数返回

方鸿羲
2023-03-14

我目前正在玩gcc,gdb和汇编,并试图理解它。我已经回顾了一些教程,并得到了一些关键点。

所以我决定用小的。c文件,看了看结果,有些东西不是很清楚。

这是文件:

#include <stdio.h>

void func1(){
    int x = 8;
    int y = x + 5;
}

void func2(){
    int x = 12;
}

void func3(){
    int x = 10+20;
}

void func4(){
    int x;
    x = 1;
}

void func5(){
    int x;
    int y;

    x = 2;
    y = 1;
}

void func6(){
    int x;
    int y;

    x=15;
    y=6;
    y += x;
}

int main(int argc, char *argv[]) {
    func1();
    func2();
    func3();
    func4();
    func5();
    func6();
    return 20;
}

以下是拆解结果:

Dump of assembler code for function main:
0x0000000100000f60 <+0> :   push   %rbp
0x0000000100000f61 <+1> :   mov    %rsp,%rbp
0x0000000100000f64 <+4> :   sub    $0x10,%rsp
0x0000000100000f68 <+8> :   movl   $0x0,-0x4(%rbp)
0x0000000100000f6f <+15>:   mov    %edi,-0x8(%rbp)
0x0000000100000f72 <+18>:   mov    %rsi,-0x10(%rbp)
0x0000000100000f76 <+22>:   callq  0x100000ed0 <func1>
0x0000000100000f7b <+27>:   callq  0x100000ef0 <func2>
0x0000000100000f80 <+32>:   callq  0x100000f00 <func3>
0x0000000100000f85 <+37>:   callq  0x100000f10 <func4>
0x0000000100000f8a <+42>:   callq  0x100000f20 <func5>
0x0000000100000f8f <+47>:   callq  0x100000f40 <func6>
0x0000000100000f94 <+52>:   mov    $0x14,%eax
0x0000000100000f99 <+57>:   add    $0x10,%rsp
0x0000000100000f9d <+61>:   pop    %rbp
0x0000000100000f9e <+62>:   retq

Dump of assembler code for function func1:
0x0000000100000ed0 <+0> :   push   %rbp
0x0000000100000ed1 <+1> :   mov    %rsp,%rbp
0x0000000100000ed4 <+4> :   movl   $0x8,-0x4(%rbp)
0x0000000100000edb <+11>:   mov    -0x4(%rbp),%eax
0x0000000100000ede <+14>:   add    $0x5,%eax
0x0000000100000ee3 <+19>:   mov    %eax,-0x8(%rbp)
0x0000000100000ee6 <+22>:   pop    %rbp
0x0000000100000ee7 <+23>:   retq
0x0000000100000ee8 <+24>:   nopl   0x0(%rax,%rax,1)

Dump of assembler code for function func2:
0x0000000100000ef0 <+0> :   push   %rbp
0x0000000100000ef1 <+1> :   mov    %rsp,%rbp
0x0000000100000ef4 <+4> :   movl   $0xc,-0x4(%rbp)
0x0000000100000efb <+11>:   pop    %rbp
0x0000000100000efc <+12>:   retq
0x0000000100000efd <+13>:   nopl   (%rax)

Dump of assembler code for function func3:
0x0000000100000f00 <+0> :   push   %rbp
0x0000000100000f01 <+1> :   mov    %rsp,%rbp
0x0000000100000f04 <+4> :   movl   $0x1e,-0x4(%rbp)
0x0000000100000f0b <+11>:   pop    %rbp
0x0000000100000f0c <+12>:   retq
0x0000000100000f0d <+13>:   nopl   (%rax)

Dump of assembler code for function func4:
0x0000000100000f10 <+0> :   push   %rbp
0x0000000100000f11 <+1> :   mov    %rsp,%rbp
0x0000000100000f14 <+4> :   movl   $0x1,-0x4(%rbp)
0x0000000100000f1b <+11>:   pop    %rbp
0x0000000100000f1c <+12>:   retq
0x0000000100000f1d <+13>:   nopl   (%rax)

Dump of assembler code for function func5:
0x0000000100000f20 <+0> :   push   %rbp
0x0000000100000f21 <+1> :   mov    %rsp,%rbp
0x0000000100000f24 <+4> :   movl   $0x2,-0x4(%rbp)
0x0000000100000f2b <+11>:   movl   $0x1,-0x8(%rbp)
0x0000000100000f32 <+18>:   pop    %rbp
0x0000000100000f33 <+19>:   retq
0x0000000100000f34 <+20>:   data16 data16 nopw %cs:0x0(%rax,%rax,1)

Dump of assembler code for function func6:
0x0000000100000f40 <+0> :   push   %rbp
0x0000000100000f41 <+1> :   mov    %rsp,%rbp
0x0000000100000f44 <+4> :   movl   $0xf,-0x4(%rbp)
0x0000000100000f4b <+11>:   movl   $0x6,-0x8(%rbp)
0x0000000100000f52 <+18>:   mov    -0x4(%rbp),%eax
0x0000000100000f55 <+21>:   mov    -0x8(%rbp),%ecx
0x0000000100000f58 <+24>:   add    %eax,%ecx
0x0000000100000f5a <+26>:   mov    %ecx,-0x8(%rbp)
0x0000000100000f5d <+29>:   pop    %rbp
0x0000000100000f5e <+30>:   retq
0x0000000100000f5f <+31>:   nop

我用以下代码编写:

gcc  -o example example.c

有几件事我不太清楚:

  • 如果所有函数的结尾都相同(在代码中,例如返回void),为什么
    • func1以nopl 0x0(%rax、%rax,1)结尾
    • 函数2
    • sub$0x10,%rsp
    • 添加$0x10,%rsp
    • 这些是为方法中的局部变量分配mem吗?如果是这样,为什么它们总是四舍五入到0x10、0x20、0x30……这不是有点浪费吗?

共有2个答案

东方高洁
2023-03-14

> < li>

函数以< code>retq语句结束。反汇编中显示的操作码对于实际执行来说只是垃圾,但在现代预测CPU中可能会被预执行(和丢弃)。你完全可以忽略它们。其他CPU有“分支延迟”的指令,但是x86没有这个特性。< code>retq和下一个16字节边界之间的间隙是自由的,以便让函数从偶数地址开始。这允许更快的执行。

data16可能意味着有一个16位数据与反汇编程序已知的任何操作码都不匹配。忽略它,它不会影响执行。

x86架构允许访问任何地址,而不需要对齐。但是访问未对齐的变量可能需要一个以上的总线周期来进行内存访问。堆栈点rsp的对齐保证了对uint64_t的访问只导致一个总线周期。

蓬思博
2023-03-14

所有这些< code>nopl 0x0(%rax,%rax,1),等等。指令是< code>nop指令的变体。它们用于确保函数的长度是16字节的倍数。你可能会问为什么他们不直接使用多个< code > 0x 90 (< code > nop )指令。答案是,如果正在执行这些nop,执行一个长的多字节nop(如< code > data 16 data 16 nopw % cs:0x 0(%rax,% rax,1)或< code>nopl (%rax))比执行多个短的nop稍快。当nop出现在函数内部时,它们可能会被执行;当编译器想要调整跳转目标以提高性能时,就会生成这样的代码。nop是由汇编html" target="_blank">程序生成的,汇编程序不知道哪些nop可以执行,哪些nop不能执行,因为这通常是不可决定的。

关于堆栈的部分:你在没有优化的情况下编译,你不应该问没有优化生成的奇怪代码。编译器被指示当你在没有优化的情况下编译时不要聪明,那么你为什么期望它节省空间呢?

 类似资料:
  • 我正在等待(从USSD请求中)检索一个值,以便返回它(getUSSD):

  • 我遇到了以下两种方式: 只有这两个选择吗?

  • 问题内容: 在基本的Postgres函数教程中,有一个带有如下参数的示例: 然后结果看起来像 但是,假设您要在执行连接所产生的列上执行该函数,并且您无权修改该函数或使用替代函数?例如,使用一些玩具数据: 在单列“ hi_lo”中返回具有2元组值的结果。 将查询括在括号中并尝试从中查询不会更改输出的格式。所以 不会影响结果形状。 以下尝试导致错误“子查询必须仅返回一列” 最后,我也尝试过,但是由于元

  • 我有一个这样的方法,它通常用于返回。 在最后一行,我从Pylance那里得到了以下信息: (方法)is_file:()- 有没有一种方法可以正确地键入提示这种情况,以便Pylance知道是?或者我应该让它总是返回路径,并有另一个方法调用将输出转换为字符串,然后返回? 谢谢 编辑1 我刚刚意识到另一个更常见的场景: 在熊猫中,输入参数可以改变输出类型,Pylance也不能处理这个问题。Pylance

  • 问题内容: 我有一个返回信息菜单(基本上是菜单,menu_headers和项)的应用程序。我想要这样的东西: 那会返回一个菜单,但是我想我需要在这里完成处理程序。 我目前有: 但是我实际上该如何退货?我99%确信这是某种类型的完成处理程序,但是,对于Swift和Alamofire来说,这是我的新手,我有点迷失了。我已经知道我无法使用Swift中的Alamofire返回值,但是知道其中某些值很快就会

  • C++ 数组 C++ 不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。 如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:int * myFunction() { . . . } 另外,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。 现在,让我们来看下面的函数,它会生成 10 个随机数,并