blob: d7280cc28a4c803a395639ca005663479052cbbd [file] [edit]
/* **********************************************************
* Copyright (c) 2011-2016 Google, 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.
*/
/* Front-end to drsyms for Windows */
#ifdef WINDOWS
/* We use drfrontendlib, whose model has us take in UTF-16 argv */
# define UNICODE
# define _UNICODE
#endif
#include "dr_api.h"
#include "drsyms.h"
#include "dr_frontend.h"
#include "dr_inject.h" /* for cross-arch support */
#include <assert.h>
#include <stdio.h>
#include <string.h>
/* Pull in BUFFER_SIZE_ELEMENTS, IF_WINDOWS, TESTALL, and other useful macros */
#include "utils.h"
#undef sscanf /* we can use sscanf */
#define MAX_FUNC_LEN 256
#define MAX_PATH_STR STRINGIFY(MAXIMUM_PATH)
#ifndef WINDOWS
# define _stricmp strcasecmp
#endif
#if defined(MACOS) && !defined(X64)
/* size_t is unsigned long */
# define SIZE_FMT "%lx"
# define SIZE_FMTX "0x%lx"
#else
# define SIZE_FMT PIFMT
# define SIZE_FMTX PIFX
#endif
/* forward decls */
static void symquery_lookup_address(const char *dllpath, size_t modoffs);
static void symquery_lookup_symbol(const char *dllpath, const char *sym);
static void enumerate_symbols(const char *dllpath, const char *match,
bool search, bool searchall);
static void enumerate_lines(const char *dllpath);
static bool check_architecture(const char *dll, char **argv);
/* options */
#define USAGE_PRE "Usage:\n\
Look up addresses for one module:\n\
%s -e <module> [-f] [-v] -a [<address relative to module base> ...]\n\
Look up addresses for multiple modules:\n\
%s [-f] [-v] -q <pairs of [module_path;address relative to module base] on stdin>\n\
Look up exact symbols for one module:\n\
%s -e <module> [-v] [--enum] -s [<symbol1> <symbol2> ...]\n"
#ifdef WINDOWS
# define USAGE_MID \
"Look up symbols matching wildcard patterns (glob-style: *,?) for one module:\n\
%s -e <module> [-v] --search -s [<symbol1> <symbol2> ...]\n\
Look up private symbols matching wildcard patterns (glob-style: *,?) for one module:\n\
%s -e <module> [-v] --searchall -s [<symbol1> <symbol2> ...]\n"
#else
# define USAGE_MID "%.0s%.0s"
#endif
#define USAGE_POST \
"List all symbols in a module:\n\
%s -e <module> [-v] --list\n\
List all source lines in a module:\n\
%s -e <module> [-v] --lines\n\
Optional parameters:\n\
-f = show function name\n\
-v = verbose\n\
--enum = look up via external enum rather than drsyms-internal enum\n"
#define PRINT_USAGE(mypath) do {\
printf(USAGE_PRE, mypath, mypath, mypath);\
printf(USAGE_MID, mypath, mypath);\
printf(USAGE_POST, mypath, mypath);\
} while (0)
static bool show_func;
static bool verbose;
/* We could expose the templates via an option */
static uint demangle_flags = (DRSYM_DEMANGLE | DRSYM_DEMANGLE_PDB_TEMPLATES);
int
_tmain(int argc, TCHAR *targv[])
{
int res = 1;
char **argv;
char dll[MAXIMUM_PATH];
int i;
/* module + address per line */
char line[MAXIMUM_PATH*2];
size_t modoffs;
/* options that can be local vars */
bool addr2sym = false;
bool addr2sym_multi = false;
bool sym2addr = false;
bool enumerate = false;
bool enumerate_all = false;
bool search = false;
bool searchall = false;
bool enum_lines = false;
#if defined(WINDOWS) && !defined(_UNICODE)
# error _UNICODE must be defined
#else
/* Convert to UTF-8 if necessary */
if (drfront_convert_args((const TCHAR **)targv, &argv, argc) != DRFRONT_SUCCESS) {
printf("ERROR: failed to process args\n");
return 1;
}
#endif
dll[0] = '\0';
for (i = 1; i < argc; i++) {
if (_stricmp(argv[i], "-e") == 0) {
bool is_readable;
if (i+1 >= argc) {
PRINT_USAGE(argv[0]);
goto cleanup;
}
i++;
if (drfront_get_absolute_path(argv[i], dll, BUFFER_SIZE_ELEMENTS(dll)) !=
DRFRONT_SUCCESS) {
printf("ERROR: invalid path %s\n", argv[i]);
goto cleanup;
}
if (drfront_access(dll, DRFRONT_READ, &is_readable) != DRFRONT_SUCCESS ||
!is_readable) {
printf("ERROR: invalid path %s\n", argv[i]);
goto cleanup;
}
} else if (_stricmp(argv[i], "-f") == 0) {
show_func = true;
} else if (_stricmp(argv[i], "-v") == 0) {
verbose = true;
} else if (_stricmp(argv[i], "-a") == 0 ||
_stricmp(argv[i], "-s") == 0) {
if (i+1 >= argc) {
PRINT_USAGE(argv[0]);
goto cleanup;
}
if (_stricmp(argv[i], "-a") == 0)
addr2sym = true;
else
sym2addr = true;
i++;
/* rest of args read below */
break;
} else if (_stricmp(argv[i], "--lines") == 0) {
enum_lines = true;
} else if (_stricmp(argv[i], "-q") == 0) {
addr2sym_multi = true;
} else if (_stricmp(argv[i], "--enum") == 0) {
enumerate = true;
} else if (_stricmp(argv[i], "--list") == 0) {
enumerate_all = true;
} else if (_stricmp(argv[i], "--search") == 0) {
search = true;
} else if (_stricmp(argv[i], "--searchall") == 0) {
search = true;
searchall = true;
} else {
PRINT_USAGE(argv[0]);
goto cleanup;
}
}
if ((!addr2sym_multi && dll[0] == '\0') ||
(addr2sym_multi && dll[0] != '\0') ||
(!sym2addr && !addr2sym && !addr2sym_multi && !enumerate_all && !enum_lines)) {
PRINT_USAGE(argv[0]);
goto cleanup;
}
dr_standalone_init();
if (dll[0] != '\0') {
if (!check_architecture(dll, argv))
goto cleanup;
}
if (drsym_init(IF_WINDOWS_ELSE(NULL, 0)) != DRSYM_SUCCESS) {
printf("ERROR: unable to initialize symbol library\n");
goto cleanup;
}
if (!addr2sym_multi) {
if (enum_lines)
enumerate_lines(dll);
else if (enumerate_all)
enumerate_symbols(dll, NULL, search, searchall);
else {
/* kind of a hack: assumes i hasn't changed and that -s/-a is last option */
for (; i < argc; i++) {
if (addr2sym) {
if (sscanf(argv[i], SIZE_FMT, &modoffs) == 1)
symquery_lookup_address(dll, modoffs);
else
printf("ERROR: unknown input %s\n", argv[i]);
} else if (enumerate || search)
enumerate_symbols(dll, argv[i], search, searchall);
else
symquery_lookup_symbol(dll, argv[i]);
}
}
} else {
while (!feof(stdin)) {
char modpath[MAXIMUM_PATH];
if (fgets(line, sizeof(line), stdin) == NULL ||
/* when postprocess.pl closes the pipe, fgets is not
* returning, so using an alternative eof code
*/
strcmp(line, ";exit\n") == 0)
break;
/* Ensure we support spaces in paths by using ; to split.
* Since ; separates PATH, no Windows dll will have ; in its name.
*/
if (sscanf(line, "%"MAX_PATH_STR"[^;];"SIZE_FMT, (char *)&modpath,
&modoffs) == 2) {
symquery_lookup_address(modpath, modoffs);
fflush(stdout); /* ensure flush in case piped */
} else if (verbose)
printf("Error: unknown input %s\n", line);
}
}
if (drsym_exit() != DRSYM_SUCCESS)
printf("WARNING: error cleaning up symbol library\n");
res = 0;
cleanup:
if (drfront_cleanup_args(argv, argc) != DRFRONT_SUCCESS)
printf("WARNING: drfront_cleanup_args failed\n");
return res;
}
static bool
check_architecture(const char *dll, char **argv)
{
bool is_64bit, also_32bit;
if (drfront_is_64bit_app(dll, &is_64bit, &also_32bit) != DRFRONT_SUCCESS) {
printf("ERROR: unable to get the architecture infomation of"
" the target module %s\n", dll);
return false;
}
if (IF_X64_ELSE(!is_64bit, is_64bit && !also_32bit)) {
char *orig_argv0 = argv[0];
char root[MAXIMUM_PATH];
char buf[MAXIMUM_PATH];
char *basename;
int errcode;
void *inject_data;
bool is_readable;
if (drfront_get_app_full_path(argv[0], root, BUFFER_SIZE_ELEMENTS(root)) !=
DRFRONT_SUCCESS) {
printf("ERROR: unable to get base dir of %s\n", argv[0]);
return false;
}
basename = root + strlen(root) - 1;
while (*basename != DIRSEP && *basename != ALT_DIRSEP && basename > root)
basename--;
if (basename <= root) {
printf("ERROR: unable to get base dir of %s\n", argv[0]);
return false;
}
*basename = '\0';
basename++;
_snprintf(buf, BUFFER_SIZE_ELEMENTS(buf) ,
"%s%c..%c%s%c%s", root, DIRSEP, DIRSEP,
IF_X64_ELSE("bin", "bin64"), DIRSEP, basename);
NULL_TERMINATE_BUFFER(buf);
if (drfront_access(buf, DRFRONT_READ, &is_readable) != DRFRONT_SUCCESS ||
!is_readable) {
printf("ERROR: unable to find frontend %s to match target file bitwidth: "
"is this an incomplete installation?\n", buf);
}
argv[0] = buf;
#ifdef UNIX
errcode = dr_inject_prepare_to_exec(buf, (const char **)argv, &inject_data);
if (errcode == 0 || errcode == WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE)
dr_inject_process_run(inject_data); /* shouldn't return */
printf("ERROR (%d): unable to launch frontend to match target file bitwidth\n",
errcode);
argv[0] = orig_argv0;
return false;
#else
errcode = dr_inject_process_create(buf, argv, &inject_data);
if (errcode == 0 || errcode == WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE) {
dr_inject_process_run(inject_data);
/* Wait for the child so user's shell prompt doesn't come back early */
errcode = WaitForSingleObject(dr_inject_get_process_handle(inject_data),
INFINITE);
if (errcode != WAIT_OBJECT_0)
printf("WARNING: failed to wait for cross-arch frontend\n");
dr_inject_process_exit(inject_data, false);
argv[0] = orig_argv0;
return false;
} else {
printf("ERROR (%d): unable to launch frontend to match target file bitwidth\n",
errcode);
argv[0] = orig_argv0;
return false;
}
#endif
}
return true;
}
static void
print_debug_kind(drsym_debug_kind_t kind)
{
printf("<debug info: type=%s, %s symbols, %s line numbers>\n",
TEST(DRSYM_ELF_SYMTAB, kind) ? "ELF symtab" :
(TEST(DRSYM_PECOFF_SYMTAB, kind) ? "PECOFF symtab" :
(TEST(DRSYM_MACHO_SYMTAB, kind) ? "Mach-O symtab" :
(TEST(DRSYM_PDB, kind) ? "PDB" : "no symbols"))),
TEST(DRSYM_SYMBOLS, kind) ? "has" : "NO",
TEST(DRSYM_LINE_NUMS, kind) ? "has" : "NO");
}
static void
get_and_print_debug_kind(const char *dllpath)
{
drsym_debug_kind_t kind;
drsym_error_t symres = drsym_get_module_debug_kind(dllpath, &kind);
if (symres == DRSYM_SUCCESS)
print_debug_kind(kind);
}
static void
symquery_lookup_address(const char *dllpath, size_t modoffs)
{
drsym_error_t symres;
drsym_info_t sym;
char name[MAX_FUNC_LEN];
char file[MAXIMUM_PATH];
sym.struct_size = sizeof(sym);
sym.name = name;
sym.name_size = MAX_FUNC_LEN;
sym.file = file;
sym.file_size = MAXIMUM_PATH;
symres = drsym_lookup_address(dllpath, modoffs, &sym, demangle_flags);
if (symres == DRSYM_SUCCESS || symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) {
if (verbose)
print_debug_kind(sym.debug_kind);
if (sym.name_available_size >= sym.name_size)
printf("WARNING: function name longer than max: %s\n", sym.name);
if (show_func)
printf("%s+"SIZE_FMTX"\n", sym.name, (modoffs - sym.start_offs));
if (symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) {
printf("??:0\n");
} else {
printf("%s:%"INT64_FORMAT"u+"SIZE_FMTX"\n", sym.file, sym.line,
sym.line_offs);
}
} else {
if (verbose)
printf("drsym_lookup_address error %d\n", symres);
else if (show_func)
printf("?\n");
}
}
static void
symquery_lookup_symbol(const char *dllpath, const char *sym)
{
size_t modoffs;
drsym_error_t symres;
if (verbose)
get_and_print_debug_kind(dllpath);
symres = drsym_lookup_symbol(dllpath, sym, &modoffs, demangle_flags);
if (symres == DRSYM_SUCCESS || symres == DRSYM_ERROR_LINE_NOT_AVAILABLE) {
printf("+"SIZE_FMTX"\n", modoffs);
} else {
if (verbose)
printf("drsym error %d looking up \"%s\" in \"%s\"\n", symres, sym, dllpath);
else
printf("??\n");
}
}
static bool
search_cb(drsym_info_t *info, drsym_error_t status, void *data)
{
const char *match = (const char *) data;
if (match == NULL || strcmp(info->name, match) == 0)
printf("%s +"SIZE_FMTX"-"SIZE_FMTX"\n", info->name, info->start_offs,
info->end_offs);
return true; /* keep iterating */
}
static void
enumerate_symbols(const char *dllpath, const char *match, bool search, bool searchall)
{
drsym_error_t symres;
if (verbose)
get_and_print_debug_kind(dllpath);
#ifdef WINDOWS
if (search)
symres = drsym_search_symbols_ex(dllpath, match,
(searchall ? DRSYM_FULL_SEARCH : 0) |
demangle_flags,
search_cb, sizeof(drsym_info_t), NULL);
else {
#endif
symres = drsym_enumerate_symbols_ex(dllpath, search_cb, sizeof(drsym_info_t),
(void *)match, demangle_flags);
#ifdef WINDOWS
}
#endif
if (symres != DRSYM_SUCCESS && verbose)
printf("search/enum error %d\n", symres);
}
static bool
enum_line_cb(drsym_line_info_t *info, void *data)
{
printf("cu=\"%s\", file=\"%s\" line=" INT64_FORMAT_STRING ", addr="PIFX"\n",
(info->cu_name == NULL) ? "<null>" : info->cu_name,
(info->file == NULL) ? "<null>" : info->file,
info->line, (ptr_uint_t)info->line_addr);
return true;
}
static void
enumerate_lines(const char *dllpath)
{
drsym_error_t symres;
if (verbose)
get_and_print_debug_kind(dllpath);
symres = drsym_enumerate_lines(dllpath, enum_line_cb, (void *) dllpath);
if (symres != DRSYM_SUCCESS && verbose)
printf("line enum error %d\n", symres);
}