/*--------------------------------------------------------------------*/ /*--- Cachetool: The cachetool interface. ct_main.c ---*/ /*--------------------------------------------------------------------*/ /* This file is a tool built up in Valgrind for tracing heap allocation and memory reference. An output trace file will be created and then fed into a cache simulator to find out cache conflicts. Copyright (C) 2005-2006 Vince Weaver vince _at_ csl.cornell.edu I-Chun Li yohowo _at_ csl.cornell.edu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. The GNU General Public License is contained in the file COPYING. */ #include "pub_tool_basics.h" //contains the basic types and other things needed everywhere. //also included libvex.h, a library for dynamic binary instrumentation and translation. #include "pub_tool_libcassert.h" //clib assert replacement #include "pub_tool_tooliface.h" //core/tool interface #include "pub_tool_replacemalloc.h" //library functions replacement:malloc #include "pub_tool_mallocfree.h" //handle free #include "pub_tool_libcprint.h" //printing #include "pub_tool_hashtable.h" //Generic type for a separately-chained hash table.(for realloc) #include "pub_tool_libcbase.h" //VG_(memset) #include "pub_tool_options.h" //command line options #include "pub_tool_libcfile.h" //file I/O #include "pub_tool_execontext.h" //PC //#include "pub_tool_stacktrace.h" #include "trace_format.h" /*file I/O*/ /*trace file 16-byte header The first thing written is a 16 byte long header: byte0 = sizeof(long) in bytes [ie 4 on a 32 bit machine, 8 on 64) byte1 = 1 if big-endian, 0 if little-endian byte2 = version of trace file byte3-byte15 reserved (0 for now) */ unsigned long output[10]; int file_ptr; unsigned char FileName[256]; //trace file's name #define BUFFER_SIZE 4096 char buffer[BUFFER_SIZE]; int buffer_pointer=0; /* Record malloc'd blocks */ VgHashTable ct_malloc_list = NULL;//initialized in void ct_pre_clo_init() //get PC unsigned long get_alloc_callsite(void *address); //from /memcheck/mac_share.h /* For malloc()/new/new[] vs. free()/delete/delete[] mismatch checking. */ typedef enum { ct_AllocMalloc = 0, ct_AllocNew = 1, ct_AllocNewVec = 2, ct_AllocCustom = 3 } ct_AllocKind; struct _ExeContext { struct _ExeContext * next; /* Variable-length array. The size is VG_(clo_backtrace_size); at * least 1, at most VG_DEEPEST_BACKTRACE. [0] is the current IP, * [1] is its caller, [2] is the caller of [1], etc. */ Addr ips[0]; }; /* first two fields must match core's VgHashNode. */ typedef struct _ct_Chunk { struct _ct_Chunk* next; Addr data; // ptr to actual block SizeT size : (sizeof(UWord)*8)-2; // size requested; 30 or 62 bits ct_AllocKind allockind : 2; // which wrapper did the allocation ExeContext* where; // where it was allocated } ct_Chunk; /* memory allocation functions */ /* table is ct_malloc_list*/ void *ct_new_block ( ThreadId tid, \ Addr p, SizeT size, SizeT align, Bool is_zeroed, \ ct_AllocKind kind, VgHashTable table); void *ct_malloc ( ThreadId tid, SizeT n ); void *ct_calloc ( ThreadId tid, SizeT nmemb, SizeT size1 ); void *ct_realloc ( ThreadId tid, void* p, SizeT new_size ); void ct_delete ( ThreadId tid, void* p ); void *ct_vec_new ( ThreadId tid, SizeT n ); void *ct_new ( ThreadId tid, SizeT n ); void *ct_memalign ( ThreadId tid, SizeT align, SizeT n ); void ct_vec_delete ( ThreadId tid, void* p ); /* free memory functions */ void ct_handle_free ( ThreadId tid, Addr p, ct_AllocKind kind ); void ct_free ( ThreadId tid, void *p ); void die_and_free_mem ( ThreadId tid, ct_Chunk* mc, ct_Chunk** prev_chunks_next_ptr); int write_to_buffer(unsigned char *input,int bytes); int write_trace(unsigned long type,unsigned long *input,int elements); int flush_buffer(void); int flush_buffer(void) { VG_(write)(file_ptr,buffer,buffer_pointer); return 0; } int write_to_buffer(unsigned char *input,int bytes) { int i; /* If buffer would overflow, write to disk... */ if (buffer_pointer+bytes>=BUFFER_SIZE) { flush_buffer(); buffer_pointer=0; } for(i=0;i=BUFFER_SIZE) { flush_buffer(); buffer_pointer=0; } /* Write out type */ *(long *)(buffer+(buffer_pointer))=type; buffer_pointer+=sizeof(long); for(i=0;idata = p; mc->size = size; mc->allockind = kind; mc->where = VG_(record_ExeContext)(tid); VG_(HT_add_node)( table, (VgHashNode*)mc ); } /* Allocate memory and note change in memory available */ /* ThreadId tid Addr p: allocation address SizeT size: allocation size SizeT align: alignment VG_(clo_alignment) Bool is_zeroed: initialization for shadow area value ct_AllocKind kind: use only ct_AllocMalloc here VgHashTable table: ct_malloc_list */ __inline__ void* ct_new_block ( ThreadId tid, Addr p, SizeT size, SizeT align, Bool is_zeroed, ct_AllocKind kind, VgHashTable table) { /* Allocate and zero if necessary */ if (!p){ p = (Addr)VG_(cli_malloc)( align, size ); if (!p) { return NULL; } if (is_zeroed) { VG_(memset)((void*)p, 0, size); /* initialize value to zero */ } } add_ct_Chunk( tid, p, size, kind, table ); return (void*)p; } unsigned long get_alloc_callsite(void *address) { ct_Chunk* mc; ct_Chunk** prev_chunks_next_ptr; /* the "chunk" has the execution context which has the stack */ /* backtrace which knows where we were called from */ mc = (ct_Chunk*)VG_(HT_get_node) ( ct_malloc_list, (UWord)address, (void*)&prev_chunks_next_ptr ); // VG_(pp_StackTrace)(mc->where->ips,2); // VG_(printf)("Malloc from %x\n",mc->where->ips[1]); return mc->where->ips[1]; } /* malloc */ void *ct_malloc ( ThreadId tid, SizeT n ) { void *temp_pointer; temp_pointer=ct_new_block ( tid, 0, n, VG_(clo_alignment), /*is_zeroed*/False, ct_AllocMalloc, ct_malloc_list); // VG_(printf) ("Malloc of size %d to address %p\n",n,temp_pointer); output[MALLOC_SIZE]=n; output[MALLOC_CALLSITE]=get_alloc_callsite(temp_pointer); write_trace(MALLOC_INFO,output,2); output[ALLOC_ADDRESS]=(unsigned long)temp_pointer; write_trace(MALLOC_ADDRESS,output,1); return temp_pointer; } /* memalign */ void *ct_memalign ( ThreadId tid, SizeT align, SizeT n ) { void *temp_pointer; temp_pointer=ct_new_block ( tid, 0, n, VG_(clo_alignment), /*is_zeroed*/False, ct_AllocMalloc, ct_malloc_list); // VG_(printf) ("Memalign of size %d to address %p\n",n,temp_pointer); output[MEMALIGN_SIZE]=n; output[MEMALIGN_ALIGN]=align; output[MEMALIGN_CALLSITE]=get_alloc_callsite(temp_pointer); write_trace(MEMALIGN_INFO,output,3); output[ALLOC_ADDRESS]=(unsigned long)temp_pointer; write_trace(MEMALIGN_ADDRESS,output,1); return temp_pointer; } /* new() */ void *ct_new ( ThreadId tid, SizeT n ) { void *temp_pointer; temp_pointer=ct_new_block ( tid, 0, n, VG_(clo_alignment), /*is_zeroed*/False, ct_AllocNew, ct_malloc_list); //VG_(printf) ("New of size %d to address %p\n",n,temp_pointer); output[NEW_SIZE]=n; output[NEW_CALLSITE]=get_alloc_callsite(temp_pointer); write_trace(NEW_INFO,output,2); output[ALLOC_ADDRESS]=(unsigned long)temp_pointer; write_trace(NEW_ADDRESS,output,1); return temp_pointer; } /* vec_new() */ void *ct_vec_new ( ThreadId tid, SizeT n ) { void *temp_pointer; temp_pointer=ct_new_block ( tid, 0, n, VG_(clo_alignment), /*is_zeroed*/False, ct_AllocNewVec, ct_malloc_list); // VG_(printf) ("Malloc of size %d to address %p\n",n,temp_pointer); output[VEC_NEW_SIZE]=n; output[VEC_NEW_CALLSITE]=get_alloc_callsite(temp_pointer); write_trace(VEC_NEW_INFO,output,2); output[ALLOC_ADDRESS]=(unsigned long)temp_pointer; write_trace(VEC_NEW_ADDRESS,output,1); return temp_pointer; } /* calloc */ void *ct_calloc ( ThreadId tid, SizeT nmemb, SizeT size1 ) { void *temp_calloc_ptr; temp_calloc_ptr=ct_new_block ( tid, 0, nmemb*size1, VG_(clo_alignment), /*is_zeroed*/True, ct_AllocMalloc,ct_malloc_list); //VG_(printf) ("Calloc of size %d to address %p\n",nmemb*size1,temp_calloc_ptr); output[CALLOC_COUNT]=nmemb; output[CALLOC_SIZE]=size1; output[CALLOC_CALLSITE]=get_alloc_callsite(temp_calloc_ptr); write_trace(CALLOC_INFO,output,3); output[ALLOC_ADDRESS]=(unsigned long)temp_calloc_ptr; write_trace(CALLOC_ADDRESS,output,1); return temp_calloc_ptr; } /* free requires 3 function: ct_free -> ct_handlefree -> die_and_free_mem */ void die_and_free_mem ( ThreadId tid, ct_Chunk* mc, ct_Chunk** prev_chunks_next_ptr) { /* Remove mc from the malloclist using prev_chunks_next_ptr to avoid repeating the hash table lookup. Can't remove until at least after free and free_mismatch errors are done because they use describe_addr() which looks for it in malloclist. */ *prev_chunks_next_ptr = mc->next; VG_(free) ( mc ); } __inline__ void ct_handle_free ( ThreadId tid, Addr p, ct_AllocKind kind ) { ct_Chunk* mc; ct_Chunk** prev_chunks_next_ptr; mc = (ct_Chunk*)VG_(HT_get_node) ( ct_malloc_list, (UWord)p, (void*)&prev_chunks_next_ptr ); die_and_free_mem ( tid, mc, prev_chunks_next_ptr); } /* free */ void ct_free ( ThreadId tid, void* p ) { //VG_(printf) ("free at: %p\n",p); output[FREE_ADDRESS]=(unsigned long)p; write_trace(FREE_INFO,output,1); write_trace(FREE_FINISHED,output,0); ct_handle_free( tid, (Addr)p, ct_AllocMalloc ); } /* delete */ void ct_delete ( ThreadId tid, void* p ) { //VG_(printf) ("delete at: %p\n",p); output[FREE_ADDRESS]=(unsigned long)p; write_trace(DELETE,output,1); ct_handle_free( tid, (Addr)p, ct_AllocNew ); } /* delete */ void ct_vec_delete ( ThreadId tid, void* p ) { //VG_(printf) ("vec_delete at: %p\n",p); output[FREE_ADDRESS]=(unsigned long)p; write_trace(VEC_DELETE,output,1); ct_handle_free( tid, (Addr)p, ct_AllocNewVec ); } /* realloc */ void* ct_realloc ( ThreadId tid, void* p, SizeT new_size ) { ct_Chunk *mc; ct_Chunk **prev_chunks_next_ptr; UInt i; mc = (ct_Chunk*)VG_(HT_get_node) ( ct_malloc_list, (UWord)p, (void*)&prev_chunks_next_ptr ); // VG_(printf)("Realloc happened!\n"); output[REALLOC_OLD_ADDRESS]=(unsigned long)p; output[REALLOC_SIZE]=(unsigned long)new_size; output[REALLOC_CALLSITE]=mc->where->ips[1]; write_trace(REALLOC_INFO,output,3); if (mc->size == new_size) {/* size unchanged */ mc->where = VG_(record_ExeContext)(tid); //VG_(printf) ("realloc of the same size %d to address %p\n",new_size,p); output[ALLOC_ADDRESS]=(unsigned long)p; write_trace(REALLOC_ADDRESS,output,1); return p; } else if (mc->size > new_size) {/* new size is smaller */ mc->size = new_size; mc->where = VG_(record_ExeContext)(tid); output[ALLOC_ADDRESS]=(unsigned long)p; // VG_(printf) ("realloc of smaller size %d to address %p\n",new_size,p); write_trace(REALLOC_ADDRESS,output,1); return p; } else {/* new size is bigger */ Addr p_new; /* Get new memory */ p_new = (Addr)VG_(cli_malloc)(VG_(clo_alignment), new_size); /* Copy from old to new */ for (i = 0; i < mc->size; i++) ((UChar*)p_new)[i] = ((UChar*)p)[i]; /* Free old memory */ die_and_free_mem ( tid, mc, prev_chunks_next_ptr ); /* this has to be after die_and_free_mem, otherwise the former succeeds in shorting out the new block, not the old, in the case when both are on the same list. */ add_ct_Chunk ( tid, p_new, new_size, ct_AllocMalloc, ct_malloc_list ); output[ALLOC_ADDRESS]=(unsigned long)p_new; // VG_(printf) ("realloc of bigger size %d to address %p\n",new_size,p_new); write_trace(REALLOC_ADDRESS,output,1); return (void*)p_new; } } /* Tell Valgrind this function has one parameter */ /* write load information to trace file*/ static VG_REGPARM(3) void print_Load (Addr a, SizeT size, Addr callsite) { //VG_(printf)(" Load of address: %p\n",a); output[ADDRESS_LOADSTORE]=ADDRESS_LOAD; output[ADDRESS_ADDRESS]=(unsigned long)a; //load address output[ADDRESS_CALLSITE]=callsite; //PC output[ADDRESS_SIZE]=size; //? write_trace(ADDRESS,output,4); } /* write store information to trace file*/ static VG_REGPARM(3) void print_Store (Addr a, SizeT size, Addr callsite) { //VG_(printf)(" Store of address: %p\n",a); output[ADDRESS_LOADSTORE]=ADDRESS_STORE; output[ADDRESS_ADDRESS]=(unsigned long)a; //store address output[ADDRESS_CALLSITE]=callsite; //PC output[ADDRESS_SIZE]=size; //? write_trace(ADDRESS,output,4); } static VG_REGPARM(3) void InstBB(Int address, Int size, Int instructions) { output[BLOCK_BLOCK]=0; output[BLOCK_ADDRESS]=address; output[BLOCK_SIZE]=size; output[BLOCK_INSTRUCTIONS]=instructions; write_trace(BLOCK_BEGIN,output,4); } /*------------------------------------------------------------*/ /*--- Our instrumenter ---*/ /*--- Translates the Basic Block passed in as "bb_in" ---*/ /*--- into a new "instrumented" basic block "bb" ---*/ /*------------------------------------------------------------*/ //from ac_main.c static IRBB* ct_instrument ( VgCallbackClosure* closure, IRBB* bb_in, VexGuestLayout* layout, VexGuestExtents* vge, IRType gWordTy, IRType hWordTy ) { Int i,access_size; IRStmt* st; IRExpr* data; IRExpr *access_address,*access_callsite; IRExpr* guard; IRDirty* di; Bool isLoad; IRBB* bb; Addr block_addr; Int block_size=0,block_instructions=0; /* Create a new basic block */ /* We'll put all of the original instructions, plus our */ /* instrumentations into it, and return it back to valgrind */ /* create an empty basic block */ bb = emptyIRBB(); /* copy over configuration from the original basic block */ bb->tyenv = dopyIRTypeEnv(bb_in->tyenv); bb->next = dopyIRExpr(bb_in->next); bb->jumpkind = bb_in->jumpkind; /* Walk through each statement, */ /* from first (0) to last (bb_in->stmts_used) */ /* see lackey if this happens */ if (bb_in->stmts[0]->tag != Ist_IMark) { VG_(printf)("Basic block doesn't start with MARK\n"); } /* Count number of original instrs and size of BB */ for (i = 0; i < bb_in->stmts_used; i++) { st = bb_in->stmts[i]; if (Ist_IMark == st->tag) { block_instructions++; block_size+=st->Ist.IMark.len; } } block_addr = (Addr)bb_in->stmts[0]->Ist.IMark.addr; di = unsafeIRDirty_0_N( 3 , "InstBB", &InstBB, mkIRExprVec_3( mkIRExpr_HWord(block_addr), mkIRExpr_HWord(block_size), mkIRExpr_HWord(block_instructions))); addStmtToIRBB( bb, IRStmt_Dirty(di) ); for (i = 0; i < bb_in->stmts_used; i++) { st = bb_in->stmts[i]; /* clear these variables */ access_size = 0; access_address = NULL; isLoad = True; switch (st->tag) { /* Ist_Tmp means we are copying data into a */ /* "Temporary" register */ case Ist_Tmp: data = st->Ist.Tmp.data; /* We only care if it's a load instruction */ if (data->tag == Iex_Load) { access_address = data->Iex.Load.addr; access_size = sizeofIRType(data->Iex.Load.ty); isLoad = True; } break; /* Ist_Store means we are storing data */ case Ist_Store: data = st->Ist.Store.data; access_address = st->Ist.Store.addr; access_size = sizeofIRType(typeOfIRExpr(bb_in->tyenv, data)); isLoad = False; break; /* We ignore these */ case Ist_Put: /* We are copying some "guest state" */ case Ist_PutI:/* We are copying some "guest state" */ case Ist_Exit:/* We are conditionally leaving a basic block */ case Ist_NoOp: case Ist_IMark: case Ist_MFence: break; /* We are in a "dirty" function? */ case Ist_Dirty: if (st->Ist.Dirty.details->mFx != Ifx_None) { /* We classify Ifx_Modify as a load. */ isLoad = st->Ist.Dirty.details->mFx != Ifx_Write; access_size = st->Ist.Dirty.details->mSize; access_address = st->Ist.Dirty.details->mAddr; if (st->Ist.Dirty.details->mFx == Ifx_Read || st->Ist.Dirty.details->mFx == Ifx_Modify) { isLoad=True; } if (st->Ist.Dirty.details->mFx == Ifx_Write || st->Ist.Dirty.details->mFx == Ifx_Modify) { isLoad=False; } } break; /* Print an error if an unknown statement type */ default: VG_(printf)("\n"); ppIRStmt(st); VG_(printf)("\n"); VG_(tool_panic)("unhandled IRStmt"); break; } /* If we were a load or store, add a call to print it */ if (access_address) { if (isLoad) { /* Create a new "instruction" called "di" */ /* This is a dirty instruction, meaning it has side effects */ /* the "0" means we don't expect a return value */ /* the "N" means we can pass many arguments */ /* We pass 1 argument, the name of the function, */ /* a pointer to the function, and an "argument vector" */ /* which in this case only has one, the address */ di = unsafeIRDirty_0_N( 3, "print_Load", &print_Load, mkIRExprVec_3(access_address, mkIRExpr_HWord(access_size), mkIRExpr_HWord(access_callsite))); } else { di = unsafeIRDirty_0_N( 3, "print_Store", &print_Store, mkIRExprVec_3(access_address, mkIRExpr_HWord(access_size), mkIRExpr_HWord(access_callsite))); } /* put the helper call into the new Basic Block */ /* before the load or store */ addStmtToIRBB( bb, IRStmt_Dirty(di) ); } /* Make sure the original instruction gets added to the basic block. */ addStmtToIRBB( bb, st ); } return bb; } static void ct_post_clo_init(void) { SysRes sysr; unsigned char header[16]; sysr=VG_(open)(FileName, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY|VKI_O_LARGEFILE, VKI_S_IRUSR|VKI_S_IWUSR); if (sysr.isError) { VG_(printf)("file %s can not be opened\n",FileName); } else { VG_(printf)("trace file %s opened\n",FileName); } file_ptr=sysr.val; header[HEADER_LONGSIZE]=sizeof(unsigned long); header[HEADER_ENDIANESS]=0; /* little-endian */ header[HEADER_VERSION]=TRACE_VERSION; header[HEADER_BUFFERSIZE]=BUFFER_SIZE/1024; write_to_buffer(header,16); VG_(printf)("header written\n"); } /* Parse the command line options */ static Bool ct_process_cmd_line_option(Char* arg) { /* 12 is length of "--tracefile=" */ if (VG_CLO_STREQN(12, arg, "--tracefile=")) { VG_(sprintf)(FileName,"%s",&arg[12]); } else { return False; } return True; } static void ct_print_usage(void) { VG_(printf) (" --tracefile= filename to use for tracefile\n"); } static void ct_print_debug_usage(void) { VG_(printf)(" (none)\n"); } static void ct_fini(Int exitcode) { flush_buffer(); VG_(close)(file_ptr); VG_(printf) ("\n\nCachetool:Exiting! \n\n"); } static void ct_pre_clo_init(void) { VG_(details_name) ("Cachetool"); VG_(details_version) (NULL); VG_(details_description) ("generates traces for the cachteool program"); VG_(details_copyright_author)("Copyright (C) 2006 - Vince Weaver/yohowo"); VG_(details_bug_reports_to) (VG_BUGS_TO); /* set up default output file */ VG_(sprintf)(FileName,"trace2.out"); VG_(basic_tool_funcs) (ct_post_clo_init, ct_instrument, ct_fini); VG_(needs_command_line_options)(ct_process_cmd_line_option, ct_print_usage, ct_print_debug_usage); VG_(needs_malloc_replacement)(ct_malloc, /* malloc() */ ct_new, /* new() */ ct_vec_new, /* vec_new() */ ct_memalign, /* memalign() */ ct_calloc, /* calloc() */ ct_free, /* free() */ ct_delete, /* delete() */ ct_vec_delete,/* vec_delete() */ ct_realloc, /* realloc() */ 16); /* redzone block size? */ //initialize the hash table,from mac_share.c ct_malloc_list = VG_(HT_construct)( 80021 ); // prime, big } VG_DETERMINE_INTERFACE_VERSION(ct_pre_clo_init) /*--------------------------------------------------------------------*/ /*--- end ---*/ /*--------------------------------------------------------------------*/