| /* ********************************************************** |
| * Copyright (c) 2014-2020 Google, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Basic Block Duplicator API Sample: |
| * hot_bbcount.c |
| * |
| * Reports the dynamic execution count of hot basic blocks. |
| * Illustrates how to use drbbdup to create different versions of |
| * basic block instrumentation. |
| * |
| * Two cases of instrumentation are set for a basic block. The first |
| * version is executed when a basic block is cold. The basic block's hit |
| * count is recorded by performing a clean call in this instrumentation case. |
| * The second version is executed when the basic block has reached the appropriate |
| * hit count (i.e., it is now considered hot). Code is inserted to count the |
| * execution of the hot basic block similar to the bbcount client. |
| */ |
| |
| #include <stddef.h> /* for offsetof */ |
| #include "dr_api.h" |
| #include "drmgr.h" |
| #include "drreg.h" |
| #include "drbbdup.h" |
| #include "drx.h" |
| #include "hashtable.h" |
| |
| /* Start counting once a bb has been executed at least 1000 times. */ |
| #define HIT_THRESHOLD 1000 |
| |
| #ifdef WINDOWS |
| # define DISPLAY_STRING(msg) dr_messagebox(msg) |
| #else |
| # define DISPLAY_STRING(msg) dr_printf("%s\n", msg); |
| #endif |
| |
| #define NULL_TERMINATE(buf) (buf)[(sizeof((buf)) / sizeof((buf)[0])) - 1] = '\0' |
| |
| /* we only have a global count */ |
| static int global_count; |
| |
| /* Global hash table to keep track of the hit count of cold basic blocks. */ |
| static hashtable_t hit_count_table; |
| #define HASH_BITS 13 |
| |
| static reg_id_t tls_raw_reg; |
| static uint tls_raw_offset; |
| |
| static void |
| event_exit(void) |
| { |
| #ifdef SHOW_RESULTS |
| char msg[512]; |
| int len; |
| len = dr_snprintf(msg, sizeof(msg) / sizeof(msg[0]), |
| "Instrumentation results:\n" |
| "%10d hot basic block executions\n", |
| global_count); |
| DR_ASSERT(len > 0); |
| NULL_TERMINATE(msg); |
| DISPLAY_STRING(msg); |
| #endif /* SHOW_RESULTS */ |
| |
| /* Delete hit count table. */ |
| hashtable_delete(&hit_count_table); |
| |
| dr_raw_tls_cfree(tls_raw_offset, 1); |
| drbbdup_exit(); |
| drx_exit(); |
| drreg_exit(); |
| drmgr_exit(); |
| } |
| |
| static uintptr_t |
| set_up_bb_dups(void *drbbdup_ctx, void *drcontext, void *tag, instrlist_t *bb, |
| bool *enable_dups, bool *enable_dynamic_handling, void *user_data) |
| { |
| /* Init hit count. */ |
| app_pc bb_pc = instr_get_app_pc(instrlist_first_app(bb)); |
| hashtable_lock(&hit_count_table); |
| if (hashtable_lookup(&hit_count_table, bb_pc) == NULL) { |
| /* If hit count is not mapped to this bb, then add a new count to the table. */ |
| uint *hit_count = dr_global_alloc(sizeof(uint)); |
| *hit_count = HIT_THRESHOLD; |
| hashtable_add(&hit_count_table, bb_pc, hit_count); |
| } |
| hashtable_unlock(&hit_count_table); |
| |
| /* Enable duplication for all basic blocks. */ |
| *enable_dups = true; |
| |
| /* Disable dynamic handling. */ |
| *enable_dynamic_handling = false; |
| |
| /* Register the case encoding for counting the execution of hot basic blocks. */ |
| drbbdup_register_case_encoding(drbbdup_ctx, (uintptr_t) true /* hot */); |
| |
| /* Set the default case encoding for tracking the hit count of basic blocks. */ |
| return 0; /* cold */ |
| } |
| |
| static void |
| analyse_orig_bb(void *drcontext, void *tag, instrlist_t *bb, void *user_data, |
| DR_PARAM_IN void **orig_analysis_data) |
| { |
| /* Extract bb_pc and store it as analysis data. */ |
| app_pc *bb_pc = dr_thread_alloc(drcontext, sizeof(app_pc)); |
| *bb_pc = instr_get_app_pc(instrlist_first_app(bb)); |
| *orig_analysis_data = bb_pc; |
| } |
| |
| static void |
| destroy_orig_analysis(void *drcontext, void *user_data, void *bb_pc) |
| { |
| /* Destroy the orig analysis data, particularly the pc of the bb. */ |
| DR_ASSERT(bb_pc != NULL); |
| dr_thread_free(drcontext, bb_pc, sizeof(app_pc)); |
| } |
| |
| static void |
| encode(app_pc bb_pc) |
| { |
| bool is_hot; |
| hashtable_lock(&hit_count_table); |
| uint *hit_count = hashtable_lookup(&hit_count_table, bb_pc); |
| DR_ASSERT_MSG(hit_count != NULL, "hit count must be present"); |
| is_hot = *hit_count == 0; |
| hashtable_unlock(&hit_count_table); |
| |
| byte *base = dr_get_dr_segment_base(tls_raw_reg); |
| uintptr_t *runtime_case = (uintptr_t *)(base + tls_raw_offset); |
| *runtime_case = (uintptr_t)is_hot; |
| } |
| |
| static void |
| insert_encode(void *drcontext, void *tag, instrlist_t *bb, instr_t *where, |
| void *user_data, void *orig_analysis_data) |
| { |
| app_pc *bb_pc = (app_pc *)orig_analysis_data; |
| dr_insert_clean_call(drcontext, bb, where, (void *)encode, false, 1, |
| OPND_CREATE_INTPTR(*bb_pc)); |
| } |
| |
| static void |
| register_hit(app_pc bb_pc) |
| { |
| /* Decrement the hit count. Once it reaches zero, |
| * basic block is considered as hot. |
| */ |
| hashtable_lock(&hit_count_table); |
| uint *hit_count = hashtable_lookup(&hit_count_table, bb_pc); |
| DR_ASSERT_MSG(hit_count != NULL, "hit count must be present"); |
| DR_ASSERT_MSG(hit_count > 0, "bb cannot be hot"); |
| (*hit_count)--; |
| hashtable_unlock(&hit_count_table); |
| } |
| |
| static void |
| instrument_instr(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, |
| instr_t *where, uintptr_t encoding, void *user_data, |
| void *orig_analysis_data, void *analysis_data) |
| { |
| bool is_start; |
| |
| /* By default drmgr enables auto-predication, which predicates all instructions with |
| * the predicate of the current instruction on ARM. |
| * We disable it here because we want to unconditionally execute the following |
| * instrumentation. |
| */ |
| drmgr_disable_auto_predication(drcontext, bb); |
| |
| /* Determine whether the instr is the first instruction in the |
| * currently considered basic block copy. |
| */ |
| drbbdup_is_first_instr(drcontext, instr, &is_start); |
| if (is_start) { |
| /* Check if hot case. */ |
| if (encoding == 1) { |
| /* racy update on the counter for better performance */ |
| drx_insert_counter_update(drcontext, bb, where /*insert always at where */, |
| /* We're using drmgr, so these slots |
| * here won't be used: drreg's slots will be. |
| */ |
| SPILL_SLOT_MAX + 1, &global_count, 1, 0); |
| |
| } else { |
| /* Basic block is cold. Therefore insert clean call to mark the hit. */ |
| app_pc *bb_pc = (app_pc *)orig_analysis_data; |
| dr_insert_clean_call(drcontext, bb, where /* insert always at where */, |
| (void *)register_hit, false, 1, |
| OPND_CREATE_INTPTR(*bb_pc)); |
| } |
| } |
| } |
| |
| static void |
| destroy_hit_count(void *entry) |
| { |
| uint *hit_count = (uint *)entry; |
| dr_global_free(hit_count, sizeof(uint)); |
| } |
| |
| DR_EXPORT void |
| dr_client_main(client_id_t id, int argc, const char *argv[]) |
| { |
| drreg_options_t drreg_ops = { sizeof(drreg_ops), 1 /* max slots needed: aflags */, |
| false }; |
| dr_set_client_name("DynamoRIO Sample Client 'hot_bbcount'", |
| "http://dynamorio.org/issues"); |
| if (!drmgr_init() || !drx_init() || drreg_init(&drreg_ops) != DRREG_SUCCESS) |
| DR_ASSERT(false); |
| |
| /* register events */ |
| dr_register_exit_event(event_exit); |
| |
| hashtable_init_ex(&hit_count_table, HASH_BITS, HASH_INTPTR, false /*!strdup*/, |
| false /*synchronization is external*/, destroy_hit_count, NULL, |
| NULL); |
| |
| /* Initialise an addressable TLS slot to contain the runtime case encoding. */ |
| if (!dr_raw_tls_calloc(&tls_raw_reg, &tls_raw_offset, 1 /* num of slots */, 0)) |
| DR_ASSERT(false); |
| |
| /* Initialise drbbdup. Essentially, drbbdup requires |
| * the client to pass a set of call-back functions and |
| * the memory operand that the dispatcher will use |
| * to load the current runtime case encoding. |
| */ |
| drbbdup_options_t drbbdup_ops = { 0 }; |
| drbbdup_ops.struct_size = sizeof(drbbdup_options_t); |
| drbbdup_ops.set_up_bb_dups = set_up_bb_dups; |
| drbbdup_ops.insert_encode = insert_encode; |
| drbbdup_ops.analyze_orig = analyse_orig_bb; |
| drbbdup_ops.destroy_orig_analysis = destroy_orig_analysis; |
| drbbdup_ops.instrument_instr = instrument_instr; |
| /* The operand referring to memory storing the current runtime case encoding. */ |
| drbbdup_ops.runtime_case_opnd = |
| dr_raw_tls_opnd(dr_get_current_drcontext(), tls_raw_reg, tls_raw_offset); |
| drbbdup_ops.non_default_case_limit = 1; /* Only one additional copy is needed. */ |
| drbbdup_ops.is_stat_enabled = false; |
| |
| if (drbbdup_init(&drbbdup_ops) != DRBBDUP_SUCCESS) |
| DR_ASSERT(false); |
| |
| /* make it easy to tell, by looking at log file, which client executed */ |
| dr_log(NULL, DR_LOG_ALL, 1, "Client 'hot_bbcount' initializing\n"); |
| #ifdef SHOW_RESULTS |
| /* also give notification to stderr */ |
| if (dr_is_notify_on()) { |
| # ifdef WINDOWS |
| /* ask for best-effort printing to cmd window. must be called at init. */ |
| dr_enable_console_printing(); |
| # endif |
| dr_fprintf(STDERR, "Client hot_bbcount is running\n"); |
| } |
| #endif |
| } |