我需要帮助弄清楚这个 C 代码的作用



我从Ghidra那里得到了这段C代码,我不太清楚它在做什么。我怀疑是某种根源,也许?

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

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;
}

理解代码的一个很好的方法是重构它。

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

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 failedn", 
i, test_cases[i][0], test_cases[i][1]);
exit(EXIT_FAILURE);
}
}
printf("All tests passedn");
}

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

之后,您可以开始重构过程。首先将整个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 << yx * pow(2,y)相同。请注意,pow具有签名double pow(double, double)因此可以强制转换或编写自己的整数变体。

然后以迭代方式完成代码,每次都运行测试。如果代码的某些部分特别棘手,您可以或当然创建一个单独的测试,其中包含该函数的适当测试用例。

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

例如,我不知道您的系统上uint有多少位,但是如果您设置了除 15 个最不重要的位之外的所有位,x & 0x1f将返回您获得的数字。这里可能出现大端序或小端序很重要的情况。我不这么认为,但我不确定。如果我是对的,那么x & 0x1fx % 32模运算相同。这也是一种常见的优化。位移比乘法和模快得多。因此,我们可以将函数describing_name重命名为modulo32

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;
}

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

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

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);

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

相关内容

最新更新