我从Ghidra获得了这段C代码,但我不太清楚它在做什么。我怀疑是某种原因吗?
传入的两个arg是平方和(有时是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;
}
答案 0 :(得分: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");
}
由于您已经知道了这些参数的条件,因此请考虑编写一个片段来为您创建测试用例。创建许多测试用例,但要注意溢出的风险。
之后,您可以开始重构的过程。首先将整个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
,但是如果设置除15位最低有效位以外的所有位,x & 0x1f
将返回您得到的数字。大小字节序可能在这里很重要。我不这么认为,但是我不确定。如果我是正确的,则x & 0x1f
与x % 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;
}
当您进一步了解代码的实际含义时,请替换函数名称或使用注释。当您知道它们的作用时,也请替换漂亮的匿名名称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);
重构代码通常是理解它的最好方法。记住,重构时测试至关重要。