ݺߣ

ݺߣShare a Scribd company logo
사례로 배우는 디스어셈블리 디버깅
넥슨코리아 데브캣스튜디오 DD1실
이승재
발표자
이승재:프로그래머
카바티나 스토리 2007~2009
데스크탑 히어로즈 2010~2011.9
마비노기2 2011.10~2013
미공개 프로젝트 2014~
이 발표는
마비노기2 개발중에 실제로 발생했던
버그의 원인을 추적하는 과정을
재구성했습니다.
주제
디스어셈블리 디버깅
• 포인터가있는언어로개발하는한피할수없다
• 아무나할수없고,누구도하기싫하일
진짜 주제
어렵지 않다
멘붕하지 말자
2013년 9월
상황
여기서 크래시 0x00000010 읽다가 사망
재현조건: 던전을 플레이한다 (;;;)
상황
0x00000010을 왜 읽지????
디버거로 sim을 보면 멀쩡한 값이 들어있다
상황
언제부터 이랬지?
하루? 이틀? 모르겠다
누가 뭘 바꿨지?
프로그래머 20명 이상…
NDC14 - 사례로 배우는 디스어셈블리 디버깅
재현조건이 명확하다면…
리비전 이분검색을 쓸 수 있다
리비전 이분검색?
- 버그 없는 리비전과 버그 있는 리비전을 알면
- 가운데 리비전을 받아서 버그가 재현되는지 본다
- 재현되면 앞쪽 절반, 재현안되면 뒤쪽 절반을 반복
두 명이 동시에 재현하면 삼분검색 가능!
그러나…
재현조건이 애매했다
• 보통 20분 정도면 재현되었지만
• 40분 동안 재현이 안 되어도 버그가 없다는 뜻은 아니다
마감 직전이면 인력을 동원해서 해결하겠지만…
무엇보다, 지루한 작업
가설: 다른 스레드가 덮어썼나?
해당 객체를 건드리는 모든 코드를 리뷰함:
값을 덮어쓰는 곳이 없었다. 멀티스레드 문제는 아니다.
디스어셈블리를 보자
나 어셈 볼 줄 모르는데…
인텔 어셈블리에 대해 알고 있던 것:
• 함수 호출할 때 인자를 스택에 푸시한다 (역순)
• 함수 호출할 때 레지스터를 스택에 푸시해서 보관한다
• 가상함수 호출 메커니즘
• mov는 복사, call은 함수호출 자세한 건 몰랐음
• 데이터 브레이크포인트라는 것이 있고, 코드에서 제어할 수 있다
마음가짐
재현만 되면 못 잡을 버그가 없다
어셈블리 그까짓거
어셈블리 그까짓거!!
디스어셈블리를 보자!
이게 다 무슨 뜻인가? eax는 정체가 뭔가?
← 여기에서, eax+0x0c를 읽다가 죽었다.
잠깐 가상함수 호출 절차
실제 멤버변수들
VTable 시작 주소
class X의 객체 가상함수 A 주소
가상함수 B 주소
가상함수 C 주소
가상함수 D 주소
가상함수 E 주소
가상함수 F 주소
class X의 VTable
Visual C++에서의
Machine code
X::가상함수 C 코드
디스어셈블리를 보자
edi에 sim이 저장되어 있다고 가정하는 코드다.
가상함수 테이블을 조회하다가 죽었구나!
↖vtbl 주소를 eax에 저장
← GameContext 함수 주소를 얻어서
←호출
디버거 Watch에 (GameSimulation*)edi 를 입력해보니
실로 멀쩡한 객체를 가리키는 값이 아니었다
edi는 함수 맨 앞에서 설정했고(mov edi,dwordptr[ebp+8])
이후에 edi를 덮어쓰는 곳이 없다
그런데 edi가 이상한 값이 되어 있다…?
디스어셈블리를 보자
구글 검색 <edi push 책임> 해봄
호출되는 쪽에서 edi를 쓰기 전에 저장할 책임이 있다고 한다
edi 저장 책임은 누가 지나?
누가? 어떻게?
레지스터를 깨먹는 함수라니 듣도 보도 못했다
이 함수에는 58건의 call이 있다. 누굴까?
일일이 들여다보는 건 시간 낭비라고 판단
탐침을 박아보자
누군가가 edi를 깨먹고 있다
모든 함수호출 전에 edi를 저장하고
모든 함수호출 후의 edi와 비교해서
달라지면 브레이크포인트
탐침을 박아보자
여러가지 방법을 시도해봄
뭘 추가하면 레지스터 배치가 달라져서
탐침이 동작 않는 경우가 부지기수
“변수 선언 순서 바꾸니까 되는데요”가 바로 이런 경우
탐침을 박고 빌드한 뒤 어셈블리를 봐서
의도한 대로 동작하는지 확인해봐야 함
탐침을 박아보자
재현 ㄱㄱ
걸렸다!
← 여기에 걸렸다
↑이 코드를 수행하던 도중에 edi가 깨진 것이다
딱히 수상한 점이 없다
한 단계 더 들어가 보자
RegionService::PickingRegion
PickingFloor
여기 어딘가 범인이 있다
디스어셈블리를 보자
edi를스택에보관하고리턴전에복구하네!
보관해놓은동안누군가스택을긁었겠다!
레지스터긁는걸찾아내는문제가
스택긁는걸찾아내는문제로바뀌었다
PickingFloor 함수첫머리
함수끝부분
가설: edi가 저장된 위치 주변의 지역변수에
버퍼 오버런한 거 아닐까?
검증: 코드리뷰해 봤는데 딱히 그런 건 안 보임
새로운 가설
스택에 보관해둔 레지스터를 복구하기 전에
그 스택 영역을 변경하는 일은 일어나선 안 된다
데이터 브레이크포인트가 출동!
다시 탐침
데이터 브레이크포인트?
이 경우 스택의 변조를 감지해야 한다
VS에서 수동으로 매번 잡았다 풀었다 할 순 없다;;
코드에서 데이터 브레이크포인트 설정/해제가 가능
참고:http://dual5651.hacktizen.com/tt/entry/BreakBreak-BreakPoint
없어진문서이지만http://web.archive.org/를통해접근가능
데이터 브레이크포인트?
edi가 push된 곳을 어떻게 알지?
구글: intel assembly stack top register → esp
• edi를push한직후에
• 디버그Watch로*(int*)esp를찍어보니…
• edi내용이똑같이저장되어있다
• 됐다!
메모리 변경을 감지하자
NDC14 - 사례로 배우는 디스어셈블리 디버깅
재현 ㄱㄱ
근데 왜??
걸렸다!
ILocationComponent* logComp에
실제로는 다른 타입의 객체가 들어있다?
새로운 가설
locComp의GetHandle()을호출하려 했다
가상함수테이블의첫번째항목인듯(오프셋0)
locComp가실제론PhysXCollisionActor를가리키고있었다면?
Phys…Actor에는가상함수가딱하나있다:소멸자
그렇다고 치면
ILocationComponent의GetHandle()을부르려는동작이
PhysXCollisionActor의소멸자를불렀다
그과정에서운좋게(?)그자리에서크래시하지않고스택을망쳐놓기만했고
함수를빠져나간이후에스택으로부터복구한레지스터에
쓰레기값이들어있어서크래시한것
문제 발생 경로
수사 종료
구체적으로 어떤 과정을 거쳐서 edi가 저장된 위치를 망가뜨린 건지는
더 파고들어 보면 알 수 있겠지만
이 정도에서 충분히 원인을 파악했다고 보고 디버깅을 마무리.
크래시 → 멘탈 붕괴 → 디스어셈블리를 보자 →
가상함수 호출하려다 죽었다 → 레지스터 edi가 깨져있네? →
다른 함수가 스택에 저장했던 걸 또다른 누군가 긁은 거다 →
데이터 브레이크포인트로 긁는 순간을 잡아내자 →
엉뚱한 객체의 함수를 호출했다 → 고기! 고기! 고기!
요약
• void* 는 위험하다.
• 어셈블리는 마법이 아니다.
• 디버깅은 가설 수립과 검증의 연속 가설이없을때는시간낭비하기쉬움
• 적절한 도구를 사용하면 도움이 됨 인라인어셈블리,데이터브레이크포인트
정리
마지막으로
디버깅 멘탈을 키워주는 교재
NDC14 - 사례로 배우는 디스어셈블리 디버깅
• 모든 디버깅이 사후postmortem디버깅
• 단서는 비행기록과 잔해 조각 뿐
• 재현해볼 수 없다
항공사고수사대: 항공 사고 디버깅
• 기껏해야 MRI, CT, 혈액검사, 요추천자, 조직검사 정도
• 시시각각 상황이 변화하고
• 환자가 죽기 전에 고쳐야 한다
HOUSE M.D.: 인간 디버깅
항공 사고, 인간 디버깅에 비하면
소프트웨어 디버깅 따위 쉽다
끝

More Related Content

NDC14 - 사례로 배우는 디스어셈블리 디버깅