이전 포스팅([도구의발견] - Break point)에서 Visual Studio 디버거의 브레이크 포인트를 사용하는 방법에 대해서 알아 보았다. 그 중 브레이크 포인트에 조건을 걸어 주는 방법(Conditional breakpoint)에 대해서도 알아 보았는데, 오늘은 conditional breakpoint의 한계와 우회방법(?)에 대해서 알아보겠다.

[도구의발견] - Break point 의 내용 대로 하는 경우, 정수 값, 문자 값에 대해서는 조건문이 정상동작하지만, 문자열(char*, std::string) 같은 경우 전~혀 제대로 동작하지 않는다. 정확한 이유는 잘 모르겠지만, 그냥 나의 생각으로는 포인터 값을 주는 경우, 해당 포인터가 가리키고 있는 문자열을 비교하는 것이 아니라 포인터 자체의 주소 값을 비교하는 것 때문이 아닐까 추측한다(실제 동일한 주소를 가리키고 있는 포인터를 비교할 때는 해당 브레이크 포인트에 정상적으로 멈추는 것을 확인 했다).

그렇다면 특정 문자열일 경우에만 브레이크 포인트가 활성화 되게 하는 것은 불가능한가?
일단 답은 '가능하다' 이다.  

1. 문자열 비교의 결과값을 이용
만일 소스코드의 수정이 가능하다면, 특정 조건의 상수 문자열과 비교하는 코드를 추가하고 그 결과를 저장하는 변수를 생성한다. 그리고 그 변수의 값을 비교하는 방법이 있다. 

비교해야할 변수의 데이터 타입이 'char*' 인 경우는 아래와 같이 작성 될 수 있겠다 :

const char* name = "kukuta";

#ifdef _DEBUG // 디버깅용 코드
int ret = strcmp(name, "kukuta");
std::cout << "result : " << ret << std::endl;
#endif

std::cout << "name : " << name << std::endl;  // 여기서 name이 "kukuta" 일때만 멈추고 싶음

(물론 위의 코드는 상식으로 전혀 디버깅 할 필요가 없는 코드이긴 하지만..예제를 위해서 작성 했다. 태클은 노노!!)
#ifdef ~ #endif 내에 있는 코드는 실제 프로그램의 목적과는 아무런 상관 없는 오직 디버깅만을 위해 추가된 코드다. 저런식으로 코드를 추가한 뒤에 브레이크 포인트가 필요한 부분에 'ret' 변수의 결과 값이 0 혹은 0이 아닌지를 비교하는 조건문을 넣으면 된다.

또한 std::string을 사용하는 경우라면 간단한 if 문과 결과를 담는 bool 값으로 위의 코드가 대신 될 수도 있다.

2. 문자 하나 하나씩의 비교
위에서는 소스코드의 수정이 가능한 경우를 알아 보았다. 하지만 소스코드의 수정이 불가능한 경우도 종종 있다. 이럴 경우 어떻게 해야 할까? 그렇다. 문자열 하나하나를 다 비교해 해야 한다. 데이터 타입이 char* 의 경우라면 브레이크 포인터의 조건문에 아래와 같이 적을 수 있다 :

name[0] == 'k' && name[1] == 'u' && ... && name[4] == 't' && name[5] == 'a'

간단한 작업이긴 하지만 상당히 귀찮은 작업이기도 하다. 하지만 어쩌겠는가. 소스코드를 수정할 수도 없으니 저렇게 하지 않으면 값의 비교가 불가능 한것을..

그리고 더욱 귀찮은 것은 데이터 타입이 std::string 인 경우다. std::string의 경우에는 위와 같이 변수 이름을 통해 바로 해당 포인터로 접근 할 수가 없다. std::string 클래스 내부의 포인터 버퍼를 직접 가리켜 줘야 정상적인 비교가 된다. 예를 들어 :

std::string name = "kukuta";
std::cout << "name : " << name << std::endl; // 여기서 name이 "kukuta" 일때만 멈추고 싶음

위와 같은 코드에서 name의 내부 구조를 보면 아래와 같다 :


필자의 경우는 '((str)._Bx)._Buf' 와 같이 해당 포인터 변수에 접근 할 수 있었다. 그렇다면 조건식에는 어떻게 적어 줘야 할까? 아래와 같이 하면 된다.

((str)._Bx)._Buf[0] == 'k' && ... && ((str)._Bx)._Buf[5] == 'a'

더 복잡해 지지 않았는가? 제발 특정 문자열을 조건으로 삼아야 할 때는 소스 코드도 같이 수정 할 수 있기만을 기도하자.

참고적으로 Visual Studio 2010 버젼에서는 디버거에서 문자열 자체에 대한 condition breakpoint에 대한 지원도 한다고 합니다.


Reference :
 - http://www.developmentnow.com/g/51_2003_7_0_0_280475/Conditional-Breakpoint-not-hit--Need-some-help.htm : 하드한 문자 하나씩 비교 방법
 - http://www.suodenjoki.dk/us/archive/2010/conditional-breakpoint.htm : VS 2010 문자열 지원 뉴스

Posted by kukuta

댓글을 달아 주세요

Break point

도구의발견 2008.08.18 21:50
버그 없는 프로그램을 만드는 것이 가장 좋은 방법이긴 하지만 현실적으로 처음 부터 그런 것을 만든다는 것은 디버거에 대한 모독이며 여지껏 나왔던 디버깅 관련 책들에 대한 도전이다. 말이 이상하긴 하지만 디버깅이 프로그래밍에서 빠져서는 안되는 중요한 요소라는 것을 강조하고 싶은 것이니 대충 '디버깅은 중요하다' 정도로 이해하고 넘어 가도록 하자.

이렇게 중요한 디버깅을 함에 있어서 Visual studio는 상당히 도움되는 툴들을 제공하고 있는데 그 중에 제일미로 꼽을 수 있는 것이 바로 '브레이크 포인트(Break point)'다. 여기 와서 브레이크 포인트가 뭐냐고 묻는다면...나도 할 말이 없다. 그냥 브레이크 포인트는 알고 있고 대충 visual studio에서 제공하는 기본적인 디버깅 관련 툴들은 사용 할 줄 안다는 가정하에 글을 진행 하도록 하겠다.

그럼 가장 먼저 알아야 할 것은 브레이크 포인트의 설정이다 :

1. 브레이크 포인트 설정

1.1 간단한 브레이크 포인트 설정

브레이크 포인트는 가장 간단하게는 소스 코드의 적절한 부분에 가서 <F9>를 누르는 방법과 소스 코드 에디터의 맨 왼쪽 빈 여백을 마우스로 클릭하는 방법이 있다. 너무 쉬운가? 그렇다면 좀 더 빨리 브레이크 포인트를 설정 하는 방법에 대해 알아 보자.

1.2 브레이크 포인트 정보 보기
'함수 혹은 클래스 이름을 이용하여 브레이크 포인트 설정'을 하기 전에 먼저 알아 두면 좋을 것이 있다. 바로 '브레이크 포인트 정보'다. 브레이크 포인트들에 관련된 세세한(?) 정보를 보여주는 아주 유용한 창이다. '브레이크 포인트 정보'창을 열기 위해서는 간단하게는 'Ctrl+Alt+B'를 누르는 방법이 있고, 조금 귀찮긴 하지만 메뉴를 이용하여 보기 위해서는 'Debug > Windows > Break Points'와 같이 찾아 갈 수 있다.

해당 윈도우에서 보여 주는 정보로는 브레이크 포인트의 이름(파일 이름과 라인으로 표시), 조건(Condition), 적중 횟수(Hit count) 등등이 있다. 자세한 설명은 다음 장에서 자세히 다루도록 하고 지금은 '브레이크 포인트들의 정보를 한번에 보여 주는 창이 있다' 정도를 알고 지나가도록 하자.

1.3 함수 혹은 클래스 이름을 이용하여 브레이크 포인트 설정
잠시 이야기가 다른 곳을 샜지만 다시 본론으로 돌아와서..브레이크 포인트를 걸어야 할 소스가 눈에 바로 보인다면 위의 방법 처럼 마우스나 <F9>를 이용하면 좋지만, 소스 코드 어디에 쳐박혀 있는 한참을 헤메야 하거나, 브레이크 포인트를 걸어야 할 곳이 한 두 곳이 아니라면 '함수 혹은 클래스 이름을 이용하여 브레이크 포인트 설정'하는 법을 알면 편하다.

위에서 설명한 '브레이크 포인트 정보'창을 띄워 보도록하자. Ctrl+Alt+B를 누르면 편하다. 키매핑을 변경했다면 직접 매뉴를 하나씩 클릭하면서 찾아 가도록 하자.

브레이크 포인트 정보 창을 띄웠다면 'New'를 클릭하자. 그냥 그 창에서 마우스 오른 클릭을 하여 '새 브레이크 포인트(New Break point)'를 선택해도 된다. 그럼 당장 눈에 보이는 것이 바로 함수(Function)탭이다.
사용자 삽입 이미지
현재 내가 사용하고 있는 Visual studio는 영문판이라 영어로 나오지만, 한글판을 사용하는 사람에게는 함수라고 한글로 나올것이다. 각설하고 위에 커서가 움직이는 곳에 브레이크 포인트를 걸 함수 이름을 적어 주면 만사 오케이다. 다만 C++같이 대소문자 구분을 하는 경우라면 여기에서도 대소문자를 구분해주어야 하고, 함수이름을 완벽하게 써줘야 한다는 것이다. 어줍잖게 쓰면 '정보가 부족한데도 계속 브레이크 포인트를 걸고 싶니?'라고 물어 온다. 괜시리 서로 피곤해지지 말고 한번에 잘 적어주자.

만일 함수가 중복(오버로드)된 것들이 많이 있다면 특정 클래스 내에서 한계를 긋고 싶기도 할 것이다. 그럴경우 C++이라면 네임스페이스를 찍어주는 것처럼 'ClassName::FunctionName'과 같은 형식으로 입력해주면 된다.  그림이 들어 가서 길어 보이기는 하지만 간단한 내용이다. 함수 이름 똑바로 써라. 그것이 핵심이다.

2. 특정 조건에서만 브레이크 포인트가 걸리도록 하기
디버깅을 하다 보면 매번 브레이크 포인트에 걸리는 것을 원하지 않을 경우가 있다. 예를 들어 for루프를 1000번 도는데 버그는 999번째에만 발생한다고 생각해 보자. 그럼 어떻게 할 것인가? Go next(F10)을 999번 눌러야 할까? 다행이 MS는 그렇게 멍청하지 않아 우리가 그토록 수고 할 필요가 없도록 했다. 간단하게 말하자면 특정 조건을 만족 할 경우에만 디버깅 중에 브레이크가 걸리 도록 할 수 있다는 말이다.

2.1 적중 횟수(Hit count)
위(1.2 브레이크 포인트 정보 보기)에서 한번 언급했고, 위의 'New Break point' 이미지에서도 확인했다. 바로 'Hit count'!! 한국말로 하자면 적중횟수가 되겠지만(혹자는 생략 횟수라고도 한다) 그냥 편하게 영어로 진행하도록 하겠다. 이 Hit point 라는 녀석의 용도가 무엇인고 하니, 바로 '지정된 코드가 특정 횟수 이상 반복 되기 전까지는 브레이크 포인트를 활성화 하지 않게 하는 것'이다. 이것을 이용하면 루프내에서 적절한 시기에 브레이크 포인트를 활성화 하는 것이 쉬워 진다.

Hit count를 설정하는 방법은 상당히 간단하다. 첫째로 소스 코드 에디터 왼쪽에 있는 브레이크 포인터(일명 빨간콩)을 오른쪽 클릭하여 'Breakpoint Porperties'를 클릭하는 것이다. 아니면 1.2장에서 설명한 '브레이크 포인트 정보'창에서 원하는 브레이크 포인트에 오른 클릭하여 Properties를 선택 해도 된다.
사용자 삽입 이미지
만일 기존의 브레이크 포인터에 속성을 조절하는게 아니라 새로운 브레이크 포인트를 만드는 것이라면 'New Break point'창에서도 Hit Count를 조절 할 수 있다.

Hit count는 기본값으로 항상 적용되도록 되어 있지만 총 네가지 방법으로 그 값을 조절 할 수 있다 :

적중 횟수 적용

설명

항상 중단(break always)

해당 위치에서는 항상 멈춘다.

적중 횟수가 같은 경우 중단

(break when the hit count is equal to)

해당 위치가 정해진 횟수만큼 실행 되었을 때 멈춘다. 횟수는 1부터 시작한다.

적중 횟수가 배수일 경우 중단

(break when the hit count is multiple of)

해당 위치의 실행 횟수가 x 배수인 경우 멈춘다.

적중 횟수가 크거나 같은 경우 중단

(break when the hit count is greater than or equal to)

적중 횟수가 정해진 횟수에 도달할 때까지 멈추지 않다가 그 후로는 계속 활성화.


간단하게 요약하자면 Hit count 기능을 잘 이용해 디버거에서 프로그램이 중단 되었을 때 프로그램이 얼마나 실행 되었는지 쉽게 유추 할 수 있다는 것이다. 그리고 힘들게 go next를 누를 필요가 없다는 소리이기도 하다.

2.2 조건 표현식(Condition)
위에서는 특정 횟수 이상 코드를 실행하게 되면 브레이크 포인트가 활성화 되는 방법에 대해 알아 보았다. 그렇다면 이제는 횟수가 아닌 특정 조건일 경우에만 브레이크 포인트가 활성화 되는 방법에 대해 알아 보도록 하자. 여기서는 Condition이라고 하겠다. 1.2 장에서 이미 언급한 바가 있고, 이제껏 나온 그림들을 봐도 Hit count가 나올 때 항상 빼 놓지 않고 나오는 'Condition' 이 있다. 한글로 번역을 하자면 '조건 표현식' 정도가 되겠지만 메뉴 그대로를 따라 가도록하자(한글 VS에는 뭐라고 나오는지 모르겠다).

이 'Condition'이라는 것의 용도가 무엇인고 하니 특정 조건을 만족하거나, 마지막으로 비교된 후로 값이 변경되었을 때만 브레이크 포인트를 활성화 시킬 수 있는, 우리들의 디버깅 시간을 눈물나게 고마울 정도로 줄여 주는 유용한 툴인 것이다.

일반적으로 'Condition'은 우리가 if 문안에서 생각할 수 있는 표현식을 지원한다. 그리고 지역 변수나 전역 변수들은 현재 실행 중인 컨텍스트에서 평가하기 때문에 이 표현식에서 사용이 가능하다. 기본적으로 조건식이 참인 경우 브레이크 포인트를 활성화 시키도록 되어 있으나 간단한 변경을 통해서 값이 변경되는 경우에 브레이크 포인트를 활성화 시킬 수 있다.
사용자 삽입 이미지
참고 할 사항은 조건이 처음으로 평가되는 순간이 조건을 입력 할 때가 아니라, 조건이 처음으로 실행 되는 순간이라는 것이다. 하지만 조건은 아직 한번도 평가된 적이 없기 때문에 디버거는 내부에 조건식에 필요한 정보를 저장하지 못하고 있다. 결국 브레이크 포인트가 활성화 되기 위해서는 해당 조건이 두번 실행 되는 경우다.

간단 예를 만들기 위해 특정 상황을 가정해 보자. 멀티 쓰레드를 이용하는 어플리케이션을 디버깅을 하다보면 여러 쓰레드에서 동일한 코드를 호출하여 쓰레드 하나 마다 디버깅이 멈춰져 매우 귀찮음을 느낀 사람들이 있을 것이다. 특정 쓰레드일 경우일 경우만 해당 브레이크 포인트를 활성화 시키는 조건식을 만들어 보도록 하자 :
  1. 먼저 쓰레드 아이디를 알아야 한다. 쓰레드 아이디는 디버깅 중에만 알 수 있다. 디버깅을 시작하고 'Ctrl+Alt+H' 나 'Debug > Windows > Threads' 를 선택하면 쓰레드 정보를 보여주는 창을 띄울 수 있다. 해당 정보 중에서 필요한 쓰레드(이름이라던지 location이라던지 활성화 화살표를 참고 하면 알 수 있다)를 찾아 아이디를 외운다(아니면 어디 적어 둬도 된다).
  2. 조건식을 입력 하는 창(Breakpoint Condition)에서 '*(long*)($TIB+0x24) == <thread id>' 조건식을 입력한다. $TIB는 쓰레드 관련 정보가 저장되어 있는 레지스터를 나타내고, 거기에 0x24를 더해주는 것은 나중에 쓰레드 정보 구조체를 살펴 보면 알 수 있을 것이다. 지금은 그냥 그러려니 하고 넘어가도록 하자.
  3. 마지막으로 디버깅을 계속 진행 하면 위에서 지정한 쓰레드 아이디를 가진 쓰레드에서만 함수가 호출 되는 것을 확인하자.
이 외에도 디버깅을 하는데 도움을 주는 도구들이 많이 있다. 예를 들자면 위에서 언급한 $TIB 같은 레지스터 같은 것들이다. 기회가 닿는다면(내가 게으름을 이긴다면...) 다음 번에는 레지스터를 이용한 디버깅에 대해서 다뤄보고 싶다.

참고 : Debugging Applications for Microsoft .Net and Microsoft Windows

'도구의발견' 카테고리의 다른 글

cvs 파일 상태 기호  (2) 2008.08.19
GDB Tutorial  (0) 2008.08.19
Break point  (0) 2008.08.18
vim Undo & Redo  (3) 2008.08.06
개발자를 위한 .vimrc 설정  (0) 2008.07.15
SVN 사용하기  (0) 2008.04.30
Posted by kukuta

댓글을 달아 주세요