mirror of
https://github.com/DynamoRIO/dynamorio.git
synced 2026-10-12 10:09:50 +00:00
- Add `-Wimplicit-fallthrough` to `BASE_CFLAGS` when available - Added a `DR_FALLTHROUGH` macro that expands to `__attribute__((__fallthrough__))` when available - Annotate existing code with `DR_FALLTHROUGH` Fixes #7986
1193 lines
51 KiB
C
1193 lines
51 KiB
C
/* ******************************************************
|
|
* Copyright (c) 2014-2026 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.
|
|
*/
|
|
|
|
#include "globals.h"
|
|
#include "hashtable.h"
|
|
#include "instr.h"
|
|
#include "instr_create_shared.h"
|
|
#include "decode_fast.h"
|
|
#include "utils.h"
|
|
#include "annotations.h"
|
|
|
|
#ifdef ANNOTATIONS /* around whole file */
|
|
|
|
# include "annotations_api.h"
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
# include "valgrind.h"
|
|
# include "memcheck.h"
|
|
# endif
|
|
|
|
/* Macros for identifying an annotation head and extracting the pointer to its name.
|
|
*
|
|
* IS_ANNOTATION_LABEL_INSTR(instr): Evaluates to true for any `instr`
|
|
* that could be the instruction which encodes the pointer to the annotation name.
|
|
* IS_ANNOTATION_LABEL_REFERENCE(opnd): Evaluates to true for any `opnd`
|
|
* that could be the operand which encodes the pointer to the annotation name.
|
|
* GET_ANNOTATION_LABEL_REFERENCE(src, instr_pc): Extracts the annotation name
|
|
* pointer. IS_ANNOTATION_LABEL_GOT_OFFSET_INSTR(instr): Evaluates to true for any
|
|
* `instr` that could encode the offset of the label's GOT entry within the GOT table.
|
|
* IS_ANNOTATION_LABEL_GOT_OFFSET_REFERENCE(opnd): Evaluates to true for any `opnd`
|
|
* that could encode the offset of the label's GOT entry within the GOT table.
|
|
*/
|
|
# ifdef WINDOWS
|
|
# ifdef X64
|
|
# define IS_ANNOTATION_LABEL_INSTR(instr) \
|
|
(instr_is_mov(instr) || (instr_get_opcode(instr) == OP_prefetchw))
|
|
# define IS_ANNOTATION_LABEL_REFERENCE(opnd) opnd_is_rel_addr(opnd)
|
|
# define GET_ANNOTATION_LABEL_REFERENCE(src, instr_pc) opnd_get_addr(src)
|
|
# else
|
|
# define IS_ANNOTATION_LABEL_INSTR(instr) instr_is_mov(instr)
|
|
# define IS_ANNOTATION_LABEL_REFERENCE(opnd) opnd_is_base_disp(opnd)
|
|
# define GET_ANNOTATION_LABEL_REFERENCE(src, instr_pc) \
|
|
((app_pc)opnd_get_disp(src))
|
|
# endif
|
|
# else
|
|
# define IS_ANNOTATION_LABEL_INSTR(instr) instr_is_mov(instr)
|
|
# define IS_ANNOTATION_LABEL_REFERENCE(opnd) opnd_is_base_disp(opnd)
|
|
# ifdef X64
|
|
# define ANNOTATION_LABEL_REFERENCE_OPERAND_OFFSET 4
|
|
# else
|
|
# define ANNOTATION_LABEL_REFERENCE_OPERAND_OFFSET 0
|
|
# endif
|
|
# define GET_ANNOTATION_LABEL_REFERENCE(src, instr_pc) \
|
|
((app_pc)(opnd_get_disp(src) + (instr_pc) + \
|
|
ANNOTATION_LABEL_REFERENCE_OPERAND_OFFSET))
|
|
# define IS_ANNOTATION_LABEL_GOT_OFFSET_INSTR(instr) \
|
|
(instr_get_opcode(scratch) == OP_bsf || instr_get_opcode(scratch) == OP_bsr)
|
|
# define IS_ANNOTATION_LABEL_GOT_OFFSET_REFERENCE(opnd) opnd_is_base_disp(opnd)
|
|
# endif
|
|
|
|
/* Annotation label components. */
|
|
# define DYNAMORIO_ANNOTATION_LABEL "dynamorio-annotation"
|
|
# define DYNAMORIO_ANNOTATION_LABEL_LENGTH 20
|
|
# define ANNOTATION_STATEMENT_LABEL "statement"
|
|
# define ANNOTATION_STATEMENT_LABEL_LENGTH 9
|
|
# define ANNOTATION_EXPRESSION_LABEL "expression"
|
|
# define ANNOTATION_EXPRESSION_LABEL_LENGTH 10
|
|
# define ANNOTATION_VOID_LABEL "void"
|
|
# define ANNOTATION_VOID_LABEL_LENGTH 4
|
|
# define IS_ANNOTATION_STATEMENT_LABEL(annotation_name) \
|
|
(strncmp((const char *)(annotation_name), ANNOTATION_STATEMENT_LABEL, \
|
|
ANNOTATION_STATEMENT_LABEL_LENGTH) == 0)
|
|
# define IS_ANNOTATION_VOID(annotation_name) \
|
|
(strncmp((const char *)(annotation_name), ANNOTATION_VOID_LABEL ":", \
|
|
ANNOTATION_VOID_LABEL_LENGTH) == 0)
|
|
|
|
/* Annotation detection factors exclusive to Windows x64. */
|
|
# if defined(WINDOWS) && defined(X64)
|
|
/* Instruction `int 2c` hints that the preceding cbr is probably an annotation
|
|
* head. */
|
|
# define WINDOWS_X64_ANNOTATION_HINT_BYTE 0xcd
|
|
# define X64_WINDOWS_ENCODED_ANNOTATION_HINT 0x2ccd
|
|
/* Instruction `int 3` acts as a boundary for compiler optimizations, to prevent
|
|
* the the annotation from being transformed into something unrecognizable.
|
|
*/
|
|
# define WINDOWS_X64_OPTIMIZATION_FENCE 0xcc
|
|
# define IS_ANNOTATION_HEADER(scratch, pc) \
|
|
(instr_is_cbr(scratch) && \
|
|
(*(ushort *)(pc) == X64_WINDOWS_ENCODED_ANNOTATION_HINT))
|
|
# endif
|
|
|
|
/* OPND_RETURN_VALUE: create the return value operand for `mov $return_value,%xax`. */
|
|
# ifdef X64
|
|
# define OPND_RETURN_VALUE(return_value) OPND_CREATE_INT64(return_value)
|
|
# else
|
|
# define OPND_RETURN_VALUE(return_value) OPND_CREATE_INT32(return_value)
|
|
# endif
|
|
|
|
/* FASTCALL_REGISTER_ARG_COUNT: Specifies the number of arguments passed in registers.
|
|
*/
|
|
# ifdef X64
|
|
# ifdef UNIX
|
|
# define FASTCALL_REGISTER_ARG_COUNT 6
|
|
# else /* WINDOWS x64 */
|
|
# define FASTCALL_REGISTER_ARG_COUNT 4
|
|
# endif
|
|
# else /* x86 (all) */
|
|
# define FASTCALL_REGISTER_ARG_COUNT 2
|
|
# endif
|
|
|
|
# define DYNAMORIO_ANNOTATE_RUNNING_ON_DYNAMORIO_NAME \
|
|
"dynamorio_annotate_running_on_dynamorio"
|
|
|
|
# define DYNAMORIO_ANNOTATE_LOG_NAME "dynamorio_annotate_log"
|
|
|
|
# define DYNAMORIO_ANNOTATE_LOG_ARG_COUNT 20
|
|
|
|
/* Facilitates timestamp substitution in `dynamorio_annotate_log()`. */
|
|
# define LOG_ANNOTATION_TIMESTAMP_TOKEN "${timestamp}"
|
|
# define LOG_ANNOTATION_TIMESTAMP_TOKEN_LENGTH 12
|
|
|
|
/* Constant factors of the Valgrind annotation, as defined in valgrind.h. */
|
|
enum {
|
|
VG_ROL_COUNT = 4,
|
|
};
|
|
|
|
typedef enum _annotation_type_t {
|
|
/* Indicates that the analyzed instruction turned out not to be an annotation head. */
|
|
ANNOTATION_TYPE_NONE,
|
|
/* To invoke an annotation as an expression, the target app calls the annotation as if
|
|
* it were a normal function. The annotation instruction sequence follows the preamble
|
|
* of each annotation function, and instrumentation replaces it with (1) a clean call
|
|
* to each registered handler for that annotation, or (2) a return value substitution,
|
|
* depending on the type of registration.
|
|
*/
|
|
ANNOTATION_TYPE_EXPRESSION,
|
|
/* To invoke an annotation as a statement, the target app calls a macro defined in
|
|
* the annotation header (via dr_annotations_asm.h), which places the annotation
|
|
* instruction sequence inline at the invocation site. The sequence includes a normal
|
|
* call to the annotation function, so instrumentation simply involves removing the
|
|
* surrounding components of the annotation to expose the call. The DR client's clean
|
|
* calls will be invoked within the annotation function itself (see above).
|
|
*/
|
|
ANNOTATION_TYPE_STATEMENT,
|
|
} annotation_type_t;
|
|
|
|
/* Specifies the exact byte position of the essential components of an annotation. */
|
|
typedef struct _annotation_layout_t {
|
|
app_pc start_pc;
|
|
annotation_type_t type;
|
|
/* Indicates whether the annotation function in the target app is of void type. */
|
|
bool is_void;
|
|
/* Points to the annotation name in the target app's data section. */
|
|
const char *name;
|
|
/* Specifies the translation of the annotation instrumentation (e.g., clean call). */
|
|
app_pc substitution_xl8;
|
|
/* Specifies the byte at which app decoding should resume following the annotation. */
|
|
app_pc resume_pc;
|
|
} annotation_layout_t;
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
typedef struct _vg_handlers_t {
|
|
dr_annotation_handler_t *handlers[DR_VG_ID__LAST];
|
|
} vg_handlers_t;
|
|
# endif
|
|
|
|
static strhash_table_t *handlers;
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
/* locked under the `handlers` table lock */
|
|
static vg_handlers_t *vg_handlers;
|
|
|
|
/* Dispatching function for Valgrind annotations (required because id of the Valgrind
|
|
* client request object cannot be determined statically).
|
|
*/
|
|
static dr_annotation_handler_t vg_router;
|
|
static dr_annotation_receiver_t vg_receiver; /* The sole receiver for `vg_router`. */
|
|
static opnd_t vg_router_arg; /* The sole argument for the clean call to `vg_router`. */
|
|
# endif
|
|
|
|
/* Required for passing the va_list in `dynamorio_annotate_log()` to the log function. */
|
|
extern ssize_t
|
|
do_file_write(file_t f, const char *fmt, va_list ap);
|
|
|
|
/*********************************************************
|
|
* INTERNAL ROUTINE DECLARATIONS
|
|
*/
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
/* Valgrind dispatcher, called by the instrumentation of the Valgrind annotations. */
|
|
static void
|
|
handle_vg_annotation(app_pc request_args);
|
|
|
|
/* Maps a Valgrind request id into a (sequential) `DR_VG_ID__*` constant. */
|
|
static dr_valgrind_request_id_t
|
|
lookup_valgrind_request(ptr_uint_t request);
|
|
|
|
static ptr_uint_t
|
|
valgrind_running_on_valgrind(dr_vg_client_request_t *request);
|
|
# endif
|
|
|
|
static bool
|
|
is_annotation_tag(dcontext_t *dcontext, DR_PARAM_INOUT app_pc *cur_pc, instr_t *scratch,
|
|
DR_PARAM_OUT const char **name);
|
|
|
|
static void
|
|
identify_annotation(dcontext_t *dcontext, DR_PARAM_INOUT annotation_layout_t *layout,
|
|
instr_t *scratch);
|
|
|
|
/* Create argument operands for the instrumented clean call */
|
|
static void
|
|
create_arg_opnds(dr_annotation_handler_t *handler, uint num_args,
|
|
dr_annotation_calling_convention_t call_type);
|
|
|
|
# ifdef DEBUG
|
|
/* Implements `dynamorio_annotate_log()`, including substitution of the string literal
|
|
* token "${timestamp}" with the current system time.
|
|
*/
|
|
static ssize_t
|
|
annotation_printf(const char *format, ...);
|
|
# endif
|
|
|
|
/* Invoked during hashtable entry removal */
|
|
static void
|
|
free_annotation_handler(void *p);
|
|
|
|
/*********************************************************
|
|
* ANNOTATION INTEGRATION FUNCTIONS
|
|
*/
|
|
|
|
void
|
|
annotation_init(void)
|
|
{
|
|
handlers = strhash_hash_create(
|
|
GLOBAL_DCONTEXT, 8, 80, /* favor a small table */
|
|
HASHTABLE_ENTRY_SHARED | HASHTABLE_SHARED | HASHTABLE_RELAX_CLUSTER_CHECKS |
|
|
HASHTABLE_PERSISTENT,
|
|
free_annotation_handler _IF_DEBUG("annotation handler hashtable"));
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
vg_handlers =
|
|
HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, vg_handlers_t, ACCT_OTHER, UNPROTECTED);
|
|
memset(vg_handlers, 0, sizeof(vg_handlers_t));
|
|
|
|
vg_router.type = DR_ANNOTATION_HANDLER_CALL;
|
|
/* The Valgrind client request object is passed in %xax. */
|
|
vg_router.num_args = 1;
|
|
vg_router_arg = opnd_create_reg(DR_REG_XAX);
|
|
vg_router.args = &vg_router_arg;
|
|
vg_router.symbol_name = NULL; /* No symbols in Valgrind annotations. */
|
|
vg_router.receiver_list = &vg_receiver;
|
|
vg_receiver.instrumentation.callback = (void *)(void (*)(void))handle_vg_annotation;
|
|
vg_receiver.save_fpstate = false;
|
|
vg_receiver.next = NULL;
|
|
# endif
|
|
|
|
dr_annotation_register_return(DYNAMORIO_ANNOTATE_RUNNING_ON_DYNAMORIO_NAME,
|
|
(void *)(ptr_uint_t) true);
|
|
# ifdef DEBUG
|
|
/* The logging annotation requires a debug build of DR. Arbitrarily allows up to
|
|
* 20 arguments, since the clean call must have a fixed number of them.
|
|
*/
|
|
dr_annotation_register_call(DYNAMORIO_ANNOTATE_LOG_NAME, (void *)annotation_printf,
|
|
false, DYNAMORIO_ANNOTATE_LOG_ARG_COUNT,
|
|
DR_ANNOTATION_CALL_TYPE_VARARG);
|
|
# endif
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
/* DR pretends to be Valgrind. */
|
|
dr_annotation_register_valgrind(DR_VG_ID__RUNNING_ON_VALGRIND,
|
|
valgrind_running_on_valgrind);
|
|
# endif
|
|
}
|
|
|
|
void
|
|
annotation_exit(void)
|
|
{
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
uint i;
|
|
for (i = 0; i < DR_VG_ID__LAST; i++) {
|
|
if (vg_handlers->handlers[i] != NULL)
|
|
free_annotation_handler(vg_handlers->handlers[i]);
|
|
}
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, vg_handlers, vg_handlers_t, ACCT_OTHER, UNPROTECTED);
|
|
# endif
|
|
|
|
strhash_hash_destroy(GLOBAL_DCONTEXT, handlers);
|
|
}
|
|
|
|
bool
|
|
instrument_annotation(dcontext_t *dcontext, DR_PARAM_INOUT app_pc *start_pc,
|
|
DR_PARAM_OUT instr_t **substitution
|
|
_IF_WINDOWS_X64(DR_PARAM_IN bool hint_is_safe))
|
|
{
|
|
annotation_layout_t layout = { 0 };
|
|
/* This instr_t is used for analytical decoding throughout the detection functions.
|
|
* It is passed on the stack and its contents are considered void on function entry.
|
|
*/
|
|
instr_t scratch;
|
|
# if defined(WINDOWS) && defined(X64)
|
|
app_pc hint_pc = *start_pc;
|
|
bool hint = true;
|
|
byte hint_byte;
|
|
# endif
|
|
/* We need to use the passed-in cxt for IR but we need a real one for TRY_EXCEPT. */
|
|
dcontext_t *my_dcontext;
|
|
if (dcontext == GLOBAL_DCONTEXT)
|
|
my_dcontext = get_thread_private_dcontext();
|
|
else
|
|
my_dcontext = dcontext;
|
|
|
|
# if defined(WINDOWS) && defined(X64)
|
|
if (hint_is_safe) {
|
|
hint_byte = *hint_pc;
|
|
} else {
|
|
if (!d_r_safe_read(hint_pc, 1, &hint_byte))
|
|
return false;
|
|
}
|
|
if (hint_byte != WINDOWS_X64_ANNOTATION_HINT_BYTE)
|
|
return false;
|
|
/* The hint is the first byte of the 2-byte instruction `int 2c`. Skip both bytes. */
|
|
layout.start_pc = hint_pc + INT_LENGTH;
|
|
layout.substitution_xl8 = layout.start_pc;
|
|
# else
|
|
layout.start_pc = *start_pc;
|
|
# endif
|
|
|
|
instr_init(dcontext, &scratch);
|
|
TRY_EXCEPT(
|
|
my_dcontext, { identify_annotation(dcontext, &layout, &scratch); },
|
|
{ /* EXCEPT */
|
|
LOG(THREAD, LOG_ANNOTATIONS, 2, "Failed to instrument annotation at " PFX "\n",
|
|
*start_pc);
|
|
/* layout.type is already ANNOTATION_TYPE_NONE */
|
|
});
|
|
if (layout.type != ANNOTATION_TYPE_NONE) {
|
|
LOG(THREAD, LOG_ANNOTATIONS, 2,
|
|
"Decoded %s annotation %s. Next pc now " PFX ".\n",
|
|
(layout.type == ANNOTATION_TYPE_EXPRESSION) ? "expression" : "statement",
|
|
layout.name, layout.resume_pc);
|
|
/* Notify the caller where to resume decoding app instructions. */
|
|
*start_pc = layout.resume_pc;
|
|
|
|
if (layout.type == ANNOTATION_TYPE_EXPRESSION) {
|
|
dr_annotation_handler_t *handler;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
handler = strhash_hash_lookup(GLOBAL_DCONTEXT, handlers, layout.name);
|
|
if (handler != NULL && handler->type == DR_ANNOTATION_HANDLER_CALL) {
|
|
/* Substitute the annotation with a label pointing to the handler. */
|
|
instr_t *call = INSTR_CREATE_label(dcontext);
|
|
dr_instr_label_data_t *label_data = instr_get_label_data_area(call);
|
|
SET_ANNOTATION_HANDLER(label_data, handler);
|
|
SET_ANNOTATION_APP_PC(label_data, layout.resume_pc);
|
|
instr_set_note(call, (void *)DR_NOTE_ANNOTATION);
|
|
instr_set_meta(call);
|
|
*substitution = call;
|
|
|
|
handler->is_void = layout.is_void;
|
|
if (!handler->is_void) {
|
|
/* Append `mov $0x0,%eax` to the annotation substitution, so that
|
|
* clients and tools recognize that %xax will be written here.
|
|
* The placeholder is "ok to mangle" because it (partially)
|
|
* implements the app's annotation. The placeholder will be
|
|
* removed post-client during mangling.
|
|
* We only support writing the return value and no other registers
|
|
* (otherwise we'd need drrg to further special-case
|
|
* DR_NOTE_ANNOTATION).
|
|
*/
|
|
instr_t *return_placeholder =
|
|
INSTR_XL8(INSTR_CREATE_mov_st(dcontext, opnd_create_reg(REG_XAX),
|
|
OPND_CREATE_INT32(0)),
|
|
layout.substitution_xl8);
|
|
instr_set_note(return_placeholder, (void *)DR_NOTE_ANNOTATION);
|
|
/* Append the placeholder manually, because the caller can call
|
|
* `instrlist_append()` with a "sublist" of instr_t.
|
|
*/
|
|
instr_set_next(*substitution, return_placeholder);
|
|
instr_set_prev(return_placeholder, *substitution);
|
|
}
|
|
} else { /* Substitute the annotation with `mov $return_value,%eax` */
|
|
void *return_value;
|
|
|
|
if (handler == NULL)
|
|
return_value = NULL; /* Return nothing if no handler is registered */
|
|
else
|
|
return_value = handler->receiver_list->instrumentation.return_value;
|
|
*substitution =
|
|
INSTR_XL8(INSTR_CREATE_mov_imm(dcontext, opnd_create_reg(REG_XAX),
|
|
OPND_RETURN_VALUE(return_value)),
|
|
layout.substitution_xl8);
|
|
}
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
}
|
|
/* else (layout.type == ANNOTATION_TYPE_STATEMENT), in which case the only
|
|
* instrumentation is to remove the jump-over-annotation such that the annotation
|
|
* function gets called like a normal function. Instrumentation of clean calls and
|
|
* return values will then occur within the annotation function (the case above).
|
|
*/
|
|
}
|
|
instr_free(dcontext, &scratch);
|
|
return (layout.type != ANNOTATION_TYPE_NONE);
|
|
}
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
void
|
|
instrument_valgrind_annotation(dcontext_t *dcontext, instrlist_t *bb, instr_t *xchg_instr,
|
|
app_pc xchg_pc, app_pc next_pc, uint bb_instr_count)
|
|
{
|
|
int i;
|
|
app_pc instrumentation_pc = NULL;
|
|
instr_t *return_placeholder;
|
|
instr_t *instr, *next_instr;
|
|
dr_instr_label_data_t *label_data;
|
|
|
|
LOG(THREAD, LOG_ANNOTATIONS, 2,
|
|
"Matched valgrind client request pattern at " PFX "\n",
|
|
instr_get_app_pc(xchg_instr));
|
|
|
|
/* We leave the argument gathering code as app instructions, because it writes to app
|
|
* registers (xref i#1423). Now delete the `xchg` instruction, and the `rol`
|
|
* instructions--unless a previous BB contains some of the `rol`, in which case they
|
|
* must be executed to avoid messing up %xdi (the 4 `rol` compose to form a nop).
|
|
* Note: in the case of a split `rol` sequence, we only instrument the second half.
|
|
*/
|
|
instr_destroy(dcontext, xchg_instr);
|
|
if (bb_instr_count > VG_ROL_COUNT) {
|
|
instr = instrlist_last(bb);
|
|
for (i = 0; i < VG_ROL_COUNT; i++) {
|
|
ASSERT(instr != NULL && instr_get_opcode(instr) == OP_rol);
|
|
next_instr = instr_get_prev(instr);
|
|
instrlist_remove(bb, instr);
|
|
instr_destroy(dcontext, instr);
|
|
instr = next_instr;
|
|
}
|
|
}
|
|
|
|
/* i#1613: if the client removes the app instruction prior to the annotation, we will
|
|
* skip the annotation instrumentation, so identify the pc of that instruction here.
|
|
*/
|
|
if (instrlist_last(bb) != NULL)
|
|
instrumentation_pc = instrlist_last(bb)->translation;
|
|
|
|
/* Substitute the annotation tail with a label pointing to the Valgrind handler. */
|
|
instr = INSTR_CREATE_label(dcontext);
|
|
instr_set_note(instr, (void *)DR_NOTE_ANNOTATION);
|
|
label_data = instr_get_label_data_area(instr);
|
|
SET_ANNOTATION_HANDLER(label_data, &vg_router);
|
|
SET_ANNOTATION_APP_PC(label_data, next_pc);
|
|
SET_ANNOTATION_INSTRUMENTATION_PC(label_data, instrumentation_pc);
|
|
instr_set_meta(instr);
|
|
instrlist_append(bb, instr);
|
|
|
|
/* Append `mov $0x0,%edx` so that clients and tools recognize that %xdx will be
|
|
* written here. The placeholder is "ok to mangle" because it (partially) implements
|
|
* the app's annotation. The placeholder will be removed post-client during mangling.
|
|
* We only support writing the return value and no other registers (otherwise
|
|
* we'd need drreg to further special-case DR_NOTE_ANNOTATION).
|
|
*/
|
|
return_placeholder = INSTR_XL8(
|
|
INSTR_CREATE_mov_st(dcontext, opnd_create_reg(REG_XDX), OPND_CREATE_INT32(0)),
|
|
xchg_pc);
|
|
instr_set_note(return_placeholder, (void *)DR_NOTE_ANNOTATION);
|
|
instrlist_append(bb, return_placeholder);
|
|
}
|
|
# endif
|
|
|
|
/*********************************************************
|
|
* ANNOTATION API FUNCTIONS
|
|
*/
|
|
|
|
bool
|
|
dr_annotation_register_call(const char *annotation_name, void *callee, bool save_fpstate,
|
|
uint num_args, dr_annotation_calling_convention_t call_type)
|
|
{
|
|
bool result = true;
|
|
dr_annotation_handler_t *handler;
|
|
dr_annotation_receiver_t *receiver;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
|
|
handler = (dr_annotation_handler_t *)strhash_hash_lookup(GLOBAL_DCONTEXT, handlers,
|
|
annotation_name);
|
|
if (handler == NULL) { /* make a new handler if never registered yet */
|
|
handler = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, dr_annotation_handler_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
memset(handler, 0, sizeof(dr_annotation_handler_t));
|
|
handler->type = DR_ANNOTATION_HANDLER_CALL;
|
|
handler->symbol_name = dr_strdup(annotation_name HEAPACCT(ACCT_OTHER));
|
|
handler->num_args = num_args;
|
|
|
|
if (num_args == 0) {
|
|
handler->args = NULL;
|
|
} else {
|
|
handler->args = HEAP_ARRAY_ALLOC(GLOBAL_DCONTEXT, opnd_t, num_args,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
create_arg_opnds(handler, num_args, call_type);
|
|
}
|
|
|
|
strhash_hash_add(GLOBAL_DCONTEXT, handlers, handler->symbol_name, handler);
|
|
}
|
|
if (handler->type == DR_ANNOTATION_HANDLER_CALL || handler->receiver_list == NULL) {
|
|
/* If the annotation previously had a return value registration, it can be changed
|
|
* to clean call instrumentation, provided the return value was unregistered.
|
|
*/
|
|
handler->type = DR_ANNOTATION_HANDLER_CALL;
|
|
receiver = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, dr_annotation_receiver_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
receiver->instrumentation.callback = callee;
|
|
receiver->save_fpstate = save_fpstate;
|
|
receiver->next = handler->receiver_list; /* push the new receiver onto the list */
|
|
handler->receiver_list = receiver;
|
|
} else {
|
|
result = false; /* A return value is registered, so no call can be added. */
|
|
}
|
|
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return result;
|
|
}
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
bool
|
|
dr_annotation_register_valgrind(
|
|
dr_valgrind_request_id_t request_id,
|
|
ptr_uint_t (*annotation_callback)(dr_vg_client_request_t *request))
|
|
{
|
|
dr_annotation_handler_t *handler;
|
|
dr_annotation_receiver_t *receiver;
|
|
if (request_id >= DR_VG_ID__LAST)
|
|
return false;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
handler = vg_handlers->handlers[request_id];
|
|
if (handler == NULL) { /* make a new handler if never registered yet */
|
|
handler = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, dr_annotation_handler_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
memset(handler, 0, sizeof(dr_annotation_handler_t));
|
|
handler->type = DR_ANNOTATION_HANDLER_VALGRIND;
|
|
|
|
vg_handlers->handlers[request_id] = handler;
|
|
}
|
|
|
|
receiver = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, dr_annotation_receiver_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
receiver->instrumentation.vg_callback = annotation_callback;
|
|
receiver->save_fpstate = false;
|
|
receiver->next = handler->receiver_list; /* push the new receiver onto the list */
|
|
handler->receiver_list = receiver;
|
|
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return true;
|
|
}
|
|
# endif
|
|
|
|
bool
|
|
dr_annotation_register_return(const char *annotation_name, void *return_value)
|
|
{
|
|
bool result = true;
|
|
dr_annotation_handler_t *handler;
|
|
dr_annotation_receiver_t *receiver;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
ASSERT_TABLE_SYNCHRONIZED(handlers, WRITE);
|
|
handler = (dr_annotation_handler_t *)strhash_hash_lookup(GLOBAL_DCONTEXT, handlers,
|
|
annotation_name);
|
|
if (handler == NULL) { /* make a new handler if never registered yet */
|
|
handler = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, dr_annotation_handler_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
memset(handler, 0, sizeof(dr_annotation_handler_t));
|
|
handler->type = DR_ANNOTATION_HANDLER_RETURN_VALUE;
|
|
handler->symbol_name = dr_strdup(annotation_name HEAPACCT(ACCT_OTHER));
|
|
strhash_hash_add(GLOBAL_DCONTEXT, handlers, handler->symbol_name, handler);
|
|
}
|
|
if (handler->receiver_list == NULL) {
|
|
/* If the annotation previously had clean call registration, it can be changed to
|
|
* return value instrumentation, provided the calls have been unregistered.
|
|
*/
|
|
handler->type = DR_ANNOTATION_HANDLER_RETURN_VALUE;
|
|
receiver = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, dr_annotation_receiver_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
receiver->instrumentation.return_value = return_value;
|
|
receiver->save_fpstate = false;
|
|
receiver->next = NULL; /* Return value can only have one implementation. */
|
|
handler->receiver_list = receiver;
|
|
} else {
|
|
result = false; /* Existing handler prevents the new return value. */
|
|
}
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
dr_annotation_pass_pc(const char *annotation_name)
|
|
{
|
|
bool result = true;
|
|
dr_annotation_handler_t *handler;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
ASSERT_TABLE_SYNCHRONIZED(handlers, WRITE);
|
|
handler = (dr_annotation_handler_t *)strhash_hash_lookup(GLOBAL_DCONTEXT, handlers,
|
|
annotation_name);
|
|
if (handler == NULL) {
|
|
result = false;
|
|
} else {
|
|
handler->pass_pc_in_slot = true;
|
|
}
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
dr_annotation_unregister_call(const char *annotation_name, void *callee)
|
|
{
|
|
bool found = false;
|
|
dr_annotation_handler_t *handler;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
handler = (dr_annotation_handler_t *)strhash_hash_lookup(GLOBAL_DCONTEXT, handlers,
|
|
annotation_name);
|
|
if (handler != NULL && handler->receiver_list != NULL) {
|
|
dr_annotation_receiver_t *receiver = handler->receiver_list;
|
|
if (receiver->instrumentation.callback == callee) { /* case 1: remove the head */
|
|
handler->receiver_list = receiver->next;
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, receiver, dr_annotation_receiver_t,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
found = true;
|
|
} else { /* case 2: remove from within the list */
|
|
while (receiver->next != NULL) {
|
|
if (receiver->next->instrumentation.callback == callee) {
|
|
dr_annotation_receiver_t *removal = receiver->next;
|
|
receiver->next = removal->next;
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, removal, dr_annotation_receiver_t,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
found = true;
|
|
break;
|
|
}
|
|
receiver = receiver->next;
|
|
}
|
|
} /* Leave the handler for the next registration (free it on exit) */
|
|
}
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return found;
|
|
}
|
|
|
|
bool
|
|
dr_annotation_unregister_return(const char *annotation_name)
|
|
{
|
|
bool found = false;
|
|
dr_annotation_handler_t *handler;
|
|
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
handler = (dr_annotation_handler_t *)strhash_hash_lookup(GLOBAL_DCONTEXT, handlers,
|
|
annotation_name);
|
|
if ((handler != NULL) && (handler->receiver_list != NULL)) {
|
|
ASSERT(handler->receiver_list->next == NULL);
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, handler->receiver_list, dr_annotation_receiver_t,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
handler->receiver_list = NULL;
|
|
found = true;
|
|
} /* Leave the handler for the next registration (free it on exit) */
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return found;
|
|
}
|
|
|
|
# if !(defined(WINDOWS) && defined(X64))
|
|
bool
|
|
dr_annotation_unregister_valgrind(
|
|
dr_valgrind_request_id_t request_id,
|
|
ptr_uint_t (*annotation_callback)(dr_vg_client_request_t *request))
|
|
{
|
|
bool found = false;
|
|
dr_annotation_handler_t *handler;
|
|
TABLE_RWLOCK(handlers, write, lock);
|
|
handler = vg_handlers->handlers[request_id];
|
|
if ((handler != NULL) && (handler->receiver_list != NULL)) {
|
|
dr_annotation_receiver_t *receiver = handler->receiver_list;
|
|
if (receiver->instrumentation.vg_callback == annotation_callback) {
|
|
handler->receiver_list = receiver->next; /* case 1: remove the head */
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, receiver, dr_annotation_receiver_t,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
found = true;
|
|
} else { /* case 2: remove from within the list */
|
|
while (receiver->next != NULL) {
|
|
if (receiver->next->instrumentation.vg_callback == annotation_callback) {
|
|
dr_annotation_receiver_t *removal = receiver->next;
|
|
receiver->next = removal->next;
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, removal, dr_annotation_receiver_t,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
found = true;
|
|
break;
|
|
}
|
|
receiver = receiver->next;
|
|
}
|
|
} /* Leave the handler for the next registration (free it on exit) */
|
|
}
|
|
TABLE_RWLOCK(handlers, write, unlock);
|
|
return found;
|
|
}
|
|
|
|
/*********************************************************
|
|
* ANNOTATION IMPLEMENTATIONS
|
|
*/
|
|
|
|
static void
|
|
handle_vg_annotation(app_pc request_args)
|
|
{
|
|
dcontext_t *dcontext = get_thread_private_dcontext();
|
|
dr_valgrind_request_id_t request_id;
|
|
dr_annotation_receiver_t *receiver;
|
|
dr_vg_client_request_t request;
|
|
ptr_uint_t result;
|
|
|
|
if (!d_r_safe_read(request_args, sizeof(dr_vg_client_request_t), &request)) {
|
|
LOG(THREAD, LOG_ANNOTATIONS, 2,
|
|
"Failed to read Valgrind client request args at " PFX
|
|
". Skipping the annotation.\n",
|
|
request_args);
|
|
return;
|
|
}
|
|
|
|
result = request.default_result;
|
|
request_id = lookup_valgrind_request(request.request);
|
|
if (request_id < DR_VG_ID__LAST) {
|
|
TABLE_RWLOCK(handlers, read, lock);
|
|
if (vg_handlers->handlers[request_id] != NULL) {
|
|
receiver = vg_handlers->handlers[request_id]->receiver_list;
|
|
while (receiver != NULL) {
|
|
result = receiver->instrumentation.vg_callback(&request);
|
|
receiver = receiver->next;
|
|
}
|
|
}
|
|
TABLE_RWLOCK(handlers, read, unlock);
|
|
} else {
|
|
LOG(THREAD, LOG_ANNOTATIONS, 2,
|
|
"Skipping unrecognized Valgrind client request id %d\n", request.request);
|
|
return;
|
|
}
|
|
|
|
/* Put the result in %xdx where the target app expects to find it. */
|
|
if (dcontext->client_data->mcontext_in_dcontext) {
|
|
get_mcontext(dcontext)->xdx = result;
|
|
} else {
|
|
priv_mcontext_t *state = get_priv_mcontext_from_dstack(dcontext);
|
|
state->xdx = result;
|
|
}
|
|
}
|
|
|
|
static dr_valgrind_request_id_t
|
|
lookup_valgrind_request(ptr_uint_t request)
|
|
{
|
|
switch (request) {
|
|
case VG_USERREQ__RUNNING_ON_VALGRIND: return DR_VG_ID__RUNNING_ON_VALGRIND;
|
|
case VG_USERREQ__DO_LEAK_CHECK: return DR_VG_ID__DO_LEAK_CHECK;
|
|
case VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE:
|
|
return DR_VG_ID__MAKE_MEM_DEFINED_IF_ADDRESSABLE;
|
|
case VG_USERREQ__DISCARD_TRANSLATIONS: return DR_VG_ID__DISCARD_TRANSLATIONS;
|
|
case VG_USERREQ__MAKE_MEM_UNDEFINED: return DR_VG_ID__MAKE_MEM_UNDEFINED;
|
|
case VG_USERREQ__MAKE_MEM_DEFINED: return DR_VG_ID__MAKE_MEM_DEFINED;
|
|
case VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE: return DR_VG_ID__CHECK_MEM_IS_ADDRESSABLE;
|
|
case VG_USERREQ__CHECK_MEM_IS_DEFINED: return DR_VG_ID__CHECK_MEM_IS_DEFINED;
|
|
case VG_USERREQ__MALLOCLIKE_BLOCK: return DR_VG_ID__MALLOCLIKE_BLOCK;
|
|
case VG_USERREQ__FREELIKE_BLOCK: return DR_VG_ID__FREELIKE_BLOCK;
|
|
}
|
|
return DR_VG_ID__LAST;
|
|
}
|
|
|
|
static ptr_uint_t
|
|
valgrind_running_on_valgrind(dr_vg_client_request_t *request)
|
|
{
|
|
return 1; /* Pretend to be Valgrind. */
|
|
}
|
|
# endif
|
|
|
|
/*********************************************************
|
|
* INTERNAL ROUTINES
|
|
*/
|
|
|
|
/* If the app code at `*cur_pc` can be read as an encoded annotation label, then:
|
|
* (1) advance `*cur_pc` beyond the last label instruction,
|
|
* (2) point `**name` to the start of the label within the app image data section, and
|
|
* (3) return true.
|
|
* If there is no annotation label at `*cur_pc`, or failure occurs while trying to read
|
|
* it, then return false with undefined values for `*cur_pc` and `**name`.
|
|
*
|
|
* On Unix and Windows x86, the label has the form
|
|
* "<annotation-label>:<return-type>:<annotation-name>, e.g.
|
|
*
|
|
* "dynamorio-annotation:void:dynamorio_annotate_log"
|
|
* "dynamorio-annotation:const char *:some_custom_client_annotation"
|
|
*
|
|
* On Windows x64, the label has an additional token for the annotation type:
|
|
* "<annotation-label>:<annotation-type>:<return-type>:<annotation-name>, e.g.
|
|
*
|
|
* "dynamorio-annotation:statement:void:dynamorio_annotate_log"
|
|
* "dynamorio-annotation:expression:const char *:some_custom_client_annotation"
|
|
*
|
|
* The encoding of the pointer to the label varies by platform:
|
|
*
|
|
* Unix expression annotation label (two instructions form <GOT-base> + <GOT-offset>):
|
|
*
|
|
* mov $<GOT-base>,%xax
|
|
* bsr $<GOT-offset>,%xax
|
|
*
|
|
* Windows x86 expression annotation label (direct pointer to the <label>):
|
|
*
|
|
* mov $<label>,%eax
|
|
*
|
|
* Windows x64 expression annotation label (same, in 2 variations):
|
|
*
|
|
* mov $<label>,%rax Or prefetch $<label>
|
|
* prefetch %rax
|
|
*
|
|
* Decoding the label pointer proceeds as follows:
|
|
*
|
|
* (step 1) check `*cur_pc` for the opcode of the first label-encoding instruction
|
|
* (step 2) check the operand of that instruction for a label-encoding operand type
|
|
* Unix only--dereference the GOT entry:
|
|
* (step 3) check the next instruction for the label offset opcode (bsr or bsf)
|
|
* (step 4) check its operand for the offset-encoding operand type (base disp)
|
|
* (step 5) add the two operands and dereference as the label's GOT entry
|
|
* (step 6) attempt to read the decoded pointer and compare to "dynamorio-annotation"
|
|
* (note there is a special case for Windows x64, which is not inline asm)
|
|
* (step 7) if it matches, point `**name` to the character beyond the separator ':'
|
|
*
|
|
* See https://dynamorio.org/page_annotations.html for complete examples.
|
|
*/
|
|
static inline bool
|
|
is_annotation_tag(dcontext_t *dcontext, DR_PARAM_INOUT app_pc *cur_pc, instr_t *scratch,
|
|
DR_PARAM_OUT const char **name)
|
|
{
|
|
app_pc start_pc = *cur_pc;
|
|
|
|
instr_reset(dcontext, scratch);
|
|
*cur_pc = decode(dcontext, *cur_pc, scratch);
|
|
if (IS_ANNOTATION_LABEL_INSTR(scratch)) { /* step 1 */
|
|
opnd_t src = instr_get_src(scratch, 0);
|
|
if (IS_ANNOTATION_LABEL_REFERENCE(src)) { /* step 2 */
|
|
char buf[DYNAMORIO_ANNOTATION_LABEL_LENGTH + 1 /*nul*/];
|
|
app_pc buf_ptr;
|
|
app_pc opnd_ptr = GET_ANNOTATION_LABEL_REFERENCE(src, start_pc);
|
|
# ifdef UNIX
|
|
app_pc got_ptr;
|
|
instr_reset(dcontext, scratch);
|
|
*cur_pc = decode(dcontext, *cur_pc, scratch);
|
|
if (!IS_ANNOTATION_LABEL_GOT_OFFSET_INSTR(scratch)) /* step 3 */
|
|
return false;
|
|
src = instr_get_src(scratch, 0);
|
|
if (!IS_ANNOTATION_LABEL_GOT_OFFSET_REFERENCE(src)) /* step 4 */
|
|
return false;
|
|
opnd_ptr += opnd_get_disp(src); /* step 5 */
|
|
if (!d_r_safe_read(opnd_ptr, sizeof(app_pc), &got_ptr))
|
|
return false;
|
|
opnd_ptr = got_ptr;
|
|
# endif
|
|
# if defined(WINDOWS) && defined(X64)
|
|
/* In Windows x64, if the `prefetch` instruction was found at
|
|
* `*cur_pc` with no intervening `mov` instruction, the label
|
|
* pointer must be an immediate operand to that `prefetch`.
|
|
*/
|
|
if (instr_get_opcode(scratch) == OP_prefetchw) { /* step 6 */
|
|
if (!d_r_safe_read(opnd_ptr, DYNAMORIO_ANNOTATION_LABEL_LENGTH, buf))
|
|
return false;
|
|
buf[DYNAMORIO_ANNOTATION_LABEL_LENGTH] = '\0';
|
|
if (strcmp(buf, DYNAMORIO_ANNOTATION_LABEL) == 0) {
|
|
*name = (const char *)(opnd_ptr + /* step 7 */
|
|
DYNAMORIO_ANNOTATION_LABEL_LENGTH +
|
|
1); /* skip the separator ':' */
|
|
return true;
|
|
}
|
|
} /* else the label pointer is the usual `mov` operand: */
|
|
# endif
|
|
if (!d_r_safe_read(opnd_ptr, sizeof(app_pc), &buf_ptr)) /* step 6 */
|
|
return false;
|
|
if (!d_r_safe_read(buf_ptr, DYNAMORIO_ANNOTATION_LABEL_LENGTH, buf))
|
|
return false;
|
|
buf[DYNAMORIO_ANNOTATION_LABEL_LENGTH] = '\0';
|
|
if (strcmp(buf, DYNAMORIO_ANNOTATION_LABEL) != 0)
|
|
return false;
|
|
*name = (const char *)(buf_ptr + /* step 7 */
|
|
DYNAMORIO_ANNOTATION_LABEL_LENGTH +
|
|
1); /* skip the separator ':' */
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
# if defined(WINDOWS) && defined(X64)
|
|
/* Identify the annotation at layout->start_pc, if any. On Windows x64, some flexibility
|
|
* is required to recognize the annotation sequence because it is compiled instead of
|
|
* explicitly inserted with inline asm (which is unavailable in MSVC for this platform).
|
|
* (step 1) check if the instruction at layout->start_pc encodes an annotation label.
|
|
* (step 2) decode arbitrary instructions up to a `prefetch`,
|
|
* following any direct branches decoded along the way.
|
|
* (step 3) skip the `int 3` following the `prefetch`.
|
|
* (step 4) set the substitution_xl8 to precede the resume_pc, so we'll resume in the
|
|
* right place if the client removes all instrs following the substitution.
|
|
* (step 5) set the resume pc, so execution resumes within the annotation body.
|
|
* (step 6) compare the next label token to determine the annotation type.
|
|
* Expression annotations only:
|
|
* (step 7) compare the return type to determine whether the annotation is void.
|
|
* (step 8) advance the label pointer to the name token
|
|
* (note that the Statement annotation concludes with a special case).
|
|
*/
|
|
static inline void
|
|
identify_annotation(dcontext_t *dcontext, DR_PARAM_INOUT annotation_layout_t *layout,
|
|
instr_t *scratch)
|
|
{
|
|
app_pc cur_pc = layout->start_pc, last_call = NULL;
|
|
|
|
if (!is_annotation_tag(dcontext, &cur_pc, scratch, &layout->name)) /* step 1 */
|
|
return;
|
|
|
|
while (instr_get_opcode(scratch) != OP_prefetchw) { /* step 2 */
|
|
if (instr_is_ubr(scratch))
|
|
cur_pc = instr_get_branch_target_pc(scratch);
|
|
instr_reset(dcontext, scratch);
|
|
cur_pc = decode(dcontext, cur_pc, scratch);
|
|
}
|
|
|
|
ASSERT(*cur_pc == WINDOWS_X64_OPTIMIZATION_FENCE); /* step 3 */
|
|
layout->substitution_xl8 = cur_pc; /* step 4 */
|
|
cur_pc++;
|
|
layout->resume_pc = cur_pc; /* step 5 */
|
|
|
|
if (IS_ANNOTATION_STATEMENT_LABEL(layout->name)) { /* step 6 */
|
|
layout->type = ANNOTATION_TYPE_STATEMENT;
|
|
layout->name += (ANNOTATION_STATEMENT_LABEL_LENGTH + 1);
|
|
layout->name = strchr(layout->name, ':') + 1; /* step 8 */
|
|
/* If the target app contains an annotation whose argument is a function call
|
|
* that gets inlined, and that function contains the same annotation, the
|
|
* compiler will fuse the headers. See
|
|
* https://dynamorio.org/page_annotations.html for a sample of
|
|
* fused headers. This loop identifies and skips any fused headers.
|
|
*/
|
|
while (true) {
|
|
instr_reset(dcontext, scratch);
|
|
cur_pc = decode(dcontext, cur_pc, scratch);
|
|
if (IS_ANNOTATION_HEADER(scratch, cur_pc)) {
|
|
cur_pc += INT_LENGTH;
|
|
while (instr_get_opcode(scratch) != OP_prefetchw) {
|
|
if (instr_is_ubr(scratch))
|
|
cur_pc = instr_get_branch_target_pc(scratch);
|
|
instr_reset(dcontext, scratch);
|
|
cur_pc = decode(dcontext, cur_pc, scratch);
|
|
}
|
|
ASSERT(*cur_pc == WINDOWS_X64_OPTIMIZATION_FENCE);
|
|
cur_pc++;
|
|
layout->resume_pc = cur_pc;
|
|
} else if (instr_is_cti(scratch))
|
|
break;
|
|
}
|
|
} else {
|
|
layout->type = ANNOTATION_TYPE_EXPRESSION;
|
|
layout->name += (ANNOTATION_EXPRESSION_LABEL_LENGTH + 1);
|
|
layout->is_void = IS_ANNOTATION_VOID(layout->name); /* step 7 */
|
|
layout->name = strchr(layout->name, ':') + 1; /* step 8 */
|
|
}
|
|
}
|
|
# else /* Windows x86 and all Unix */
|
|
/* Identify the annotation at layout->start_pc, if any. In summary:
|
|
* (step 1) check if the instruction at layout->start_pc encodes an annotation label.
|
|
* (step 2) determine the annotation type based on instruction opcodes
|
|
* Expression annotations only:
|
|
* (step 3) compare the return type to determine whether the annotation is void.
|
|
* (step 4) adjust the substitution xl8 to the current pc.
|
|
* (step 5) decode past the jump over the annotation body.
|
|
* (step 6) set the resume pc to the current instruction, which is a jump over the
|
|
* native version of the annotation body (specified by the target app).
|
|
* (step 7) advance the label pointer to the name token.
|
|
*/
|
|
static inline void
|
|
identify_annotation(dcontext_t *dcontext, DR_PARAM_INOUT annotation_layout_t *layout,
|
|
instr_t *scratch)
|
|
{
|
|
app_pc cur_pc = layout->start_pc;
|
|
if (is_annotation_tag(dcontext, &cur_pc, scratch, &layout->name)) { /* step 1 */
|
|
# ifdef WINDOWS
|
|
if (*(cur_pc++) == RAW_OPCODE_pop_eax) { /* step 2 */
|
|
# else
|
|
if (instr_get_opcode(scratch) == OP_bsf) { /* step 2 */
|
|
# endif
|
|
layout->type = ANNOTATION_TYPE_STATEMENT;
|
|
} else {
|
|
layout->type = ANNOTATION_TYPE_EXPRESSION;
|
|
layout->is_void = IS_ANNOTATION_VOID(layout->name); /* step 3 */
|
|
}
|
|
layout->substitution_xl8 = cur_pc; /* step 4 */
|
|
instr_reset(dcontext, scratch);
|
|
cur_pc = decode_cti(dcontext, cur_pc, scratch); /* step 5 */
|
|
ASSERT(instr_is_ubr(scratch));
|
|
layout->resume_pc = cur_pc; /* step 6 */
|
|
layout->name = strchr(layout->name, ':') + 1; /* step 7 */
|
|
}
|
|
}
|
|
# endif
|
|
|
|
# ifdef X64
|
|
# ifdef UNIX
|
|
static inline void /* UNIX x64 */
|
|
create_arg_opnds(dr_annotation_handler_t *handler, uint num_args,
|
|
dr_annotation_calling_convention_t call_type)
|
|
{
|
|
uint i, arg_stack_location;
|
|
ASSERT(call_type == DR_ANNOTATION_CALL_TYPE_FASTCALL); /* architecture constraint */
|
|
switch (num_args) { /* Create up to six register args */
|
|
default:
|
|
case 6: handler->args[5] = opnd_create_reg(DR_REG_R9); DR_FALLTHROUGH;
|
|
case 5: handler->args[4] = opnd_create_reg(DR_REG_R8); DR_FALLTHROUGH;
|
|
case 4: handler->args[3] = opnd_create_reg(DR_REG_XCX); DR_FALLTHROUGH;
|
|
case 3: handler->args[2] = opnd_create_reg(DR_REG_XDX); DR_FALLTHROUGH;
|
|
case 2: handler->args[1] = opnd_create_reg(DR_REG_XSI); DR_FALLTHROUGH;
|
|
case 1: handler->args[0] = opnd_create_reg(DR_REG_XDI);
|
|
}
|
|
/* Create the remaining args on the stack */
|
|
for (i = FASTCALL_REGISTER_ARG_COUNT; i < num_args; i++) {
|
|
/* The clean call will appear at the top of the annotation function body, where
|
|
* the stack arguments follow the return address and the caller's saved stack
|
|
* pointer. Rewind `i` to 0 and add 2 to skip over these pointers.
|
|
*/
|
|
arg_stack_location = sizeof(ptr_uint_t) * (i - FASTCALL_REGISTER_ARG_COUNT + 2);
|
|
/* Use the stack pointer because the base pointer is a general register in x64 */
|
|
handler->args[i] = OPND_CREATE_MEMPTR(DR_REG_XSP, arg_stack_location);
|
|
}
|
|
}
|
|
# else /* WINDOWS x64 */
|
|
static inline void
|
|
create_arg_opnds(dr_annotation_handler_t *handler, uint num_args,
|
|
dr_annotation_calling_convention_t call_type)
|
|
{
|
|
uint i, arg_stack_location;
|
|
ASSERT(call_type == DR_ANNOTATION_CALL_TYPE_FASTCALL); /* architecture constraint */
|
|
switch (num_args) { /* Create up to four register args */
|
|
default:
|
|
case 4: handler->args[3] = opnd_create_reg(DR_REG_R9); DR_FALLTHROUGH;
|
|
case 3: handler->args[2] = opnd_create_reg(DR_REG_R8); DR_FALLTHROUGH;
|
|
case 2: handler->args[1] = opnd_create_reg(DR_REG_XDX); DR_FALLTHROUGH;
|
|
case 1: handler->args[0] = opnd_create_reg(DR_REG_XCX);
|
|
}
|
|
/* Create the remaining args on the stack */
|
|
for (i = FASTCALL_REGISTER_ARG_COUNT; i < num_args; i++) {
|
|
/* The clean call will appear at the top of the annotation function body, where
|
|
* the stack arguments follow the return address and 32 bytes of empty space.
|
|
* Since `i` is already starting at 4, just add one more to reach the args.
|
|
*/
|
|
arg_stack_location = sizeof(ptr_uint_t) * (i + 1);
|
|
/* Use the stack pointer because the base pointer is a general register in x64 */
|
|
handler->args[i] = OPND_CREATE_MEMPTR(DR_REG_XSP, arg_stack_location);
|
|
}
|
|
}
|
|
# endif
|
|
# else /* x86 (all) */
|
|
static inline void
|
|
create_arg_opnds(dr_annotation_handler_t *handler, uint num_args,
|
|
dr_annotation_calling_convention_t call_type)
|
|
{
|
|
uint i, arg_stack_location;
|
|
if (call_type == DR_ANNOTATION_CALL_TYPE_FASTCALL) {
|
|
switch (num_args) { /* Create 1 or 2 register args */
|
|
default:
|
|
case 2: handler->args[1] = opnd_create_reg(DR_REG_XDX); DR_FALLTHROUGH;
|
|
case 1: handler->args[0] = opnd_create_reg(DR_REG_XCX);
|
|
}
|
|
/* Create the remaining args on the stack */
|
|
for (i = FASTCALL_REGISTER_ARG_COUNT; i < num_args; i++) {
|
|
/* The clean call will appear at the top of the annotation function body,
|
|
* where the stack args follow the return address and the caller's saved base
|
|
* pointer. Since `i` already starts at 2, use it to skip those pointers.
|
|
*/
|
|
arg_stack_location = sizeof(ptr_uint_t) * i;
|
|
handler->args[i] = OPND_CREATE_MEMPTR(DR_REG_XBP, arg_stack_location);
|
|
}
|
|
} else { /* DR_ANNOTATION_CALL_TYPE_STDCALL: Create all args on the stack */
|
|
for (i = 0; i < num_args; i++) {
|
|
/* The clean call will appear at the top of the annotation function body,
|
|
* where the stack args follow the return address and the caller's saved base
|
|
* pointer. Since `i` starts at 0, add 2 to skip those pointers.
|
|
*/
|
|
arg_stack_location = sizeof(ptr_uint_t) * (i + 2);
|
|
handler->args[i] = OPND_CREATE_MEMPTR(DR_REG_XBP, arg_stack_location);
|
|
}
|
|
}
|
|
}
|
|
# endif
|
|
|
|
# ifdef DEBUG
|
|
static ssize_t
|
|
annotation_printf(const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
ssize_t count;
|
|
const char *timestamp_token_start;
|
|
char *timestamped_format = NULL;
|
|
uint buffer_length = 0;
|
|
|
|
if (d_r_stats == NULL || d_r_stats->loglevel == 0)
|
|
return 0; /* No log is available for writing. */
|
|
if ((d_r_stats->logmask & LOG_VIA_ANNOTATIONS) == 0)
|
|
return 0; /* Filtered out by the user. */
|
|
|
|
/* Substitute the first instance of the timestamp token with a timestamp string.
|
|
* Additional timestamp tokens will be ignored, because it would be pointless.
|
|
*/
|
|
timestamp_token_start = strstr(format, LOG_ANNOTATION_TIMESTAMP_TOKEN);
|
|
if (timestamp_token_start != NULL) {
|
|
char timestamp_buffer[PRINT_TIMESTAMP_MAX_LENGTH];
|
|
buffer_length = (uint)(strlen(format) + PRINT_TIMESTAMP_MAX_LENGTH);
|
|
if (print_timestamp_to_buffer(timestamp_buffer, PRINT_TIMESTAMP_MAX_LENGTH) > 0) {
|
|
uint length_before_token =
|
|
(uint)((ptr_uint_t)timestamp_token_start - (ptr_uint_t)format);
|
|
/* print the timestamped format string into this heap buffer */
|
|
timestamped_format = HEAP_ARRAY_ALLOC(GLOBAL_DCONTEXT, char, buffer_length,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
|
|
/* copy the original format string up to the timestamp token */
|
|
d_r_snprintf(timestamped_format, length_before_token, "%s", format);
|
|
/* copy the timestamp and the remainder of the original format string */
|
|
d_r_snprintf(timestamped_format + length_before_token,
|
|
(buffer_length - length_before_token), "%s%s", timestamp_buffer,
|
|
timestamp_token_start + LOG_ANNOTATION_TIMESTAMP_TOKEN_LENGTH);
|
|
/* use the timestamped format string */
|
|
format = (const char *)timestamped_format;
|
|
} else {
|
|
LOG(GLOBAL, LOG_ANNOTATIONS, 2,
|
|
"Failed to obtain a system timestamp for "
|
|
"substitution in annotation log statements '%s'\n",
|
|
format);
|
|
}
|
|
}
|
|
|
|
va_start(ap, format);
|
|
count = do_file_write(GLOBAL, format, ap);
|
|
va_end(ap);
|
|
if (timestamped_format != NULL) { /* free the timestamp heap buffer, if any */
|
|
HEAP_ARRAY_FREE(GLOBAL_DCONTEXT, format, char, buffer_length, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
}
|
|
return count;
|
|
}
|
|
# endif
|
|
|
|
static void
|
|
free_annotation_handler(void *p)
|
|
{
|
|
dr_annotation_handler_t *handler = (dr_annotation_handler_t *)p;
|
|
dr_annotation_receiver_t *next, *receiver = handler->receiver_list;
|
|
while (receiver != NULL) {
|
|
next = receiver->next;
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, receiver, dr_annotation_receiver_t, ACCT_OTHER,
|
|
UNPROTECTED);
|
|
receiver = next;
|
|
}
|
|
if (handler->num_args > 0) {
|
|
HEAP_ARRAY_FREE(GLOBAL_DCONTEXT, handler->args, opnd_t, handler->num_args,
|
|
ACCT_OTHER, UNPROTECTED);
|
|
}
|
|
if ((handler->type == DR_ANNOTATION_HANDLER_CALL) ||
|
|
(handler->type == DR_ANNOTATION_HANDLER_RETURN_VALUE))
|
|
dr_strfree(handler->symbol_name HEAPACCT(ACCT_OTHER));
|
|
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, p, dr_annotation_handler_t, ACCT_OTHER, UNPROTECTED);
|
|
}
|
|
|
|
#endif /* ANNOTATIONS */
|