1 //--------------------------------------------------------------------*/ 2 //--- BBV: a SimPoint basic block vector generator bbv_main.c ---*/ 3 //--------------------------------------------------------------------*/ 4 5 /* 6 This file is part of BBV, a Valgrind tool for generating SimPoint 7 basic block vectors. 8 9 Copyright (C) 2006-2011 Vince Weaver 10 vince _at_ csl.cornell.edu 11 12 pcfile code is Copyright (C) 2006-2011 Oriol Prat 13 oriol.prat _at _ bsc.es 14 15 This program is free software; you can redistribute it and/or 16 modify it under the terms of the GNU General Public License as 17 published by the Free Software Foundation; either version 2 of the 18 License, or (at your option) any later version. 19 20 This program is distributed in the hope that it will be useful, but 21 WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 General Public License for more details. 24 25 You should have received a copy of the GNU General Public License 26 along with this program; if not, write to the Free Software 27 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 28 02111-1307, USA. 29 30 The GNU General Public License is contained in the file COPYING. 31 */ 32 33 34 #include "pub_tool_basics.h" 35 #include "pub_tool_tooliface.h" 36 #include "pub_tool_options.h" /* command line options */ 37 38 #include "pub_tool_vki.h" /* vki_stat */ 39 #include "pub_tool_libcbase.h" /* VG_(strlen) */ 40 #include "pub_tool_libcfile.h" /* VG_(write) */ 41 #include "pub_tool_libcprint.h" /* VG_(printf) */ 42 #include "pub_tool_libcassert.h" /* VG_(exit) */ 43 #include "pub_tool_mallocfree.h" /* plain_free */ 44 #include "pub_tool_machine.h" /* VG_(fnptr_to_fnentry) */ 45 #include "pub_tool_debuginfo.h" /* VG_(get_fnname) */ 46 47 #include "pub_tool_oset.h" /* ordered set stuff */ 48 49 /* instruction special cases */ 50 #define REP_INSTRUCTION 0x1 51 #define FLDCW_INSTRUCTION 0x2 52 53 /* interval variables */ 54 #define DEFAULT_GRAIN_SIZE 100000000 /* 100 million by default */ 55 static Int interval_size=DEFAULT_GRAIN_SIZE; 56 57 /* filenames */ 58 static UChar *clo_bb_out_file="bb.out.%p"; 59 static UChar *clo_pc_out_file="pc.out.%p"; 60 static UChar *pc_out_file=NULL; 61 static UChar *bb_out_file=NULL; 62 63 64 /* output parameters */ 65 static Bool instr_count_only=False; 66 static Bool generate_pc_file=False; 67 68 /* write buffer */ 69 static UChar buf[1024]; 70 71 /* Global values */ 72 static OSet* instr_info_table; /* table that holds the basic block info */ 73 static Int block_num=1; /* global next block number */ 74 static Int current_thread=0; 75 static Int allocated_threads=1; 76 struct thread_info *bbv_thread=NULL; 77 78 /* Per-thread variables */ 79 struct thread_info { 80 ULong dyn_instr; /* Current retired instruction count */ 81 ULong total_instr; /* Total retired instruction count */ 82 Addr last_rep_addr; /* rep counting values */ 83 ULong rep_count; 84 ULong global_rep_count; 85 ULong unique_rep_count; 86 ULong fldcw_count; /* fldcw count */ 87 Int bbtrace_fd; /* file descriptor */ 88 }; 89 90 #define FUNCTION_NAME_LENGTH 20 91 92 struct BB_info { 93 Addr BB_addr; /* used as key, must be first */ 94 Int n_instrs; /* instructions in the basic block */ 95 Int block_num; /* unique block identifier */ 96 Int *inst_counter; /* times entered * num_instructions */ 97 Bool is_entry; /* is this block a function entry point */ 98 UChar fn_name[FUNCTION_NAME_LENGTH]; /* Function block is in */ 99 }; 100 101 102 /* dump the optional PC file, which contains basic block number to */ 103 /* instruction address and function name mappings */ 104 static void dumpPcFile(void) 105 { 106 struct BB_info *bb_elem; 107 Int pctrace_fd; 108 SysRes sres; 109 110 pc_out_file = 111 VG_(expand_file_name)("--pc-out-file", clo_pc_out_file); 112 113 sres = VG_(open)(pc_out_file, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY, 114 VKI_S_IRUSR|VKI_S_IWUSR|VKI_S_IRGRP|VKI_S_IWGRP); 115 if (sr_isError(sres)) { 116 VG_(umsg)("Error: cannot create pc file %s\n", pc_out_file); 117 VG_(exit)(1); 118 } else { 119 pctrace_fd = sr_Res(sres); 120 } 121 122 /* Loop through the table, printing the number, address, */ 123 /* and function name for each basic block */ 124 VG_(OSetGen_ResetIter)(instr_info_table); 125 while ( (bb_elem = VG_(OSetGen_Next)(instr_info_table)) ) { 126 VG_(write)(pctrace_fd,"F",1); 127 VG_(sprintf)( buf,":%d:%x:%s\n", 128 bb_elem->block_num, 129 (Int)bb_elem->BB_addr, 130 bb_elem->fn_name); 131 VG_(write)(pctrace_fd, (void*)buf, VG_(strlen)(buf)); 132 } 133 134 VG_(close)(pctrace_fd); 135 } 136 137 static Int open_tracefile(Int thread_num) 138 { 139 SysRes sres; 140 UChar temp_string[2048]; 141 142 /* For thread 1, don't append any thread number */ 143 /* This lets the single-thread case not have any */ 144 /* extra values appended to the file name. */ 145 if (thread_num==1) { 146 VG_(strncpy)(temp_string,bb_out_file,2047); 147 } 148 else { 149 VG_(sprintf)(temp_string,"%s.%d",bb_out_file,thread_num); 150 } 151 152 sres = VG_(open)(temp_string, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY, 153 VKI_S_IRUSR|VKI_S_IWUSR|VKI_S_IRGRP|VKI_S_IWGRP); 154 155 if (sr_isError(sres)) { 156 VG_(umsg)("Error: cannot create bb file %s\n",temp_string); 157 VG_(exit)(1); 158 } 159 160 return sr_Res(sres); 161 } 162 163 static void handle_overflow(void) 164 { 165 struct BB_info *bb_elem; 166 167 if (bbv_thread[current_thread].dyn_instr > interval_size) { 168 169 if (!instr_count_only) { 170 171 /* If our output fd hasn't been opened, open it */ 172 if (bbv_thread[current_thread].bbtrace_fd < 0) { 173 bbv_thread[current_thread].bbtrace_fd=open_tracefile(current_thread); 174 } 175 176 /* put an entry to the bb.out file */ 177 178 VG_(write)(bbv_thread[current_thread].bbtrace_fd,"T",1); 179 180 VG_(OSetGen_ResetIter)(instr_info_table); 181 while ( (bb_elem = VG_(OSetGen_Next)(instr_info_table)) ) { 182 if ( bb_elem->inst_counter[current_thread] != 0 ) { 183 VG_(sprintf)( buf,":%d:%d ", 184 bb_elem->block_num, 185 bb_elem->inst_counter[current_thread]); 186 VG_(write)(bbv_thread[current_thread].bbtrace_fd, 187 (void*)buf, VG_(strlen)(buf)); 188 bb_elem->inst_counter[current_thread] = 0; 189 } 190 } 191 192 VG_(write)(bbv_thread[current_thread].bbtrace_fd,"\n",1); 193 } 194 195 bbv_thread[current_thread].dyn_instr -= interval_size; 196 } 197 } 198 199 200 static void close_out_reps(void) 201 { 202 bbv_thread[current_thread].global_rep_count+=bbv_thread[current_thread].rep_count; 203 bbv_thread[current_thread].unique_rep_count++; 204 bbv_thread[current_thread].rep_count=0; 205 } 206 207 /* Generic function to get called each instruction */ 208 static VG_REGPARM(1) void per_instruction_BBV(struct BB_info *bbInfo) 209 { 210 Int n_instrs=1; 211 212 tl_assert(bbInfo); 213 214 /* we finished rep but didn't clear out count */ 215 if (bbv_thread[current_thread].rep_count) { 216 n_instrs++; 217 close_out_reps(); 218 } 219 220 bbInfo->inst_counter[current_thread]+=n_instrs; 221 222 bbv_thread[current_thread].total_instr+=n_instrs; 223 bbv_thread[current_thread].dyn_instr +=n_instrs; 224 225 handle_overflow(); 226 } 227 228 /* Function to get called if instruction has a rep prefix */ 229 static VG_REGPARM(1) void per_instruction_BBV_rep(Addr addr) 230 { 231 /* handle back-to-back rep instructions */ 232 if (bbv_thread[current_thread].last_rep_addr!=addr) { 233 if (bbv_thread[current_thread].rep_count) { 234 close_out_reps(); 235 bbv_thread[current_thread].total_instr++; 236 bbv_thread[current_thread].dyn_instr++; 237 } 238 bbv_thread[current_thread].last_rep_addr=addr; 239 } 240 241 bbv_thread[current_thread].rep_count++; 242 243 } 244 245 /* Function to call if our instruction has a fldcw instruction */ 246 static VG_REGPARM(1) void per_instruction_BBV_fldcw(struct BB_info *bbInfo) 247 { 248 Int n_instrs=1; 249 250 tl_assert(bbInfo); 251 252 /* we finished rep but didn't clear out count */ 253 if (bbv_thread[current_thread].rep_count) { 254 n_instrs++; 255 close_out_reps(); 256 } 257 258 /* count fldcw instructions */ 259 bbv_thread[current_thread].fldcw_count++; 260 261 bbInfo->inst_counter[current_thread]+=n_instrs; 262 263 bbv_thread[current_thread].total_instr+=n_instrs; 264 bbv_thread[current_thread].dyn_instr +=n_instrs; 265 266 handle_overflow(); 267 } 268 269 /* Check if the instruction pointed to is one that needs */ 270 /* special handling. If so, set a bit in the return */ 271 /* value indicating what type. */ 272 static Int get_inst_type(Int len, Addr addr) 273 { 274 int result=0; 275 276 #if defined(VGA_x86) || defined(VGA_amd64) 277 278 unsigned char *inst_pointer; 279 unsigned char inst_byte; 280 int i,possible_rep; 281 282 /* rep prefixed instructions are counted as one instruction on */ 283 /* x86 processors and must be handled as a special case */ 284 285 /* Also, the rep prefix is re-used as part of the opcode for */ 286 /* SSE instructions. So we need to specifically check for */ 287 /* the following: movs, cmps, scas, lods, stos, ins, outs */ 288 289 inst_pointer=(unsigned char *)addr; 290 i=0; 291 inst_byte=0; 292 possible_rep=0; 293 294 while (i<len) { 295 296 inst_byte=*inst_pointer; 297 298 if ( (inst_byte == 0x67) || /* size override prefix */ 299 (inst_byte == 0x66) || /* size override prefix */ 300 (inst_byte == 0x48) ) { /* 64-bit prefix */ 301 } else if ( (inst_byte == 0xf2) || /* rep prefix */ 302 (inst_byte == 0xf3) ) { /* repne prefix */ 303 possible_rep=1; 304 } else { 305 break; /* other byte, exit */ 306 } 307 308 i++; 309 inst_pointer++; 310 } 311 312 if ( possible_rep && 313 ( ( (inst_byte >= 0xa4) && /* movs,cmps,scas */ 314 (inst_byte <= 0xaf) ) || /* lods,stos */ 315 ( (inst_byte >= 0x6c) && 316 (inst_byte <= 0x6f) ) ) ) { /* ins,outs */ 317 318 result|=REP_INSTRUCTION; 319 } 320 321 /* fldcw instructions are double-counted by the hardware */ 322 /* performance counters on pentium 4 processors so it is */ 323 /* useful to have that count when doing validation work. */ 324 325 inst_pointer=(unsigned char *)addr; 326 if (len>1) { 327 /* FLDCW detection */ 328 /* opcode is 0xd9/5, ie 1101 1001 oo10 1mmm */ 329 if ((*inst_pointer==0xd9) && 330 (*(inst_pointer+1)<0xb0) && /* need this case of fldz, etc, count */ 331 ( (*(inst_pointer+1) & 0x38) == 0x28)) { 332 result|=FLDCW_INSTRUCTION; 333 } 334 } 335 336 #endif 337 return result; 338 } 339 340 341 342 /* Our instrumentation function */ 343 /* sbIn = super block to translate */ 344 /* layout = guest layout */ 345 /* gWordTy = size of guest word */ 346 /* hWordTy = size of host word */ 347 static IRSB* bbv_instrument ( VgCallbackClosure* closure, 348 IRSB* sbIn, VexGuestLayout* layout, 349 VexGuestExtents* vge, 350 IRType gWordTy, IRType hWordTy ) 351 { 352 Int i,n_instrs=1; 353 IRSB *sbOut; 354 IRStmt *st; 355 struct BB_info *bbInfo; 356 Addr64 origAddr,ourAddr; 357 IRDirty *di; 358 IRExpr **argv, *arg1; 359 Int regparms,opcode_type; 360 361 /* We don't handle a host/guest word size mismatch */ 362 if (gWordTy != hWordTy) { 363 VG_(tool_panic)("host/guest word size mismatch"); 364 } 365 366 /* Set up SB */ 367 sbOut = deepCopyIRSBExceptStmts(sbIn); 368 369 /* Copy verbatim any IR preamble preceding the first IMark */ 370 i = 0; 371 while ( (i < sbIn->stmts_used) && (sbIn->stmts[i]->tag!=Ist_IMark)) { 372 addStmtToIRSB( sbOut, sbIn->stmts[i] ); 373 i++; 374 } 375 376 /* Get the first statement */ 377 tl_assert(sbIn->stmts_used > 0); 378 st = sbIn->stmts[i]; 379 380 /* double check we are at a Mark statement */ 381 tl_assert(Ist_IMark == st->tag); 382 383 origAddr=st->Ist.IMark.addr; 384 385 /* Get the BB_info */ 386 bbInfo = VG_(OSetGen_Lookup)(instr_info_table, &origAddr); 387 388 if (bbInfo==NULL) { 389 390 /* BB never translated before (at this address, at least; */ 391 /* could have been unloaded and then reloaded elsewhere in memory) */ 392 393 /* allocate and initialize a new basic block structure */ 394 bbInfo=VG_(OSetGen_AllocNode)(instr_info_table, sizeof(struct BB_info)); 395 bbInfo->BB_addr = origAddr; 396 bbInfo->n_instrs = n_instrs; 397 bbInfo->inst_counter=VG_(calloc)("bbv_instrument", 398 allocated_threads, 399 sizeof(Int)); 400 401 /* assign a unique block number */ 402 bbInfo->block_num=block_num; 403 block_num++; 404 /* get function name and entry point information */ 405 VG_(get_fnname)(origAddr,bbInfo->fn_name,FUNCTION_NAME_LENGTH); 406 bbInfo->is_entry=VG_(get_fnname_if_entry)(origAddr, bbInfo->fn_name, 407 FUNCTION_NAME_LENGTH); 408 /* insert structure into table */ 409 VG_(OSetGen_Insert)( instr_info_table, bbInfo ); 410 } 411 412 /* Iterate through the basic block, putting the original */ 413 /* instructions in place, plus putting a call to updateBBV */ 414 /* for each original instruction */ 415 416 /* This is less efficient than only instrumenting the BB */ 417 /* But it gives proper results given the fact that */ 418 /* valgrind uses superblocks (not basic blocks) by default */ 419 420 421 while(i < sbIn->stmts_used) { 422 st=sbIn->stmts[i]; 423 424 if (st->tag == Ist_IMark) { 425 426 ourAddr = st->Ist.IMark.addr; 427 428 opcode_type=get_inst_type(st->Ist.IMark.len,ourAddr); 429 430 regparms=1; 431 arg1= mkIRExpr_HWord( (HWord)bbInfo); 432 argv= mkIRExprVec_1(arg1); 433 434 435 if (opcode_type&REP_INSTRUCTION) { 436 arg1= mkIRExpr_HWord(ourAddr); 437 argv= mkIRExprVec_1(arg1); 438 di= unsafeIRDirty_0_N( regparms, "per_instruction_BBV_rep", 439 VG_(fnptr_to_fnentry)( &per_instruction_BBV_rep ), 440 argv); 441 } 442 else if (opcode_type&FLDCW_INSTRUCTION) { 443 di= unsafeIRDirty_0_N( regparms, "per_instruction_BBV_fldcw", 444 VG_(fnptr_to_fnentry)( &per_instruction_BBV_fldcw ), 445 argv); 446 } 447 else { 448 di= unsafeIRDirty_0_N( regparms, "per_instruction_BBV", 449 VG_(fnptr_to_fnentry)( &per_instruction_BBV ), 450 argv); 451 } 452 453 454 /* Insert our call */ 455 addStmtToIRSB( sbOut, IRStmt_Dirty(di)); 456 } 457 458 /* Insert the original instruction */ 459 addStmtToIRSB( sbOut, st ); 460 461 i++; 462 } 463 464 return sbOut; 465 } 466 467 static struct thread_info *allocate_new_thread(struct thread_info *old, 468 Int old_number, Int new_number) 469 { 470 struct thread_info *temp; 471 struct BB_info *bb_elem; 472 Int i; 473 474 temp=VG_(realloc)("bbv_main.c allocate_threads", 475 old, 476 new_number*sizeof(struct thread_info)); 477 478 /* init the new thread */ 479 /* We loop in case the new thread is not contiguous */ 480 for(i=old_number;i<new_number;i++) { 481 temp[i].last_rep_addr=0; 482 temp[i].dyn_instr=0; 483 temp[i].total_instr=0; 484 temp[i].global_rep_count=0; 485 temp[i].unique_rep_count=0; 486 temp[i].rep_count=0; 487 temp[i].fldcw_count=0; 488 temp[i].bbtrace_fd=-1; 489 } 490 /* expand the inst_counter on all allocated basic blocks */ 491 VG_(OSetGen_ResetIter)(instr_info_table); 492 while ( (bb_elem = VG_(OSetGen_Next)(instr_info_table)) ) { 493 bb_elem->inst_counter = 494 VG_(realloc)("bbv_main.c inst_counter", 495 bb_elem->inst_counter, 496 new_number*sizeof(Int)); 497 for(i=old_number;i<new_number;i++) { 498 bb_elem->inst_counter[i]=0; 499 } 500 } 501 502 return temp; 503 } 504 505 static void bbv_thread_called ( ThreadId tid, ULong nDisp ) 506 { 507 if (tid >= allocated_threads) { 508 bbv_thread=allocate_new_thread(bbv_thread,allocated_threads,tid+1); 509 allocated_threads=tid+1; 510 } 511 current_thread=tid; 512 } 513 514 515 516 517 /*--------------------------------------------------------------------*/ 518 /*--- Setup ---*/ 519 /*--------------------------------------------------------------------*/ 520 521 static void bbv_post_clo_init(void) 522 { 523 bb_out_file = 524 VG_(expand_file_name)("--bb-out-file", clo_bb_out_file); 525 526 /* Try a closer approximation of basic blocks */ 527 /* This is the same as the command line option */ 528 /* --vex-guest-chase-thresh=0 */ 529 VG_(clo_vex_control).guest_chase_thresh = 0; 530 } 531 532 /* Parse the command line options */ 533 static Bool bbv_process_cmd_line_option(Char* arg) 534 { 535 if VG_INT_CLO (arg, "--interval-size", interval_size) {} 536 else if VG_STR_CLO (arg, "--bb-out-file", clo_bb_out_file) {} 537 else if VG_STR_CLO (arg, "--pc-out-file", clo_pc_out_file) { 538 generate_pc_file = True; 539 } 540 else if VG_BOOL_CLO (arg, "--instr-count-only", instr_count_only) {} 541 else { 542 return False; 543 } 544 545 return True; 546 } 547 548 static void bbv_print_usage(void) 549 { 550 VG_(printf)( 551 " --bb-out-file=<file> filename for BBV info\n" 552 " --pc-out-file=<file> filename for BB addresses and function names\n" 553 " --interval-size=<num> interval size\n" 554 " --instr-count-only=yes|no only print total instruction count\n" 555 ); 556 } 557 558 static void bbv_print_debug_usage(void) 559 { 560 VG_(printf)(" (none)\n"); 561 } 562 563 static void bbv_fini(Int exitcode) 564 { 565 Int i; 566 567 if (generate_pc_file) { 568 dumpPcFile(); 569 } 570 571 for(i=0;i<allocated_threads;i++) { 572 573 if (bbv_thread[i].total_instr!=0) { 574 575 VG_(sprintf)(buf,"\n\n" 576 "# Thread %d\n" 577 "# Total intervals: %d (Interval Size %d)\n" 578 "# Total instructions: %lld\n" 579 "# Total reps: %lld\n" 580 "# Unique reps: %lld\n" 581 "# Total fldcw instructions: %lld\n\n", 582 i, 583 (Int)(bbv_thread[i].total_instr/(ULong)interval_size), 584 interval_size, 585 bbv_thread[i].total_instr, 586 bbv_thread[i].global_rep_count, 587 bbv_thread[i].unique_rep_count, 588 bbv_thread[i].fldcw_count); 589 590 /* Print results to display */ 591 VG_(umsg)("%s\n", buf); 592 593 /* open the output file if it hasn't already */ 594 if (bbv_thread[i].bbtrace_fd < 0) { 595 bbv_thread[i].bbtrace_fd=open_tracefile(i); 596 } 597 /* Also print to results file */ 598 VG_(write)(bbv_thread[i].bbtrace_fd,(void*)buf,VG_(strlen)(buf)); 599 VG_(close)(bbv_thread[i].bbtrace_fd); 600 } 601 } 602 } 603 604 static void bbv_pre_clo_init(void) 605 { 606 VG_(details_name) ("exp-bbv"); 607 VG_(details_version) (NULL); 608 VG_(details_description) ("a SimPoint basic block vector generator"); 609 VG_(details_copyright_author)( 610 "Copyright (C) 2006-2011 Vince Weaver"); 611 VG_(details_bug_reports_to) (VG_BUGS_TO); 612 613 VG_(basic_tool_funcs) (bbv_post_clo_init, 614 bbv_instrument, 615 bbv_fini); 616 617 VG_(needs_command_line_options)(bbv_process_cmd_line_option, 618 bbv_print_usage, 619 bbv_print_debug_usage); 620 621 VG_(track_start_client_code)( bbv_thread_called ); 622 623 624 instr_info_table = VG_(OSetGen_Create)(/*keyOff*/0, 625 NULL, 626 VG_(malloc), "bbv.1", VG_(free)); 627 628 bbv_thread=allocate_new_thread(bbv_thread,0,allocated_threads); 629 } 630 631 VG_DETERMINE_INTERFACE_VERSION(bbv_pre_clo_init) 632 633 /*--------------------------------------------------------------------*/ 634 /*--- end ---*/ 635 /*--------------------------------------------------------------------*/ 636