최적화 켜면 디컴파일 결과가 원본이랑 많이 달라진다.
곱셈 → 시프트
// 원본
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 끝. 다음은 소스코드 복원.