2012년 1월 8일 일요일

Purify - 고급 사용법

출처 : http://www.ibm.com/developerworks/rational/library/08/0226_gupta-gaurav/index.html

본 자료는 Purify 옵션지시문에 대한 개요를 먼저 소개하고 다음 항목에 대한 설명을 하고자 합니다:
  • 캐시 관리: 삽입한 바이너리 파일의 관리 및 공유하는 방법
  • 부분 Purify: 프로그램의 일부부만을 삽입하여 실행 시간 오버헤드와 탐지 영역을 줄이는 방법
  • 힙 메모리 관리: 프로그램 실행시 메모리 오버헤드를 제한하고 힙 사용 보고서를 맞춤 설정하는 방법
Purify 옵션

Purify에서는 아래의 두가지 옵션을 통해 세밀한 제어를 제공합니다.
  • 빌드타임 옵션
  • 런타임 옵션
빌드타임 옵션은 삽입시에 사용됩니다. 예를 들어, 정적 데이터에 대한 버퍼 오버런을 체크하지 않을 경우, 삽입시에 -static-checking 옵션을 사용합니다:
$ purify -static-checking=no cc your_prog.c

런타임 옵션은 삽입된 프로그램의 런타임 행위에 영향을 줍니다. 예를 들어 보고된 에러에 대한 콜 스택 체인을 더 길게 보여주고자 할 때 아래와 같은 옵션을 사용합니다:
$ purify -chain-length=10 cc your_prog.c

옵션은 링크 라인 뿐아니라 환경변수 PUREOPTIONS 와 PURIFYOPTIONS 를 통해 표현할 수 있습니다.
  • 환경변수 PUREOPTIONS는 Purify, Quantify 및 PureCoverage에도 적용됩니다.
  • 환경변수 PURIFYOPTIONS는 Purify에만 적용됩니다.
sh, ksh, 및 bash 쉘을 사용하는 경우:
$ export PURIFYOPTIONS="-static-checking=no -chain-length=10"

csh 또는 tcsh 쉘을 사용하는 경우:
% setenv PURIFYOPTIONS "-static-checking=no -chain-length=10"

링크 라인에 명시된 빌드타임 옵션은 환경변수에 나타난 같은 옵션을 덮어씁니다.

Purify는 삽입시점의 런타임 옵션을 삽입된 프로그램에 저장합니다. 실행시, (-ignore-runtime-environment 빌드타임 옵션을 사용하지 않는 경우) 환경변수는 삽입된 프로그램에 저장된 런타임 옵션을 덮어씁니다.

Purify GUI를 통해 명시한 런타임 옵션은 환경변수 및 링크라인에 명시된 옵션을 덮어씁니다.

Purify는 도움말 및 버전 문자열을 제공하기 위해 여러 빌드타임 옵션을 제공합니다:
$ purify -version 
$ purify -usage 
$ purify -help 
$ purify -onlinehelp


-print-home-dir 빌드타임 옵션은 Purify가 설치된 디렉토리를 출력합니다. 이를 이용하여, 아래처럼 purify_what_options 스크립트를 실행하여 삽입시 사용된 옵션을 알아볼 수 있습니다:
$ `purify -print-home-dir`/purify_what_options <your_prog.pure>


Purify API를 사용하기 위해 purify.h를 프로그램에 포함시키는 경우, 컴파일러의 헤더파일 탐색경로를 아래처럼 표기할 수 있습니다:
$ cc -c -I`purify -print-home-dir` your_prog.c

Purify 지시문


커맨드 라인 옵션외에 추가로 다양한 지시문을 명시함으로써 삽입 및 에러 보고에 대한 상세 제어를 할 수 있습니다.

예를 들어 써드파티 라이브러리(libfoo)에 있는 함수(foo)에 메모리에러(uninitialized memory read, UMR)가 있다고 가정해 봅니다. 라이브러리 제공 벤더에 에러를 보고했고, 이에 대한 픽스를 기다리는 동안에, Purify 분석 결과에서 해당 에러를 보이지 않게 하고 싶다면, suppress 지시문을 아래와 같이 사용합니다:
suppress umr foo

만약 suppress 지시문을 통해 보이지않는 에러를 보고싶다면 Purify GUI의 View 메뉴에서 Suppressed를 클릭합니다.

Purify는 아래 예처럼 구체적으로 또는 일반적으로 기술할 수 있도록 합니다:
  • 특정 콜 체인 상의 UMR를 숨기고자 할때는 전체 콜 체인을 제시합니다:
    suppress umr printf; foo; bar; main

  • 일부 콜 체인과 매칭되는 UMR를 숨기고자 할때는 생략(...) 기호를 통해 제시합니다:
    suppress umr ...; foo; ...; main

  • 특정 라이브러리의 모든 UMR를 숨기고자 할때는 해당 라이브러리를 제시합니다:
    suppress umr "libfoo*"

  • 모든 UMR를 숨기고자 할때는 다음과 같습니다 (물론 위험천만한(?) 생각입니다):
    suppress umr *

Purify는 지시문이 저장된 세곳의 .purify 파일을 다음 순서로 검색합니다:
  1. <purify-installation-home>/.purify
  2. <users-home-dir>/.purify
  3. <current-working-directory>/.purify
모든 Purify 사용자, 프로젝트 사용자, 자기 자신 등 맞춤 설정 영역에 맞게 해당 파일을 수정하여 지시문을 제공합니다.

또 다른 방법으로 Purify GUI에서 에러를 선택하고 팝업메뉴에서 Suppress를 선택할 수 있습니다. 그리고 그림 1에서 처럼, 에러타입, 콜체인타입 등을 명시할 수 있습니다. .purify 파일 위치를 지정한 후 Make permanent를 클릭하여 저장할 수도 있습니다.

그림 1. Purify Suppression 대화상자
Purify Suppression dialog screen capture

또다른 지시문 kill은  suppress와 거의 유사하지만,  GUI에서 View > Suppressed Messages 메뉴를 사용해도 볼 수 없습니다. 어떤 에러가 아주 많이 발생하는 경우에 suppress 대신 kill을 사용하면 프로그램 실행이 더 빨라집니다.


캐시 관리


프로그램에 대한 삽입시에, Purify는 프로그램에서 직접 또는 간접적으로(예 libc) 사용하는 모든 라이브러리들 또한 삽입을 하고 삽입된 라이브러리를 원 라이브러리 대신 프로그램과 바인딩을 합니다. 만약 라이브러리가 있는 디렉토리에 쓰기권한이 있다면 해당 라이브러리 디렉토리에 삽입된 라이브러리를 생성합니다.

만약 쓰기 권한이 없다면, Purify 설치 영역내 디폴트 캐시 디렉토리에 삽입된 라이브러리를 생성합니다.
이러한 캐시관련된 여러 빌드타임 옵션이 있습니다. 삽입된 라이브러리를 여러 디렉토리에 산재하지 않고 캐시 디렉토리에 저장하기를 원한다면 -always-use-cache-dir 옵션을 사용합니다. 디폴트 캐시 디렉토리 대신 별도 캐시 디렉토리를 지정하길 원한다면 -cache-dir=<dir-name> 옵션을 사용합니다. 이러한 옵션들을 통해서, 손쉽게 캐시 디렉토리를 삭제함으로써 모든 삽입된 라이브러리를 제거할 수 있습니다.

프로그램에 대한 삽입시에, Purify는 마지막 삽입이후 변경이 있거나 삽입된 라이브러리를 찾을 수 없는 경우에만 라이브러리를 삽입합니다. 이는 삽입 시간을 절약하는 데 도움이 됩니다. 만약 이전에 삽입된 라이브러리를 무시하고 모든 필요한 라이브러리를 삽입하고자 할때는 -force-rebuild 옵션을 사용합니다. 이는 고급 옵션 (예 -static-checking-guardzone)를 사용하여 재 삽입을 강제할 경우 유용합니다.


일반적으론, 하나의 머신에서 삽입한 프로그램은 다른 머신에서 실행할 수 없습니다. 일반적으로 프로그램은 시스템 라이브러리들(예 libc)에 의존하는 데, 시스템 라이브러리들은 OS 패키지들의 다양한 패치 수준에 따라서 머신마다 달라질 수 있습니다.

프로그램이 같은 시스템의 원래 라이브러리에 상응하는 삽입된 라이브러리를 사용하도록 보장하기 위해서, Purify는 삽입된 라이브러리를 호스트 이름 별로 캐시하는 메커니즘을 제공합니다.

한편, 프로그램은 모든 머신에서 똑같거나 같은 네트워크 위치로 접근할 수 있는 라이브러리나 써드파티 라이브러리를 사용할 수도 있습니다. 이런 경우, 각각의 호스트별로 삽입된 해당 라이브러리를 가지고 싶어하지 않을 겁니다. 사본이 똑같기에 저장공간을 낭비하기 때문입니다. 이를 위해 Purify는 IBM® AIX®, Linux® 및 Solaris® UNIX® 플랫폼에서 repure 메커니즘을 제공합니다. 다만 삽입시에 다음과 같은 점을 주의해야 합니다:
  1. 먼저, 모든 머신상에 존재하는 로컬 경로를 선정합니다.예를 들어 /tmp 디렉토리는 모든 머신에 있지만 로컬 경로이고, 머신 간에 공유되고 있지 않습니다. 팁: 홈 디렉토리나 NFS 상의 디렉토리를 사용하지 마십시요.
  2. -local-cache-dir 옵션을 사용하여, 시스템에 특화된 라이브러리의 삽입 결과를 저장할 경로를 지정합니다. 예:
    $ purify -always-use-cache-dir -local-cache-dir=/tmp -cache-dir=./cache \
     cc -g test.c -o test.pure
  3. 같은 머신에서 삽입된 프로그램을 실행할 수 있습니다:
    $ ./test.pure

  4. 다른 머신에서 삽입된 프로그램을 실행하길 원한다면, 다른 머신에서 repure를 실행합니다:
    $ repure ./test.pure

  5. repure 실행 후  다른 머신에서 프로그램을 실행할 수 있습니다:
    $ ./test.pure

오래된 삽입된 파일들을 삭제하고 싶다면, Purify 설치 영역의 pure_remove_old_files 스크립트를 실행합니다:
`purify -print-home-dir`/pure_remove_old_files <path> <days>

이 스크립트는 _pure_를 포함한 파일이나 .so 나 .sl 확장자를 갖는 공유 라이브러리 파일등 삽입된 라이브러리들만을 제거합니다. 예를 들어 파일시스템 상의 14일 이상된 모든 삽입된 파일을 삭제할 경우, 아래와 같이 실행합니다:
$ pure_remove_old_files / 14



부분 Purify


Purify는 부당한 메모리 엑세스를 체크하기 위해 관심있는 곳에 삽입을 합니다. 체크를 하고, 체크에 필요한 데이터를 관리하는 것은 성능 상의 오버헤드를 초래합니다. 때때로 프로그램의 특정 콤포넌트들에 있는 메모리 에러에만 관심이 가능 경우도 있습니다. 이러한 경우 부분적으로 프로그램에 삽입을 하고 관심없는 공유 라이브러리를 배제할 수 있는 메커니즘을 통해 삽입된 프로그램 실행시 실행시 오버헤드를 많이 줄일 수 있습니다.


주의:
부분 삽입은 HP-UX와 AIX 플랫폼에서만 동작합니다. Solaris SPARC 플랫폼은 제한적으로 지원하고, Solaris x86 및 Linux 플랫폼은 지원하지 않습니다.
공유 라이브러리를 배제하더라도, Purify는 배제된 공유라이브러리에서 할당받은 메모리 누스 등 힙 관리 에러를 탐지합니다.
부분 삽입을 통해 아래처럼 라이브러리를 배제할 수 있습니다:
$ purify -selective -exclude-libs=libfoo1.so:libfoo2.so cc -g app.c \
 -o a.out.pure -lfoo1 -lfoo2 -lbar

-exclude-libs 옵션에 콜론으로 나열한 라이브러리 목록을 제공하거나 .purify 지시문 파일에 exclude 지시문을 사용할 수도 있습니다:
exclude libfoo*

-selective 옵션을 사용하면 Purify가 라이브러리 배제로 인해 발행할 수 있는 에러를 탐지해서 배제할 수 있도록 지시합니다:
  • 예를 들어 main()이 foo()를 호출하고, foo()는 bar()를 호출하는 경우에 foo()를 삽입에서 배제한다고 가정합니다. 추가로 main()에서 메모리를 할당받고, foo()에서 메모리를 초기화 하고, bar()에서 메모리를 사용한다고 가정합니다. foo()를 삽입처리하지 않은 경우, Purify는 foo()에서 메모리가 초기화되었다는 것을 알지 못하므로, bar()에서 메모리를 사용할 때, uninitialized memory read (UMR) 에러를 보고할 수 있습니다. -selective 옵션은 이런 경우 Purify가 이런 류의 에러를 제거하도록 지시합니다.
  • 라이브러리를 배제한 경우 Purify가 몇몇 에러를 찾지 못할 수 있습니다. 예를 들어 main()이 foo()를 호출하고 초기화되지 않은 버퍼를 전달한다고 가정합니다. Purify는 foo()에서 버퍼를 사용할 때마다 UMR를 보고합니다. 만약 foo()가 libfoo.so 공유라이브러리에 있고, 이를 배제했다고 한다면, Purify는 관련 메모리 체킹 코드를 삽입하지 못했기 때문에, 삽입된 코드에서 실행이 된다고 하더라도 foo()에서 초기화하지 않은 버퍼를 사용하더라도 탐지를 할 수 없습니다.
위의 두개의 예는 포괄적인 에러탐지와 실행시의 오버헤드에는 상당한 상충이 있다는 것을 보여줍니다. 그러므로, 신중히 라이브러리의 역할을 파악해서 배제할 공유 라이브러리를 선택해야 합니다.

Purify는 삽입처리되지 않는 코드에서 발생한 버퍼 오버런 에러를 탐지할 수 있습니다만 에러가 발생한 시점이 아닌 버퍼가 해제되는 시점에 탐지합니다. Purify는 에러가 발생할 때 보고를 하지만 삽입되지 않는 코드에 대한 에러는 제때 보고할 수 없습니다. -late-detect-logic 옵션은 힙 메모리 블럭이 해제될 때 추가적인 체크를 하도록 합니다. 버퍼 오버런을 탐지한 경우, Array Bound Write Late (ABWL) 에러를 보고합니다.


힙 메모리 관리 에러

Purify 는 삽입된 프로그램 실행시 메모리 및 실행 시간 오버헤드를 제어할 수 있는 몇가지 옵션을 제공합니다. 프로그램이 메모리블럭을 해제할 때, Purify는 곧바로 해당 메모리를 해제하지 않습니다. 대신에, 해제된 메모리를 FIFO 큐에 넣습니다. 이를 통해 Purify는 해제된 메모리를 가리키는 포인터에 대한 에러(Free Memory Read/Write, FMR/FMW)를 탐지하고 보고할 수 있습니다. 만약 해제된 메모리가 바로 힙으로 반환됐다면 곧바로 재사용될 수 있었지만 Purify는 해제된 블럭에 대한 부당한 엑세스인지 할당된 블럭에 대한 합당한 엑세스인지 알수 없을 겁니다.

큐의 디폴트 크기는 100입니다. 큐가 꽉 차면, Purify는 큐의 첫번째 메모리 블럭을 해제한 뒤 새로 할당받은 블럭을 추가합니다. 큐의 크기는 -free-queue-length=<value> 옵션으로 변경할 수 있습니다. 큐 크기가 커지면 부당한 포인터를 탐지할 가능성을 높이지만, 메모리 오버헤드 비용은 커지게 됩니다..

프리 큐는 작은 블럭에만 사용되며 임계치 이하의 블럭을 관리합니다. 큰 블럭은 바로 해제하여 힙으로 넘겨서 많은 메모리 사용의 위험성을 회피합니다. -free-queue-threshold=<value> 옵션으로 해당 임계치를 명시할 수 있습니다. 디폴트는 10000 바이트입니다. 즉 10000 바이트 이상의 블럭은 곧바로 해제됩니다.

Purify는 프로그램 종료시점에 모든 메모리 누수를 보고합니다. 사용중인 메모리 블럭에 대한 정보가 필요하다면, -inuse-at-exit=yes 옵션을 사용합니다. 마찬가지로 프로그램 종료시에 사용중인 파일 디스크립터에 대한 정보가 필요하면, -fds-inuse-at-exit=yes 옵션을 사용합니다.

AIX 시스템에서는, 메모리 누수에만 관심이 있는 경우, -memory-leaks-only 옵션을 사용할 수 있으며, 이 경우 Purify는 메모리 누수 및 Freeing Memory Mismatch (FMM) 에러 같은 몇몇 힙 관리 에러 탐지를 위한 매우 가벼운 삽입처리를 합니다. 이로 인해 실행시 일상적인 메모리 엑세스 검증을 하지 않아서 실행이 훨씬 빨리집니다.

댓글 없음:

댓글 쓰기