PIN does not provide a direct API to remove a basic block instrumentation call on a particular address.What you could do is keep track of basic blocks which have been executed at least once.
You would insert instrumentation calls only for those basic blocks which have not been executed previously.
Here is a crude example based on the official documentation.
// The running count of instructions is kept here
// make it static to help the compiler optimize docount
static UINT64 icount = 0;
// Count the number of instructions in this basic block
VOID docount(UINT32 c) { icount += c; }
VOID Trace(TRACE trace, VOID *v)
{
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
ADDRINT addr = BBL_Address(bbl);
if (alreadyVistited(addr) == false)
{
// Add instrumentation call for this basic block
BBL_InsertCall(bbl, IPOINT_ANYWHERE, (AFUNPTR)docount, IARG_UINT32, BBL_NumIns(bbl), IARG_END);
}
else
{
addToVisitedList(addr);
}
}
}
int main(int argc, char * argv[])
{
...
TRACE_AddInstrumentFunction(Trace, 0);
...
}
The overhead of this entirely depends on the complexity of the instrumentation code added.
In general to speed things up, you should turn on maximum compiler optimizations. If possible you may also use a better compiler such as the Intel Compiler which is capable of generating extremely fast code.
UPDATE
If you use dynamoRIO instead of PIN, you can more easily achieve what you want to do. Here is a sample code which logs all basic blocks executed by the program in the main module.
#define WINDOWS
#define X86_32
#define _CRT_SECURE_NO_DEPRECATE
#include "dr_api.h"
#pragma comment(lib, "dynamorio.lib")
static file_t logFile;
static LPBYTE mainModStartAddr, mainModEndAddr;
// This is called once for each basic block as it's discovered
static dr_emit_flags_t event_basic_block(void *drcontext, void *tag, instrlist_t *ilist, bool for_trace, bool translating)
{
// The IP of the basoc block
LPBYTE addr = dr_fragment_app_pc(tag);
// Basic Block must be within the main module
if (addr >= mainModStartAddr && addr <= mainModEndAddr)
dr_fprintf(logFile, "0x%08X", addr);
return DR_EMIT_DEFAULT;
}
static void event_exit()
{
dr_close_file(logFile);
}
DR_EXPORT void dr_init(client_id_t id)
{
module_data_t *data = dr_get_main_module();
mainModStartAddr = data->start;
mainModEndAddr = data->end;
dr_free_module_data(data);
logFile = dr_open_file("logfile.txt", DR_FILE_WRITE_OVERWRITE);
dr_register_bb_event(event_basic_block);
dr_register_exit_event(event_exit);
}
Note that there is no analysis function needed at all. The instrumentation function event_basic_block
will be called once per basic block. It will not be called more than once, as from second time onwards, the code will be executed from the code cache.
The difference between dynamoRIO and PIN is that the former directly allows to register a callback for basic block generation. PIN on the other hand allows a callback for trace generation. A trace a collection of basic blocks. Also since not all basic blocks may be executed in a trace, using PIN in this situation is comparatively difficult.