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