C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[863] 빌더에서 액세스바이올레이션 발생시 뜨는 주소값으로 에러난 라인 찾기.
김태선 [cppbuilder] 15020 읽음    2009-02-27 15:28
고의로 엑세스바이올레이션 에러를 내 보겠습니다.
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    int *p = 0;

    int  n = *p; // **: 여기서 에러가 납니다.
}
//---------------------------------------------------------------------------

실행파일을 만들어 실행하는데 에러가 나면  위 ** 지점에서 멈추죠.
화면에는
Access violation at address 00401AD4 in module 'Project1.exe'. Read of address 00000000.
이런 메시지가 뜹니다.

디버깅 할때는 그 곳을 바로 가르키니 문제가 없습니다.
또 여기저기 소스를 훓다가 아까 그 번지가 가르키는 소스 위치를 바로 가고자 할 때는
빌더의 풀다운 메뉴의 Search->Go to Address 를 선택해 위에서 나온 번지
0x401AD4 를 입력해 주면 해당 소스의 그 위치로 바로 이동합니다.
Search->Go to Address 는 디버깅 모드에서만 활성화 되므로,
브레이크 포인트를 걸거나 디버깅 모드로 실행시 번지를 입력할 수 있습니다.


그런데, 만일 C++빌더로 릴리즈한 프로그램에서 위와 같은 에러가 뜬다고 하고 그 위치가
소스의 어느 부분인지 알고 싶을 때는 어떻게 하면 될까요?

이를 위해서는 릴리즈한 실행파일에 대한 map 파일을 보관하고 있어야 합니다.
map은 링커에 의해 만들어 지고 map 파일을 만드는 방법은
Project -> Options -> Linker -> Map file 에 설정할 수 있습니다.
여기에서 Detailed를 선택합니다.
Show mangled names 는 체크하지 않는 것이 좋습니다.
이는 컴파일러에 의해 변형된 메소드명으로 표시하므로 오히려 보는데 방해가 됩니다.
이는 디스어셈블러로 디버깅할 때는 유용하지만 일반적으로는 필요 없습니다.

그리고 릴리즈를 위해 컴파일 하면 프로젝트명.map 파일이 생성됩니다.
이 map 파일을 잘 보관해 두어야, 에러난 곳을 찾을 수 있습니다.

위에 보인 간단한 소스가 담긴 파일을 릴리즈 했는데 에러가 났습니다.
Access violation at address 00401AD4 in module 'Project1.exe'. Read of address 00000000.
라는 정보로 소스 위치를 찾고 싶습니다.

그러면 보관해둔 map 을 꺼냅니다. 그냥 text 에디터로 열면 됩니다.
그리고 위 00401AD4 에 해당하는 번지를 찾아야 합니다.
윈도2000,XP 에서는 프로그램의 인스턴스 위치가 0x00400000 이고,
윈도가 아닌 도스에서 로딩될때의 경고를 위해 도스용 로더 및 윈도 PE 헤더와 로더를 위해
0x1000 바이트를 사용하므로, 결과적으로 0x00401000 번지를 차감한 번지를 계산합니다..
말이 계산이지 그냥 보면 척 알수 있죠.
0x00401AD4 - 0x00401000 = 0xAD4
그러면 AD4 번지가 에러난 곳입니다.

그러면 프로젝트 map 파일에서 이 번지를 찾으면 됩니다.
그렇지만 이 번지는 map 파일에 존재하지 않습니다.
이유는 에러난 곳이 함수의 시작 번지가 아니라 함수 중간에 있기 때문입니다.

그래서 map 파일이
1. Start Length Name Class
2. Detailed map of segments
3. Address Publics by Name
4. Address Publics by Value

이렇게 네개의 파트로 구성되어 있는데
번지를 기준으로 찾아야 하기 때문에 4번째 파트로 가서 살펴 봅니다.
번지가 순서대로 되어 있기 때문에,
0xAD4 번지가 있을 곳을 찾습니다.
예제 컴파일한 제 컴의 프로젝트 map 파일에서는 다음과 같은 곳을 볼수 있는데
0001:00000ACC  __fastcall TForm1::Button1Click(System::TObject *)
0001:00000AEC  __fastcall TForm1::Button2Click(System::TObject *)
곧 AD4 번지는 위 Button1Click 메소드 안에 있다는 것을 알수 있습니다.

아쉽게도 정확하게 에러난 소스 라인은 알수가 없습니다.
빌더에서 알수 있는 것은 에러난 함수 위치가 최선입니다.
물론, 이게 끝은 아닙니다.

에러난 함수 내의 적당한 곳에 브레이크 포인트를 걸고,
프로그램 코드가 그곳에서 멈추게 한 뒤,
CPU 창을 열어 디스어셈블러를 보면 번지가 열거되므로
정확한 위치를 찾을 수 있습니다.

그간에 소스가 변경이 되어 위치를 정확하게 찾기 어려운 경우라면
그 함수를 집중적으로 디버깅하면 될 것입니다.


참고적으로 델파이에서는
개발자가 만든 소스 라인에 대해 해당 코드의 위치가 map 파일에 열거되어 있으므로,
빌더가 에러난 곳의 함수 위치를 알수 있는데 비해
델파이는 그 라인 위치까지 정확하게 알수 있습니다.
델파이 map 파일의 끝 부분에 보면
Line numbers for Unit2(Unit2.pas) segment .text

    32 0001:0004C944    33 0001:0004C946    34 0001:0004C948    35 0001:0004C94D
    37 0001:0004C980    37 0001:0004C987

Line numbers for Project2(D:\Test\map_to_addr\Project2.dpr) segment .text

     9 0001:0004CB90    10 0001:0004CBA0    11 0001:0004CBAC    12 0001:0004CBC4
    13 0001:0004CBD0

식으로 유닛별로 소스상의 코드가 메모리 상의 어디에 대응되는지 알수 있게 되어 있는데
아쉽게도 컴파일러의 구조적인 문제로 빌더에서는 이런 서비스가 없습니다.



추가.

DLL의 경우도 map 파일을 같은 방식으로 만들 수 있습니다.
만일 DLL에서 에러가 나서 에러난 번지만 알 때는 어떻게 그곳을 찾을 수 있을까요?
DLL도 사실은 EXE의 변형이기 때문에, 구조가 거의 같습니다.
map 파일의 선두를 보면
Start Length Name Class
0001:00401000 000009CE0H _TEXT                  CODE
와 비슷한 내용을 볼수 있는데
EXE가  0x00401000에서 인스턴스가 시작하는 것과 마찬가지 구조라는 것을 알수 있습니다.

하지만 DLL은 메모리로 로드되면
DLL을 호출한 EXE와 같은 메모리 공간에 있어야 합니다.
그러므로 0x00401000에 놓일 수가 없습니다.
그래서 DLL은 로딩 되면 OS에 의해 적당한 메모리 위치에  놓이게 됩니다.

테스트 삼아 DLL 에서 고의적으로 에러를 내보면
Access violation at address 00E914CD in module 'test.dll'. Read of address 00000000.
이와 같은 식으로 나타나는데 이를 map 파일에서 찾으려면,
DLL이 놓인 인스턴스의 번지를 빼주어야 합니다.
원래 EXE가 00401000에 위치하는데 이는 0x00400000 + EXE 헤더 0x1000 의 구조인 것처럼
DLL도 로딩된 메모리 위치 + DLL 헤더 0x1000 구조로 되어 있습니다.
그런데 로딩된 메모리 위치는 OS에 의해 동적으로 결정되기 때문에, 따로 이를
기록해 놓지 않으면 정확하게 알수 없습니다만 DLL가 거대하지 않으면 대략적으로 알수 있습니다.
00E914CD 는 메모리 0x00E90000 번지에 놓였다는 것을 알수 있고 여기에 헤더 0x1000바이트를 빼면
결국 0x4CD 만 남습니다.
그러면 map 파일의 4번째 파트에서 이 번지가  속한 메소드를 찾으면 됩니다.
DLL이 놓이는 영역은 WinXP, Win2000에서는 대략 0x00E00000 부근이고 DLL이 로딩될때마다 동적으로 결정되고,
DLL이 작으면 0x00E90000 등과 같이 위치를 쉽게 알수 있는데, DLL이 크고
에러난 번지가 로딩된 번지와 많이 떨어져 있으면 DLL이 어느 번지로 로딩되었는지
인스턴스 번지를 따로 어디에 기록해 두고 그것을 확인해야 합니다.

DLL 인스턴스 번지를 확인 할수 없을 때는 감으로 대략 위치를 잡아야 합니다.
DLL은 보통 0x00E80000 부터 뒤쪽 번지로 계속 놓이므로 이를 기준 삼으면 됩니다.



그럼...
장성호 [nasilso]   2009-02-27 15:41 X
ㅋㅋ 얼마전에 이걸가지고 한참 살펴봤었죠...
CBuilder에서는 6.0 또는 2007에서 아무리 map 파일 옵션을 이리저리 봐꿔바도
Delphi처럼 Line-Number는 기록되지 않더군요..

그래서 저도 김태선님처럼 위치를 찾았었드랬죠..

Exception이 정보를 이용하여 map파일과 연동해 error발생 위치(함수명)을 찾는
간단한 tool을 만들다가 .. 보류중이라는.....
김태선 [cppbuilder]   2009-02-27 15:48 X
ㅋㅋ그러게요.
빌더도 라인번호가 표시되었으면 훨씬 좋았을텐데요.
아무래도 C++언어 구조 특성상 어려웠나 봅니다.
장성호 [nasilso]   2009-02-28 20:50 X
쓰레드도 시작주소가 좀 다르던것 같든데...  자세한 내용좀..  부탁드립니다.
김태선 [cppbuilder]   2009-02-28 23:49 X
쓰레드만 특별하게 조사를 해본 것은 아니라서, 지금 잠시 코딩해서 실험해보니
쓰레드 코드는 프로그램 코드와 마찬가지의 영역에 놓이므로,
쓰레드에서 발생한 예외에 대해서도 마찬가지 방법으로 찾을 수 있겠네요.
용맨소녀 [doyongid]   2009-03-06 11:55 X
이런 기술이 있었군요.. 에러메시지 뜨면 그냥 모래사장에 바늘찾기로 찾았었는데..
이홍규 [antonio24]   2009-05-25 16:28 X
좋은 정보 감사 드립니다~ ^_^

+ -

관련 글 리스트
863 빌더에서 액세스바이올레이션 발생시 뜨는 주소값으로 에러난 라인 찾기. 김태선 15020 2009/02/27
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.