0

我从 Ghidra 那里得到了这段 C 代码片段,但我不太明白它在做什么。我怀疑某种根,也许?

传入的两个 args 是平方和(有时是 2 有时是 3 个项)和一个额外的值,例如 0x18、0x10 或 0(有时这个 arg 不存在!)

uint FUN_80059070(uint param_1,uint param_2)

{
  uint uVar1;
  uint uVar2;
  uint uVar3;
  uint uVar4;
  uint uVar5;
  uint uVar6;

  uVar5 = 0;
  uVar4 = 1;
  uVar6 = 0;
  uVar3 = 1 << (param_2 & 0x1f);
  while ((uVar3 < param_1 && (uVar3 << 2 != 0))) {
    uVar4 = uVar4 + 1;
    uVar3 = uVar3 << 2;
  }
  uVar1 = 1 << (uVar4 + (param_2 - 1) & 0x1f);
  while (uVar3 != 0) {
    uVar2 = uVar5 << (uVar4 & 0x1f);
    if ((int)uVar4 < 0) {
      uVar2 = uVar5 >> (-uVar4 & 0x1f);
    }
    uVar2 = uVar2 + uVar6 + uVar3;
    if (uVar2 <= param_1) {
      uVar5 = uVar5 + uVar1;
      uVar6 = uVar2;
    }
    uVar1 = uVar1 >> 1;
    uVar3 = uVar3 >> 2;
    uVar4 = uVar4 - 1;
  }
  return uVar5;
}
4

1 回答 1

2

理解代码的一个不错的方法是重构它。

首先,创建一个测试函数和几个测试用例。然后重写函数。它可以看起来像这样。它非常简单,对于更大的重构,我会让它更复杂一点。

bool test(uint param_1, uint param_2) 
{
    return (FUN_80059070(param_1, param_2) == my_func(param_1, param2));
}

int main()
{
    uint test_cases[3][2] = { {0,0}, {8, 12}, {12, 14}};

    for(int i=0; i<3; i++) {
        if(! test(test_cases[i][0], test_cases[i][1])) {
             printf("Case %d with values %d and %d failed\n", 
                     i, test_cases[i][0], test_cases[i][1]);
             exit(EXIT_FAILURE);
        }
    }

    printf("All tests passed\n");
}

由于您已经知道参数的条件,请考虑编写一个为您创建测试用例的片段。创建大量测试用例,但要注意溢出的风险。

之后,您可以开始重构过程。首先复制 to 的整个主体,FUN_80059070然后my_func我们将替换行和代码块。

例如,首先通过1 << (param_2 & 0x1f);谷歌搜索和测试不同的值来调查实际做了什么。当您了解它的作用时,您就创建了一个函数。

uint describing_name(uint x) { return (x & 0x1f); }

并将行初始化更改uVar3

uVar3 = 1 << describing_name(param_2);

然后迈出一小步。例如,uVar3 << 2等价于uVar * 4,但后者更易于阅读。在更一般的情况下,x << y与 相同x * pow(2,y)。请注意,pow具有签名double pow(double, double),因此要么强制转换,要么编写您自己的整数变体。

然后迭代地处理代码并每次运行测试。如果代码的某些部分特别棘手,您可以或当然可以为该功能创建一个带有适当测试用例的单独测试。

请注意,替换<<pow不一定总是有意义的。有时它们用于位操作,有时也用于更快的乘法。对于带有坏或没有优化器的编译器,它可以产生巨大的性能差异。在这些情况下,将它们替换为有意义,pow但在其他情况下<<可用于删除最高有效位。

例如,我不知道uint您的系统上有多少位,但x & 0x1f如果您设置除 15 个最低有效位之外的所有位,我将返回您获得的数字。大端或小端在这里可能很重要。我不这么认为,但我不确定。如果我是正确的,那么与模运算x & 0x1f相同。x % 32这也是常见的优化。位移比乘法和取模快得多。所以我们可以将函数重命名describing_namemodulo32.

if((int)uVar4 < 0)基本上是一种“聪明”的方式来检查是否设置了最高有效位,或者是否uVar4包含比signed int可以表示的更大的数字。两种解释是等价的。

现在它看起来像这样:

uint modulo32(uint x) { return (x & 0x1f); }

bool larger_than_INT_MAX(uint x) { return (int)x<0; }

uint my_func(uint param_1, uint param_2)
{
         uint uVar1, uVar2, uVar3, uVar4, uVar5, uVar6;

         uVar5 = 0;
         uVar4 = 1;
         uVar6 = 0;
         uVar3 = powi(2, modulo32(param_2));
         while ((uVar3 < param_1 && (uVar3 * 4 != 0))) {
                 uVar4 = uVar4 + 1;
                 uVar3 = uVar3 * 4;
         }

         uVar1 = powi(2, uVar4 + (modulo32(param_2-1)));
         while (uVar3 != 0) {
                 uVar2 = uVar5 * powi(2, modulo32(uVar4));
                 if (larger_than_INT_MAX(uVar4)) {
                         uVar2 = uVar5 / powi(2, -uVar4);
                 }

                 uVar2 = uVar2 + uVar6 + uVar3;
                 if (uVar2 <= param_1) {
                         uVar5 = uVar5 + uVar1;
                         uVar6 = uVar2;
                 }
                 uVar1 = uVar1 / 2;
                 uVar3 = uVar3 / 4;
                 uVar4 = uVar4 - 1;
         }
         return uVar5;
}

powi是我写的一个简单的整数幂函数。上面的代码仍然不是很容易理解,但至少怪异的位操作和“聪明”的代码部分不碍事。

现在我注意到一些事情。uVar3 * 4 != 0作为数学运算并没有真正意义,因为这仅适用于uVar3 == 0. 但这所做的是检查除了两个最重要的位之外的所有位是否为零。所以你可以用这个函数替换它:

bool fourteen_least_significant_bits_are_not_zero(uint x) {
    return x << 2 != 0;
}

当您更了解代码的实际含义时,替换函数名称或使用注释。当您知道它们的作用时uVar1,还要替换漂亮的匿名名称等。uVar2

在此之后,我建议尝试重命名此函数:

void describing_name(uint *uVar3p, uint *uVar4p, uint param_1)
         // These are declared so that you can just copy paste the code
         uint uVar3 = *uVar3p;
         uint uVar4 = *uVar4p;

         // Copy paste with no modifications
         while ((uVar3 < param_1 && 
                 fourteen_least_significant_bits_are_not_zero(uVar3)) {
                 uVar4 = uVar4 + 1;
                 uVar3 = uVar3 * 4;
         }

         // And write back the values
         *uVar3p = uVar3;
         *uVar4p = uVar4;
}

将 while 循环替换为:

describing_name(&uVar3, &uVar4, param_1);

重构代码通常是理解它的最佳方式。请记住,重构时测试至关重要。

于 2020-05-18T06:53:42.677 回答