Мне нужна помощь в выяснении того, что делает этот код 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);

Рефакторинг кода часто является лучшим способом понять его. И помните, что тестирование жизненно важно при рефакторинге.

Другие вопросы по тегам