개발/Developing

아주 이상한 std::thread의 stack smashing

lazykuna 2021. 4. 10. 01:30

요 며칠, 개인적으로 작업하던 프로젝트에 아주 난감한 문제가 발생했다. 바로 특정 상황에서 스레드 정리할 때 쓰레드의 EIP가 말도 안 되는 곳으로 가서 죽는 경우.

40800000 여기는 대체 어떻게 간 거지...

이런 경우는 처음 보는 경우라서 살짝 뇌정지가 왔다. 이걸 어떻게 해결한다...

보통 디버깅 모드에서 스택 스매싱[각주:1]이 일어나면, 스택을 빠져나가거나 하는 그 즉시 값이 오염되었는지를 확인하여 ASSERT를 띄운다. 그런데 이번 경우는 어째서인지 그런 증상도 전혀 일어나지 않아서 ... 스택 스매싱이 아닌가? 의심도 해 보았다.

 

문제 원인을 파악하는 것 부터가 난관이었는데, 어찌어찌 보다 보니 굉장히 이상한 특징을 찾았다.

쓰레드 정리 직전에 콜스택이 굉장히 이상하게 찍히는데, 원래 일반적인 Win32에서의 std::thread는 아래처럼 thread_start 함수에서부터 시작된다. [각주:2]

그런데 이렇게 멀쩡하던 콜스택이 쓰레드 종료 직전 상황에 오니까 ...

...?

콜스택이 증발했다. 저기서 메인 쓰레드 한두줄만 더 넘기면 바로 해괴한 EIP로 가 버린다. 아마 thread_start 부근에서 스택 스매싱이 일어나면 탐지가 안 되는 모양이다.

감이 오는 부분이 있어서 프로그램 실행 영역을 좁혀 보니, 원인을 금방 찾을 수 있었다. double pointer으로 인자를 넘겼는데 이걸 일반 pointer으로 받아 버려서 문제가 발생했던 것이었다.

 

여하튼, 이번 삽질은 스택 스매싱은 디버그 모드에서 잡기 쉽다는 내 신념을 깨뜨려 주었고, 믿을 만한 건 오로지 내 자신 뿐이라는 걸 다시금 깨닫는 좋은 계기가 되었다 ...

 

이런 별거 아닌 멀티스레딩 프로그램 하나 짜는 데도 이렇게 삽질하는 거 보면 아직 멀었구만.


추가 - 스택 스매싱 탐지는 디버깅 컴파일 시 메모리 가드 영역이 있어서, 스택을 빠져나갈 때 거기에 있는 값이 손상될 경우 런타임 오류를 띄우는 방식이다. 그런데 손상된 부분이 외부 코드 영역이라면, 메모리 가드 영역이 없는 부분일테니 탐지가 안 될 수 있을 것 같다.

  1. 콜스택이 쌓일 때마다 담는 EBP, ESP 등의 정보를 담아 둔 메모리 값 [본문으로]
  2. 물론 시스템 환경 등에 따라 이 마저도 천차만별일 수 있다 [본문으로]