Game Dev/Article

Float 상수의 컴파일-타임 최적화

AKer 2011. 3. 31. 00:00
반응형
도저히 메모리를 변조할 수가 없는 상황에서 해킹이 발생하였다. 예를들어 이런 코드다. 
 
float foo(float input)
{
	if (input > 0)
		return input * 10.0f;
	else
		return input * -20.0f;
}

void main()
{
	float f = 0;
	scanf("%f", &f); // 사용자는 항상 올바른 입력을 한다고 가정

	float g = foo(f);
	printf("%f", g);
}

예제이므로 코드 내용에는 큰 의미를 부여할 필요는 없음

foo 함수의 input이 매번 새로 입력되고 새로 계산되므로 리턴 값은 사용자가 입력한 값의 10배 혹은 -20배가 되어야 한다. 하지만 10이나 -20 이외의 수가 곱해져 나왔다면? 이건 무언가 잘못된 것이다. 왜 그럴까?

원인은 부동소수점 상수를 컴파일러가 최적화를 한 것 때문이었다. 

알다시피 우리가 하이레벨 언어로 짠 코드는 컴파일시 기계어로 바뀌어지고, 모든 변수나 상수는 실행시 레지스터나 메모리에 적재되어야 한다. 즉 위 상수 10.0이나 -20.0도 곱하기 연산을 실행하기 전 어딘가에 적재될 것이다. 하지만 컴파일러는 해당 상수가 레지스터에 적재되는 시간도 아깝다고 생각하고 미리 RDATA라는 Section에 상수 값을 저장해 놓는다. 따라서 위 코드는 다음과 비슷하게 최적화 된다. (초록색 : 원래 코드, 검은색 : ASM)

if (input > 0)
fld         dword ptr [input]  
fcomp   qword ptr [__real@0000000000000000 (845760h)]  
fnstsw  ax  
test       ah,41h  
jne        foo+47h (841407h)  

return input * 10.0f;
fld         dword ptr [input]  
fmul      qword ptr [__real@4024000000000000 (845750h)]  
fstp       dword ptr [ebp-0C4h]  
fld         dword ptr [ebp-0C4h]  
jmp       foo+5Ch (84141Ch)  

else
jmp       foo+5Ch (84141Ch)  

return input * -20.0f;
fld         dword ptr [input]  
fmul      qword ptr [__real@c034000000000000 (845740h)]  
fstp       dword ptr [ebp-0C4h]  
fld         dword ptr [ebp-0C4h]  


비교에 들어가는 0부터, 곱해지는 10과 -20에 대해서 모두 최적화가 진행된 것을 알 수 있다. 위에 빨간색으로 표시된 곳의 메모리를 본다면 각각 대응하는 수들이 저장되어 있다. 해커들은 이 주소의 값을 바꿔놓은 것이다. 재밌는 것은 float이 아닌 int 연산일 경우 최적화가 일어나지 않는다. ^^

따라서 이를 우회하기 위해서는 컴파일러가 해당 상수를 최적화하지 않도록 하는 것이 중요한데 현재 생각한 바로는 const float 변수에 상수를 저장하여 계산하는 것이나, 함수를 이용한 우회, volatile을 사용한 최적화 방지 등이 있다. 몇가지는 테스트 해보았는데 "Maximize Speed"에서만 최적화가 되지 않고, "Minimize Size" 컴파일 옵션에서는 꿋꿋하게 최적화가 발생하였다. 최소 크기 옵션에서는 이를 어떻게 해결할지 고민해 봐야 할 문제인 것 같다. 




  
반응형