| /* ********************************************************** |
| * Copyright (c) 2013-2023 Google, Inc. All rights reserved. |
| * Copyright (c) 2008-2010 VMware, 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. |
| */ |
| |
| /* Code Manipulation API Sample: |
| * stats.c |
| * |
| * Serves as an example of how to create custom statistics and export |
| * them in shared memory. It uses the Windows API, which will be redirected |
| * by DynamoRIO in order to maintain isolation and transparency. |
| * The current version only supports viewing statistics from processes |
| * in the same session and by the same user. Look at older versions of |
| * the sources for statistics that can be read across sessions (but only |
| * when the target process and the viewer are run as administrator). |
| * |
| * These statistics can be viewed using the provided statistics |
| * viewer. This code also documents the official shared memory layout |
| * required by the statistics viewer. |
| */ |
| |
| #define _CRT_SECURE_NO_DEPRECATE 1 |
| |
| #include "dr_api.h" |
| #include "drmgr.h" |
| #include "drx.h" |
| #include "utils.h" |
| #include <stddef.h> /* for offsetof */ |
| #include <wchar.h> /* _snwprintf */ |
| |
| #ifndef WINDOWS |
| # error WINDOWS-only! |
| #endif |
| |
| #define ALIGNED(x, alignment) ((((ptr_uint_t)x) & ((alignment)-1)) == 0) |
| |
| /* We export a set of stats in shared memory. |
| * drstats.exe reads and displays them. |
| */ |
| const char *stat_names[] = { |
| /* drstats.exe displays CLIENTSTAT_NAME_MAX_LEN chars for each name */ |
| "Instructions", |
| "Floating point instrs", |
| "System calls", |
| }; |
| |
| /* We do not prefix "Global\", so these stats are NOT visible across sessions |
| * (that requires running as admiministrator, on Vista+). |
| * On NT these prefixes are not supported. |
| */ |
| #define CLIENT_SHMEM_KEY_NT_L L"DynamoRIO_Client_Statistics" |
| #define CLIENT_SHMEM_KEY_L L"Local\\DynamoRIO_Client_Statistics" |
| #define CLIENTSTAT_NAME_MAX_LEN 47 |
| #define NUM_STATS (sizeof(stat_names) / sizeof(char *)) |
| |
| /* Statistics are all 64-bit for x64. At some point we'll add per-stat |
| * typing, but for now we have identical types dependent on the platform. |
| */ |
| #ifdef X86_64 |
| typedef int64 stats_int_t; |
| # define STAT_FORMAT_CODE UINT64_FORMAT_CODE |
| #else |
| typedef int stats_int_t; |
| # define STAT_FORMAT_CODE "d" |
| #endif |
| |
| /* We allocate this struct in the shared memory: */ |
| typedef struct _client_stats_t { |
| uint num_stats; |
| bool exited; |
| process_id_t pid; |
| /* We need a copy of all the names here. */ |
| char names[NUM_STATS][CLIENTSTAT_NAME_MAX_LEN]; |
| stats_int_t num_instrs; |
| stats_int_t num_flops; |
| stats_int_t num_syscalls; |
| } client_stats_t; |
| |
| /* Counters that we collect for each basic block. */ |
| typedef struct _per_bb_data_t { |
| uint num_instrs; |
| uint num_flops; |
| uint num_syscalls; |
| } per_bb_data_t; |
| |
| /* we directly increment the global counters using a lock prefix */ |
| static client_stats_t *stats; |
| |
| /***************************************************************************/ |
| /* Shared memory setup */ |
| |
| /* We have multiple shared memories: one that holds the count of |
| * statistics instances, and then one per statistics struct. |
| */ |
| static HANDLE shared_map_count; |
| static PVOID shared_view_count; |
| static int *shared_count; |
| static HANDLE shared_map; |
| static PVOID shared_view; |
| #define KEYNAME_MAXLEN 128 |
| static wchar_t shared_keyname[KEYNAME_MAXLEN]; |
| |
| /* Returns current contents of addr and replaces contents with value. */ |
| static uint |
| atomic_swap(void *addr, uint value) |
| { |
| return _InterlockedExchange((long *)addr, value); |
| } |
| |
| static bool |
| is_windows_NT(void) |
| { |
| dr_os_version_info_t ver; |
| return (dr_get_os_version(&ver) && ver.version == DR_WINDOWS_VERSION_NT); |
| } |
| |
| static client_stats_t * |
| shared_memory_init(void) |
| { |
| bool is_NT = is_windows_NT(); |
| int num; |
| int pos; |
| /* We do not want to rely on the registry. |
| * Instead, a piece of shared memory with the key base name holds the |
| * total number of stats instances. |
| */ |
| shared_map_count = CreateFileMappingW( |
| INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(client_stats_t), |
| is_NT ? CLIENT_SHMEM_KEY_NT_L : CLIENT_SHMEM_KEY_L); |
| DR_ASSERT(shared_map_count != NULL); |
| shared_view_count = |
| MapViewOfFile(shared_map_count, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); |
| DR_ASSERT(shared_view_count != NULL); |
| shared_count = (int *)shared_view_count; |
| /* ASSUMPTION: memory is initialized to 0! |
| * otherwise our protocol won't work |
| * it's hard to build a protocol to initialize it to 0 -- if you want |
| * to add one, feel free, but make sure it's correct |
| */ |
| do { |
| pos = (int)atomic_swap(shared_count, (uint)-1); |
| /* if get -1 back, someone else is looking at it */ |
| } while (pos == -1); |
| /* now increment it */ |
| atomic_swap(shared_count, pos + 1); |
| |
| num = 0; |
| while (1) { |
| _snwprintf(shared_keyname, KEYNAME_MAXLEN, L"%s.%03d", |
| is_NT ? CLIENT_SHMEM_KEY_NT_L : CLIENT_SHMEM_KEY_L, num); |
| shared_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, |
| sizeof(client_stats_t), shared_keyname); |
| if (shared_map != NULL && GetLastError() == ERROR_ALREADY_EXISTS) { |
| dr_close_file(shared_map); |
| shared_map = NULL; |
| } |
| if (shared_map != NULL) |
| break; |
| num++; |
| } |
| dr_log(NULL, DR_LOG_ALL, 1, "Shared memory key is: \"%S\"\n", shared_keyname); |
| #ifdef SHOW_RESULTS |
| dr_fprintf(STDERR, "Shared memory key is: \"%S\"\n", shared_keyname); |
| #endif |
| shared_view = MapViewOfFile(shared_map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); |
| DR_ASSERT(shared_view != NULL); |
| return (client_stats_t *)shared_view; |
| } |
| |
| static void |
| shared_memory_exit(void) |
| { |
| int pos; |
| stats->exited = true; |
| /* close down statistics */ |
| UnmapViewOfFile(shared_view); |
| dr_close_file(shared_map); |
| /* decrement count, then unmap */ |
| do { |
| pos = atomic_swap(shared_count, (uint)-1); |
| /* if get -1 back, someone else is looking at it */ |
| } while (pos == -1); |
| /* now increment it */ |
| atomic_swap(shared_count, pos - 1); |
| UnmapViewOfFile(shared_view_count); |
| CloseHandle(shared_map_count); |
| } |
| /***************************************************************************/ |
| |
| static void |
| event_exit(void); |
| |
| static dr_emit_flags_t |
| event_analyze_bb(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, |
| bool translating, void **user_data); |
| |
| static dr_emit_flags_t |
| event_insert_instrumentation(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, |
| bool for_trace, bool translating, void *user_data); |
| |
| static client_id_t my_id; |
| |
| DR_EXPORT void |
| dr_client_main(client_id_t id, int argc, const char *argv[]) |
| { |
| uint i; |
| |
| dr_set_client_name("DynamoRIO Sample Client 'stats'", "http://dynamorio.org/issues"); |
| my_id = id; |
| /* Make it easy to tell by looking at the log which client executed. */ |
| dr_log(NULL, DR_LOG_ALL, 1, "Client 'stats' initializing\n"); |
| |
| if (!drmgr_init()) |
| DR_ASSERT(false); |
| drx_init(); |
| |
| stats = shared_memory_init(); |
| memset(stats, 0, sizeof(stats)); |
| stats->num_stats = NUM_STATS; |
| stats->pid = dr_get_process_id(); |
| for (i = 0; i < NUM_STATS; i++) { |
| strncpy(stats->names[i], stat_names[i], CLIENTSTAT_NAME_MAX_LEN); |
| stats->names[i][CLIENTSTAT_NAME_MAX_LEN - 1] = '\0'; |
| } |
| dr_register_exit_event(event_exit); |
| if (!drmgr_register_bb_instrumentation_event(event_analyze_bb, |
| event_insert_instrumentation, NULL)) |
| DR_ASSERT(false); |
| } |
| |
| #ifdef WINDOWS |
| # define IF_WINDOWS(x) x |
| #else |
| # define IF_WINDOWS(x) /* nothing */ |
| #endif |
| |
| static void |
| event_exit(void) |
| { |
| file_t f; |
| /* Display the results! */ |
| char msg[512]; |
| int len; |
| len = dr_snprintf(msg, sizeof(msg) / sizeof(msg[0]), |
| "Instrumentation results:\n" |
| " saw %" STAT_FORMAT_CODE " flops\n", |
| stats->num_flops); |
| DR_ASSERT(len > 0); |
| msg[sizeof(msg) / sizeof(msg[0]) - 1] = '\0'; |
| #ifdef SHOW_RESULTS |
| DISPLAY_STRING(msg); |
| #endif /* SHOW_RESULTS */ |
| |
| /* On Windows we need an absolute path so we place it in |
| * the same directory as our library. |
| */ |
| f = log_file_open(my_id, NULL, NULL /* client lib path */, "stats", 0); |
| DR_ASSERT(f != INVALID_FILE); |
| dr_fprintf(f, "%s\n", msg); |
| dr_close_file(f); |
| |
| shared_memory_exit(); |
| |
| drx_exit(); |
| if (!drmgr_unregister_bb_instrumentation_event(event_analyze_bb)) |
| DR_ASSERT(false); |
| drmgr_exit(); |
| } |
| |
| /* This event is passed the instruction list for the whole bb. */ |
| static dr_emit_flags_t |
| event_analyze_bb(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, |
| bool translating, void **user_data) |
| { |
| /* Count the instructions and pass the result to event_insert_instrumentation. */ |
| per_bb_data_t *per_bb = dr_thread_alloc(drcontext, sizeof(*per_bb)); |
| instr_t *instr; |
| uint num_instrs = 0; |
| uint num_flops = 0; |
| uint num_syscalls = 0; |
| dr_instr_category_t fp_type; |
| |
| for (instr = instrlist_first_app(bb); instr != NULL; |
| instr = instr_get_next_app(instr)) { |
| num_instrs++; |
| if (instr_is_floating_type(instr, &fp_type) && |
| /* We exclude loads and stores (and reg-reg moves) and state preservation */ |
| (DR_INSTR_CATEGORY_CONVERT && fp_type != 0 || |
| DR_INSTR_CATEGORY_MATH && fp_type != 0)) { |
| #ifdef VERBOSE |
| dr_print_instr(drcontext, STDOUT, instr, "Found flop: "); |
| #endif |
| num_flops++; |
| } |
| if (instr_is_syscall(instr)) { |
| num_syscalls++; |
| } |
| } |
| |
| per_bb->num_instrs = num_instrs; |
| per_bb->num_flops = num_flops; |
| per_bb->num_syscalls = num_syscalls; |
| *(per_bb_data_t **)user_data = per_bb; |
| |
| return DR_EMIT_DEFAULT; |
| } |
| |
| /* This event is called separately for each individual instruction in the bb. */ |
| static dr_emit_flags_t |
| event_insert_instrumentation(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, |
| bool for_trace, bool translating, void *user_data) |
| { |
| per_bb_data_t *per_bb = (per_bb_data_t *)user_data; |
| /* We increment the per-bb counters just once, at the top of the bb. */ |
| if (drmgr_is_first_instr(drcontext, instr)) { |
| /* drx will analyze whether to save the flags for us. */ |
| uint flags = DRX_COUNTER_LOCK; |
| if (per_bb->num_instrs > 0) { |
| drx_insert_counter_update(drcontext, bb, instr, SPILL_SLOT_MAX + 1, |
| &stats->num_instrs, per_bb->num_instrs, flags); |
| } |
| if (per_bb->num_flops > 0) { |
| drx_insert_counter_update(drcontext, bb, instr, SPILL_SLOT_MAX + 1, |
| &stats->num_flops, per_bb->num_flops, flags); |
| } |
| if (per_bb->num_syscalls > 0) { |
| drx_insert_counter_update(drcontext, bb, instr, SPILL_SLOT_MAX + 1, |
| &stats->num_syscalls, per_bb->num_syscalls, flags); |
| } |
| } |
| if (drmgr_is_last_instr(drcontext, instr)) |
| dr_thread_free(drcontext, per_bb, sizeof(*per_bb)); |
| return DR_EMIT_DEFAULT; |
| } |