본문 바로가기

Programming

Intel PIN Tutorial

PIN 은 인텔에서 만든 실행파일 분석기로서 Windows / Linux 상의 x86, 64 바이너리를 지원한다. 기본적으로 PIN 과 Pintool 은 다르다, PIN 자체는 stand alone 프로세스이며 자세한 구현은 모르지만 일종의 gdb 가 다른 프로세스를 디버깅해서 돌리듯이 분석대상 프로세스를 띄울 본체가 된다.  Pintool 은 공유라이브러리로서 PIN 이 분석대상 프로세스를 자신의 위에서 실행시킬때 어떠한 분석을 할지를 PIN API 를 이용하여 프로그래밍해 넣는부분이다. PIN 에 대한 소개는 구글링하면 아주 잘 나와있으므로 생략하고... 여기서는 리눅스상에서 PIN 을 설치하고 본격적으로 PIN API 를 이용한 pintool 개발을 시작하기위한 단계까지만을 정리한다.



1. PIN 다운로드 및 설치

http://software.intel.com/en-us/articles/pintool-downloads

리눅스에밖에 안해봤지만 컴파일할필요없이 바로 바이너리를 다운받을 수 있다. tar.gz 형태의 파일을 받아야하길래 컴파일해서 쓰라는건줄 알았는데 까보면 pin 이라는 바이너리가 이미 있고, 그대신 pintool 공유라이브러리 개발을 위한 수많은 예제들에 대한 소스코드와 헤더파일등이 첨부되어있다. 다운받아서 압축을푼다음 pin 바이너리가 있는 경로를 /etc/environments 에 추가해주자.



2. PINTool 컴파일

make 로 컴파일을 해야하는데, 기본적으로 커널모듈 make 할때랑 비슷하다. 일단 샘플 소스들이 있는 위치를 보자.

디렉토리 구조에서 특별히 파악할건없고 source/tools 디렉토리에 온갖 샘플 예제코드들이 존재한다. 위의 그림에서 각 디렉토리마다 makefile 과 makefile.rules 가 존재한다. makefile.rules 속에보면 아래와 같이 컴파일할 obj 파일명을 적어서 나열해주는 TEST_TOOL_ROOTS 라는 변수가 있다. 여기에 컴파일하고싶은 소스코드를 스페이스바로 이어붙여서 추가해주고 make all 을 하면 전부 컴파일한다. 특정한 파일만 컴파일하고싶으면 make dir obj-ia32/[소스파일명].so 과 같은식으로 해주면된다.  여기서 obj-ia32 는 상위 makefile 에서 약속된 이름이다. 만약 x64 시스템인경우 obj-intel64 로 지정해줘야한다.

pintool 의 결과물은 so 파일이 된다.



3. PINTool 사용

소스코드로부터 so 파일을 컴파일했으면 다음과 같이 사용한다


pin -t [so파일경로] -- [분석대상프로그램경로]


아래는 샘플소스중에 프로그램 시작부터 종료시까지 수행된 명령어의 갯수를 세는 예제이다. 소스코드는 C++ 로 되어있으며 분석결과를 파일로 출력한다. 여기서 중요한건 분석대상 프로그램과 pintool 공유라이브러리는 같은 프로세스라는 점이다. 따라서 파일과같은 프로세스 전역적인 공유자원을 사용할때는 신경써줘야한다. 아래의 예제는 docount 라는 함수를 정의하고 이것을 INS_InsertCall 이라는 PIN API 에 IPOINT_BEFORE 타입으로서 등록한다.

main 함수에서 INS_AddInstrumentFunction 이라는 PIN API 를 통해서 Instruction 이라느 함수를 콜백함수로 등록한다. 그렇게되면 타겟프로세스가 매 인스트럭션을 실행할때마다 Instruction 이 호출되게 된다. 여기서 카운팅을 해도 상관없지만 예제이므로.. 다시 INS_InsertCall API 를 써서 Instruction 함수의 파라미터로 전달받은 ins 명령어의 실행전에 호출될 콜백함수를 다시 등록하고, 거기서 카운팅을 한다. INS 는 명령어를 나타내는 pin 타입변수인데 적절한 API 를 써저 disassemble 된 string 으로 뽑아낼 수도 있다.  그리고 OP 코드의 종류별로 각종 기능들을 만들수도 있고 아무튼 할수있는건 무궁무진하다...


// inscount0.cpp

#include <iostream>

#include <fstream>

#include "pin.H"


ofstream OutFile;


// The running count of instructions is kept here

// make it static to help the compiler optimize docount

static UINT64 icount = 0;


// This function is called before every instruction is executed

VOID docount() { icount++; }

    

// Pin calls this function every time a new instruction is encountered

VOID Instruction(INS ins, VOID *v)

{

    // Insert a call to docount before every instruction, no arguments are passed

    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);

}


KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",

    "o", "inscount.out", "specify output file name");


// This function is called when the application exits

VOID Fini(INT32 code, VOID *v)

{

    // Write to a file since cout and cerr maybe closed by the application

    OutFile.setf(ios::showbase);

    OutFile << "Count " << icount << endl;

    OutFile.close();

}


/* ===================================================================== */

/* Print Help Message                                                    */

/* ===================================================================== */


INT32 Usage()

{

    cerr << "This tool counts the number of dynamic instructions executed" << endl;

    cerr << endl << KNOB_BASE::StringKnobSummary() << endl;

    return -1;

}


/* ===================================================================== */

/* Main                                                                  */

/* ===================================================================== */

/*   argc, argv are the entire command line: pin -t <toolname> -- ...    */

/* ===================================================================== */


int main(int argc, char * argv[])

{

    // Initialize pin

    if (PIN_Init(argc, argv)) return Usage();


    OutFile.open(KnobOutputFile.Value().c_str());


    // Register Instruction to be called to instrument instructions

    INS_AddInstrumentFunction(Instruction, 0);


    // Register Fini to be called when the application exits

    PIN_AddFiniFunction(Fini, 0);

    

    // Start the program, never returns

    PIN_StartProgram();

    

    return 0;

}


비슷하게 다른 샘플예제들도 많이 있다.  아래는 샘플예제를 가지고 cat 에 대한 분석을 간단하게 뽑은 결과이다.



기본적으로 Basic Block 별로 콜백을 걸수도 있고, 라이브러리가 로딩되는 시점을 잡을수도 있으며, 각각의 라이브러리별 수행된 명령어를 구별하고, 메모리 접근종류에따라서도 구분할수있다.

더 자세한 설명들은 아래의 메뉴얼에 잘 나와있다...

http://software.intel.com/sites/landingpage/pintool/docs/49306/Pin/html/index.html



윈도우즈의 경우에는 Visual Studio 가 기 설치되어있어야 하는데, Visual Studio 버전에 맞는 PIN 패키지 zip 파일을받으면 똑같은 디렉토리 구조를 갖는데, 샘플소스에 대해서 Visual Studio 프로젝트 파일이 존재한다. 열어서 컴파일하면 DLL 이 생성되고 나머지는 아래와같은 식으로 똑같이 하면된다. pin.exe 환경변수에 넣고 옵션 똑같이...





'Programming' 카테고리의 다른 글

Debugging multi-threaded application with GDB attaching  (0) 2014.03.18
Linux DDD Debuger  (0) 2014.03.13
Linux gdb ASLR disabling  (0) 2014.03.04
IDA/gdb Debugging After Attaching  (0) 2014.03.03
Building KVM from source in Ubuntu  (1) 2014.02.13