본문 바로가기

4.개발 및 운영 환경

함수 호출 시각화 하기

다음 내용은 M. Tim Jones의 "Graphviz를 이용하여 함수 호출을 그림으로 나타내기"를 간략하게 요약해서 정리한 내용이다. 자세한 내용은 아래 출처를 참고하길 바란다.

다음 글은 소스 분석시 상당히 유용한 팁이다. 결과물로 전체 시스템 구조를 한꺼번에 파악할 수 있다. 그러나 상당히 복잡한 결과를 얻어내기 때문에 쉽게 분석하기 어려울 수 있다. 그만큼 코드 자체도 복잡하게 구성되었기에 코드를 직접보면 더욱 이해하기 힘들 것이다.

작성자: 박재성(ospace114@empal.com), 2007.10.31

전체적인 작업 순서는 아래 그림과 같다.


그림1. 트레이스 컬렉션, 감소 , 시각화 프로세스
(출처: Grpahviz를 이용하여 함수호출을 그림으로 나타내기)

단계1: 데이터 컬렉션

함수 호출을 트레이스 하여 캡쳐하기

GNU에서 함수 호출에 대한 기록은 GNU 프로파일링 함수를 사용하면 된다.

void __cyg_profile_func_enter( void *func_address, void *call_site )
                               __attribute__ ((no_instrument_function));

void __cyg_profile_func_exit ( void *func_address, void *call_site )
                               __attribute__ ((no_instrument_function));

각 함수는 함수 호출 전에 호출 후에 불러지게 된다. 그리고 해당 함수 호출 전후에 불려질때 해당 함수 포인터 주소가 func_address로 넘겨지고, 해당 함수를 불러오는 곳의 주소가 call_site에 넘겨진다. 이렇게 해서 함수 호출과 종료시에서 중요한 정보를 추출하게 된다.

위 함수를 사용하기 위해서는 __cyg_* 프로파일링 함수가 설치되어 있어야 한다. 그리고 이 함수를 사용하기 위해서는 아래의 간단한 추가 작업이 필요하다.

컴파일 옵션: -finstrument-function

그리고 디버그 옵션(-g)을 같이 사용한다.

no_instrument_function은 함수 애트리뷰트는 프로파일링 함수에 제공되어 프로파일링 함수가 루프가 되지 않도록하여 필요없는 추가 데이터를 생기지 않도록 한다.

간단한 구현예를 보자. 각 호출되는 함수 포인터를 파일로 저장되는 예제이다.

void __cyg_profile_func_enter( void *this, void *callsite )
{
  /* Function Entry Address */
  fprintf(fp, "E%p\n", (int *)this);
}


void __cyg_profile_func_exit( void *this, void *callsite )
{
  /* Function Exit Address */
  fprintf(fp, "X%p\n", (int *)this);
}

위의 함수를 사용하기 위해서 프로파일링 생성자와 소멸자 함수를 만들고 파일 포인터도 초기화하는 함수를 정의 한다.

/* Constructor and Destructor Prototypes */

void main_constructor( void )
__attribute__ ((no_instrument_function, constructor));

void main_destructor( void )
__attribute__ ((no_instrument_function, destructor));


/* Output trace file pointer */
static FILE *fp;

void main_constructor( void )
{
  fp = fopen( "trace.txt", "w" );
  if (fp == NULL) exit(-1);
}


void main_deconstructor( void )
{
  fclose( fp );
}

단계2: 데이터 감소

앞에서 추출된 값은 함수 주소만 들어있는 텍스트 파일이다. 실제 함수 명을 알지 못한다. 그래서 해당 주소 값을 함수명으로 변환할 수 있는 값이 필요하다. 이때 필요한게 함수맵이다.

함수 맵을 생성해보자.

$ cat >> test.c
#include <stdio.h>

int main()
{
  printf("Hello World\n");
  return 0;
}
<ctld-d>
$ gcc -Wl,-Map=test.map -g -o test test.c
$ grep main test.map
0x08048258 __libc_start_main@@GLIBC_2.0
0x08048258 main
$ addr2line 0x08048258 -e test -f
main
/home/mtj/test/test.c:4
$

함수맵이 test.map이라는 파일로 생성되었다. 한번 addr2line으로 시험해보자. addr2line은 해당 함수 주소를 홤수 명으로 변환해주는 역활을 한다. 해당 주소가 main 함수라는 것을 표시해주고 있다.

이제 함수 트레이스 데이터를 줄여보자.


그림 2. 트레이스 데이터를 매트릭스 폼으로 파싱 및 줄여나가기
(출처: Graphviz를 이용하여 함수 호출을 그림으로 나타내기)

trace.txt에 데이터에서 E와 X가 있는데 E는 함수로 들어가는 것이고 X 는 함수에서 빠져나오는 의미이다. 그러면 함수가 어떤 함수를 호출하는지 분류할 수 있다.

E0x8048f00은 E로 호출되면 다음 E0x8048711로 다시 함수로 들어가기데 0x8048f00에서 0x8048711함수를 호출한다. 그리고 다시 E0x8048658이고 현재 0x8048711 함수 안에 있기 때문에 이 함수에서 0x8048658 함수를 호출한다. 다시 X0x8048658은 0x8048658을 빠져나가 0x8048771로 돌아간다. 그리고 다시 빠져나가면서 처음함수로 들어간다. 이렇게 해서 각 함수간의 호출 관계를 아래 표처럼 정리할 수 있게된다.

이 자료로 가지고 Graphviz에서 그래프로 표현할 수 있다.

단계3: 시각화하기

Graphiviz의 간단한 예를 보면 다음과 같다. 먼저 그래프로 그림 샘플 그래프를 정의한다.

1:  digraph G {
2:    node1;
3:    node2;
4:    node3;
5:
6:    node1 -> node2 [label="edge_1_2"];
7:    node1 -> node3 [label="edge_1_3"];
8:    node2 -> node3 [label="edge_2_3"];
9:  }

이를 Graphviz를 이용해서 이미지 파일로 변환한다.

$ dot -Tjpg test.dot -o test.jpg

결과 이미지는 다음과 같다.

이제 앞에서 데이터 정리한 내용을 가지고 그래프로 변환하면 끝이다. 이렇게 변환하는 작업을 만들어야 한다. 참고 문서에서 이를 작업해주는 툴이 있다. Pvtrace 유틸리티이다. PVtrace 유틸리티에서 Addr2line도 같이 해주기 때문에 작업이 단순하게 이뤄진다.

pvtrace.zip
3.8 kB

앞의 작업이 단순하게 이뤄지게 된다. 그러면 전체 작업 과정을 다시 보면 다음과 같다.

$ ls
instrument.c    test.c
$ gcc -g -finstrument-functions test.c instrument.c -o test
$ ./test
$ ls
instrument.c     test.c
test             trace.txt
$ pvtrace test 
$ ls
graph.dot test trace.txt 
instrument.c test.c 
$ dot -Tjpg graph.dot -o graph.jpg
$ ls
graph.dot        instrument.c   test.c
graph.jpg        test           trace.txt
$

참고하는 글에서 사용하는 예제로 만든 그래프 이미지는 다음과 같이된다.


(출처:Graphviz를 이용하여 함수 호출을 그림으로 나타내기)

한 눈에 각 함수에서 어떤 값을 혹은 함수를 호출하는지 쉽게 눈으로 확인 할 수 있다. 그리고 각 함수 호출 개수까지 확인할 수 있다.

상당히 유용한 팁이다. M. Tim Jones에게 감사를 드린다.

Thank you so much, M. Tim jones, for this article.

출처: http://www.ibm.com/developerworks/kr/library/l-graphvis/index.html

반응형

'4.개발 및 운영 환경' 카테고리의 다른 글

디버깅툴 dbgview  (0) 2008.08.13
프로그래밍 언어 사용 비율  (0) 2007.12.12
SourceSafe에서 Solution 삭제  (0) 2007.06.08
An outline HTA for a MS word processing system  (0) 2007.05.10
Singleton  (0) 2006.11.28