c++creverse-engineeringintel-pin

Intel pin: how to detect realloc size


I have a following pin tool, say trace.cpp (exactly as is in a pin tool manual):

#include "pin.H"
#include <iostream>
#include <fstream>

/* ===================================================================== */
/* Names of malloc and free */
/* ===================================================================== */
#if defined(TARGET_MAC)
#define MALLOC "_malloc"
#define FREE "_free"
#else
#define MALLOC "malloc"
#define FREE "free"
#endif

/* ===================================================================== */
/* Global Variables */
/* ===================================================================== */

std::ofstream TraceFile;

/* ===================================================================== */
/* Commandline Switches */
/* ===================================================================== */

KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",
    "o", "malloctrace.out", "specify trace file name");

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


/* ===================================================================== */
/* Analysis routines                                                     */
/* ===================================================================== */
 
VOID Arg1Before(CHAR * name, ADDRINT size)
{
    std::cout << name << "(" << size << ")" << endl;

}

VOID MallocAfter(ADDRINT ret)
{
    std::cout << "  returns " << ret << endl;
}


/* ===================================================================== */
/* Instrumentation routines                                              */
/* ===================================================================== */
   
VOID Image(IMG img, VOID *v)
{
    // Instrument the malloc() and free() functions.  Print the input argument
    // of each malloc() or free(), and the return value of malloc().
    //
    //  Find the malloc() function.
    RTN mallocRtn = RTN_FindByName(img, MALLOC);
    if (RTN_Valid(mallocRtn))
    {
        RTN_Open(mallocRtn);

        // Instrument malloc() to print the input argument value and the return value.
        RTN_InsertCall(mallocRtn, IPOINT_BEFORE, (AFUNPTR)Arg1Before,
                       IARG_ADDRINT, MALLOC,
                       IARG_FUNCARG_ENTRYPOINT_VALUE, 0,
                       IARG_END);
        RTN_InsertCall(mallocRtn, IPOINT_AFTER, (AFUNPTR)MallocAfter,
                       IARG_FUNCRET_EXITPOINT_VALUE, IARG_END);

        RTN_Close(mallocRtn);
    }

    // Find the free() function.
    RTN freeRtn = RTN_FindByName(img, FREE);
    if (RTN_Valid(freeRtn))
    {
        RTN_Open(freeRtn);
        // Instrument free() to print the input argument value.
        RTN_InsertCall(freeRtn, IPOINT_BEFORE, (AFUNPTR)Arg1Before,
                       IARG_ADDRINT, FREE,
                       IARG_FUNCARG_ENTRYPOINT_VALUE, 0,
                       IARG_END);
        RTN_Close(freeRtn);
    }
}

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

VOID Fini(INT32 code, VOID *v)
{
    TraceFile.close();
}

/* ===================================================================== */
/* Print Help Message                                                    */
/* ===================================================================== */
   
INT32 Usage()
{
    cerr << "This tool produces a trace of calls to malloc." << endl;
    cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
    return -1;
}

/* ===================================================================== */
/* Main                                                                  */
/* ===================================================================== */

int main(int argc, char *argv[])
{
    // Initialize pin & symbol manager
    PIN_InitSymbols();
    if( PIN_Init(argc,argv) )
    {
        return Usage();
    }
    
    // Write to a file since cout and cerr maybe closed by the application
    TraceFile.open(KnobOutputFile.Value().c_str());
    TraceFile << hex;
    TraceFile.setf(ios::showbase);
    
    // Register Image to be called to instrument functions.
    IMG_AddInstrumentFunction(Image, 0);
    PIN_AddFiniFunction(Fini, 0);

    // Never returns
    PIN_StartProgram();
    
    return 0;
}

(The only variation is that it prints the instead of storing it in the output file)

I have a following c code example - example.c:

#include <stdio.h>
#include <stdlib.h>

struct A {
    int x[10];
    int y[1];
};

int main()
{
  struct A *ptr = calloc(1, sizeof(struct A));
  ptr->x[10] = 4;
  printf("%i\n", ptr->x[10]);
  ptr = realloc(ptr, sizeof(int));
  ptr->x[10] = 4;
  printf("%i\n", ptr->x[10]);
  free(ptr);
  return 0;
}

When I run it with the pin tool, it produces the following output:

$ pin -t obj-intel64/trace.so -- ./example.o | tail
  returns 139865991781744
malloc(272)
  returns 139865991785984
malloc(44)
  returns 33456736
malloc(4096)
  returns 33456800
free(33456736)
4
4

Notice the couple of malloc calls:

malloc(44)
malloc(4096)

It detects the first one perfectly (notice calloc call in the c code), but it detects 4096 for realloc (correct me if I'm wrong here). But, I believe it supposed to detect 4 (sizeof int) instead.

Where am I going wrong? Is there any way by which the correct size can be detected (or maybe I am missing something here)?


Solution

  • You are using the trace tool to trace actual calls to malloc and free. You don't seem to be tracing calls to calloc and realloc, presumably on the assumption that those functions will eventually call malloc.

    That's not necessarily true, though. For example, if realloc detects that the existing memory block is already sufficiently big to satisfy the request, it can just return its first argument without doing anything more, so you won't see any call to malloc. This is evidently what happened in your example, since your realloc call asks for less memory than was allocated by calloc, and we can see that it didn't do that here, since the address of ptr when free is called is the same as the block allocated by calloc.

    If the existing allocation is much bigger than the request, realloc may choose to copy the block into a smaller allocation, possibly acquired with malloc. . But even if realloc does decide to reduce the size of the allocated memory, there is no guarantee that it wiil need a new allocation. On some implementations, it is possible to split the existing block, adding the unneeded part to the free list. That won't involve a call to either free or malloc.

    So if the malloc(4096) doesn't come from the realloc, where does it come from? Most likely, the answer is that malloc was called from the standard library. For example, printf might have noticed that it needs to allocate an output buffer for stdout, so it calls malloc to get some memory.

    In short, you probably need to trace all memory management functions to get a clear idea of what is going on.