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