12. 리비전 이분검색?
- 버그 없는 리비전과 버그 있는 리비전을 알면
- 가운데 리비전을 받아서 버그가 재현되는지 본다
- 재현되면 앞쪽 절반, 재현안되면 뒤쪽 절반을 반복
두 명이 동시에 재현하면 삼분검색 가능!
13. 그러나…
재현조건이 애매했다
• 보통 20분 정도면 재현되었지만
• 40분 동안 재현이 안 되어도 버그가 없다는 뜻은 아니다
마감 직전이면 인력을 동원해서 해결하겠지만…
무엇보다, 지루한 작업
14. 가설: 다른 스레드가 덮어썼나?
해당 객체를 건드리는 모든 코드를 리뷰함:
값을 덮어쓰는 곳이 없었다. 멀티스레드 문제는 아니다.
디스어셈블리를 보자
15. 나 어셈 볼 줄 모르는데…
인텔 어셈블리에 대해 알고 있던 것:
• 함수 호출할 때 인자를 스택에 푸시한다 (역순)
• 함수 호출할 때 레지스터를 스택에 푸시해서 보관한다
• 가상함수 호출 메커니즘
• mov는 복사, call은 함수호출 자세한 건 몰랐음
• 데이터 브레이크포인트라는 것이 있고, 코드에서 제어할 수 있다
17. 디스어셈블리를 보자!
이게 다 무슨 뜻인가? eax는 정체가 뭔가?
← 여기에서, eax+0x0c를 읽다가 죽었다.
18. 잠깐 가상함수 호출 절차
실제 멤버변수들
VTable 시작 주소
class X의 객체 가상함수 A 주소
가상함수 B 주소
가상함수 C 주소
가상함수 D 주소
가상함수 E 주소
가상함수 F 주소
class X의 VTable
Visual C++에서의
Machine code
X::가상함수 C 코드
19. 디스어셈블리를 보자
edi에 sim이 저장되어 있다고 가정하는 코드다.
가상함수 테이블을 조회하다가 죽었구나!
↖vtbl 주소를 eax에 저장
← GameContext 함수 주소를 얻어서
←호출
20. 디버거 Watch에 (GameSimulation*)edi 를 입력해보니
실로 멀쩡한 객체를 가리키는 값이 아니었다
edi는 함수 맨 앞에서 설정했고(mov edi,dwordptr[ebp+8])
이후에 edi를 덮어쓰는 곳이 없다
그런데 edi가 이상한 값이 되어 있다…?
디스어셈블리를 보자
21. 구글 검색 <edi push 책임> 해봄
호출되는 쪽에서 edi를 쓰기 전에 저장할 책임이 있다고 한다
edi 저장 책임은 누가 지나?
22. 누가? 어떻게?
레지스터를 깨먹는 함수라니 듣도 보도 못했다
이 함수에는 58건의 call이 있다. 누굴까?
일일이 들여다보는 건 시간 낭비라고 판단
탐침을 박아보자
누군가가 edi를 깨먹고 있다
23. 모든 함수호출 전에 edi를 저장하고
모든 함수호출 후의 edi와 비교해서
달라지면 브레이크포인트
탐침을 박아보자
24. 여러가지 방법을 시도해봄
뭘 추가하면 레지스터 배치가 달라져서
탐침이 동작 않는 경우가 부지기수
“변수 선언 순서 바꾸니까 되는데요”가 바로 이런 경우
탐침을 박고 빌드한 뒤 어셈블리를 봐서
의도한 대로 동작하는지 확인해봐야 함
탐침을 박아보자
33. 이 경우 스택의 변조를 감지해야 한다
VS에서 수동으로 매번 잡았다 풀었다 할 순 없다;;
코드에서 데이터 브레이크포인트 설정/해제가 가능
참고:http://dual5651.hacktizen.com/tt/entry/BreakBreak-BreakPoint
없어진문서이지만http://web.archive.org/를통해접근가능
데이터 브레이크포인트?
34. edi가 push된 곳을 어떻게 알지?
구글: intel assembly stack top register → esp
• edi를push한직후에
• 디버그Watch로*(int*)esp를찍어보니…
• edi내용이똑같이저장되어있다
• 됐다!
메모리 변경을 감지하자