了解此x86汇编函数的作用,递归

问题描述

在这里有这个Assembly函数,并多次运行它以试图了解它的功能和模式。在理解它的模式方面,我碰到了一堵砖墙。在此感谢任何形式的指导。

   0x0000555555555cbe <+0>: push   %rbx
   0x0000555555555cbf <+1>: mov    %edx,%eax
   0x0000555555555cc1 <+3>: sub    %esi,%eax
   0x0000555555555cc3 <+5>: mov    %eax,%ebx
   0x0000555555555cc5 <+7>: shr    $0x1f,%ebx
   0x0000555555555cc8 <+10>: add    %eax,%ebx
   0x0000555555555cca <+12>: sar    %ebx
   0x0000555555555ccc <+14>: add    %esi,%ebx
   0x0000555555555cce <+16>: cmp    %edi,%ebx
   0x0000555555555cd0 <+18>: jg     0x555555555cda <func4+28>
   0x0000555555555cd2 <+20>: cmp    %edi,%ebx
   0x0000555555555cd4 <+22>: jl     0x555555555ce6 <func4+40>
   0x0000555555555cd6 <+24>: mov    %ebx,%eax
   0x0000555555555cd8 <+26>: pop    %rbx
   0x0000555555555cd9 <+27>: retq  
   0x0000555555555cda <+28>: lea    -0x1(%rbx),%edx
   0x0000555555555cdd <+31>: callq  0x555555555cbe <func4>
   0x0000555555555ce2 <+36>: add    %eax,%ebx
   0x0000555555555ce4 <+38>: jmp    0x555555555cd6 <func4+24>
   0x0000555555555ce6 <+40>: lea    0x1(%rbx),%esi
   0x0000555555555ce9 <+43>: callq  0x555555555cbe <func4>
   0x0000555555555cee <+48>: add    %eax,%ebx
   0x0000555555555cf0 <+50>: jmp    0x555555555cd6 <func4+24>

如果我的输入为“ 6 1”,则寄存器如下:

rax            0x2 2
rbx            0x7fffffffe0c8 140737488347336
rcx            0x0 0
rdx            0xe 14
rsi            0x0 0
rdi            0x6 6
rbp            0x555555557170 0x555555557170 <__libc_csu_init>
rsp            0x7fffffffdfb8 0x7fffffffdfb8
r8             0x0 0
r9             0x0 0
r10            0x7ffff7b82f20 140737349431072
r11            0x55555555763a 93824992245306
r12            0x5555555558f0 93824992237808
r13            0x7fffffffe0c0 140737488347328
r14            0x0 0
r15            0x0 0
rip            0x555555555cbe 0x555555555cbe <func4>
eflags         0x293 [ CF AF SF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0 0
es             0x0 0
fs             0x0 0
gs             0x0 0
k0             0x0 0
k1             0x0 0
k2             0x0 0
k3             0x0 0
k4             0x0 0
k5             0x0 0
k6             0x0 0
k7             0x0 0

我已经尝试了很多代码,并一步步遵循代码,似乎无法理解此函数遵循的模式。

RDX似乎总是为14,无论输入如何。从我的理解中可以看出,存在一个循环,可以查看移位和减法的某种组合是否大于或小于原始输入。

这些是我尝试过的输入以及它们的以下输出

14,1 = 45

13,1 = 31

5,1 = 15

6,1 = 21

7,1 =返回7?

8,1 = 35

解决方法

我总是喜欢为程序集添加一个调用图,所以我在Binary Ninja中进行了组装:

(intel语法) intel syntax call graph

(at&t语法) at&t syntax call graph

就个人而言,我更喜欢intel语法,因此从现在开始我将继续使用它。我们可以看到3个寄存器,它们是在写入之前被读取的,分别是edi,esi和edx。这些也恰好是标准x86调用约定中的前3个参数寄存器,因此我们可以推断此函数具有3个参数。这是带注释的(要理解为什么将0x1f移位会产生符号位,您必须首先了解two's complement): commented call graph

以下是等效的C代码:

int func4(int a,int b,int c) {
    int num = (b + c + (c < b)) / 2;
    if (num > a) {
        return func4(a,b,num - 1) + num;
    } else if (num < a) {
        return func4(a,num + 1,c) + num;
    } else {
        return num;
    }
}

现在,我们有了清晰易读的C代码,我们可以开始思考该函数的实际作用。 (b + c) / 2是平均值,c < b始终为0或1,这意味着num变量必须非常接近平均值。实际上,如果bc均为偶数或均为奇数,则num就是平均值,因为可能的加法1被整数除法取整并且不会改变价值。但是,如果bc中只有一个是偶数,则可能的加法实际上很重要。我们来看两种情况:

  1. b小于c,在这种情况下c < b的取值为0,因此num只是平均值,由于整数除法而四舍五入。由于b小于c,因此四舍五入是朝b舍入。
  2. b大于c,在这种情况下,c < b的取值为1,因此num是平均值加一半,将其推高到下一个值。这意味着num是四舍五入的平均值,并且由于b大于c,因此将其舍入到b

因此,b本质上是bc的平均值,但四舍五入为b的值。现在我们来看一下实际的递归情况。要注意的一件事是a永远不会改变。实际上,该函数永远不会终止,直到bc的平均值达到a为止。如果平均值大于a,则将c减小为平均值减去1,以使平均值下降;如果平均值小于a,则b增加到平均值加一以提高平均值。现在,假设b < c。为什么?由于将b > c设置为平均值实际上会减少 b,因此b及其所有逻辑中断并进入无限递归时,此函数确实令人讨厌而不是增加b,永远不要让平均值达到a。因此,最后,您进行了一些奇怪的二进制搜索,其中平均值开始于期望值之上或之下,然后到达另一侧,然后缓慢接近期望值,并沿途返回所有平均值的总和。这也是func4(7,1,14)返回7:7是114的平均值的原因,因此它立即返回。