1 //--------------------------------------------------------------------*/
2 //--- BBV: a SimPoint basic block vector generator bbv_main.c ---*/
3 //--------------------------------------------------------------------*/
6 This file is part of BBV, a Valgrind tool for generating SimPoint
9 Copyright (C) 2006-2011 Vince Weaver
10 vince _at_ csl.cornell.edu
12 pcfile code is Copyright (C) 2006-2011 Oriol Prat
13 oriol.prat _at _ bsc.es
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.
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.
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
30 The GNU General Public License is contained in the file COPYING.
34 #include "pub_tool_basics.h"
35 #include "pub_tool_tooliface.h"
36 #include "pub_tool_options.h" /* command line options */
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) */
47 #include "pub_tool_oset.h" /* ordered set stuff */
49 /* instruction special cases */
50 #define REP_INSTRUCTION 0x1
51 #define FLDCW_INSTRUCTION 0x2
53 /* interval variables */
54 #define DEFAULT_GRAIN_SIZE 100000000 /* 100 million by default */
55 static Int interval_size
=DEFAULT_GRAIN_SIZE
;
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
;
64 /* output parameters */
65 static Bool instr_count_only
=False
;
66 static Bool generate_pc_file
=False
;
69 static UChar buf
[1024];
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
;
78 /* Per-thread variables */
80 ULong dyn_instr
; /* Current retired instruction count */
81 ULong total_instr
; /* Total retired instruction count */
82 Addr last_rep_addr
; /* rep counting values */
84 ULong global_rep_count
;
85 ULong unique_rep_count
;
86 ULong fldcw_count
; /* fldcw count */
87 Int bbtrace_fd
; /* file descriptor */
90 #define FUNCTION_NAME_LENGTH 20
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 */
102 /* dump the optional PC file, which contains basic block number to */
103 /* instruction address and function name mappings */
104 static void dumpPcFile(void)
106 struct BB_info
*bb_elem
;
111 VG_(expand_file_name
)("--pc-out-file", clo_pc_out_file
);
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
);
119 pctrace_fd
= sr_Res(sres
);
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",
129 (Int
)bb_elem
->BB_addr
,
131 VG_(write
)(pctrace_fd
, (void*)buf
, VG_(strlen
)(buf
));
134 VG_(close
)(pctrace_fd
);
137 static Int
open_tracefile(Int thread_num
)
140 UChar temp_string
[2048];
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. */
146 VG_(strncpy
)(temp_string
,bb_out_file
,2047);
149 VG_(sprintf
)(temp_string
,"%s.%d",bb_out_file
,thread_num
);
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
);
155 if (sr_isError(sres
)) {
156 VG_(umsg
)("Error: cannot create bb file %s\n",temp_string
);
163 static void handle_overflow(void)
165 struct BB_info
*bb_elem
;
167 if (bbv_thread
[current_thread
].dyn_instr
> interval_size
) {
169 if (!instr_count_only
) {
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
);
176 /* put an entry to the bb.out file */
178 VG_(write
)(bbv_thread
[current_thread
].bbtrace_fd
,"T",1);
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 ",
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;
192 VG_(write
)(bbv_thread
[current_thread
].bbtrace_fd
,"\n",1);
195 bbv_thread
[current_thread
].dyn_instr
-= interval_size
;
200 static void close_out_reps(void)
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;
207 /* Generic function to get called each instruction */
208 static VG_REGPARM(1) void per_instruction_BBV(struct BB_info
*bbInfo
)
214 /* we finished rep but didn't clear out count */
215 if (bbv_thread
[current_thread
].rep_count
) {
220 bbInfo
->inst_counter
[current_thread
]+=n_instrs
;
222 bbv_thread
[current_thread
].total_instr
+=n_instrs
;
223 bbv_thread
[current_thread
].dyn_instr
+=n_instrs
;
228 /* Function to get called if instruction has a rep prefix */
229 static VG_REGPARM(1) void per_instruction_BBV_rep(Addr addr
)
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
) {
235 bbv_thread
[current_thread
].total_instr
++;
236 bbv_thread
[current_thread
].dyn_instr
++;
238 bbv_thread
[current_thread
].last_rep_addr
=addr
;
241 bbv_thread
[current_thread
].rep_count
++;
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
)
252 /* we finished rep but didn't clear out count */
253 if (bbv_thread
[current_thread
].rep_count
) {
258 /* count fldcw instructions */
259 bbv_thread
[current_thread
].fldcw_count
++;
261 bbInfo
->inst_counter
[current_thread
]+=n_instrs
;
263 bbv_thread
[current_thread
].total_instr
+=n_instrs
;
264 bbv_thread
[current_thread
].dyn_instr
+=n_instrs
;
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
)
276 #if defined(VGA_x86) || defined(VGA_amd64)
278 unsigned char *inst_pointer
;
279 unsigned char inst_byte
;
282 /* rep prefixed instructions are counted as one instruction on */
283 /* x86 processors and must be handled as a special case */
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 */
289 inst_pointer
=(unsigned char *)addr
;
296 inst_byte
=*inst_pointer
;
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 */
305 break; /* other byte, exit */
313 ( ( (inst_byte
>= 0xa4) && /* movs,cmps,scas */
314 (inst_byte
<= 0xaf) ) || /* lods,stos */
315 ( (inst_byte
>= 0x6c) &&
316 (inst_byte
<= 0x6f) ) ) ) { /* ins,outs */
318 result
|=REP_INSTRUCTION
;
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. */
325 inst_pointer
=(unsigned char *)addr
;
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
;
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
)
355 struct BB_info
*bbInfo
;
356 Addr64 origAddr
,ourAddr
;
358 IRExpr
**argv
, *arg1
;
359 Int regparms
,opcode_type
;
361 /* We don't handle a host/guest word size mismatch */
362 if (gWordTy
!= hWordTy
) {
363 VG_(tool_panic
)("host/guest word size mismatch");
367 sbOut
= deepCopyIRSBExceptStmts(sbIn
);
369 /* Copy verbatim any IR preamble preceding the first IMark */
371 while ( (i
< sbIn
->stmts_used
) && (sbIn
->stmts
[i
]->tag
!=Ist_IMark
)) {
372 addStmtToIRSB( sbOut
, sbIn
->stmts
[i
] );
376 /* Get the first statement */
377 tl_assert(sbIn
->stmts_used
> 0);
380 /* double check we are at a Mark statement */
381 tl_assert(Ist_IMark
== st
->tag
);
383 origAddr
=st
->Ist
.IMark
.addr
;
385 /* Get the BB_info */
386 bbInfo
= VG_(OSetGen_Lookup
)(instr_info_table
, &origAddr
);
390 /* BB never translated before (at this address, at least; */
391 /* could have been unloaded and then reloaded elsewhere in memory) */
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",
401 /* assign a unique block number */
402 bbInfo
->block_num
=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
);
412 /* Iterate through the basic block, putting the original */
413 /* instructions in place, plus putting a call to updateBBV */
414 /* for each original instruction */
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 */
421 while(i
< sbIn
->stmts_used
) {
424 if (st
->tag
== Ist_IMark
) {
426 ourAddr
= st
->Ist
.IMark
.addr
;
428 opcode_type
=get_inst_type(st
->Ist
.IMark
.len
,ourAddr
);
431 arg1
= mkIRExpr_HWord( (HWord
)bbInfo
);
432 argv
= mkIRExprVec_1(arg1
);
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
),
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
),
448 di
= unsafeIRDirty_0_N( regparms
, "per_instruction_BBV",
449 VG_(fnptr_to_fnentry
)( &per_instruction_BBV
),
454 /* Insert our call */
455 addStmtToIRSB( sbOut
, IRStmt_Dirty(di
));
458 /* Insert the original instruction */
459 addStmtToIRSB( sbOut
, st
);
467 static struct thread_info
*allocate_new_thread(struct thread_info
*old
,
468 Int old_number
, Int new_number
)
470 struct thread_info
*temp
;
471 struct BB_info
*bb_elem
;
474 temp
=VG_(realloc
)("bbv_main.c allocate_threads",
476 new_number
*sizeof(struct thread_info
));
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;
483 temp
[i
].total_instr
=0;
484 temp
[i
].global_rep_count
=0;
485 temp
[i
].unique_rep_count
=0;
487 temp
[i
].fldcw_count
=0;
488 temp
[i
].bbtrace_fd
=-1;
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;
505 static void bbv_thread_called ( ThreadId tid
, ULong nDisp
)
507 if (tid
>= allocated_threads
) {
508 bbv_thread
=allocate_new_thread(bbv_thread
,allocated_threads
,tid
+1);
509 allocated_threads
=tid
+1;
517 /*--------------------------------------------------------------------*/
519 /*--------------------------------------------------------------------*/
521 static void bbv_post_clo_init(void)
524 VG_(expand_file_name
)("--bb-out-file", clo_bb_out_file
);
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;
532 /* Parse the command line options */
533 static Bool
bbv_process_cmd_line_option(Char
* arg
)
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
;
540 else if VG_BOOL_CLO (arg
, "--instr-count-only", instr_count_only
) {}
548 static void bbv_print_usage(void)
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"
558 static void bbv_print_debug_usage(void)
560 VG_(printf
)(" (none)\n");
563 static void bbv_fini(Int exitcode
)
567 if (generate_pc_file
) {
571 for(i
=0;i
<allocated_threads
;i
++) {
573 if (bbv_thread
[i
].total_instr
!=0) {
575 VG_(sprintf
)(buf
,"\n\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",
583 (Int
)(bbv_thread
[i
].total_instr
/(ULong
)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
);
590 /* Print results to display */
591 VG_(umsg
)("%s\n", buf
);
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
);
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
);
604 static void bbv_pre_clo_init(void)
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
);
613 VG_(basic_tool_funcs
) (bbv_post_clo_init
,
617 VG_(needs_command_line_options
)(bbv_process_cmd_line_option
,
619 bbv_print_debug_usage
);
621 VG_(track_start_client_code
)( bbv_thread_called
);
624 instr_info_table
= VG_(OSetGen_Create
)(/*keyOff*/0,
626 VG_(malloc
), "bbv.1", VG_(free
));
628 bbv_thread
=allocate_new_thread(bbv_thread
,0,allocated_threads
);
631 VG_DETERMINE_INTERFACE_VERSION(bbv_pre_clo_init
)
633 /*--------------------------------------------------------------------*/
635 /*--------------------------------------------------------------------*/