회사에서 php개발 할 일이 생겨서 쓸만한 IDE가 없는가 찾다가 이클립스를 이용하여 개발환경을 구축 하는 방법을 찾았습니다.

일단 조합은..

eclipse 3.4 + php 5.2.6 + apache 2.2 + PHPEclipse(eclipse php plug-in) + XDebug(php debugger)가 되겠습니다.

PHPEclipse를 쓰기 위해서는 eclipse 버전이 최소 3.2어야만 합니다. 그 아래 버전은 지원을 하지 않더군요.

eclipse는 http://www.eclipse.org/downloads/ 에서 적당한 버전을 다운로드 하시고..
apache는 http://httpd.apache.org/download.cgi 에서 최신버전을 다운로드 하시고..
php는 http://www.php.net/downloads.php 에서 최신버전을 다운로드 하시고..
PHPEclipse와 XDebug는 eclipse를 설치 후 http://www.phpeclipse.de/wiki/Installation 를 참고하여 설치를 하시면

연장은 모두 구비가 되었습니다.

그리고 php 디버깅을 하기 위해서 필요한 세팅은 다음과 같습니다.

http://www.phpeclipse.de/wiki/Howto/XDebugAndPHPEclipse

그리고 마지막에 주의할 점은 디버거가 활성화 되기 위해서는 디버깅을 하기 위한 주소뒤에 "XDEBUG_SESSION"라는 쿼리스트링에 값은 위의 세팅에서 한 "IDE Identification string"에 입력한 값을 넣어주어야한다는 것입니다.

예를 들면 다음과 같습니다.

http://localhost/My_Project/xdebug.php?XDEBUG_SESSION_START=firstProject

"IDE Identification string"는 한 서버에 여러개의 프로젝트가 존재 할 때 프로젝트를 구분하기 위한 구분자입니다. 즉, 여러개의 프로젝트를 디버깅 하기 위해서는 "IDE Identification string"값이 다르게 위의 URL에 있는 세팅을 프로젝트마다 해주어야한다는 이야기입니다.

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
2008/09/10 12:52 2008/09/10 12:52

IsDebuggerPresent

프로그래밍 : 2007/08/09 21:29
IsDebuggerPresent라는 함수가 있다..

이 함수는 현재 실행되고 있는 프로세서에 디버거가 붙었는지를 검사해주는 함수이다.
이 함수를 이용하면 현재 디버거가 붙었는지 검사를 해서 자기 자신을 디버깅 하지 못하게 할수 있다.

하지만......

뭐 별거 있나.. 저 함수 호출하는 부분에서 리턴값을 비교하는 부분을 가볍게 뛰어 넘는 어셈코드로 대체해줘버리면 쓸모가..................


없지요...
크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
2007/08/09 21:29 2007/08/09 21:29
릴리즈모드로 컴파일 된 프로그램이 충돌(널 포인터 참조, 잘못 된 메모리 액세스)이 일어났을 때 아래와 같은 창을 만나게 될 것이다.



이 경우 프로그램은 충돌 된 위치 (여기서는 0x0041181d)를 표시하고 종료되게 된다. 이미 릴리즈 모드로 컴파일 된 프로그램은 심볼정보가 없으므로 이런 충돌이 일어 날 경우 디버깅 하기가 매우 까다롭다. 이런 오류를 디버깅 하기 위해서는 이 상황을 다시 재현해야 하는데 프로그램이 작다면 금방 하겠지만 덩치큰 3D온라인 게임과 같은 프로그램은 이런 상황을 재현하기가 무지 어렵다.

따라서 이 상황에서는 저런 에러메시지를 가지고 최대한 가깝게 충돌 위치를 찾아낼 수 만 있다면 디버깅이 무지 쉬워진다.

이 글에서는 저런 충돌 위치를 가지고 실제 코드에서 어느 부분이 문제인가를 알아보도록 하자.

그러기 위해서는 우선 몇가지 사전 작업이 필요하다. 릴리즈모드로 컴파일 된 실행파일은 컴파일 옵션에 강제로 심볼을 넣지 않으면 심볼 정보가 없기 때문에 충돌 위치를 가지고 역으로 찾아내는게 불가능하다. 이를 가능하게 할려면 아래 그림과 같이 링커 옵션을 설정 해야 한다.



여기서 주의깊게 봐야 할 것은 맵파일 생성부분이다. 컴파일러가 코드를 컴파일 할 때 각종 함수와 어셈블리들을 메모리상의 어떤곳에 위치 시킬 것인가를 결정하게 된다. 그러한 정보가 들어있는 파일이 맵파일이다. 이 맵파일을 생성하여 열어보면 프로그램 내부에서 참조하는 각종 함수들의 메모리상의 위치 정보와 각 함수들이 호출 된 소스상의 라인 번호가 들어있다.

이 맵파일을 생성할려면 위의 그림에 있는 대로 "맵파일 생성" "맵내보내기" "맵줄"을 모두 예로 설정하고 "맵파일 이름"에 생성할 맵 파일을 적어주면 된다.

아래는 그렇게 해서 생성된 맵파일의 일부이다.

Timestamp is 4368d7ce (Thu Nov 03 00:14:22 2005)

Preferred load address is 00400000

Start Length Name Class
0001:00000000 000a612aH .text CODE
0001:000a6130 00009da2H .text$x CODE
0001:000afee0 0000d785H .text$yc CODE
0001:000bd670 00000104H .text$yd CODE
0002:00000000 00000810H .idata$5 DATA

Address Publics by Value Rva+Base Lib:Object

0000:00000000 ___safe_se_handler_count 00000000
0000:00000000 ___safe_se_handler_table 00000000
0000:00000000 __except_list 00000000
0000:00009876 __ldused 00009876
0000:00009876 __fltused 00009876
0000:00000000 ___ImageBase 00400000
0001:00000000 ?GetMessageMap@CAnimateRadioButton@@MBEPBUAFX_MSGMAP@@XZ 00401000 f AnimateRadioButton.obj
0001:00000010 ?SetAniTime@CAnimateRadioButton@@QAEXH@Z 00401010 f AnimateRadioButton.obj
0001:00000020 ??$fill@PAPAUHBITMAP__@@PAU1@@std@@YAXPAPAUHBITMAP__@@0ABQAU1@@Z 00401020 f i AnimateRadioButton.obj
0001:00000020 ??$fill@PAPAVAMSGUIRadioButton@@PAV1@@std@@YAXPAPAVAMSGUIRadioButton@@0ABQAV1@@Z 00401020 f i AMSLib:AMSGUIDialog.obj
0001:00000020 ??$fill@PAHH@std@@YAXPAH0ABH@Z 00401020 f i MainFrm.obj
0001:00000040 ??1logic_error@std@@UAE@XZ 00401040 f i AnimateRadioButton.obj
0001:000000a0 ??_Elogic_error@std@@UAEPAXI@Z 004010a0 f i AnimateRadioButton.obj
0001:000000a0 ??_Glogic_error@std@@UAEPAXI@Z 004010a0 f i AnimateRadioButton.obj
0001:000000c0 ??_Elength_error@std@@UAEPAXI@Z 004010c0 f i AnimateRadioButton.obj
0001:000000c0 ??_Glength_error@std@@UAEPAXI@Z 004010c0 f i AnimateRadioButton.obj
0001:000000e0 ??1length_error@std@@UAE@XZ 004010e0 f i AnimateRadioButton.obj
0001:000000f0 ??1tagBLINK_CARD@@QAE@XZ 004010f0 f i MainFrm.obj

Line numbers for .\Release\MainFrm.obj(g:\projects\naroshaproject\newclient\mainfrm.cpp) segment .text

14 0001:000008d0 16 0001:000008e0 218 0001:000008f0 220 0001:000008f6
229 0001:000008fe 236 0001:0000093a 243 0001:00000970 255 0001:000009a6
260 0001:000009df 255 0001:000009e3 260 0001:000009f4 283 0001:00000a00
284 0001:00000a00 288 0001:00000a0f 387 0001:00000a20 394 0001:00000a23
395 0001:00000a2e 396 0001:00000a39 398 0001:00000a44 399 0001:00000a47
403 0001:00000a50 412 0001:00000a53 413 0001:00000a5e 414 0001:00000a69
416 0001:00000a74 417 0001:00000a77 885 0001:00000a80 886 0001:00000a82
888 0001:00000a93 892 0001:00000aa1 2183 0001:00000ab0 2182 0001:00000ab0
2205 0001:00000ac6 2212 0001:00000ad4 2205 0001:00000ad7 2212 0001:00000ae5
2205 0001:00000ae8 2212 0001:00000af6 2183 0001:00000af9 2205 0001:00000b00
2212 0001:00000b0e 3133 0001:00000b20 3134 0001:00000b23 3135 0001:00000b30
...생략
0001:00008190 ?OnPaint@CMainFrame@@IAEXXZ 00409190 f MainFrm.obj
0001:00008330 ?OnMouseHover@CMainFrame@@IAEJIJ@Z 00409330 f MainFrm.obj
0001:00008380 ?OnMouseLeave@CMainFrame@@IAEJIJ@Z 00409380 f MainFrm.obj
0001:000083d0 ?OnBnClickedExit@CMainFrame@@IAEXXZ 004093d0 f MainFrm.obj
0001:000083f0 ?OnBnClickedExitWatingRoom@CMainFrame@@IAEXXZ 004093f0 f MainFrm.obj
0001:00008490 ?OnBnClickedCreateRoom@CMainFrame@@IAEXXZ 00409490 f MainFrm.obj
0001:00008630 ?OnCreate@CMainFrame@@IAEHPAUtagCREATESTRUCTA@@@Z 00409630 f MainFrm.obj
0001:00008910 ?OnDrawItem@CMainFrame@@IAEXHPAUtagDRAWITEMSTRUCT@@@Z 00409910 f MainFrm.obj
0001:00008940 ?WindowProc@CMainFrame@@MAEJIIJ@Z 00409940 f MainFrm.obj


여기서 맨 위의 Timestamp는 이 맵파일 생성된 타임 스탬프를 나타내며 그 아래의 Preferred load address is 00400000 는 실제 메모리상에 코드가 배치 될 때의 베이스 어드레스를 나타낸다. 이 베이스 어드레스는 뒤에서 충돌이 일어난 위치를 가지고 라인 번호를 찾는데 사용이 된다.

그리고 Address Publics by Value Rva+Base Lib:Object 부분에서 Rva+Base 열에 나타난 숫자가 Publics by Value열에 나타난 함수가 위치하는 메모리의 위치이다. 어드레스를 뺀 값이다. 그리고 한가지 더 알아두어야 할 것은 리스트에 있는 함수중 실제 코드상에 있는 함수가 아닌 dll로 임포트 된 함수들도 표시가 된다는 점이다. 우리가 관심이 있는 것은 이런 임포트된 함수가 아니라 우리가 작성한 소스코드상의 함수이다. 이를 구별하기 위해서는 Rva+Base열에 나타난 주소 다음에 "f"가 있으면 이는 우리가 작성한 함수를 나타내며 없다면 임포트된 함수이다.

?SetAniTime@CAnimateRadioButton@@QAEXH@Z 00401010 f


그럼 실제로 충돌 위치를 가지고 어떤 함수에서 충돌이 났으며 몇번째 줄어서 충돌이 났는지 알아보자.

우선 어떤 함수에서 충돌이 일어났는지 알아볼려면 충돌이 일어난 위치를 알아야한다. 여기서는 예시로 맨 위의 그림에 있는 충돌 보고 메시지에서 나온 "0x00409714"를 이용해서 찾아보도록 하자. 찾는 방법은 간단하다. 충돌 위치를 넘지 않는 메모리 주소가 충돌이 일어난 함수이다.


충돌이 일어난 위치는 0x00409714이므로 이 값을 가지고 맵파일의 리스트에서 찾아보면 이 값을 넘지 않는 가장 가까운 주소는

0001:00008630 ?OnCreate@CMainFrame@@IAEHPAUtagCREATESTRUCTA@@@Z 00409630 f MainFrm.obj
가 된다.

이 주소에서 보면 CMainFrame클래스의 OnCreate 함수에서 충돌이 발생 했음을 알 수 있다. 그렇다면 이 충돌 주소를 가지고 실제 라인을 구해보자. 실제 라인을 구하기 위해서는 약간의 산수계산이 필요하다. 아래의 공식이 그것이다.

베이스 어드레스 - 충돌위치 - 0x1000


나머지는 이해가 되는데 맨 마지막에 빼는 0x1000가 있어야 하는 이유가 궁금할 것이다. 아래쪽에 가보면 주소별 라인이 기술 되어있는 곳이 있다. 그곳에 나타난 주소는 윈도우의 실행파일 포맷인 PE파일에서 DOS섹션의 헤더 부분의 크기를 뺀 값이 된다. 즉 운영체제 로더가 실행파일을 메모리에 올릴 때는 실행파일의 이미지를 그대로 메모리에 올리게 된다. 이 이미지는 DOS색션도 포함이 되어있으므로 충돌이 일어날 경우에는 이 DOS색션의 크기가 포함된 위치가 충돌 위치가 된다. 그런데 맵파일에서 생성된 라인별 주소는 이 DOS색션의 크기가 빠진 값이 된다. 따라서 DOS색션의 크기인 0x1000을 빼주어야 정확한 위치를 구할수 있게 된다.

위의 공식대로 계산을 해보면 8714라는 값을 구할 수 있게 된다. 이를 가지고 맵파일에서 라인을 찾아보자. Lib:Object 열에 나타난 값이 충돌이 일어난 코드를 포함한 오브젝트 파일을 나타낸다. 이를가지고 Line numbers for 라고 시작하는 부분에서 해당 오브젝트 파일이 있는 곳을 찾도록 한다. 여기서는 충돌이 일어난 파일 이름이 MainFrm.obj가 되므로 아래와 같은 곳을 찾았다.

Line numbers for .\Release\MainFrm.obj(g:\projects\naroshaproject\newclient\mainfrm.cpp) segment .text

14 0001:000008d0 16 0001:000008e0 218 0001:000008f0 220 0001:000008f6
229 0001:000008fe 236 0001:0000093a 243 0001:00000970 255 0001:000009a6
260 0001:000009df 255 0001:000009e3 260 0001:000009f4 283 0001:00000a00
284 0001:00000a00 288 0001:00000a0f 387 0001:00000a20 394 0001:00000a23
395 0001:00000a2e 396 0001:00000a39 398 0001:00000a44 399 0001:00000a47
403 0001:00000a50 412 0001:00000a53 413 0001:00000a5e 414 0001:00000a69
416 0001:00000a74 417 0001:00000a77 885 0001:00000a80 886 0001:00000a82
888 0001:00000a93 892 0001:00000aa1 2183 0001:00000ab0 2182 0001:00000ab0
2205 0001:00000ac6 2212 0001:00000ad4 2205 0001:00000ad7 2212 0001:00000ae5
2205 0001:00000ae8 2212 0001:00000af6 2183 0001:00000af9 2205 0001:00000b00
2212 0001:00000b0e 3133 0001:00000b20 3134 0001:00000b23 3135 0001:00000b30
3138 0001:00000b38 4783 0001:00000b50 4784 0001:00000b50 4788 0001:00000b60
... 생략
7608 0001:0000866d 7613 0001:00008674 7618 0001:00008676 7621 0001:000086aa
7626 0001:000086be 7632 0001:000086dc 7633 0001:000086ec 7635 0001:000086fe
7637 0001:00008727 7639 0001:00008741 7641 0001:0000876c 7643 0001:00008780
7645 0001:00008791 7647 0001:000087b6 7648 0001:000087d0 7649 0001:000087d9
7651 0001:000087ef 7653 0001:00008801 7654 0001:00008808 7656 0001:0000880f

여기를 보면 7651 0001:000087ef라는 형식으로 기술이 되어있다. 여기서 7651은 라인번호를 나타내며 000087ef는 해당 라인이 있는 주소를 나타낸다. 그런데 이곳에 쓰인 주소는 함수의 호출 주소이기 때문에 우리가 계산한 8714와 일치하는 라인번호는 찾을 수 없고 이에 근사하는 곳을 찾아야 한다. 즉 8714를 넘지 않는 가장 큰 위치를 찾으면 된다. 위의 리스트에서 찾는다면 아래를 찾을 수 있다.
7635 0001:000086fe


자.. 이제 7635라는 라인번호를 찾았으므로 MainFrm.obj 의 소스파일으로 가서 해당 라인을 봐보자.



하지만 라인번호로 찾아봤지만 정작 문제가 있는 곳은 라인번호에 있는 함수가 아니라 그 위에 있는 *p = 100; 이라는 코드이다.

릴리즈 모드에서는 컴파일러가 최적화를 하기 때문에 일부 코드들이 바로바로 실행이 안될 수도 있다. 그렇기 때문에 라인번호가 가르키는 곳이 문제가 되는것이 아니라 해당 라인 근처에 문제가 생겼다고 생각하고 해당 함수의 첫부분부터 라인번호까지를 찾아보면 문제가 있는 부분을 찾을 수 있다.

ps. 아래 파일은 예제로 썼던 맵파일이다.
크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
2005/11/03 01:11 2005/11/03 01:11