July 29, 2013

Using Pintools to Detect Bugs

Recently I spent some time to get into Pin and to explore how feasible to write Pin-based tools to detect programming errors. Here is the summary of my experiment. I think it could be useful for someone who might want to write Pintools.

I had been thinking of dynamic ways to catch programming error without making the detection logic complex. My decision was to write a tool that can detect division by zero errors when the division is performed by a function argument. The tool works as follows.

The tool inspects division instructions that are reading stack via EBP.
   if (INS_Opcode(ins) == XED_ICLASS_IDIV || INS_Opcode(ins) == XED_ICLASS_DIV)
   {
[...]
      if (INS_IsStackRead(ins) && INS_RegRContain(ins, REG_EBP) && INS_IsMemoryRead(ins))
      {
         INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)TrackDivisionByArg, IARG_INST_PTR, IARG_MEMORYREAD_EA, IARG_REG_VALUE, REG_EBP, IARG_END);
      }
Since the function arguments are stored at EBP+XX it's possible to detect when a division is performed by a function argument.
VOID TrackDivisionByArg(ADDRINT inst_ptr, ADDRINT memoryread_ea, ADDRINT reg_ebp)
{
   // Do we read argument of the function (EBP+XX)?
   if (memoryread_ea >= reg_ebp)
   {
[...]
However, this doesn't mean there is a division by zero error because there might be a test for zero. To filter out obvious false positives the tool inspects if the parameter is accessed before the division.
   else if ((INS_Opcode(ins) == XED_ICLASS_CMP || INS_Opcode(ins) == XED_ICLASS_MOV) && INS_IsStackRead(ins) && INS_RegRContain(ins, REG_EBP) && INS_OperandIsImmediate(ins, 1))
   {
      INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)TrackAccessToArgs, IARG_INST_PTR, IARG_MEMORYREAD_EA, IARG_REG_VALUE, REG_EBP, IARG_REG_VALUE, REG_ESP, IARG_END);
   }
The tool checks the functions on isolation so if the function parameter is checked for zero by the caller the program may report division by zero error. This is the consequence of the design.

The tool uses std::map data structure to maintain the state of the analysis. It also contains other research implementation that is not mentioned in this blog post. The source code is available to download here.

If you use std::map functions you have to use mutex and write lock to protect the data structure from potential corruption unless the target uses no threads.

To get better performance it's better to do analysis once the application exists rather than on-the-fly. However, this increases the memory footprint as the data can be accumulating during the execution.

Sometimes it might be a good idea to use Window Beep function to make noise if a bug is detected.

While it's possible to write efficient Pintools to detect bugs, sometimes if the tool is made to depend on the target application it can perform better.
  This blog is written and maintained by Attila Suszter. Read in Feed Reader.