lk_main.c revision fd73ebb56f414b2d1807160cabffab4ebb20e3ab
1
2/*--------------------------------------------------------------------*/
3/*--- An example Valgrind tool.                                    ---*/
4/*---                                                    lk_main.c ---*/
5/*--------------------------------------------------------------------*/
6
7/*
8   This file is part of Lackey, an example Valgrind tool that does
9   some simple program measurement.
10
11   Copyright (C) 2002-2005 Nicholas Nethercote
12      njn@valgrind.org
13
14   This program is free software; you can redistribute it and/or
15   modify it under the terms of the GNU General Public License as
16   published by the Free Software Foundation; either version 2 of the
17   License, or (at your option) any later version.
18
19   This program is distributed in the hope that it will be useful, but
20   WITHOUT ANY WARRANTY; without even the implied warranty of
21   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22   General Public License for more details.
23
24   You should have received a copy of the GNU General Public License
25   along with this program; if not, write to the Free Software
26   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27   02111-1307, USA.
28
29   The GNU General Public License is contained in the file COPYING.
30*/
31
32// This tool shows how to do some basic instrumentation.
33//
34// In particular, if you are interested in tracing every load and store a
35// program does, use the --trace-mem=yes option.  Please note that the
36// address trace is good, but not perfect;  see Section 3.3.7 of Nicholas
37// Nethercote's PhD dissertation "Dynamic Binary Analysis and
38// Instrumentation", 2004, for details about the few loads and stores that
39// it misses, and other caveats about the accuracy of the address trace.
40//
41// If you want to modify how the memory traces are printed/gathered, look at
42// the code that is controlled by the variable 'lk_clo_trace_mem' and the
43// functions 'trace_load()' and 'trace_mem'..  With a bit of effort you
44// should be able to see which other bits of code can be removed, if that's
45// what you want.  If you want to do more complex modifications, please read
46// VEX/pub/libvex_ir.h to understand the intermediate representation.
47//
48// For further inspiration, you should look at cachegrind/cg_main.c which
49// handles memory accesses in a more sophisticated way -- it groups them
50// together for processing into twos and threes so that fewer C calls are
51// made and things run faster.
52
53#include "pub_tool_basics.h"
54#include "pub_tool_tooliface.h"
55#include "pub_tool_libcassert.h"
56#include "pub_tool_libcprint.h"
57#include "pub_tool_debuginfo.h"
58#include "pub_tool_libcbase.h"
59#include "pub_tool_options.h"
60#include "pub_tool_machine.h"     // VG_(fnptr_to_fnentry)
61
62/* The name of the function of which the number of calls is to be
63 * counted, with default. Override with command line option
64 * --fnname. */
65static Char* lk_clo_fnname = "_dl_runtime_resolve";
66
67/* If true, show statistics about loads, stores and alu ops. Set
68 * with command line option --detailed-counts. */
69static Bool lk_clo_detailed_counts = False;
70
71/* If true, print the trace of loads and stores.  Set with --trace-mem. */
72static Bool lk_clo_trace_mem = False;
73
74/***********************************************************************
75 * Implement the needs_command_line_options for Valgrind.
76 **********************************************************************/
77
78static Bool lk_process_cmd_line_option(Char* arg)
79{
80   VG_STR_CLO(arg, "--fnname", lk_clo_fnname)
81   else VG_BOOL_CLO(arg, "--detailed-counts", lk_clo_detailed_counts)
82   else VG_BOOL_CLO(arg, "--trace-mem",       lk_clo_trace_mem)
83   else
84      return False;
85
86   tl_assert(lk_clo_fnname);
87   tl_assert(lk_clo_fnname[0]);
88   return True;
89}
90
91static void lk_print_usage(void)
92{
93   VG_(printf)(
94"    --fnname=<name>           count calls to <name> [_dl_runtime_resolve]\n"
95"    --detailed-counts=no|yes  count loads, stores and alu ops [no]\n"
96   );
97}
98
99static void lk_print_debug_usage(void)
100{
101}
102
103/***********************************************************************
104 * Data and helpers related to the default operation of Lackey.
105 **********************************************************************/
106
107/* Nb: use ULongs because the numbers can get very big */
108static ULong n_func_calls    = 0;
109static ULong n_BBs_entered   = 0;
110static ULong n_BBs_completed = 0;
111static ULong n_IRStmts      = 0;
112static ULong n_guest_instrs  = 0;
113static ULong n_Jccs          = 0;
114static ULong n_Jccs_untaken  = 0;
115
116static void add_one_func_call(void)
117{
118   n_func_calls++;
119}
120
121static void add_one_BB_entered(void)
122{
123   n_BBs_entered++;
124}
125
126static void add_one_BB_completed(void)
127{
128   n_BBs_completed++;
129}
130
131static void add_one_IRStmt(void)
132{
133   n_IRStmts++;
134}
135
136static void add_one_guest_instr(void)
137{
138   n_guest_instrs++;
139}
140
141static void add_one_Jcc(void)
142{
143   n_Jccs++;
144}
145
146static void add_one_Jcc_untaken(void)
147{
148   n_Jccs_untaken++;
149}
150
151/***********************************************************************
152 * Data and helpers related to --detailed-counts.
153 **********************************************************************/
154
155/* --- Operations --- */
156
157typedef enum { OpLoad=0, OpStore=1, OpAlu=2 } Op;
158
159#define N_OPS 3
160
161
162/* --- Types --- */
163
164#define N_TYPES 9
165
166static Int type2index ( IRType ty )
167{
168   switch (ty) {
169      case Ity_I1:      return 0;
170      case Ity_I8:      return 1;
171      case Ity_I16:     return 2;
172      case Ity_I32:     return 3;
173      case Ity_I64:     return 4;
174      case Ity_I128:    return 5;
175      case Ity_F32:     return 6;
176      case Ity_F64:     return 7;
177      case Ity_V128:    return 8;
178      default: tl_assert(0); break;
179   }
180}
181
182static HChar* nameOfTypeIndex ( IRType ty )
183{
184   switch (ty) {
185      case 0: return "I1";   break;
186      case 1: return "I8";   break;
187      case 2: return "I16";  break;
188      case 3: return "I32";  break;
189      case 4: return "I64";  break;
190      case 5: return "I128"; break;
191      case 6: return "F32";  break;
192      case 7: return "F64";  break;
193      case 8: return "V128"; break;
194      default: tl_assert(0); break;
195   }
196}
197
198
199/* --- Counts --- */
200
201static ULong detailCounts[N_OPS][N_TYPES];
202
203/* The helper that is called from the instrumented code. */
204static VG_REGPARM(1)
205void increment_detail(ULong* detail)
206{
207   (*detail)++;
208}
209
210/* A helper that adds the instrumentation for a detail. */
211static void instrument_detail(IRBB* bb, Op op, IRType type)
212{
213   IRDirty* di;
214   IRExpr** argv;
215   const UInt typeIx = type2index(type);
216
217   tl_assert(op < N_OPS);
218   tl_assert(typeIx < N_TYPES);
219
220   argv = mkIRExprVec_1( mkIRExpr_HWord( (HWord)&detailCounts[op][typeIx] ) );
221   di = unsafeIRDirty_0_N( 1, "increment_detail",
222                              VG_(fnptr_to_fnentry)( &increment_detail ),
223                              argv);
224   addStmtToIRBB( bb, IRStmt_Dirty(di) );
225}
226
227/* Summarize and print the details. */
228
229static void print_details ( void )
230{
231   Int typeIx;
232   VG_(message)(Vg_UserMsg,
233                "   Type        Loads       Stores       AluOps");
234   VG_(message)(Vg_UserMsg,
235                "   -------------------------------------------");
236   for (typeIx = 0; typeIx < N_TYPES; typeIx++) {
237      VG_(message)(Vg_UserMsg,
238                   "   %4s %,12llu %,12llu %,12llu",
239                   nameOfTypeIndex( typeIx ),
240                   detailCounts[OpLoad ][typeIx],
241                   detailCounts[OpStore][typeIx],
242                   detailCounts[OpAlu  ][typeIx]
243      );
244   }
245}
246
247
248/***********************************************************************
249 * Data and helpers related to --trace-mem.
250 **********************************************************************/
251
252static VG_REGPARM(2) void trace_load(Addr addr, SizeT size)
253{
254   VG_(printf)("load : %p, %d\n", addr, size);
255}
256
257static VG_REGPARM(2) void trace_store(Addr addr, SizeT size)
258{
259   VG_(printf)("store: %p, %d\n", addr, size);
260}
261
262/***********************************************************************
263 * Implement the basic_tool_funcs for Valgrind.
264 **********************************************************************/
265
266static void lk_post_clo_init(void)
267{
268   Int op, tyIx;
269
270   for (op = 0; op < N_OPS; op++)
271      for (tyIx = 0; tyIx < N_TYPES; tyIx++)
272         detailCounts[op][tyIx] = 0;
273}
274
275static
276IRBB* lk_instrument( IRBB* bb_in, VexGuestLayout* layout,
277                     Addr64 orig_addr_noredir, VexGuestExtents* vge,
278                     IRType gWordTy, IRType hWordTy )
279{
280   IRDirty* di;
281   Int      i;
282   IRBB*    bb;
283   Char     fnname[100];
284   IRType   type;
285   IRExpr** argv;
286   IRExpr*  addr_expr;
287   IRExpr*  size_expr;
288
289   if (gWordTy != hWordTy) {
290      /* We don't currently support this case. */
291      VG_(tool_panic)("host/guest word size mismatch");
292   }
293
294   /* Set up BB */
295   bb           = emptyIRBB();
296   bb->tyenv    = dopyIRTypeEnv(bb_in->tyenv);
297   bb->next     = dopyIRExpr(bb_in->next);
298   bb->jumpkind = bb_in->jumpkind;
299
300   // Copy verbatim any IR preamble preceding the first IMark
301   i = 0;
302   while (i < bb_in->stmts_used && bb_in->stmts[i]->tag != Ist_IMark) {
303      addStmtToIRBB( bb, bb_in->stmts[i] );
304      i++;
305   }
306
307   /* Count this basic block. */
308   di = unsafeIRDirty_0_N( 0, "add_one_BB_entered",
309                              VG_(fnptr_to_fnentry)( &add_one_BB_entered ),
310                              mkIRExprVec_0() );
311   addStmtToIRBB( bb, IRStmt_Dirty(di) );
312
313   for (/*use current i*/; i < bb_in->stmts_used; i++) {
314      IRStmt* st = bb_in->stmts[i];
315      if (!st || st->tag == Ist_NoOp) continue;
316
317      /* Count one VEX statement. */
318      di = unsafeIRDirty_0_N( 0, "add_one_IRStmt",
319                                 VG_(fnptr_to_fnentry)( &add_one_IRStmt ),
320                                 mkIRExprVec_0() );
321      addStmtToIRBB( bb, IRStmt_Dirty(di) );
322
323      switch (st->tag) {
324         case Ist_IMark:
325            /* Count guest instruction. */
326            di = unsafeIRDirty_0_N( 0, "add_one_guest_instr",
327                                       VG_(fnptr_to_fnentry)( &add_one_guest_instr ),
328                                       mkIRExprVec_0() );
329            addStmtToIRBB( bb, IRStmt_Dirty(di) );
330
331            /* An unconditional branch to a known destination in the
332             * guest's instructions can be represented, in the IRBB to
333             * instrument, by the VEX statements that are the
334             * translation of that known destination. This feature is
335             * called 'BB chasing' and can be influenced by command
336             * line option --vex-guest-chase-thresh.
337             *
338             * To get an accurate count of the calls to a specific
339             * function, taking BB chasing into account, we need to
340             * check for each guest instruction (Ist_IMark) if it is
341             * the entry point of a function.
342             */
343            tl_assert(lk_clo_fnname);
344            tl_assert(lk_clo_fnname[0]);
345            if (VG_(get_fnname_if_entry)(st->Ist.IMark.addr,
346                                         fnname, sizeof(fnname))
347                && 0 == VG_(strcmp)(fnname, lk_clo_fnname)) {
348               di = unsafeIRDirty_0_N(
349                       0, "add_one_func_call",
350                          VG_(fnptr_to_fnentry)( &add_one_func_call ),
351                          mkIRExprVec_0() );
352               addStmtToIRBB( bb, IRStmt_Dirty(di) );
353            }
354            addStmtToIRBB( bb, st );
355            break;
356
357         case Ist_Exit:
358            /* Count Jcc */
359            di = unsafeIRDirty_0_N( 0, "add_one_Jcc",
360                                       VG_(fnptr_to_fnentry)( &add_one_Jcc ),
361                                       mkIRExprVec_0() );
362            addStmtToIRBB( bb, IRStmt_Dirty(di) );
363
364            addStmtToIRBB( bb, st );
365
366            /* Count non-taken Jcc */
367            di = unsafeIRDirty_0_N( 0, "add_one_Jcc_untaken",
368                                       VG_(fnptr_to_fnentry)( &add_one_Jcc_untaken ),
369                                       mkIRExprVec_0() );
370            addStmtToIRBB( bb, IRStmt_Dirty(di) );
371            break;
372
373         /* Someone on the users list asked for something like this
374          * just the other day (Christian Stimming, "Fast profiling in
375          * valgrind?", 25 Oct).  Personally I think it'd be a
376          * valuable addition.
377          *
378          * Not hard to do either: for stores, examine Ist_Store, and
379          * use typeOfIRExpr(bb->tyenv, st->Ist.Store.data) to get the
380          * store type.  For loads and ALU ops, you only need to look
381          * at Ist_Tmp cases where the Ist.Tmp.data is either Iex_Load
382          * or Iex_{Unop,Binop}.  All statements you will ever
383          * encounter will satisfy isFlatIRStmt which essentially
384          * constrains them to being flat SSA-style.
385          */
386         case Ist_Store:
387            // Add a call to trace_store() if --trace-mem=yes.
388            if (lk_clo_trace_mem) {
389               addr_expr = st->Ist.Store.addr;
390               size_expr = mkIRExpr_HWord(
391                             sizeofIRType(
392                               typeOfIRExpr(bb->tyenv, st->Ist.Store.data)));
393               argv = mkIRExprVec_2( addr_expr, size_expr );
394               di = unsafeIRDirty_0_N( /*regparms*/2,
395                                       "trace_store",
396                                       VG_(fnptr_to_fnentry)( trace_store ),
397                                       argv );
398               addStmtToIRBB( bb, IRStmt_Dirty(di) );
399            }
400            if (lk_clo_detailed_counts) {
401               type = typeOfIRExpr(bb->tyenv, st->Ist.Store.data);
402               tl_assert(type != Ity_INVALID);
403               instrument_detail( bb, OpStore, type );
404            }
405            addStmtToIRBB( bb, st );
406            break;
407
408         case Ist_Tmp:
409            // Add a call to trace_load() if --trace-mem=yes.
410            if (lk_clo_trace_mem) {
411               IRExpr* data = st->Ist.Tmp.data;
412               if (data->tag == Iex_Load) {
413                  addr_expr = data->Iex.Load.addr;
414                  size_expr = mkIRExpr_HWord( sizeofIRType(data->Iex.Load.ty) );
415                  argv = mkIRExprVec_2( addr_expr, size_expr );
416                  di = unsafeIRDirty_0_N( /*regparms*/2,
417                                          "trace_load",
418                                          VG_(fnptr_to_fnentry)( trace_load ),
419                                          argv );
420                  addStmtToIRBB( bb, IRStmt_Dirty(di) );
421               }
422            }
423            if (lk_clo_detailed_counts) {
424               IRExpr* expr = st->Ist.Tmp.data;
425               type = typeOfIRExpr(bb->tyenv, expr);
426               tl_assert(type != Ity_INVALID);
427               switch (expr->tag) {
428                  case Iex_Load:
429                     instrument_detail( bb, OpLoad, type );
430                     break;
431                  case Iex_Unop:
432                  case Iex_Binop:
433                  case Iex_Mux0X:
434                     instrument_detail( bb, OpAlu, type );
435                     break;
436                  default:
437                     break;
438               }
439            }
440            addStmtToIRBB( bb, st );
441            break;
442
443         default:
444            addStmtToIRBB( bb, st );
445      }
446   }
447
448   /* Count this basic block. */
449   di = unsafeIRDirty_0_N( 0, "add_one_BB_completed",
450                              VG_(fnptr_to_fnentry)( &add_one_BB_completed ),
451                              mkIRExprVec_0() );
452   addStmtToIRBB( bb, IRStmt_Dirty(di) );
453
454   return bb;
455}
456
457static void lk_fini(Int exitcode)
458{
459   char percentify_buf[4]; /* Two digits, '%' and 0. */
460   const int percentify_size = sizeof(percentify_buf);
461   const int percentify_decs = 0;
462
463   tl_assert(lk_clo_fnname);
464   tl_assert(lk_clo_fnname[0]);
465   VG_(message)(Vg_UserMsg,
466      "Counted %,llu calls to %s()", n_func_calls, lk_clo_fnname);
467
468   VG_(message)(Vg_UserMsg, "");
469   VG_(message)(Vg_UserMsg, "Jccs:");
470   VG_(message)(Vg_UserMsg, "  total:         %,llu", n_Jccs);
471   VG_(percentify)((n_Jccs - n_Jccs_untaken), (n_Jccs ? n_Jccs : 1),
472      percentify_decs, percentify_size, percentify_buf);
473   VG_(message)(Vg_UserMsg, "  taken:         %,llu (%s)",
474      (n_Jccs - n_Jccs_untaken), percentify_buf);
475
476   VG_(message)(Vg_UserMsg, "");
477   VG_(message)(Vg_UserMsg, "Executed:");
478   VG_(message)(Vg_UserMsg, "  BBs entered:   %,llu", n_BBs_entered);
479   VG_(message)(Vg_UserMsg, "  BBs completed: %,llu", n_BBs_completed);
480   VG_(message)(Vg_UserMsg, "  guest instrs:  %,llu", n_guest_instrs);
481   VG_(message)(Vg_UserMsg, "  IRStmts:       %,llu", n_IRStmts);
482
483   VG_(message)(Vg_UserMsg, "");
484   VG_(message)(Vg_UserMsg, "Ratios:");
485   tl_assert(n_BBs_entered); // Paranoia time.
486   VG_(message)(Vg_UserMsg, "  guest instrs : BB entered  = %3u : 10",
487      10 * n_guest_instrs / n_BBs_entered);
488   VG_(message)(Vg_UserMsg, "       IRStmts : BB entered  = %3u : 10",
489      10 * n_IRStmts / n_BBs_entered);
490   tl_assert(n_guest_instrs); // Paranoia time.
491   VG_(message)(Vg_UserMsg, "       IRStmts : guest instr = %3u : 10",
492      10 * n_IRStmts / n_guest_instrs);
493
494   if (lk_clo_detailed_counts) {
495      VG_(message)(Vg_UserMsg, "");
496      VG_(message)(Vg_UserMsg, "IR-level counts by type:");
497      print_details();
498   }
499
500   VG_(message)(Vg_UserMsg, "");
501   VG_(message)(Vg_UserMsg, "Exit code:       %d", exitcode);
502}
503
504static void lk_pre_clo_init(void)
505{
506   VG_(details_name)            ("Lackey");
507   VG_(details_version)         (NULL);
508   VG_(details_description)     ("an example Valgrind tool");
509   VG_(details_copyright_author)(
510      "Copyright (C) 2002-2005, and GNU GPL'd, by Nicholas Nethercote.");
511   VG_(details_bug_reports_to)  (VG_BUGS_TO);
512   VG_(details_avg_translation_sizeB) ( 175 );
513
514   VG_(basic_tool_funcs)          (lk_post_clo_init,
515                                   lk_instrument,
516                                   lk_fini);
517   VG_(needs_command_line_options)(lk_process_cmd_line_option,
518                                   lk_print_usage,
519                                   lk_print_debug_usage);
520}
521
522VG_DETERMINE_INTERFACE_VERSION(lk_pre_clo_init)
523
524/*--------------------------------------------------------------------*/
525/*--- end                                                lk_main.c ---*/
526/*--------------------------------------------------------------------*/
527