포스트

주소에 의한 전달과 반환 시의 런타임 스택 영역 시뮬레이션

Flash로 만든 스택 영역 시뮬레이션

주소에 의한 전달과 반환 시의 런타임 스택 영역 시뮬레이션

시작하며…

위의 플래시는 주소에 의한 전달과 반환시의의 모형이다.

흔히 말하는 포인터를 매개변수로 넘기고 받는 경우라고 할 수 있겠다.

하지만 필자가 플래시로 만든 위의 시뮬레이션을 보고나면 어떤 의구심이 생겨야 정상이다.

그렇다.

fSub() 함수내의 지역 변수인 nLocalVar의 주소를 리턴하고 있는 것이 함정이다!

생각해 보라.

ret 명령어에 의해 복귀 주소를 esi로 팝하는 순간 스택 프레임은 클린업 된다.

즉 fSub() 함수의 스택 프레임 공간이 사라졌으므로 그 안에 있던 nLocalVar의 값도 같이 사라지게 되는 것이다.

엄밀하게 말하면 mov esp, ebp에 의해 스택 포인터를 베이스 포인터가 가리키고 있는 곳으로 옮기며 클린업 효과를 얻는 것이기 때문에 실제 데이터는 그대로 남아 있는 상태다.

이는 위의 코드에서 nLocalVar의 주소를 eax를 통해 리턴 받은 뒤에 [eax]에 의해 eax가 가리키고 있는 nLocalVar의 값 64h(10진수 100)이 nResult로 무사히 값이 전달되는 것을 보면 알 수 있다.

하지만 분명히해야 할것은 이는 위와 같은 경우에만 가능하다는 것이다.

다음의 사례를 통해 위의 코드가 어떻게 잘못된 결과를 불러올 수 있는지 살펴보도록 하자.

“OtherFunc() 함수는 32비트 int형 정수를 리턴 한다.”

1
nResult = func(&nVar) + OtherFunc();

위의 디스어셈블리 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
00CC35BB  lea         eax,[nVar]
00CC35BE  push        eax
00CC35BF  call        func (0CC1005h)
00CC35C4  add         esp,4
00CC35C7  mov         esi,eax
00CC35C9  call        OtherFunc (0CC11D6h)
00CC35CE  add         eax,dword ptr [esi]
00CC35D0  mov         ecx,dword ptr [nResult]
00CC35D3  mov         dword ptr [ecx],eax

여기서, 00CC35BF call func (0CC1005h) 을 통해 func() 함수로 분기한다. 스택 프레임을 할당 받는다는 것이다.

그리고 func()를 빠져나오며 스택프레임을 반환하고 00CC35C7 mov esi,eax 를 통해 eax로 받은 nLocalVar의 주소를 esi로 넘긴다.

그리고 이어서,
00CC35C9 call OtherFunc (0CC11D6h) 를 통해 OtherFunc() 함수로 분기하며 역시 스택 프레임을 할당 받는다.

이때, OtherFunc()의 스택프레임은 조금 전의 func()가 사용했던 공간과 같은 공간에 스택프레임을 할당 받게 된다.

이는 func의 함수가 사용했던 스택프레임 공간에 OtherFunc()의 스택프레임 공간이 덮어씌어진 꼴이다.

여하튼 OtherFunc()를 무사히 빠져나와

1
00CC35CE  add         eax,dword ptr [esi] 

OtherFunc()의 반환 값을 가지고 있는 eax와 funcI()의 지역변수인 nLocalVar의 주소를 가지고 있는 esi를 더하게 되는데

여기서 [esi]에 의해 접근하는 공간은 이미 OtherFunc()에서 스택프레임을 할당 받으면서 사라진 공간이 되었으므로 [esi]로 접근한다 한들 이미 예전의 데이터가 없는 상황에 직면하게 되는 것이다.

반면 아래 코드와 같이 함수 호출의 순서를 바꿀 경우에는 또 원하는 결과를 얻을 수 있게 된다.

1
nResult = OtherFunc() + func(&nVar);

다음은 위 명령어줄의 디스어셈블리 코드다.

1
2
3
4
5
6
7
8
9
009B35BB  call        OtherFunc (9B11D6h)  
009B35C0  mov         esi,eax  
009B35C2  lea         eax,[nVar]
009B35C5  push        eax
009B35C6  call        func (9B1005h)  
009B35CB  add         esp,4
009B35CE  add         esi,dword ptr [eax]  
009B35D0  mov         ecx,dword ptr [nResult]
009B35D3  mov         dword ptr [ecx],esi

지역 변수의 주소를 리턴 하는 func()가 나중에 호출되고 뒤에 호출 되는 함수가 존재하지 않기 때문에 이는 당연히 가능한 것이다.

다만 OtherFunc()가 정수 값을 리턴하는 것이 아니라 func()와 같이 지역 변수의 주소를 리턴하는 경우라면 두 함수의 호출 순서를 아무리 뒤바꾼다 한들 원하는 결과를 얻을 수 없다.

이전의 스택 프레임 내의 지역변수의 주소 반환은 스택 프레임이 클린업 되는 순간 무용지물이 되어 버린다.

이는 위의 경우들과 같이 이어서 호출되는 다른 함수가 스택 프레임 공간을 할당 받는 순간 이전에 사용했던 스택 프레임 공간이 현재 호출되는 함수에 의해 초기화 작업을 수행해 버리기 때문임을 잊지 말자는 것이다.

혹여 다행스럽게도 func()의 지역 변수인 nLocalVar의 위치가 OtherFunc()의 스택프레임 범위내, 즉 초기화 작업 범위 밖에 존재한다 할지라도 OtherFunc() 내의 추가 작업 등에 의해 스택 프레임 공간이 추가 할당 되어지거나 한다면 이 마저도 무용지물이 되어버릴 것이다.

짧게 요약하자면..

스택프레임이 클린업 되어 해제되는 순간 스택프레임에 있던 데이터는 무용지물이 되어 신뢰성을 보장받을 수 없게 된다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.