최적화 켜면 디컴파일 결과가 원본이랑 많이 달라진다.


곱셈 → 시프트

// 원본
x = y * 8;

// 최적화 후
x = y << 3;

2의 거듭제곱 곱셈은 시프트로 바뀐다.


나눗셈 → 곱셈 + 시프트

// 원본
x = y / 10;

// 최적화 후 (대략)
x = (y * 0xCCCCCCCD) >> 35;

나눗셈은 느려서 매직 넘버 곱셈으로 변환. 디컴파일러가 복원 못하면 이상한 상수가 보인다.


루프 언롤링

// 원본
for (int i = 0; i < 4; i++) {
    buf[i] = 0;
}

// 최적화 후
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;

짧은 루프는 펼쳐버린다.


꼬리 재귀

// 원본
void func(int n) {
    if (n == 0) return;
    do_something();
    func(n - 1);  // 재귀 호출
}

// 최적화 후
void func(int n) {
    while (n != 0) {
        do_something();
        n--;
    }
}

꼬리 재귀는 루프로 변환된다.


분기 제거

// 원본
if (flag) {
    x = 1;
} else {
    x = 0;
}

// 최적화 후
x = flag != 0;

조건문이 사라지고 비교 결과를 직접 대입.


실전 예시

디컴파일 결과:

result = (value * 0x51EB851F) >> 37;

뭔 소리야? 이건 /100이다.

// 원본은 아마
result = value / 100;

대응법

  • 이상한 상수 보이면 나눗셈 의심
  • 시프트 보이면 2의 거듭제곱 곱셈/나눗셈
  • 반복 코드 보이면 언롤링된 루프

Ghidra가 복원 못하면 직접 계산해봐야 한다.


Part 6 끝. 다음은 소스코드 복원.

#27 - main() 복원