Intel Pinでcmptraceを作ってみる

「Intel Pinを使ってみる」では、Intel Pinをダウンロードし、付属しているコードを使ってみた。 ここでは、実際にコードを書き、cmp命令のトレースを行うコードcmptraceを作ってみる。

環境

Ubuntu 14.04.4 LTS 64bit版

$ uname -a
Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.4 LTS
Release:        14.04
Codename:       trusty

$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4

なお、Intel Pinの実行ファイルが32 bit用バイナリであるため、ここでは「64 bitのUbuntu Linuxで32 bitの実行ファイルを動かす方法のメモ」に書いた方法で32 bit用バイナリも実行できるようにしてある。

プログラムコードを書いてみる

リファレンスを参考にしつつ、cmp命令のトレースを行うコードを書くと次のようになる。

// cmptrace.cpp
#include <cstdio>
#include "pin.H"

VOID print_cmp_mem(VOID *ip, UINT64 * addr, ADDRINT value) {
    fprintf(stderr, "[%016lx] cmp 0x%lx, 0x%lx\n", (UINT64)ip, *addr, value);
}

VOID print_cmp_reg(VOID *ip, ADDRINT lvalue, ADDRINT rvalue) {
    fprintf(stderr, "[%016lx] cmp 0x%lx, 0x%lx\n", (UINT64)ip, lvalue, rvalue);
}

VOID Instruction(INS ins, VOID *v)
{
    if (INS_Opcode(ins) == XED_ICLASS_CMP) {
        if (INS_MemoryOperandCount(ins) == 1) {
            if (INS_OperandIsImmediate(ins, 1)) {
                INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_cmp_mem, IARG_INST_PTR, IARG_MEMORYOP_EA, 0, IARG_ADDRINT, INS_OperandImmediate(ins, 1), IARG_END);
            } else if (INS_OperandIsReg(ins, 0)) {
                INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_cmp_mem, IARG_INST_PTR, IARG_MEMORYOP_EA, 0, IARG_REG_VALUE, INS_OperandReg(ins, 0), IARG_END);
            } else {
                INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_cmp_mem, IARG_INST_PTR, IARG_MEMORYOP_EA, 0, IARG_REG_VALUE, INS_OperandReg(ins, 1), IARG_END);
            }
        } else {
            if (INS_OperandIsImmediate(ins, 1)) {
                INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_cmp_reg, IARG_INST_PTR, IARG_REG_VALUE, INS_OperandReg(ins, 0), IARG_ADDRINT, INS_OperandImmediate(ins, 1), IARG_END);
            } else {
                INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)print_cmp_reg, IARG_INST_PTR, IARG_REG_VALUE, INS_OperandReg(ins, 0), IARG_REG_VALUE, INS_OperandReg(ins, 1), IARG_END);
            }
        }
    }
}

int main(int argc, char *argv[])
{
    PIN_Init(argc, argv);
    INS_AddInstrumentFunction(Instruction, 0);
    PIN_StartProgram();
    return 0;
}

上のコードにおいて、Instruction関数の中では、cmp命令が取るオペランドのパターンに応じて分岐を行っている。 そして、print_cmp_mem、print_cmp_reg関数で実際にメモリやレジスタに入っている値を標準エラー出力に表示する。

コードをコンパイルして実行してみる。

$ make cmptrace.test TARGET=intel64

$ ../../../pin -t obj-intel64/cmptrace.so -- /bin/ls 2>cmptrace.log
buffer_linux.cpp    divide_by_zero_linux.c    follow_child_tool.cpp  inscount2.cpp     makefile         pinatrace.cpp         stack-debugger-tutorial.sln              thread_unix.c
buffer_windows.cpp  divide_by_zero_windows.c  fork_app.cpp           inscount_tls.cpp  makefile.rules   pin.log               stack-debugger-tutorial.vcxproj          thread_win.c
cmptrace.cpp        emudiv.cpp                fork_jit_tool.cpp      invocation.cpp    malloc_mt.cpp    proccount.cpp         stack-debugger-tutorial.vcxproj.filters  w_malloctrace.cpp
cmptrace.log        fibonacci.cpp             imageload.cpp          isampling.cpp     malloctrace.cpp  replacesigprobed.cpp  statica.cpp
countreps.cpp       follow_child_app1.cpp     inscount0.cpp          itrace.cpp        nonstatica.cpp   safecopy.cpp          staticcount.cpp
detach.cpp          follow_child_app2.cpp     inscount1.cpp          little_malloc.c   obj-intel64      stack-debugger.cpp    strace.cpp

$ head cmptrace.log
[00007f051296cb1f] cmp 0xe, 0x21
[00007f051296cb1f] cmp 0x4, 0x21
[00007f051296cb1f] cmp 0x6ffffef5, 0x21
[00007f051296cb2b] cmp 0x10a, 0xf
[00007f051296cd35] cmp 0xeffffef5, 0xfffffffffffffffc
[00007f051296cd5e] cmp 0xffffffffffffff0a, 0xb
[00007f051296cece] cmp 0xa, 0xa
[00007f051296cb1f] cmp 0x5, 0x21
[00007f051296cb1f] cmp 0x6, 0x21
[00007f051296cb1f] cmp 0xa, 0x21

上の結果から、命令アドレスとそのとき実際に比較されている値の組が出力されていることがわかる。

使い道について

次のようにして、何らかの要因で終了しているプログラムの終了要因の特定に利用できるかもしれない。

$ ../../../pin -t obj-intel64/cmptrace.so -- /bin/grep 2>cmptrace.log

$ grep -v 00007f cmptrace.log
[0000000000403ec0] cmp 0x0, 0x0
[00000000004255e1] cmp 0x1, 0x1
[0000000000419742] cmp 0x5, 0x6
[000000000040622f] cmp 0x4257b0, 0x0
[0000000000403094] cmp 0xffffffcf, 0x9
[00000000004031b0] cmp 0x7ffd784776b0, 0x7ffd784776b0
[0000000000403112] cmp 0xffffffff, 0xffffffffffffffff
[00000000004031fc] cmp 0x0, 0x2
[000000000040320e] cmp 0x0, 0x0
[0000000000403ada] cmp 0x0, 0x0
[0000000000403245] cmp 0xffffffffffffffff, 0x0
[0000000000403259] cmp 0xffffffffffffffff, 0x0
[0000000000403267] cmp 0x0, 0x0
[0000000000403274] cmp 0x0, 0x0
[0000000000403281] cmp 0x0, 0x0
[00000000004032b2] cmp 0x2000, 0x8000
[00000000004032c8] cmp 0x0, 0x0
[0000000000403d5f] cmp 0x1, 0x1
Usage: /bin/grep [OPTION]... PATTERN [FILE]...
Try '/bin/grep --help' for more information.