2. Bug, Debugging
• 버그(Bug)
• 프로그래머가 작성한 Logic이 의도한 대로 동작하지 않는 상태 또는 결
과.
• 디버깅(Debugging)
• 버그가 발생한 이유를 찾고 해결하는 과정
3. 버그는 왜 발생하는가?
• 컴퓨터의 기본 원리
• 입력, 처리, 출력
• 프로그래머가 작성한 처리 Logic이 원활하게 동작하기 위한 입
력 데이터가 올바르게 입력되지 않았을 때
• 프로그래머가 작성한 처리 Logic이 잘못 작성되었을 때
• 프로그래머가 출력을 제대로 정의하지 않았을 때
4. 다른 형태의 버그도 발생할 수 있는가?
• 하드웨어의 오류로 인해 로직이 제대로 처리되지 않을 수 있다.
• 가령 하드웨어의 온도가 낮아져 처리 속도가 낮아 짐으로서 원래의 역
할을 못할 수도 있다.
• 예> 급속 냉각을 의도적으로 적용해 데이터를 해킹 할 수 있다.
• 암호 데이터가 담겨 있는 메모리가 있고 전원이 날아간 상태에서 급속
냉각하여 데이터를 중간에 취득하거나 소멸되지 않게 할 수 있다.
5. 컴퓨터 소프트웨어 디버깅
• 이 PT에서는 소프트웨어 디버깅. 비주얼 스튜디오를 사용한 디
버깅에 대한 이야기만 할 예정.
6. 디버거의 원리, 컴퓨터의 동작 원리
• 컴퓨터는 순서대로 명령어를 실행
• 순서는 누가 기억하는가? IP(Instruction Pointer)
• EIP(Extended Instruction Pointer)
• 다음에 실행될 명령어의 주소를 보관하고 있다.
• 만일 EIP가 올바른 명령어 주소를 보관하고 있지
않다면 CPU가 뭔짓을 할지 모른다!
• 흔히 버퍼오버플로우(Buffer Overflow)라는
해킹 기법은 EIP 조작을 주로 한다.
명령어1
명령어2
명령어3
…
명령어n
8. • 두 개의 정수 값을 더하는 sum
이라는 함수가 정상 동작하지 않
고 있다. 입력 값 각각 10, 20을
주었을 때 30이 c에 들어가야 하
지만 의도치 않게 200이 설정된
다.
• 중단점을 사용해 프로그램의 명
령어 실행을 중단 시키고 디버깅
해보도록 하자.
13. Watch Window
• 다음 명령어를 실행하기 위해 (EIP를 다음으로 진행) F10을 누르
면 프로시저 단위로 실행이 된다. 즉, 프로시저가 있다면(지금은
sum함수) sum함수의 내부로 들어가지 않고 sum을 실행한 이
후에 다음 명령어에서 멈춘다.
• F10을 눌러서 다음 명령어를 실행한다.
14. • 그림을 보면 왼쪽에 노란색 화
살표가 int c = sum(a, b); 아래
로 내려간 것을 확인할 수 있다.
즉 명령어가 실행되었고 다음에
실행될 명령어가 return 1;이라
는 뜻이다.
• 이 시점에서 int c의 값은 sum
의 결과로 할당(Assign)되어 있
다.
• 이제 이 값을 확인해 보도록 하
자.
15. Watch Window
• 변수 c를 선택한 상태에서
Shift+F9키를 누르거나 디버그
-> 간략한 조사식 메뉴 아이템
을 선택하여 조자식을 표시한
다.
16. • 간략한 조사식을 보면 현재 c의
값이 200으로 설정된 것을 볼 수
있다. 엄연히 잘못된 값이 할당되
어 있음을 알 수 있다.
17. 디버깅은 왜 버그가 발생했는지 알아내는 과정
• 이제 sum이 어떤 일을 하길래 200을 결과값으로 내뱉는지 알아
보도록 하자.
• 프로그램을 다시 F5키로 실행하여 디버거를 붙인후 진행한다.
• 프로그램이 중단점에서 멈추면 F10이 아니라 F11을 눌러 ‘한 단
계 코드 실행’을 실행한다.
18. • 이전의 상황과 다르게 노란색
화살표(IP)가 return 1;로 가지
않고 sum 함수의 내부로 들어간
다. 왜냐면 프로시저 단위로 실
행된 것이 아니라 코드 하나씩
실행했기 때문에 내부로 들어간
다. IP를 더 진행하기 위해 F10
을 눌러 보자.
19. • 이제 노란색 화살표가 return a*
b;를 지시하고 있다. 기억할 것
은 현재 a * b라는 코드가 실행
되기 전이다. 그렇다면 해당 코
드가 실제로 우리가 원하는 결
과를 나타내는 코드인지 조사식
을 통해서 보도록 하자.
• Shift+F9키를 눌러서 조사식을
띄운다.
20. • 조사식에서 a*b를 입력하면 계산
이 실행되며 값이 200이 나온다.
이것은 엄연히 sum이 아니라
mul(곱)이다.
• 이제 a+b를 식에 넣고 실행해 보
자.
21. • 값이 30이 되었고 이것이 우리
가 원하는 a + b가 되는 sum의
정의다. 즉, 코드는 a*b가 아니
라 a+b이어야 한다. 이제 프로
그램 실행을 멈추고 해당 코드
를 return a+b;로 수정하면 버그
가 해결된다.
22. Options for watch window
• 조사식은 표현식을 실행 할 수 있으며 값을 표현하는 방법을 설
정할 수 있다. 예를 들어 아래와 같이 char buffer[]가 있을 때
24. 배열 형태로 보기
• 배열 형태로 보려면 buffer, 12라고 하면 된다.
• 12는 원소의 개수다.
25. 데이터 타입에 따라 값을 편하게 보기
• 데이터 타입에 따라 비주얼스튜디오는 값을 편하게 보기 위해
Visualizer를 제공하는데 고급 주제이므로 이것은 다음 링크를
참고 하길 바란다.
• https://msdn.microsoft.com/en-us/library/esta7c62.aspx
26. EIP 조작
• 컴퓨터는 EIP를 이용해 다음에 실행될 명령어를 알아낸다. 이제
EIP를 어떻게 조작하며 어떠한 디버깅을 할 수 있는지 알아 보
자.
28. • Ctrl + Alt + G를 누르거나 해당 디버그 -> 창 -> 레지스터를 선
택해 레지스터의 현재 값들을 볼 수 있다.
• x86 architecture, 컴퓨터 구조, Compiler를 공부하면 이러한 레
지스터의 용도를 알 수 있다.)
29. • 현재 EIP는 009E16AE의 값을 가지고 있다. 여기에 다음 실행될
명령어의 주소가 담겨 있다.
30. • 다음에 실행될 명령어 mov eax, dword ptr [a]의 주소는
009E16AE임을 디스어셈블리 윈도우에서 확인할 수 있다.
31. EIP 조작 방법
• 다시 왼쪽 그림과 같이 프로그
램의 명령어가 return 1;에 있고
sum이라는 함수는 입력 값들을
계산하고 끝이 났다. 이 상황에
서 다시 프로그램을 실행한 후
중단점에서 디버깅을 다시 해도
되지만 sum이라는 함수가
stateful하지 않은 stateless 로
직이라면 eip를 조작해 다시 구
문을 실행할 수 있다.
32. • 왼쪽 노란색 화살표를 마우스 왼
쪽 버튼으로 클릭한 후 드래그 하
여 int c = sum(a, b);로 움직여서
드랍하면 EIP를 int c = sum(a, b);
로 설정할 수 있다. 즉, 이미 실행
된 로직을 eip를 조작함으로서 해
당 구문을 다시 실행할 수 있게 해
준다.
• 이렇게 하면 프로그램을 종료했다
가 다시 실행할 필요가 없다.
• 물론 이것이 가능 하려면 지금 예
(sum함수)에서 함수가 stateful하지
않고 stateless해야 한다.
33. • 이제 F11을 누르면 sum 함수의 내부 로직으로 들어가며 다시
디버깅 가능하다.
34. Stateful vs Stateless
• 쉽게 말해서 어떠한 로직이 실행되면서 내부 상태를 바꾸어 상
태에 따라 다른 결과를 내놓는다면 ‘stateful하다.’라고 말할 수
있고 그렇지 않고 로직이 단순 계산 로직이고 내부 상태를 바꾸
지 않는다면 ‘stateless하다’라고 말할 수 있다.
• EIP 조작에서 Stateful한 경우는 조작할 수 없다는 의미는 내부
상태의 값에 따라 이전과 같은 초기 상태를 보장할 수 없으므로
좋은 디버깅 조건이 아니기 때문에 EIP 조작을 권하지 않는다.
35. 함수의 결과값 디버깅
• 변수의 경우 메모리 조사를 통해서 알 수 있지만 함수의 결과물
은 보통 메모리가 아닌 레지스터에 저장된다. 앞서 sum의 결과
값이 어디에 저장되는지 알아 보는 방법은 다음과 같다.
36. • 왼쪽 그림처럼 int c = sum(a,
b);가 실행된 후 결과값은 c라는
변수에 저장되어 있다. 물론 c값
을 조사해서 함수의 결과값을
볼 수 있지만 c라는 값을 쉽게
볼 수 없는 경우도 이럴 때는 레
지스터 EAX를 보면 함수의 결
과값을 볼 수 있다.
37. • EAX의 값은 현재 0xC8이고 이 값은 10진수 값으로 200이다.
• 조사식에서 0xC8을 입력해서 값을 확인할 수도 있다.
38. 계산기 사용 팁
• 계산기를 이용해 16진수 C8을 입력하고
10진수 (DEC)로 컨버팅해서 결과값을 확인할 수도 있다.
46. 메모리 윈도우에서 값 변조하기
• 메모리 윈도우에서 값을 볼 수도 있지만 변경도 가능하다. 16진
수 값 c8을 다른 값으로 변경해 보도록 하자. 예를 들어 FF와 같
이 수정하자.
47. • 이제 c값을 조사식에서 보면 값이 200이 아닌 255로 되어 있는
것을 볼 수 있다.
48. 컴퓨터의 데이터 표현은 결국 메모리 메
모리 메모리 메모리!
• 메모리를 조작하면 프로그램을 원하는 방식으로 바꿀 수 있다.
49. 0xCDCDCDCD?
• 매직 넘버(Magic Number)
• 디버깅할 때 프로그래머가 의미를 알아보기 쉽게 하기 위한 미
리 정의된 값들.
• https://en.wikipedia.org/wiki/Magic_number_(programming)
CDCDCDCD
Used by Microsoft's C/C++ debug malloc() func
tion to mark uninitialized heap memory, usually
returned from HeapAlloc()[19]
50.
51.
52. • 16진수로 초기화되지 않은 값을 보면 0xcccccccc으로 되어 있다.
0xcccccccc은 무슨 뜻인가?
• Used by Microsoft's C++ debugging runtime library and many DOS
environments to mark uninitialized stack memory.
• 초기화되지 않은 스택 메모리를 의미한다.
53. 콜스택, 스택프레임
• 프로그램은 차례대로 명령어를 수행하며 함수의 구현을 위해
스택 프레임(StackFrame)이라는 것을 사용한다.
• 앞서 설명한 sum 함수는 대표적인 값을 반환하는 함수이며 이
것의 내부 구현이 스택 프레임 기반으로 작동된다.
• 함수를 호출하는 것을 콜(Call)한다고 하며 이 콜이 내부적으로
중첩되면서 콜스택으로 표현된다.
54.
55. 콜스택에서 호출 순서
• 호출 순서는 아래에서 위로
• mainCRTStartup()에서
• __scrt_common_main()을 호출하고
• 다음에 __scrt_common_main_she를 호출한다.
• 다음으로 invoke_main()을 호출하고
• 다음에 main()이 호출된다. 그리고 이 main에서
• Sum을 호출한다.
• 현재 그림에서 노란색 화살표가 sum을 지시하고 있으므로 현재 명령어는
Sum함수에서 멈춰 있다는 것을 알 수 있다.
56. 콜스택이 왜 중요한가?
• 콜스택을 분석하면 어떤 함수가 어떤 경로를 통해서 호출되었
는지 알 수 있다.
• 버그의 재구현에 아주 큰 역할을 한다. 즉, 버그가 발견되었을
때 최소한 콜스택만 알아도 수백만의 코드에서 어느 부분이 문
제가 되는 것인지 알 수 있다.
57. Dump(덤프)를 뜬다라는 말.
• 보통 ‘Dump를 뜬다.’라는 말은 현재 프로세스의 메모리 상태와
명령어의 콜스택을 프로그래머가 알 수 있는 형태로 저장하는
것을 말한다.
59. Advanced topics
• Real life cases.
• memory overflow
• Displacement memory allocation
• gcc compiler bug
• Wrong compiler optimization
• desync on online play
• Hidden watch window expression(legacy case)
60. Memory Overflow
• 메모리는 데이터(Data), 자료
• 데이터에는 범위, 크기가 있다.
• 예를 들어 int, float, double, dword, etc…
• 자료의 크기가 곧 범위. 데이터의 값이 데이터의 표현 범위를 넘
어가는 것이 곧 메모리 오버플로우.
61. • 데이터의 크기를 결정하는 것이 매우 중요하다.
• 데이터의 크기는 성능에 큰 영향을 끼친다.
• 가령 나이를 저장하는 데이터의 범위는 몇 바이트(Byte)를 사용
하는 것이 좋은가?
62. • 보통 나이는 255살 넘어가는 경우가 없으므로 1Byte면 충분하
다.
• 이러한 나이를 4Byte에 저장할 경우에는?
• 아무런 문제가 없다. 다만 더 많은 메모리를 사용할 뿐이다.
• 진짜?
63. 축구 선수 데이터라면?
• 축구 선수를 표현하기 위한 데이터는?
• 이름
• 나이
• 키
• 몸무게
• 스킬
• 포지션
• 볼 컨트롤
• 드리블
• 마킹
• 슬라이드 태클
• 스탠딩 태클
• 크로싱
• 짧은 패스
• 긴 패스
• 등등…
64. 단순한 산수
• 구조체 필드 데이터가 하나 당 무조건 1바이트라면 4바이트로
사용했을 때 무조건 4배.
• 경우에 따라 꽤 큰 비용(?)이 될 수 있음.
• 성능, 네트워크 트래픽 등등
65. 그래서?
• 용량을 줄여서 최대한 데이터의 값이 딱 맞게 데이터의 범위를
설정해야 한다. (좋은 프로그래머가 되고 싶다면!)
• 하지만 처리 로직을 잘못 작성하거나 본의 아니게 이러한 범위
를 넘어가는 경우 메모리 오버플로우가 생길 수 있다.
67. Memory manager using Displacement
Memory Allocation.
• Create a custom memory manager.
• There are bunch of benefits of using displacement memory
allocation. Such as…
• Find a memory overflow
• Memory leaks
• Speed optimization
• Avoid memory defragmentation.
• Memory Viewer
• And so on…
68. GCC Compiler Bug
• Compiler is a software and could be exist a bug which is
critical and hard to find.
• Stuffs related with hardware such as optimization of vector operation.
• Xbox360 and ps3 are different platform and compiler couldn’t handle it
somehow.
• How did I fix it?
• Try to fix it.
• Can’t? Report it to sony.
• Wait
• If we don’t have enough time? Then turn off the optimization.