Мне нужна помощь в выяснении того, что делает этот код C
У меня есть этот фрагмент кода C от Ghidra, и я не могу понять, что он делает. Подозреваю какой-то рут, может?
Два переданных аргумента представляют собой сумму квадратов (иногда 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;
}
1 ответ
Достаточно хороший способ понять код - это его рефакторинг.
Сначала создайте тестовую функцию и пару тестовых случаев. Затем вы переписываете функцию. Это может выглядеть так. Это очень упрощенно, и для более масштабного рефакторинга я бы сделал его немного сложнее.
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);
на самом деле делает это путем поиска в Google и тестирования различных значений. Когда вы понимаете, что он делает, вы создаете функцию.
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_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);
Рефакторинг кода часто является лучшим способом понять его. И помните, что тестирование жизненно важно при рефакторинге.