48
사례로 배우는 디스어셈블리 디버깅 넥슨코리아 데브캣스튜디오 DD1실 이승재

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

Embed Size (px)

DESCRIPTION

 

Citation preview

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

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

넥슨코리아 데브캣스튜디오 DD1실

이승재

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

발표자

이승재 : 프로그래머

카바티나 스토리 2007~2009

데스크탑 히어로즈 2010~2011.9

마비노기2 2011.10~2013

미공개 프로젝트 2014~

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

이 발표는

마비노기2 개발중에 실제로 발생했던 버그의 원인을 추적하는 과정을 재구성했습니다.

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

주제

디스어셈블리 디버깅

• 포인터가 있는 언어로 개발하는 한 피할 수 없다

• 아무나 할 수 없고, 누구도 하기 싫어하는 일

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

진짜 주제

어렵지 않다

멘붕하지 말자

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

2013년 9월

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

상황

여기서 크래시 0x00000010 읽다가 사망

재현조건: 던전을 플레이한다 (;;;)

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

상황

0x00000010을 왜 읽지???? 디버거로 sim을 보면 멀쩡한 값이 들어있다

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

상황

언제부터 이랬지? 하루? 이틀? 모르겠다 누가 뭘 바꿨지? 프로그래머 20명 이상…

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

재현조건이 명확하다면…

리비전 이분검색을 쓸 수 있다

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

리비전 이분검색?

- 버그 없는 리비전과 버그 있는 리비전을 알면 - 가운데 리비전을 받아서 버그가 재현되는지 본다 - 재현되면 앞쪽 절반, 재현안되면 뒤쪽 절반을 반복 두 명이 동시에 재현하면 삼분검색 가능!

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

그러나…

재현조건이 애매했다 • 보통 20분 정도면 재현되었지만 • 40분 동안 재현이 안 되어도 버그가 없다는 뜻은 아니다 마감 직전이면 인력을 동원해서 해결하겠지만… 무엇보다, 지루한 작업

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

가설: 다른 스레드가 덮어썼나?

해당 객체를 건드리는 모든 코드를 리뷰함: 값을 덮어쓰는 곳이 없었다. 멀티스레드 문제는 아니다. 디스어셈블리를 보자

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

나 어셈 볼 줄 모르는데…

인텔 어셈블리에 대해 알고 있던 것: • 함수 호출할 때 인자를 스택에 푸시한다 (역순) • 함수 호출할 때 레지스터를 스택에 푸시해서 보관한다 • 가상함수 호출 메커니즘 • mov는 복사, call은 함수호출 자세한 건 몰랐음

• 데이터 브레이크포인트라는 것이 있고, 코드에서 제어할 수 있다

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

마음가짐

재현만 되면 못 잡을 버그가 없다 어셈블리 그까짓거

어셈블리 그까짓거!!

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

디스어셈블리를 보자!

이게 다 무슨 뜻인가? eax는 정체가 뭔가?

← 여기에서, eax+0x0c를 읽다가 죽었다.

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

잠깐 가상함수 호출 절차

실제 멤버변수들

VTable 시작 주소

class X의 객체 가상함수 A 주소

가상함수 B 주소

가상함수 C 주소

가상함수 D 주소

가상함수 E 주소

가상함수 F 주소

class X의 VTable

Visual C++에서의

Machine code

X::가상함수 C 코드

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

디스어셈블리를 보자

edi에 sim이 저장되어 있다고 가정하는 코드다. 가상함수 테이블을 조회하다가 죽었구나!

↖vtbl 주소를 eax에 저장

← GameContext 함수 주소를 얻어서

←호출

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

디버거 Watch에 (GameSimulation*)edi 를 입력해보니 실로 멀쩡한 객체를 가리키는 값이 아니었다

edi는 함수 맨 앞에서 설정했고 (mov edi,dword ptr [ebp+8])

이후에 edi를 덮어쓰는 곳이 없다

그런데 edi가 이상한 값이 되어 있다…?

디스어셈블리를 보자

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

구글 검색 <edi push 책임> 해봄

호출되는 쪽에서 edi를 쓰기 전에 저장할 책임이 있다고 한다

edi 저장 책임은 누가 지나?

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

누가? 어떻게?

레지스터를 깨먹는 함수라니 듣도 보도 못했다

이 함수에는 58건의 call이 있다. 누굴까?

일일이 들여다보는 건 시간 낭비라고 판단

탐침을 박아보자

누군가가 edi를 깨먹고 있다

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

모든 함수호출 전에 edi를 저장하고

모든 함수호출 후의 edi와 비교해서

달라지면 브레이크포인트

탐침을 박아보자

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

여러가지 방법을 시도해봄

뭘 추가하면 레지스터 배치가 달라져서 탐침이 동작 않는 경우가 부지기수

“변수 선언 순서 바꾸니까 되는데요”가 바로 이런 경우

탐침을 박고 빌드한 뒤 어셈블리를 봐서 의도한 대로 동작하는지 확인해봐야 함

탐침을 박아보자

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

재현 ㄱㄱ

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

걸렸다!

← 여기에 걸렸다

↑이 코드를 수행하던 도중에 edi가 깨진 것이다

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

딱히 수상한 점이 없다

한 단계 더 들어가 보자

RegionService::PickingRegion

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

PickingFloor

여기 어딘가 범인이 있다

디스어셈블리를 보자

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

edi를 스택에 보관하고 리턴 전에 복구하네!

보관해놓은 동안 누군가 스택을 긁었겠다!

레지스터 긁는 걸 찾아내는 문제가

스택 긁는 걸 찾아내는 문제로 바뀌었다

PickingFloor 함수 첫머리

함수 끝부분

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

가설: edi가 저장된 위치 주변의 지역변수에 버퍼 오버런한 거 아닐까?

검증: 코드리뷰해 봤는데 딱히 그런 건 안 보임

새로운 가설

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

스택에 보관해둔 레지스터를 복구하기 전에 그 스택 영역을 변경하는 일은 일어나선 안 된다

데이터 브레이크포인트가 출동!

다시 탐침

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

데이터 브레이크포인트?

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

이 경우 스택의 변조를 감지해야 한다

VS에서 수동으로 매번 잡았다 풀었다 할 순 없다;;

코드에서 데이터 브레이크포인트 설정/해제가 가능

참고: http://dual5651.hacktizen.com/tt/entry/BreakBreak-BreakPoint

없어진 문서이지만 http://web.archive.org/ 를 통해 접근 가능

데이터 브레이크포인트?

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

edi가 push된 곳을 어떻게 알지?

구글: intel assembly stack top register → esp • edi를 push한 직후에

• 디버그 Watch로 *(int*)esp 를 찍어보니…

• edi 내용이 똑같이 저장되어 있다

• 됐다!

메모리 변경을 감지하자

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

재현 ㄱㄱ

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

근데 왜??

걸렸다!

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

ILocationComponent* logComp에

실제로는 다른 타입의 객체가 들어있다?

새로운 가설

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

locComp의 GetHandle()을 호출하려 했다

가상함수테이블의 첫번째 항목인 듯 (오프셋 0)

locComp가 실제론 PhysXCollisionActor를 가리키고 있었다면?

Phys…Actor에는 가상함수가 딱 하나 있다: 소멸자

그렇다고 치면

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

ILocationComponent의 GetHandle()을 부르려는 동작이 PhysXCollisionActor의 소멸자를 불렀다

그 과정에서 운좋게(?) 그 자리에서 크래시하지 않고 스택을 망쳐놓기만 했고

함수를 빠져나간 이후에 스택으로부터 복구한 레지스터에 쓰레기 값이 들어있어서 크래시한 것

문제 발생 경로

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

수사 종료 구체적으로 어떤 과정을 거쳐서 edi가 저장된 위치를 망가뜨린 건지는 더 파고들어 보면 알 수 있겠지만 이 정도에서 충분히 원인을 파악했다고 보고 디버깅을 마무리.

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

크래시 → 멘탈 붕괴 → 디스어셈블리를 보자 →

가상함수 호출하려다 죽었다 → 레지스터 edi가 깨져있네? →

다른 함수가 스택에 저장했던 걸 또다른 누군가 긁은 거다 →

데이터 브레이크포인트로 긁는 순간을 잡아내자 →

엉뚱한 객체의 함수를 호출했다 → 고기! 고기! 고기!

요약

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

• void* 는 위험하다.

• 어셈블리는 마법이 아니다.

• 디버깅은 가설 수립과 검증의 연속 가설이 없을 때는 시간낭비하기 쉬움

• 적절한 도구를 사용하면 도움이 됨 인라인 어셈블리, 데이터 브레이크포인트

정리

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

마지막으로

디버깅 멘탈을 키워주는 교재

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

• 모든 디버깅이 사후postmortem디버깅

• 단서는 비행기록과 잔해 조각 뿐

• 재현해볼 수 없다

항공사고수사대: 항공 사고 디버깅

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

• 기껏해야 MRI, CT, 혈액검사, 요추천자, 조직검사 정도

• 시시각각 상황이 변화하고

• 환자가 죽기 전에 고쳐야 한다

HOUSE M.D.: 인간 디버깅

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

항공 사고, 인간 디버깅에 비하면

소프트웨어 디버깅 따위 쉽다