Branch Listing Using Intel Pin Tool

Having read this blog post, you will be able to generate a branch listing using Intel Pin Tool. If you are interested in installing pin, please look at  this article. Instead, if you are interested in the basics of writing a pin tool, I strongly suggest to refer to this article.

Pin ships with many examples and I would highly recommend to look at those examples before resorting to your own tool.

source/tools/ManualExamples, source/tools/SimpleExamples

are some directories that contain examples of Pin use. In fact, this article was inspired by branch_target_addr.cpp tool in the directory source/tools/ToolUnitTests.

Outline of Branch Listing

To keep track of branches and how many times each branch instruction was executed will require a data structure. In our case, a map structure allows to easily keep track of the instruction as well as store a data structure to store additional information.

For example, if we had a COUNTER class, we could declare a map as,

std::map<ADDRINT, COUNTER> counterMap

A COUNTER class would look like below.

class COUNTER
{
  public:
    UINT64 _branch;
    UINT64 _taken;

    COUNTER() : _branch(0), _taken(0)  {}

    UINT64 Total()
    {
        return _branch;
    }
};

Now, we can write our analysis function which will be executed at each branch instruction.

// This function is executed at each branch
static VOID AtBranch(ADDRINT ip, ADDRINT target, BOOL taken)
{
    counterMap[ip]._branch ++;
    if (taken)
	   counterMap[ip]._taken ++; 
}

We can also declare the function call within Pin’s Instruction function.

// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID *v)
{
    if (INS_IsBranch(ins) && INS_HasFallThrough(ins)) {
        INS_InsertCall(ins, IPOINT_BEFORE, 
                       (AFUNPTR)AtBranch,
                       IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR, 
                       IARG_BRANCH_TAKEN, IARG_END);
    }
}

Finally, we need to output our results to a file.

// 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);
    // Output results
    for (std::map<ADDRINT, COUNTER>::iterator it=counterMap.begin();
         it!=counterMap.end(); ++it) {
        if (it->second._branch != LIMIT) continue;
        OutFile << it->first << " => branch count: "
        << it->second._branch << " => taken count: " 
        << it->second._taken << '\n';
    }
    OutFile.close();
}

Methods to Improve Branch Listing using Intel Pin Tool

One thing to note is that in many complicated programs, you will have thousands of branches. So, how do you figure out the branch that you are interested in? Well, there are several ways.

In this example, I know the number of iterations the branch that I’m interested will be executed. Therefore, I’m using a variable called LIMIT to check whether this is the branch that I’m interested in. There are other ways of isolating the branch you are interested in. For example, by providing an address range and checking whether the instruction currently being executed falls within that range.

In the Instruction(INS ins, VOID *v) function, you can include the following.

ADDRINT debug_start_addr = 0;
ADDRINT debug_end_addr = 0;
if (INS_Address(ins) >= debug_start_addr 
    && INS_Addresss(ins) < debug_end_addr) {
}

However, this might not be straight forward. In each execution, the virtual addresses of the instructions change (because of address space layout randomization). One option would be to trace the first instruction within the pin tool and subtracting it from each executed instruction to figure out the offset. The offset can then be used as debug_start_addr and debug_end_addr.

Here’s the full example.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <cstdlib>
#include <map>
#include "pin.H"
#include "instlib.H"
using std::iostream;
using std::ostringstream;
using std::cerr;
using std::ofstream;
using std::ios;
using std::string;
using std::endl;

#define LIMIT 200

ofstream OutFile;

/* ===================================================================== */
/* Global Variables */
/* ===================================================================== */
class COUNTER
{
  public:
    UINT64 _branch;
    UINT64 _taken;

    COUNTER() : _branch(0), _taken(0)  {}

    UINT64 Total()
    {
        return _branch;
    }
};

std::map<ADDRINT, COUNTER> counterMap;

// This function is executed at each branch
static VOID AtBranch(ADDRINT ip, ADDRINT target, BOOL taken)
{
    counterMap[ip]._branch ++;
    if (taken)
	   counterMap[ip]._taken ++; 
}

// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID *v)
{
    if (INS_IsBranch(ins) && INS_HasFallThrough(ins)) {
        INS_InsertCall(ins, IPOINT_BEFORE, 
                       (AFUNPTR)AtBranch,
                       IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR, 
                       IARG_BRANCH_TAKEN, IARG_END);
    }
}

KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",
    "o", "branchpred.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);
    // Output results
    for (std::map<ADDRINT, COUNTER>::iterator it=counterMap.begin();
         it!=counterMap.end(); ++it) {
        if (it->second._branch != LIMIT) continue;
        OutFile << it->first << " => branch count: "
        << it->second._branch << " => taken count: " 
        << it->second._taken << '\n';
    }
    OutFile.close();
}

/* ===================================================================== */
/* Print Help Message                                                    */
/* ===================================================================== */

INT32 Usage()
{
    cerr << "This tool predicts the outcome of conditional branches 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());

    cerr <<  "===============================================" << endl;
    cerr <<  "This application is instrumented by branchpred" << endl;
    if (!KnobOutputFile.Value().empty())
    {
        cerr << "See file " << KnobOutputFile.Value() << " for analysis results" << endl;
    }
    cerr <<  "===============================================" << endl;

    // 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;
}

The code is also available at github. Now you are capable of generating a branch listing using Intel Pin Tool.

Please don’t forget to leave a comment / like behind if the post was helpful!

Leave a Reply

Your email address will not be published. Required fields are marked *