blob: 0d3ad4e77954214c5b298b4874a2e6bb7b7d6745 [file] [edit]
/* **********************************************************
* Copyright (c) 2010-2024 Google, Inc. All rights reserved.
* Copyright (c) 2008-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/***************************************************************************
* replace.c: replacement routines for str* and mem* libc routines that
* tend to cause false positives due to their optimizations.
* Ours may be a little slower, but Dr. Memory's general slowdown
* far outweighs that, and replacing avoids having to use fragile
* pattern matches (though we still need those for inlined (or static,
* until we have symbols) copies of these routines).
* Xref PR 485412.
*/
#include "dr_api.h"
#include "drwrap.h"
#include "utils.h"
#include "heap.h"
#include "drmemory.h"
#include "shadow.h"
#include "drsymcache.h"
#include <limits.h> /* UCHAR_MAX */
#ifdef UNIX
# include <unistd.h> /* size_t */
#endif
#ifdef WINDOWS
# include <rpc.h> /* RPC_STATUS */
#endif
#include "drsyms.h"
#undef strcasecmp /* undo def from utils.h */
/* On Windows, keep this updated with drmemory.pl which queries these pre-run.
*
* When adding, make sure the regexs passed to find_syms_regex() cover
* the new name!
*
* Template: REPLACE_NAME_DEF(app-name, replace_ func name, corresponding-wide-char)
*
* The order does matter for routines that are sometimes aliases to one another:
* the later one takes precedence (thus, memmove is later than memcpy: i#1868).
*/
#define REPLACE_DEF(name, wide) REPLACE_NAME_DEF(name, name, wide)
#define REPLACE_DEFS() \
REPLACE_DEF(memset, NULL) \
REPLACE_DEF(memcpy, NULL) \
REPLACE_DEF(memchr, NULL) \
IF_UNIX(REPLACE_DEF(memrchr, NULL)) \
IF_UNIX(REPLACE_DEF(rawmemchr, NULL)) \
REPLACE_DEF(strchr, "wcschr") \
REPLACE_DEF(strrchr, "wcsrchr")\
IF_UNIX(REPLACE_DEF(strchrnul, NULL)) \
REPLACE_DEF(strlen, "wcslen") \
REPLACE_DEF(strnlen, "wcsnlen") \
REPLACE_DEF(strcmp, "wcscmp") \
REPLACE_DEF(strncmp, "wcsncmp")\
REPLACE_DEF(strcpy, "wcscpy") \
REPLACE_DEF(strncpy, NULL) \
REPLACE_DEF(strcat, NULL) \
REPLACE_DEF(strncat, NULL) \
REPLACE_DEF(memmove, NULL) \
/* DO NOT ADD ABOVE HERE w/o updating drmemory.pl -libc_addrs */ \
REPLACE_DEF(memcmp, NULL) \
REPLACE_DEF(wmemset, NULL) \
REPLACE_DEF(wmemcpy, NULL) \
REPLACE_DEF(wmemchr, NULL) \
REPLACE_DEF(wcslen, NULL) \
REPLACE_DEF(wcsnlen, NULL) \
REPLACE_DEF(wcscmp, NULL) \
REPLACE_DEF(wcsncmp, NULL) \
REPLACE_DEF(wcscpy, NULL) \
REPLACE_DEF(wmemcmp, NULL) \
REPLACE_DEF(wcschr, NULL) \
REPLACE_DEF(wcsrchr, NULL) \
REPLACE_DEF(strcasecmp, NULL) \
REPLACE_DEF(strncasecmp, NULL) \
REPLACE_DEF(strspn, NULL) \
REPLACE_DEF(strcspn, NULL) \
REPLACE_DEF(stpcpy, NULL) \
REPLACE_DEF(strstr, "wcsstr") \
REPLACE_DEF(wcsstr, NULL) \
IF_UNIX(REPLACE_DEF(bzero, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_memchr, memchr, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_memcmp, memcmp, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_memmove, memmove, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_memset, memset, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_strchr, strchr, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_strcmp, strcmp, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_strncmp, strncmp, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_strlen, strlen, NULL)) \
IF_MACOS(REPLACE_NAME_DEF(_platform_bzero, bzero, NULL))
/* XXX i#350: add wrappers for wcsncpy, wcscat,
* wcsncat, wmemmove.
*/
static const char *replace_routine_name[] = {
#define REPLACE_NAME_DEF(nm, func, wide) STRINGIFY(nm),
REPLACE_DEFS()
#undef REPLACE_NAME_DEF
};
#define REPLACE_NUM (sizeof(replace_routine_name)/sizeof(replace_routine_name[0]))
#ifdef WINDOWS
static const char * const replace_routine_wide_alt[] = {
# define REPLACE_NAME_DEF(nm, func, wide) wide,
REPLACE_DEFS()
# undef REPLACE_NAME_DEF
};
#endif
static app_pc replace_routine_start;
static size_t replace_routine_size;
static int index_memcpy;
static int index_memmove;
/* for passing data to sym enum callback */
typedef struct _sym_enum_data_t {
bool add;
bool processed[REPLACE_NUM];
app_pc indirect[REPLACE_NUM];
const module_data_t *mod;
} sym_enum_data_t;
/* for considering wildcard symbols */
#define REPLACE_NAME_TABLE_HASH_BITS 6
static hashtable_t replace_name_table;
static int
replace_tolower_ascii(int c);
/* for locale-specific tolower() for str{,n}casecmp */
static int (*app_tolower)(int) = replace_tolower_ascii;
/***************************************************************************
* The replacements themselves.
* These routines are not static so that under gdb a fault will show
* up as drmemory!replace_strlen and the user can see it's strlen.
*/
/* To distinguish these routines, we place into a separate page-aligned
* section.
* We really only use this separate section for debugging purposes, currently.
* If we later hide client libraries, we should probably move these
* to a visible library.
*/
#ifdef MACOS
asm(".section __TEXT,__replace");
asm(".align 12"); /* 2^12 */
# define IN_REPLACE_SECTION __attribute__ ((section ("__TEXT,__replace")))
#elif defined(LINUX)
asm(".section .replace, \"ax\", " IF_X86_ELSE("@progbits","%progbits"));
asm(".align " IF_X86_ELSE("0x1000","12"));
# define IN_REPLACE_SECTION __attribute__ ((section (".replace")))
#else /* WINDOWS */
ACTUAL_PRAGMA( code_seg(".replace") )
# define IN_REPLACE_SECTION /* nothing */
#endif
/* prevent cl from replacing our loop with a call to ntdll!memset,
* which we replace with this routine, which results an infinite loop!
*/
DO_NOT_OPTIMIZE
IN_REPLACE_SECTION void *
replace_memset(void *dst, int val_in, size_t size)
{
register unsigned char *ptr = (unsigned char *) dst;
unsigned char val = (unsigned char) val_in;
unsigned int val4 = (val << 24) | (val << 16) | (val << 8) | val;
while (!ALIGNED(ptr, 4) && size > 0) {
*ptr++ = val;
size--;
}
while (size > 3) {
*((unsigned int *)ptr) = val4;
ptr += 4;
size -= 4;
}
while (size > 0) {
*ptr++ = val;
size--;
}
return dst;
}
IN_REPLACE_SECTION wchar_t *
replace_wmemset(wchar_t *dst, wchar_t val_in, size_t size)
{
wchar_t *ret = dst;
while (size-- > 0)
*dst++ = val_in;
return ret;
}
END_DO_NOT_OPTIMIZE
DO_NOT_OPTIMIZE
IN_REPLACE_SECTION void
replace_bzero(void *dst, size_t size)
{
replace_memset(dst, 0, size);
}
IN_REPLACE_SECTION void *
replace_memcpy(void *dst, const void *src, size_t size)
{
register unsigned char *d = (unsigned char *) dst;
register unsigned char *s = (unsigned char *) src;
if (((ptr_uint_t)dst & 3) == ((ptr_uint_t)src & 3)) {
/* same alignment, so we can do 4 aligned bytes at a time and stay
* on fastpath. when not same alignment, I'm assuming it's faster
* to have all 1-byte moves on fastpath rather than half 4-byte
* (aligned) on fastpath and half 4-byte (unaligned) on slowpath.
*/
while (!ALIGNED(d, 4) && size > 0) {
*d++ = *s++;
size--;
}
while (size > 3) {
*((unsigned int *)d) = *((unsigned int *)s);
s += 4;
d += 4;
size -= 4;
}
while (size > 0) {
*d++ = *s++;
size--;
}
} else {
while (size-- > 0) /* loop will terminate before underflow */
*d++ = *s++;
}
return dst;
}
IN_REPLACE_SECTION void *
replace_memmove(void *dst, const void *src, size_t size);
IN_REPLACE_SECTION wchar_t *
replace_wmemcpy(wchar_t *dst, const wchar_t *src, size_t size)
{
#if defined(WINDOWS) && defined(X64)
/* i#1868: 64-bit Visual Studio labels memmove as "memcpy" so we are
* forced to handle overlap (though mingw doesn't need it).
*/
return (wchar_t*)replace_memmove(dst, src, size * sizeof(wchar_t));
#else
return (wchar_t*)replace_memcpy(dst, src, size * sizeof(wchar_t));
#endif
}
IN_REPLACE_SECTION void *
replace_memchr(const void *mem, int find, size_t size)
{
register const unsigned char *s = (unsigned char *) mem;
register unsigned char c = (unsigned char) find;
while (size-- > 0) { /* loop will terminate before underflow */
if (*s == c)
return (void *) s;
s++;
}
return NULL;
}
IN_REPLACE_SECTION wchar_t *
replace_wmemchr(wchar_t *s, wchar_t c, size_t size)
{
while (size-- > 0) { /* loop will terminate before underflow */
if (*s == c)
return s;
s++;
}
return NULL;
}
IN_REPLACE_SECTION void *
replace_memrchr(const void *mem, int find, size_t size)
{
register const unsigned char *s = ((unsigned char *) mem) + size - 1;
register unsigned char c = (unsigned char) find;
while (size-- > 0) { /* loop will terminate before underflow */
if (*s == c)
return (void *) s;
s--;
}
return NULL;
}
#ifdef UNIX
IN_REPLACE_SECTION void *
replace_rawmemchr(const void *mem, int find)
{
register unsigned char *s = (unsigned char *) mem;
register unsigned char c = (unsigned char) find;
while (*s != c)
s++;
return s;
}
#endif
IN_REPLACE_SECTION char *
replace_strchr(const char *str, int find)
{
register const char *s = str;
register char c = (char) find;
/* be sure to match the terminating 0 instead of failing (i#275) */
while (true) {
if (*s == c)
return (char *) s;
if (*s == '\0')
return NULL;
s++;
}
return NULL;
}
IN_REPLACE_SECTION wchar_t *
replace_wcschr(const wchar_t *str, int find)
{
register const wchar_t *s = str;
register wchar_t c = (wchar_t) find;
/* be sure to match the terminating 0 instead of failing (i#275) */
while (true) {
if (*s == c)
return (wchar_t *) s;
if (*s == L'\0')
return NULL;
s++;
}
return NULL;
}
IN_REPLACE_SECTION char *
replace_strrchr(const char *str, int find)
{
register const char *s = str;
register char c = (char) find;
const char *last = NULL;
/* be sure to match the terminating 0 instead of failing (i#275) */
while (true) {
if (*s == c)
last = s;
if (*s == '\0')
break;
s++;
}
return (char *) last;
}
IN_REPLACE_SECTION wchar_t *
replace_wcsrchr(const wchar_t *str, int find)
{
register const wchar_t *s = str;
register wchar_t c = (wchar_t) find;
const wchar_t *last = NULL;
/* be sure to match the terminating 0 instead of failing (i#275) */
while (true) {
if (*s == c)
last = s;
if (*s == L'\0')
break;
s++;
}
return (wchar_t *) last;
}
#ifdef UNIX
IN_REPLACE_SECTION char *
replace_strchrnul(const char *str, int find)
{
register const char *s = str;
register char c = (char) find;
while (*s != '\0') {
if (*s == c)
return (char *) s;
s++;
}
return (char *) s;
}
#endif
IN_REPLACE_SECTION size_t
replace_strlen(const char *str)
{
register const char *s = str;
while (*s != '\0')
s++;
return (s - str);
}
IN_REPLACE_SECTION size_t
replace_wcslen(const wchar_t *str)
{
register const wchar_t *s = str;
while (*s != L'\0')
s++;
return (s - str);
}
IN_REPLACE_SECTION size_t
replace_strnlen(const char *str, size_t max)
{
register const char *s = str;
while ((s - str) < max && *s != '\0')
s++;
return (s - str);
}
IN_REPLACE_SECTION size_t
replace_wcsnlen(const wchar_t *str, size_t max)
{
register const wchar_t *s = str;
while ((s - str) < max && *s != L'\0')
s++;
return (s - str);
}
IN_REPLACE_SECTION int
replace_strncmp(const char *str1, const char *str2, size_t size)
{
register const unsigned char *s1 = (const unsigned char *) str1;
register const unsigned char *s2 = (const unsigned char *) str2;
while (size-- > 0) { /* loop will terminate before underflow */
if (*s1 == '\0') {
if (*s2 == '\0')
return 0;
return -1;
}
if (*s2 == '\0')
return 1;
if (*s1 < *s2)
return -1;
if (*s1 > *s2)
return 1;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION int
replace_wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t size)
{
while (size-- > 0) { /* loop will terminate before underflow */
if (*s1 == L'\0') {
if (*s2 == L'\0')
return 0;
return -1;
}
if (*s2 == L'\0')
return 1;
if ((unsigned int)*s1 < (unsigned int)*s2)
return -1;
if ((unsigned int)*s1 > (unsigned int)*s2)
return 1;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION int
replace_strcmp(const char *str1, const char *str2)
{
register const unsigned char *s1 = (const unsigned char *) str1;
register const unsigned char *s2 = (const unsigned char *) str2;
while (1) {
if (*s1 == '\0') {
if (*s2 == '\0')
return 0;
return -1;
}
if (*s2 == '\0')
return 1;
if (*s1 < *s2)
return -1;
if (*s1 > *s2)
return 1;
s1++;
s2++;
}
return 0;
}
/* used by replace_str{,n}casecmp when we can't find a locale-aware version */
IN_REPLACE_SECTION static int
replace_tolower_ascii(int c)
{
if (c >= 'A' && c <= 'Z')
return (c - ('A' - 'a'));
return c;
}
IN_REPLACE_SECTION int
replace_strcasecmp(const char *str1, const char *str2)
{
register const unsigned char *s1 = (const unsigned char *) str1;
register const unsigned char *s2 = (const unsigned char *) str2;
while (1) {
register unsigned char l1 = (unsigned char) app_tolower(*s1);
register unsigned char l2 = (unsigned char) app_tolower(*s2);
if (l1 == '\0') {
if (l2 == '\0')
return 0;
return -1;
}
if (l2 == '\0')
return 1;
if (l1 < l2)
return -1;
if (l1 > l2)
return 1;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION int
replace_strncasecmp(const char *str1, const char *str2, size_t size)
{
register const unsigned char *s1 = (const unsigned char *) str1;
register const unsigned char *s2 = (const unsigned char *) str2;
while (size-- > 0) { /* loop will terminate before underflow */
register unsigned char l1 = (unsigned char) app_tolower(*s1);
register unsigned char l2 = (unsigned char) app_tolower(*s2);
if (l1 == '\0') {
if (l2 == '\0')
return 0;
return -1;
}
if (l2 == '\0')
return 1;
if (l1 < l2)
return -1;
if (l1 > l2)
return 1;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION int
replace_wcscmp(const wchar_t *s1, const wchar_t *s2)
{
while (1) {
if (*s1 == L'\0') {
if (*s2 == L'\0')
return 0;
return -1;
}
if (*s2 == L'\0')
return 1;
if ((unsigned int)*s1 < (unsigned int)*s2)
return -1;
if ((unsigned int)*s1 > (unsigned int)*s2)
return 1;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION char *
replace_strcpy(char *dst, const char *src)
{
register const char *s = (char *) src;
register char *d = (char *) dst;
while (*s != '\0')
*d++ = *s++;
*d = '\0';
return dst;
}
IN_REPLACE_SECTION wchar_t *
replace_wcscpy(wchar_t *dst, const wchar_t *src)
{
register const wchar_t *s = (wchar_t *) src;
register wchar_t *d = (wchar_t *) dst;
while (*s != '\0')
*d++ = *s++;
*d = '\0';
return dst;
}
IN_REPLACE_SECTION char *
replace_strncpy(char *dst, const char *src, size_t size)
{
register const char *s = (char *) src;
register char *d = (char *) dst;
while (size > 0 && *s != '\0') {
*d++ = *s++;
size--;
}
/* if hit size, do not null terminate */
while (size > 0) {
*d++ = '\0';
size--;
}
return dst;
}
IN_REPLACE_SECTION char *
replace_stpcpy(char *dst, const char *src)
{
register const char *s = (char *) src;
register char *d = (char *) dst;
while (*s != '\0')
*d++ = *s++;
*d = '\0';
return d;
}
IN_REPLACE_SECTION char *
replace_strcat(char *dst, const char *src)
{
register char *d = (char *) dst;
while (*d != '\0')
d++;
replace_strcpy(d, src);
return dst;
}
IN_REPLACE_SECTION char *
replace_strncat(char *dst, const char *src, size_t size)
{
/* we can't easily use replace_strncpy b/c we don't want
* to fill to size, and we want to null-terminate
*/
register const char *s = (char *) src;
register char *d = (char *) dst;
while (*d != '\0')
d++;
while (size > 0 && *s != '\0') {
*d++ = *s++;
size--;
}
*d = '\0';
return dst;
}
IN_REPLACE_SECTION size_t
replace_strspn(const char *str, const char *accept)
{
const char *cur = str;
bool table[UCHAR_MAX];
replace_memset(table, 0, sizeof(table));
while (*accept != '\0') {
table[(unsigned char)*accept] = true;
accept++;
}
while (*cur != '\0' && table[(unsigned char)*cur])
cur++;
return cur - str;
}
IN_REPLACE_SECTION size_t
replace_strcspn(const char *str, const char *reject)
{
const char *cur = str;
bool table[UCHAR_MAX];
replace_memset(table, 1, sizeof(table));
while (*reject != '\0') {
table[(unsigned char)*reject] = false;
reject++;
}
while (*cur != '\0' && table[(unsigned char)*cur])
cur++;
return cur - str;
}
IN_REPLACE_SECTION char *
replace_strstr(const char *haystack, const char *needle)
{
register const char *hs = haystack;
register const char *n = needle;
/* empty needle should return haystack */
if (*n == '\0')
return (char *) haystack;
while (*hs != '\0') {
if (*hs != *n && n > needle) {
hs -= n - 1 - needle; /* backtrack */
n = needle;
}
if (*hs == *n) {
n++;
if (*n == '\0')
return (char *) (hs - (n - 1 - needle));
}
hs++;
}
return NULL;
}
IN_REPLACE_SECTION wchar_t *
replace_wcsstr(const wchar_t *haystack, const wchar_t *needle)
{
register const wchar_t *hs = haystack;
register const wchar_t *n = needle;
/* empty needle should return haystack */
if (*n == '\0')
return (wchar_t *) haystack;
while (*hs != '\0') {
if (*hs != *n && n > needle) {
hs -= n - 1 - needle; /* backtrack */
n = needle;
}
if (*hs == *n) {
n++;
if (*n == '\0')
return (wchar_t *) (hs - (n - 1 - needle));
}
hs++;
}
return NULL;
}
IN_REPLACE_SECTION void *
replace_memmove(void *dst, const void *src, size_t size)
{
if (((ptr_uint_t)dst) - ((ptr_uint_t)src) >= size) {
/* forward walk won't clobber: either no overlap or dst < src */
register const char *s = (const char *) src;
register char *d = (char *) dst;
if (((ptr_uint_t)dst & 3) == ((ptr_uint_t)src & 3)) {
/* same alignment, so we can do 4 aligned bytes at a time and stay
* on fastpath. when not same alignment, I'm assuming it's faster
* to have all 1-byte moves on fastpath rather than half 4-byte
* (aligned) on fastpath and half 4-byte (unaligned) on slowpath.
*/
while (!ALIGNED(d, 4) && size > 0) {
*d++ = *s++;
size--;
}
while (size > 3) {
*((unsigned int *)d) = *((unsigned int *)s);
s += 4;
d += 4;
size -= 4;
}
while (size > 0) {
*d++ = *s++;
size--;
}
} else {
while (size > 0) {
*d++ = *s++;
size--;
}
}
} else {
/* walk backward to avoid clobbering since overlaps and src < dst */
register const char *s = ((const char *) src) + size;
register char *d = ((char *) dst) + size;
if (((ptr_uint_t)dst & 3) == ((ptr_uint_t)src & 3)) {
/* same alignment, so we can do 4 aligned bytes at a time and stay
* on fastpath. we want to do 4 aligned on mod 3 since backward.
*/
while (!ALIGNED(d, 4) && size > 0) {
*(--d) = *(--s);
size--;
}
while (size > 3) {
*((unsigned int *)(d-4)) = *((unsigned int *)(s-4));
s -= 4;
d -= 4;
size -= 4;
}
while (size > 0) {
*(--d) = *(--s);
size--;
}
} else {
while (size > 0) {
*(--d) = *(--s);
size--;
}
}
}
return dst;
}
IN_REPLACE_SECTION int
replace_memcmp(const void *p1, const void *p2, size_t size)
{
register const unsigned char *s1 = (unsigned char *) p1;
register const unsigned char *s2 = (unsigned char *) p2;
ssize_t diff;
while (size-- > 0) { /* loop will terminate before underflow */
diff = (*s1 - *s2);
if (diff != 0)
return diff;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION int
replace_wmemcmp(const wchar_t *s1, const wchar_t *s2, size_t count)
{
ssize_t diff;
while (count-- > 0) { /* loop will terminate before underflow */
diff = (*s1 - *s2);
if (diff != 0)
return diff;
s1++;
s2++;
}
return 0;
}
IN_REPLACE_SECTION void
replace_final_routine(void)
{
}
/* do not add .replace routines below here */
#ifdef MACOS
asm(".section __TEXT,.text");
asm(".align 12");
asm(".text");
#elif defined(LINUX)
asm(".section .text, \"ax\", " IF_X86_ELSE("@progbits","%progbits"));
asm(".align " IF_X86_ELSE("0x1000","12"));
#else
ACTUAL_PRAGMA( code_seg() )
#endif
/*
***************************************************************************/
static const void *replace_routine_addr[] = {
#define REPLACE_NAME_DEF(nm, func, wide) replace_##func,
REPLACE_DEFS()
#undef REPLACE_NAME_DEF
};
static app_pc
get_function_entry(app_pc C_var)
{
void *drcontext = dr_get_current_drcontext();
byte *pc;
instr_t inst;
instr_init(drcontext, &inst);
pc = decode(drcontext, C_var, &inst);
ASSERT(pc != NULL, "invalid instr at function entry");
if (instr_get_opcode(&inst) == OP_jmp) {
/* skip jmp in ILT */
ASSERT(opnd_is_pc(instr_get_target(&inst)), "decoded jmp should have pc tgt");
pc = opnd_get_pc(instr_get_target(&inst));
} else
pc = C_var;
instr_free(drcontext, &inst);
return pc;
}
void
replace_init(void)
{
if (options.replace_libc) {
app_pc addr;
int i;
char *s;
/* replace_module_load will be called for each module to populate the hashtable */
ASSERT(sizeof(int) >= sizeof(wchar_t),
"wchar_t replacement functions assume wchar_t is not larger than int");
replace_routine_start = (app_pc)
PAGE_START(get_function_entry((app_pc)replace_memset));
/* For now we assume the routines are laid out in source file order.
* It doesn't matter much because currently they all fit on on page.
* XXX i#1069: we could implement a more complex scheme to get the
* real bounds of .section.
*/
replace_routine_size =
ALIGN_FORWARD(get_function_entry((app_pc)replace_final_routine), PAGE_SIZE) -
PAGE_START(get_function_entry((app_pc)replace_memset));
/* PR 485412: we support passing in addresses of libc routines to
* be replaced if statically included in the executable and if
* we have no symbols available
*/
s = options.libc_addrs;
i = 0;
while (s != NULL) {
if (dr_sscanf(s, PIFX, (ptr_uint_t *)&addr) == 1 ||
/* we save option space by having no 0x prefix but assuming hex */
dr_sscanf(s, PIFMT, (ptr_uint_t *)&addr) == 1) {
LOG(2, "replacing %s @"PFX" in executable from options\n",
replace_routine_name[i], addr);
if (!drwrap_replace((app_pc)addr, (app_pc)replace_routine_addr[i], false))
ASSERT(false, "failed to replace");
}
s = strchr(s, ',');
if (s != NULL)
s++;
i++;
}
hashtable_init(&replace_name_table, REPLACE_NAME_TABLE_HASH_BITS, HASH_STRING,
false/*!strdup*/);
for (i=0; i<REPLACE_NUM; i++) {
if (strcmp(replace_routine_name[i], "memcpy") == 0)
index_memcpy = i;
if (strcmp(replace_routine_name[i], "memmove") == 0)
index_memmove = i;
hashtable_add(&replace_name_table, (void *) replace_routine_name[i],
(void *)(ptr_int_t)(i+1)/*since 0 is "not found"*/);
}
}
}
void
replace_exit(void)
{
if (options.replace_libc) {
hashtable_delete(&replace_name_table);
}
}
static void
replace_routine(bool add, const module_data_t *mod,
app_pc addr, int index)
{
IF_DEBUG(const char *modname = dr_module_preferred_name(mod);)
/* look for partial map (i#730) */
if (!dr_module_contains_addr(mod, addr)) {
LOG(1, "NOT replacing %s @"PFX" beyond end of mapping for module %s\n",
replace_routine_name[index], addr, modname == NULL ? "<noname>" : modname);
return;
}
LOG(2, "%s %s @"PFX" in %s (base "PFX")\n",
add ? "replacing" : "removing replacement",
replace_routine_name[index], addr,
modname == NULL ? "<noname>" : modname, mod->start);
/* We can't store 0 in the table (==miss) so we store index + 1 */
if (add) {
const void *replace_addr = replace_routine_addr[index];
#if defined(WINDOWS) && defined(X64)
/* i#1868: 64-bit Visual Studio labels memmove as "memcpy" so we are
* forced to handle overlap (though mingw doesn't need it). It's more
* efficient to swap here than to have replace_memcpy call replace_memmove.
*/
if (index == index_memcpy)
replace_addr = replace_routine_addr[index_memmove];
#endif
if (options.use_symcache)
drsymcache_add(mod, replace_routine_name[index], addr - mod->start);
/* Replacement strategy: we assume these routines will always be entered in
* a new bb (we're not going to request elision or indcall2direct from DR).
* We want to interpret our own routines, so we replace the whole bb with
* a jump to the replacement routine. This avoids having faults in
* our lib, for which DR will abort.
*/
/* Pass true to override b/c we do end up w/ dups b/c we process exports
* and then all symbols (see below). This also lets us handle aliases
* (e.g., memcpy aliased to memmove on MacOS: xref i#1868) by having
* the later-listed routine take precedence (memmove over memcpy, e.g.).
*/
if (!drwrap_replace((app_pc)addr, (app_pc)replace_addr, true))
ASSERT(false, "failed to replace");
} else {
if (!drwrap_replace((app_pc)addr, NULL, true)) {
/* We can't easily assert b/c we may have already removed an instance
* of this symbol (i#1200) or even of a different symbol with the
* same address (i#1818).
*/
}
}
}
/* Modern glibc uses an ELF indirect code object to enable strlen to
* dynamically resolve to an arch-specific optimized version.
* However, some internal glibc routines like fputs have
* hardcoded calls to strlen that sometimes target the version
* that is not the one resolved at runtime, and these versions
* are not exported. If we had full debug information we could
* find them as they are named __strlen_ia32 and __strlen_sse2.
* This routine tries to identify all such routines and replace
* them all. It assumes the strlen() resolution routine first
* does the typical "call thunk to get retaddr in ebx, add immed to ebx"
* to find the GOT, and then has "lea <offs of func>+GOT into eax"
* for each return possibility. Xref PR 623449.
* 32-bit example:
* 0x4efd50 <strlen>: push %ebx
* 0x4efd51 <strlen+1>: call 0x48f9ef <__i686.get_pc_thunk.bx>
* 0x4efd56 <strlen+6>: add $0xfa29e,%ebx
* 0x4efd5c <strlen+12>: cmpl $0x0,0x354c(%ebx)
* 0x4efd63 <strlen+19>: jne 0x4efd6a <strlen+26>
* 0x4efd65 <strlen+21>: call 0x48ffa0 <__init_cpu_features>
* 0x4efd6a <strlen+26>: lea -0xfa1e4(%ebx),%eax
* 0x4efd70 <strlen+32>: testl $0x4000000,0x3560(%ebx)
* 0x4efd7a <strlen+42>: je 0x4efd82 <strlen+50>
* 0x4efd7c <strlen+44>: lea -0xfa264(%ebx),%eax
* 0x4efd82 <strlen+50>: pop %ebx
* 0x4efd83 <strlen+51>: ret
*
* For 64-bit, we simply look for "lea <rip-rel+offs> into eax".
* Example:
* 0x37a4284b90 <strcmp>: cmpl $0x0,0x339029(%rip) # <__cpu_features>
* 0x37a4284b97 <strcmp+7>: jne 0x37a4284b9e <strcmp+14>
* 0x37a4284b99 <strcmp+9>: callq 0x37a4221fa0 <__init_cpu_features>
* 0x37a4284b9e <strcmp+14>: testl $0x200,0x339020(%rip) # <__cpu_features+8>
* 0x37a4284ba8 <strcmp+24>: jne 0x37a4284bbd <strcmp+45>
* 0x37a4284baa <strcmp+26>: lea 0xac32f(%rip),%rax # <__strcmp_sse42>
* 0x37a4284bb1 <strcmp+33>: testl $0x100000,0x339015(%rip) # <__cpu_features+16>
* 0x37a4284bbb <strcmp+43>: jne 0x37a4284bd7 <strcmp+71>
* 0x37a4284bbd <strcmp+45>: lea 0xb763c(%rip),%rax # <__strcmp_ssse3>
* 0x37a4284bc4 <strcmp+52>: testl $0x200,0x339002(%rip) # <__cpu_features+16>
* 0x37a4284bce <strcmp+62>: jne 0x37a4284bd7 <strcmp+71>
* 0x37a4284bd0 <strcmp+64>: lea 0x9(%rip),%rax # <__strcmp_sse2>
* 0x37a4284bd7 <strcmp+71>: retq
*/
static void
replace_all_indirect(bool add, const module_data_t *mod,
int index, app_pc indir, app_pc resolved)
{
#ifdef X86
void *drcontext = dr_get_current_drcontext();
instr_t inst;
app_pc pc = indir;
# if !defined(X64) || defined(DEBUG)
app_pc prev_pc;
# endif
# ifndef X64
bool last_was_call = false, first_call = false;
app_pc addr_got = NULL;
# endif
instr_init(drcontext, &inst);
do {
instr_reset(drcontext, &inst);
# if !defined(X64) || defined(DEBUG)
prev_pc = pc;
# endif
/* look for partial map (i#730) */
if (!dr_module_contains_addr(mod, pc + MAX_INSTR_SIZE)) {
WARN("WARNING: decoding off end of module for %s\n",
replace_routine_name[index]);
break;
}
/* use safe_decode() in case of addr in gap in ELF module */
if (!safe_decode(drcontext, pc, &inst, &pc) ||
pc == NULL || !instr_valid(&inst)) {
LOG(1, "WARNING: invalid instr at indir func %s "PFX"\n",
replace_routine_name[index], prev_pc);
break;
}
# ifndef X64
if (last_was_call) {
/* At instr after call to thunk: should be add of immed to ebx */
first_call = true;
if (instr_get_opcode(&inst) == OP_add &&
opnd_is_immed_int(instr_get_src(&inst, 0)) &&
opnd_is_reg(instr_get_dst(&inst, 0)) &&
opnd_get_reg(instr_get_dst(&inst, 0)) == REG_XBX) {
addr_got = opnd_get_immed_int(instr_get_src(&inst, 0)) + prev_pc;
LOG(2, "\tfound GOT "PFX" for indir func %s\n",
addr_got, replace_routine_name[index]);
if (addr_got < mod->start || !dr_module_contains_addr(mod, addr_got))
addr_got = NULL;
}
last_was_call = false;
}
if (addr_got != NULL &&
instr_get_opcode(&inst) == OP_lea &&
opnd_get_reg(instr_get_dst(&inst, 0)) == REG_XAX &&
opnd_is_base_disp(instr_get_src(&inst, 0)) &&
opnd_get_base(instr_get_src(&inst, 0)) == REG_XBX &&
opnd_get_index(instr_get_src(&inst, 0)) == REG_NULL &&
opnd_get_scale(instr_get_src(&inst, 0)) == 0) {
app_pc addr = addr_got + opnd_get_disp(instr_get_src(&inst, 0));
LOG(2, "\tfound return value "PFX" for indir func %s @"PFX"\n",
addr, replace_routine_name[index], prev_pc);
if (addr < mod->start || !dr_module_contains_addr(mod, addr)) {
LOG(1, "WARNING: unknown code in indir func %s @"PFX"\n",
replace_routine_name[index], prev_pc);
break;
}
if (addr != resolved)
replace_routine(add, mod, addr, index);
}
if (!first_call && instr_is_call_direct(&inst))
last_was_call = true;
# else
if (instr_get_opcode(&inst) == OP_lea &&
opnd_get_reg(instr_get_dst(&inst, 0)) == REG_XAX &&
opnd_is_rel_addr(instr_get_src(&inst, 0))) {
app_pc addr = opnd_get_addr(instr_get_src(&inst, 0));
LOG(2, "\tfound return value "PFX" for indir func %s @"PFX"\n",
addr, replace_routine_name[index], prev_pc);
if (addr < mod->start || !dr_module_contains_addr(mod, addr)) {
LOG(1, "WARNING: unknown code in indir func %s @"PFX"\n",
replace_routine_name[index], prev_pc);
break;
}
if (addr != resolved)
replace_routine(add, mod, addr, index);
}
# endif
} while (!instr_is_return(&inst));
instr_reset(drcontext, &inst);
#elif defined(ARM)
/* NYI: our current ARM targets do not have indirected routines in glibc. */
ASSERT_NOT_IMPLEMENTED();
#endif
}
/* It's faster to search for multiple symbols at once via regex
* and strcmp to identify precise targets (i#315).
*/
static bool
enumerate_syms_cb(drsym_info_t *info, drsym_error_t status, void *data)
{
uint i;
sym_enum_data_t *edata = (sym_enum_data_t *) data;
bool replace = true;
const char *name = info->name;
size_t modoffs = info->start_offs;
ASSERT(edata != NULL && edata->processed != NULL, "invalid param");
LOG(2, "%s: %s "PIFX"\n", __FUNCTION__, name, modoffs);
/* Using hashtable lookup to avoid linear walk of strcmp.
* Linear walk isn't much slower now, but will become worse
* as we add more routines.
*/
i = (uint)(ptr_uint_t) hashtable_lookup(&replace_name_table, (void *)name);
if (i == 0)
return true; /* keep iterating */
#ifdef WINDOWS /* drsym_get_func_type NYI on Linux, and wchar_t rarely used there */
/* i#682: chrome has str* routines that take in wchar_t. We ask for types
* and treat wide as corresponding wcs*. We only look at the str* routines
* and we assume each has a char* (or const char*) pointer as its first arg.
* Could have a bool in REPLACE_DEFS.
*/
if (name[0] == 's' && name[1] == 't' && name[2] == 'r') {
size_t bufsz = 256; /* good starting size: big enough for str* in practice */
char *buf = (char *) global_alloc(bufsz, HEAPSTAT_MISC);
drsym_func_type_t *func_type;
drsym_error_t err;
do {
err = drsym_get_func_type(edata->mod->full_path, modoffs,
buf, bufsz, &func_type);
if (err != DRSYM_ERROR_NOMEM)
break;
global_free(buf, bufsz, HEAPSTAT_MISC);
bufsz *= 2;
buf = (char *) global_alloc(bufsz, HEAPSTAT_MISC);
} while (true);
if (err == DRSYM_SUCCESS &&
func_type->num_args >= 1 &&
func_type->arg_types[0]->kind == DRSYM_TYPE_PTR) {
drsym_ptr_type_t *arg_type = (drsym_ptr_type_t*) func_type->arg_types[0];
if (arg_type->elt_type->kind == DRSYM_TYPE_INT &&
arg_type->elt_type->size == sizeof(wchar_t)) {
WARN("WARNING: %s "PIFX" arg type is wchar_t*!\n", name, modoffs);
if (i != 0 && replace_routine_wide_alt[i-1] != NULL) {
/* Replace as wide-char routine instead. It will get
* put into symcache under the wide name, so we will
* avoid this type lookup next time.
*/
name = replace_routine_wide_alt[i-1];
i = (uint)(ptr_uint_t)
hashtable_lookup(&replace_name_table, (void *)name);
} else
replace = false;
} else if (arg_type->elt_type->kind != DRSYM_TYPE_INT ||
arg_type->elt_type->size != sizeof(char)) {
WARN("WARNING: %s "PIFX" has unknown arg types!\n", name, modoffs);
replace = false;
}
}
global_free(buf, bufsz, HEAPSTAT_MISC);
}
#endif
if (replace) {
/* i#617: some modules have multiple addresses for one symbol:
* e.g., unit_tests.exe strlen, strchr, and strrchr
*/
if (i != 0 && !edata->processed[i-1] &&
/* i#1710: do not replace indirected top-level object */
edata->mod->start + modoffs != edata->indirect[i-1]) {
i--;
replace_routine(edata->add, edata->mod, edata->mod->start + modoffs, i);
}
}
return true; /* keep iterating */
}
static void
find_syms_regex(sym_enum_data_t *edata, const char *regex)
{
if (!lookup_all_symbols(edata->mod, regex, false/*!full*/,
enumerate_syms_cb, (void *)edata))
LOG(2, "WARNING: failed to look up symbols: %s\n", regex);
}
#ifdef WINDOWS
/* i#511: Save UuidCreate's outparam so we can mark it as defined in the post
* callback.
*
* RPC_STATUS UuidCreate(UUID __RPC_FAR *Uuid);
*/
static void
wrap_UuidCreate_pre(void *wrapcxt, DR_PARAM_OUT void **user_data)
{
/* Save arg to mark as initialized afterwards. */
*user_data = drwrap_get_arg(wrapcxt, 0);
}
static void
wrap_UuidCreate_post(void *wrapcxt, void *user_data)
{
app_pc cur;
app_pc start = user_data;
RPC_STATUS status;
ASSERT(options.check_uninitialized, "invalid shadow mode");
if (wrapcxt == NULL)
return; /* Do nothing on unwind. */
/* Check for success. It's not clear if the output is written on other
* status codes.
*/
status = (RPC_STATUS)(ptr_uint_t) drwrap_get_retval(wrapcxt);
if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
return;
/* Mark the outparam as defined. */
shadow_set_non_matching_range(start, sizeof(GUID), SHADOW_DEFINED,
SHADOW_UNADDRESSABLE);
}
#endif
/* XXX: better to walk hashtable on remove, like alloc.c does, instead of
* re-doing all these symbol queries
*/
static void
replace_in_module(const module_data_t *mod, bool add)
{
/* We want to replace str/mem in libc, and in the executable if
* it has statically included libc, and in any library that has done
* so: so we aggressively apply to all libraries.
* FIXME: we need symbols to find statically-included libc.
*
* It's possible some library exports a routine that is slightly
* different w/ the same name but that seems very unlikely since it
* would conflict w/ the libc routine. On Windows it's more likely
* since there's no global namespace.
*/
int i;
app_pc libc = get_libc_base(NULL);
void *drcontext = dr_get_current_drcontext();
sym_enum_data_t edata = {add, {0,}, {0}, mod};
bool missing_entry = false;
#ifdef DEBUG
const char *print_name = (dr_module_preferred_name(mod) == NULL) ?
"<NULL>" : dr_module_preferred_name(mod);
#endif
#ifdef WINDOWS
const char *modname = dr_module_preferred_name(mod);
/* i#511: Wrap UuidCreate in rpcrt4.dll. */
if (options.check_uninitialized &&
text_matches_pattern("rpcrt4.dll", modname, true/*ignore case */)) {
generic_func_t fn = dr_get_proc_address(mod->handle, "UuidCreate");
if (fn != NULL) {
if (add) {
drwrap_wrap((app_pc)fn, wrap_UuidCreate_pre,
wrap_UuidCreate_post);
} else {
drwrap_unwrap((app_pc)fn, wrap_UuidCreate_pre,
wrap_UuidCreate_post);
}
}
}
if (options.skip_msvc_importers &&
module_imports_from_msvc(mod) &&
(modname == NULL ||
(!text_matches_pattern(modname, "msvcp*.dll", true/*ignore case*/) &&
!text_matches_pattern(modname, "msvcr*.dll", true/*ignore case*/)))) {
/* i#963: assume there are no static libc routines if the module
* imports from dynamic libc (unless it's dynamic C++ lib).
* XXX: We'll miss some non-libc custom routine: but those may be
* unlikely to have the optimizations that cause false positives.
* XXX: Look through imported funcs?
*/
LOG(2, "module %s imports from msvc* so not searching inside it\n",
modname == NULL ? "" : modname);
return;
}
#else
if (mod->start == libc) {
/* prefer locale-aware tolower from libc to our English version (i#181) */
generic_func_t func = dr_get_proc_address(mod->handle, "tolower");
if (func != NULL) {
if (add)
app_tolower = (int (*)(int)) func;
else
app_tolower = replace_tolower_ascii;
}
}
#endif
ASSERT(options.replace_libc, "should not be called if op not on");
/* step 1: find and replace symbols in exports
* if we find an export we can't mark as processed b/c
* there can be other symbols of same name.
*/
for (i=0; i<REPLACE_NUM; i++) {
dr_export_info_t info;
app_pc addr = NULL;
if (dr_get_proc_address_ex(mod->handle, replace_routine_name[i],
&info, sizeof(info))) {
addr = (app_pc) info.address;
ASSERT(addr != NULL, "can't succeed yet have NULL addr!");
if (info.is_indirect_code) {
/* i#248/PR 510905: new ELF indirect code object type.
* We have to call the export to get the real impl.
* This could be unsafe: but we're already trusting the app and
* assuming it's not trying to intentionally subvert us.
*/
app_pc (*indir)(void) = (app_pc (*)(void)) cast_to_func(addr);
app_pc orig_addr = addr;
DR_TRY_EXCEPT(drcontext, {
addr = (*indir)();
}, { /* EXCEPT */
addr = NULL;
});
LOG(2, "export %s indirected from "PFX" to "PFX"\n",
replace_routine_name[i], info.address, addr);
if (mod->start == libc)
replace_all_indirect(add, mod, i, orig_addr, addr);
/* i#1710: while we do want to find other instances, we can't
* let the symbol enumerator replace the top-level indirected
* object, so we store its address.
*/
edata.indirect[i] = (app_pc) info.address;
}
}
if (addr != NULL) {
replace_routine(add, mod, addr, i);
} else {
/* We should find every single routine in libc on linux: on windows
* the wide-char ones aren't always there
*/
IF_UNIX(ASSERT(mod->start != libc ||
/* i#1747: bionic libc does not have all replace routines.
* We could add a generated array of whether the routine is
* expected to be there instead if more routines are missing.
*/
(strcmp(replace_routine_name[i], "rawmemchr") == 0 ||
strcmp(replace_routine_name[i], "strchrnul") == 0),
"can't find libc routine to replace"));
}
}
/* step 2, find and replace symbols in symcache
* i#617: some modules have multiple addresses for one symbol:
* e.g., unit_tests.exe: strlen, strchr, and strrchr,
* so we always need check if we can find it in the symcache.
* We assume that we have all the entries of a symbol if we can find one
* entry for that symbol in the symcache.
*/
for (i = 0; i < REPLACE_NUM; i++) {
size_t *modoffs, single;
uint count;
uint idx;
bool res;
LOG(3, "Search %s in %s symcache\n", replace_routine_name[i], print_name);
if (options.use_symcache &&
drsymcache_module_is_cached(mod, &res) == DRMF_SUCCESS && res &&
drsymcache_lookup(mod, replace_routine_name[i],
&modoffs, &count, &single) == DRMF_SUCCESS) {
STATS_INC(symbol_search_cache_hits);
edata.processed[i] = true;
for (idx = 0; idx < count; idx++) {
if (modoffs[idx] != 0) {
replace_routine(add, mod, mod->start + modoffs[idx], i);
}
}
drsymcache_free_lookup(modoffs, count);
}
if (!edata.processed[i]) {
LOG(2, "%s: did not find %s in symcache\n", print_name,
replace_routine_name[i]);
missing_entry = true;
}
}
/* step 3, some symbols are not found in symcache, lookup them in modules */
if (missing_entry) {
/* PR 486382: look up these symbols online for all modules.
* We rely on drsym_init() having been called during init.
* It's faster to look up multiple via regex (xref i#315)
* when most modules don't have any of the replacement syms.
*/
/* add 0, which will be replaced if we find it */
if (options.use_symcache) {
for (i = 0; i < REPLACE_NUM; i++) {
if (!edata.processed[i])
drsymcache_add(mod, replace_routine_name[i], 0);
}
}
/* These regex cover all function names we replace. Both
* number of syms and number of queries count. This is a good
* compromise. "*mem*" has too many matches, while
* "mem[scrm]*", "*wmem*", "str[crln]*", and "wcs*" is too
* many queries. Note that dbghelp does not support a regex
* symbol for "0 or 1 chars".
*/
if (lookup_has_fast_search(mod)) {
/* N.B.: if you change these regexes, bump SYMCACHE_VERSION! */
find_syms_regex(&edata, "[msw]?????");
find_syms_regex(&edata, "[msw]??????");
#ifdef UNIX
find_syms_regex(&edata, "strchrnul");
find_syms_regex(&edata, "rawmemchr");
#endif
} else {
/* better to do just one walk */
LOG(2, "looking up all symbols for %s\n", print_name);
if (!lookup_all_symbols(edata.mod, "", false/*!full*/,
enumerate_syms_cb, (void *)&edata))
LOG(2, "WARNING: failed to look up symbols to replace\n");
}
}
}
void
replace_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
/* Skip DR (its forwarders raise a curiosity in module_shared.c, plus
* we don't want to replace its or client uses)
*/
if (options.replace_libc &&
!dr_memory_is_dr_internal(info->start) &&
!dr_memory_is_in_client(info->start))
replace_in_module(info, true/*add*/);
}
void
replace_module_unload(void *drcontext, const module_data_t *info)
{
/* Skip DR (its forwarders raise a curiosity in module_shared.c, plus
* we don't want to replace its or client uses)
*/
if (options.replace_libc &&
!dr_memory_is_dr_internal(info->start) &&
!dr_memory_is_in_client(info->start))
replace_in_module(info, false/*remove*/);
}
bool
in_replace_routine(app_pc pc)
{
return (options.replace_libc &&
pc >= replace_routine_start &&
pc < replace_routine_start + replace_routine_size);
}
bool
in_replace_memset(app_pc pc)
{
/* we assume the layout for memset is all in one spot (optimizations
* are disabled) and that memcpy follows it. it wouldn't be
* disastrous to include another routine since only used for
* heap-unaddr checks.
*/
static app_pc memset_entry, memcpy_entry;
if (memset_entry == NULL) {
memset_entry = get_function_entry((app_pc)replace_memset);
memcpy_entry = get_function_entry((app_pc)replace_memcpy);
}
return (pc >= (app_pc)memset_entry && pc < (app_pc)memcpy_entry);
}