blob: f793ca5bd090451254a4257680ec7df767ba01f9 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2003-2008 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* winsysnums.c: analyzes a dll's exported routines, looking for system call
* numbers or Ki* routines -- typically pointed at a new ntdll.dll
*
* now additionally uses drsyms to analyze all symbols and thus locate
* system call wrappers that are not exported.
*/
/* Uses the DR CLIENT_INTERFACE API, using DR as a standalone library, rather than
* being a client library working with DR on a target program.
*
* To build, use cmake. Build as 64-bit (no reason to build a 32-bit version
* as it won't be able to analyze 64-bit dlls, while a 64-bit build can analyze
* 32-bit dlls).
* Something like:
* % x64
* % mkdir ~/dr/git/build_winsysnums
* % cd !$
* % cmake -DDynamoRIO_DIR=e:/src/dr/git/exports/cmake ../src/clients/standalone
* % cmake --build .
*
* To run, you need to put dynamorio.dll, drsyms.dll, and dbghelp.dll into the
* same directory as winsysnums.exe. (If you build drsyms statically you don't
* need to copy it of course.)
*/
#include "dr_api.h"
#include "drsyms.h"
#include <assert.h>
#include <imagehlp.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef UNIX
# define EXPORT
#else
# define EXPORT __declspec(dllexport)
#endif
#define BUFFER_SIZE_BYTES(buf) sizeof(buf)
#define BUFFER_SIZE_ELEMENTS(buf) (BUFFER_SIZE_BYTES(buf) / sizeof((buf)[0]))
#define BUFFER_LAST_ELEMENT(buf) (buf)[BUFFER_SIZE_ELEMENTS(buf) - 1]
#define NULL_TERMINATE_BUFFER(buf) BUFFER_LAST_ELEMENT(buf) = 0
/* global params */
static bool expect_int2e = false;
static bool expect_sysenter = false;
static bool expect_wow = false;
static bool expect_x64 = false;
static bool verbose = false;
static bool list_exports = false;
static bool list_forwards = false;
static bool list_Ki = false;
static bool list_syscalls = false;
static bool ignore_Zw = false;
static void
common_print(char *fmt, va_list ap)
{
vfprintf(stdout, fmt, ap);
fflush(stdout);
}
static void
verbose_print(char *fmt, ...)
{
if (verbose) {
va_list ap;
va_start(ap, fmt);
common_print(fmt, ap);
va_end(ap);
}
}
static void
print(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
common_print(fmt, ap);
va_end(ap);
}
/* We expect the win8 x86 sysenter adjacent "inlined" callee to be as simple as
* 75caeabc 8bd4 mov edx,esp
* 75caeabe 0f34 sysenter
* 75caeac0 c3 ret
*/
#define MAX_INSTRS_SYSENTER_CALLEE 4
/* the max distance from call to the sysenter callee target */
#define MAX_SYSENTER_CALLEE_OFFSET 0x50
#define MAX_INSTRS_BEFORE_SYSCALL 16
#define MAX_INSTRS_IN_FUNCTION 256
typedef struct {
int sysnum;
int num_args;
int fixup_index; /* WOW dlls only */
} syscall_info_t;
/* returns false on failure */
static bool
decode_function(void *dcontext, byte *entry)
{
byte *pc;
int num_instr = 0;
bool found_ret = false;
instr_t *instr;
if (entry == NULL)
return false;
instr = instr_create(dcontext);
pc = entry;
while (true) {
instr_reset(dcontext, instr);
pc = decode(dcontext, pc, instr);
dr_print_instr(dcontext, STDOUT, instr, "");
if (instr_is_return(instr)) {
found_ret = true;
break;
}
num_instr++;
if (num_instr > MAX_INSTRS_IN_FUNCTION) {
print("ERROR: hit max instr limit %d\n", MAX_INSTRS_IN_FUNCTION);
break;
}
}
instr_destroy(dcontext, instr);
return found_ret;
}
static void
check_Ki(const char *name)
{
/* FIXME: eventually we should automatically analyze these, but
* not worth the time at this point. Once we have automatic
* analysis code, should put it into DR debug build init too! For
* now we issue manual instructions about verifying our
* assumptions, and look for unknown Ki routines.
*/
if (strcmp(name, "KiUserApcDispatcher") == 0) {
print("verify that:\n"
"\t1) *esp == call* target (not relied on)\n"
"\t2) *(esp+16) == CONTEXT\n");
} else if (strcmp(name, "KiUserExceptionDispatcher") == 0) {
print("verify that:\n"
"\t1) *esp = EXCEPTION_RECORD*\n"
"\t2) *(esp+4) == CONTEXT*\n");
} else if (strcmp(name, "KiRaiseUserExceptionDispatcher") == 0) {
print("we've never seen this guy invoked\n");
} else if (strcmp(name, "KiUserCallbackDispatcher") == 0) {
print("verify that:\n"
"\t1) peb->KernelCallbackTable[*(esp+4)] == call* target (not relied on)\n");
} else if (strcmp(name, "KiFastSystemCall") == 0) {
print("should be simply \"mov esp,edx; sysenter; ret\"\n");
} else if (strcmp(name, "KiFastSystemCallRet") == 0) {
print("should be simply \"ret\"\n");
} else if (strcmp(name, "KiIntSystemCall") == 0) {
print("should be simply \"lea 0x8(esp),edx; int 2e; ret\"\n");
} else {
print("WARNING! UNKNOWN Ki ROUTINE!\n");
}
}
static void
process_ret(instr_t *instr, syscall_info_t *info)
{
assert(instr_is_return(instr));
if (opnd_is_immed_int(instr_get_src(instr, 0)))
info->num_args = (int) opnd_get_immed_int(instr_get_src(instr, 0));
else
info->num_args = 0;
}
/* returns whether found a syscall
* - found_eax: whether the caller has seen "mov imm => %eax"
* - found_edx: whether the caller has seen "mov $0x7ffe0300 => %edx",
* xref the comment below about "mov $0x7ffe0300 => %edx".
*/
static bool
process_syscall_instr(void *dcontext, instr_t *instr, bool found_eax, bool found_edx)
{
/* ASSUMPTION: a mov imm of 0x7ffe0300 into edx followed by an
* indirect call via edx is a system call on XP and later
* On XP SP1 it's call *edx, while on XP SP2 it's call *(edx)
* For wow it's a call through fs.
* FIXME - core exports various is_*_syscall routines (such as
* instr_is_wow64_syscall()) which we could use here instead of
* duplicating if they were more flexible about when they could
* be called (instr_is_wow64_syscall() for ex. asserts if not
* in a wow process).
*/
if (/* int 2e or x64 or win8 sysenter */
(instr_is_syscall(instr) &&
found_eax && (expect_int2e || expect_x64 || expect_sysenter)) ||
/* sysenter case */
(expect_sysenter && found_edx && found_eax &&
instr_is_call_indirect(instr) &&
/* XP SP{0,1}, 2003 SP0: call *edx */
((opnd_is_reg(instr_get_target(instr)) &&
opnd_get_reg(instr_get_target(instr)) == REG_EDX) ||
/* XP SP2, 2003 SP1: call *(edx) */
(opnd_is_base_disp(instr_get_target(instr)) &&
opnd_get_base(instr_get_target(instr)) == REG_EDX &&
opnd_get_index(instr_get_target(instr)) == REG_NULL &&
opnd_get_disp(instr_get_target(instr)) == 0))) ||
/* wow case
* we don't require found_ecx b/c win8 does not use ecx
*/
(expect_wow && found_eax &&
instr_is_call_indirect(instr) &&
opnd_is_far_base_disp(instr_get_target(instr)) &&
opnd_get_base(instr_get_target(instr)) == REG_NULL &&
opnd_get_index(instr_get_target(instr)) == REG_NULL &&
opnd_get_segment(instr_get_target(instr)) == SEG_FS))
return true;
return false;
}
/* returns whether found a syscall
* - found_eax: whether the caller has seen "mov imm => %eax"
* - found_edx: whether the caller has seen "mov $0x7ffe0300 => %edx",
* xref the comment in process_syscall_instr.
*/
static bool
process_syscall_call(void *dcontext, byte *next_pc, instr_t *call,
bool found_eax, bool found_edx)
{
int num_instr;
byte *pc;
instr_t instr;
bool found_syscall = false;
assert(instr_get_opcode(call) == OP_call && opnd_is_pc(instr_get_target(call)));
pc = opnd_get_pc(instr_get_target(call));
if (pc > next_pc + MAX_SYSENTER_CALLEE_OFFSET ||
pc <= next_pc /* assuming the call won't go backward */)
return false;
/* handle win8 x86 which has sysenter callee adjacent-"inlined"
* ntdll!NtYieldExecution:
* 77d7422c b801000000 mov eax,1
* 77d74231 e801000000 call ntdll!NtYieldExecution+0xb (77d74237)
* 77d74236 c3 ret
* 77d74237 8bd4 mov edx,esp
* 77d74239 0f34 sysenter
* 77d7423b c3 ret
*
* or DrMem-i#1366-c#2
* USER32!NtUserCreateWindowStation:
* 75caea7a b841110000 mov eax,0x1141
* 75caea7f e838000000 call user32!...+0xd (75caeabc)
* 75caea84 c22000 ret 0x20
* ...
* USER32!GetWindowStationName:
* 75caea8c 8bff mov edi,edi
* 75caea8e 55 push ebp
* ...
* 75caeabc 8bd4 mov edx,esp
* 75caeabe 0f34 sysenter
* 75caeac0 c3 ret
*/
/* We expect the win8 x86 sysenter adjacent "inlined" callee to be as simple as
* 75caeabc 8bd4 mov edx,esp
* 75caeabe 0f34 sysenter
* 75caeac0 c3 ret
*/
instr_init(dcontext, &instr);
num_instr = 0;
do {
instr_reset(dcontext, &instr);
pc = decode(dcontext, pc, &instr);
if (verbose)
dr_print_instr(dcontext, STDOUT, &instr, "");
if (pc == NULL || !instr_valid(&instr))
break;
if (instr_is_syscall(&instr) || instr_is_call_indirect(&instr)) {
found_syscall = process_syscall_instr(dcontext, &instr, found_eax, found_edx);
break;
} else if (instr_is_cti(&instr)) {
break;
}
num_instr++;
} while (num_instr <= MAX_INSTRS_SYSENTER_CALLEE);
instr_free(dcontext, &instr);
return found_syscall;
}
/* returns false on failure */
static bool
decode_syscall_num(void *dcontext, byte *entry, syscall_info_t *info)
{
/* FIXME: would like to fail gracefully rather than have a DR assertion
* on non-code! => use DEBUG=0 INTERNAL=1 DR build!
*/
bool found_syscall = false, found_eax = false, found_edx = false, found_ecx = false;
bool found_ret = false;
byte *pc;
int num_instr = 0;
instr_t *instr;
if (entry == NULL)
return false;
info->num_args = -1; /* if find sysnum but not args */
info->sysnum = -1;
info->fixup_index = -1;
instr = instr_create(dcontext);
pc = entry;
/* FIXME - we don't support decoding 64bit instructions in 32bit mode, but I want
* this to work on 32bit machines. Hack fix based on the wrapper pattern, we skip
* the first instruction (mov r10, rcx) here, the rest should decode ok.
* Xref PR 236203. */
if (expect_x64 && *pc == 0x4c && *(pc+1) == 0x8b && *(pc+2) == 0xd1)
pc += 3;
while (true) {
instr_reset(dcontext, instr);
pc = decode(dcontext, pc, instr);
if (verbose)
dr_print_instr(dcontext, STDOUT, instr, "");
if (pc == NULL || !instr_valid(instr))
break;
if (instr_is_syscall(instr) || instr_is_call_indirect(instr)) {
/* If we see a syscall instr or an indirect call which is not syscall,
* we assume this is not a syscall wrapper.
*/
found_syscall = process_syscall_instr(dcontext, instr, found_eax, found_edx);
if (!found_syscall)
break; /* assume not a syscall wrapper, give up gracefully */
} else if (instr_is_return(instr)) {
/* we must break on return to avoid case like win8 x86
* which has sysenter callee adjacent-"inlined"
* ntdll!NtYieldExecution:
* 77d7422c b801000000 mov eax,1
* 77d74231 e801000000 call ntdll!NtYieldExecution+0xb (77d74237)
* 77d74236 c3 ret
* 77d74237 8bd4 mov edx,esp
* 77d74239 0f34 sysenter
* 77d7423b c3 ret
*/
if (!found_ret) {
process_ret(instr, info);
found_ret = true;
}
break;
} else if (instr_get_opcode(instr) == OP_call) {
found_syscall = process_syscall_call(dcontext, pc, instr,
found_eax, found_edx);
/* If we see a call and it is not a sysenter callee,
* we assume this is not a syscall wrapper.
*/
if (!found_syscall)
break; /* assume not a syscall wrapper, give up gracefully */
} else if (instr_is_cti(instr)) {
/* We expect only ctis like ret or ret imm, syscall, and call, which are
* handled above. Give up gracefully if we hit any other cti.
* XXX: what about jmp to shared ret (seen in the past on some syscalls)?
*/
break;
} else if ((!found_eax || !found_edx || !found_ecx) &&
instr_get_opcode(instr) == OP_mov_imm &&
opnd_is_reg(instr_get_dst(instr, 0))) {
if (!found_eax && opnd_get_reg(instr_get_dst(instr, 0)) == REG_EAX) {
info->sysnum = (int) opnd_get_immed_int(instr_get_src(instr, 0));
found_eax = true;
} else if (!found_edx && opnd_get_reg(instr_get_dst(instr, 0)) == REG_EDX) {
int imm = (int) opnd_get_immed_int(instr_get_src(instr, 0));
if (imm == 0x7ffe0300)
found_edx = true;
} else if (!found_ecx && opnd_get_reg(instr_get_dst(instr, 0)) == REG_ECX) {
found_ecx = true;
info->fixup_index = (int) opnd_get_immed_int(instr_get_src(instr, 0));
}
} else if (instr_get_opcode(instr) == OP_xor &&
opnd_is_reg(instr_get_src(instr, 0)) &&
opnd_get_reg(instr_get_src(instr, 0)) == REG_ECX &&
opnd_is_reg(instr_get_dst(instr, 0)) &&
opnd_get_reg(instr_get_dst(instr, 0)) == REG_ECX) {
/* xor to 0 */
found_ecx = true;
info->fixup_index = 0;
}
num_instr++;
if (num_instr > MAX_INSTRS_BEFORE_SYSCALL) /* wrappers should be short! */
break; /* avoid weird cases like NPXEMULATORTABLE */
}
instr_destroy(dcontext, instr);
return found_syscall;
}
static void
process_syscall_wrapper(void *dcontext, byte *addr, const char *string,
const char *type)
{
syscall_info_t sysinfo;
if (ignore_Zw && string[0] == 'Z' && string[1] == 'w')
return;
if (decode_syscall_num(dcontext, addr, &sysinfo)) {
if (sysinfo.sysnum == -1) {
/* we expect this sometimes */
if (strcmp(string, "KiFastSystemCall") != 0 &&
strcmp(string, "KiIntSystemCall") != 0) {
print("ERROR: unknown syscall #: %s\n", string);
}
} else {
/* be sure to print all digits b/c win8 now uses the top 16 bits for wow64 */
if (expect_wow) {
print("syscall # 0x%08x %-6s %2d args fixup 0x%02x = %s\n",
sysinfo.sysnum, type, sysinfo.num_args,
sysinfo.fixup_index, string);
} else if (expect_x64) {
print("syscall # 0x%08x %-6s = %s\n",
sysinfo.sysnum, type, string);
} else {
print("syscall # 0x%08x %-6s %2d args = %s\n",
sysinfo.sysnum, type, sysinfo.num_args, string);
}
}
}
}
typedef struct _search_data_t {
void *dcontext;
LOADED_IMAGE *img;
} search_data_t;
/* not only do we have NtUser*, NtWow64*, etc., but also user32!UserContectToServer,
* so we go through all symbols
*/
#define SYM_PATTERN "*"
static bool
search_syms_cb(const char *name, size_t modoffs, void *data)
{
search_data_t *sd = (search_data_t *) data;
byte *addr = ImageRvaToVa(sd->img->FileHeader, sd->img->MappedAddress,
(ULONG) modoffs, NULL);
verbose_print("Found symbol \"%s\" at offs "PIFX" => "PFX"\n", name, modoffs, addr);
process_syscall_wrapper(sd->dcontext, addr, name, "pdb");
return true; /* keep iterating */
}
static void
process_symbols(void *dcontext, char *dllname, LOADED_IMAGE *img)
{
/* We have to specify the module via "modname!symname".
* We must use the same modname as in full_path.
*/
char fullpath[MAX_PATH];
# define MAX_SYM_WITH_MOD_LEN 256
char sym_with_mod[MAX_SYM_WITH_MOD_LEN];
int len;
drsym_error_t symres;
char *fname = NULL, *c;
search_data_t sd;
if (drsym_init(NULL) != DRSYM_SUCCESS) {
print("WARNING: unable to initialize symbol engine\n");
return;
}
if (dllname == NULL)
return;
fname = dllname;
for (c = dllname; *c != '\0'; c++) {
if (*c == '/' || *c == '\\')
fname = c + 1;
}
assert(fname != NULL && "unable to get fname for module");
if (fname == NULL)
return;
/* now get rid of extension */
for (; c > fname && *c != '.'; c--)
; /* nothing */
assert(c > fname && "file has no extension");
assert(c - fname < BUFFER_SIZE_ELEMENTS(sym_with_mod) && "sizes way off");
len = dr_snprintf(sym_with_mod, BUFFER_SIZE_ELEMENTS(sym_with_mod), "%.*s!%s",
c - fname, fname, SYM_PATTERN);
assert(len > 0 && "error printing modname!symname");
NULL_TERMINATE_BUFFER(sym_with_mod);
len = GetFullPathName(dllname, BUFFER_SIZE_ELEMENTS(fullpath), fullpath, NULL);
assert(len > 0);
NULL_TERMINATE_BUFFER(dllname);
sd.dcontext = dcontext;
sd.img = img;
verbose_print("Searching \"%s\" for \"%s\"\n", fullpath, sym_with_mod);
symres = drsym_search_symbols(fullpath, sym_with_mod, true, search_syms_cb, &sd);
if (symres != DRSYM_SUCCESS)
print("Error %d searching \"%s\" for \"%s\"\n", symres, fullpath, sym_with_mod);
drsym_exit();
}
static void
process_exports(void *dcontext, char *dllname)
{
LOADED_IMAGE img;
IMAGE_EXPORT_DIRECTORY *dir;
IMAGE_SECTION_HEADER *sec;
DWORD *name, *code;
WORD *ordinal;
const char *string;
ULONG size;
BOOL res;
uint i;
byte *addr, *start_exports, *end_exports;
verbose_print("Processing exports of \"%s\"\n", dllname);
res = MapAndLoad(dllname, NULL, &img, FALSE, TRUE);
if (!res) {
print("Error loading %s\n", dllname);
return;
}
dir = (IMAGE_EXPORT_DIRECTORY *)
ImageDirectoryEntryToData(img.MappedAddress, FALSE,
IMAGE_DIRECTORY_ENTRY_EXPORT, &size);
verbose_print("mapped at 0x%08x, exports is at 0x%08x, size is 0x%x\n",
img.MappedAddress, dir, size);
start_exports = (byte *) dir;
end_exports = start_exports + size;
verbose_print("name=%s, ord base=0x%08x, names=%d 0x%08x\n",
(char *) ImageRvaToVa(img.FileHeader, img.MappedAddress,
dir->Name, NULL),
dir->Base, dir->NumberOfNames, dir->AddressOfNames);
/* don't limit functions to lie in .text --
* for ntdll, some exported routines have their code after .text, inside
* ECODE section!
*/
sec = img.Sections;
for (i = 0; i < img.NumberOfSections; i++) {
verbose_print("Section %d %s: 0x%x + 0x%x == 0x%08x through 0x%08x\n",
i, sec->Name, sec->VirtualAddress, sec->SizeOfRawData,
ImageRvaToVa(img.FileHeader, img.MappedAddress,
sec->VirtualAddress, NULL),
(uint) ImageRvaToVa(img.FileHeader, img.MappedAddress,
sec->VirtualAddress, NULL) +
sec->SizeOfRawData);
sec++;
}
name = (DWORD *) ImageRvaToVa(img.FileHeader, img.MappedAddress,
dir->AddressOfNames, NULL);
code = (DWORD *) ImageRvaToVa(img.FileHeader, img.MappedAddress,
dir->AddressOfFunctions, NULL);
ordinal = (WORD *) ImageRvaToVa(img.FileHeader, img.MappedAddress,
dir->AddressOfNameOrdinals, NULL);
verbose_print("names: from 0x%08x to 0x%08x\n",
ImageRvaToVa(img.FileHeader, img.MappedAddress, name[0], NULL),
ImageRvaToVa(img.FileHeader, img.MappedAddress,
name[dir->NumberOfNames-1], NULL));
for (i = 0; i < dir->NumberOfNames; i++) {
string = (char *) ImageRvaToVa(img.FileHeader, img.MappedAddress, name[i], NULL);
/* ordinal is biased (dir->Base), but don't add base when using as index */
assert(dir->NumberOfFunctions > ordinal[i]);
/* I don't understand why have to do RVA to VA here, when dumpbin /exports
* seems to give the same offsets but by simply adding them to base we
* get the appropriate code location -- but that doesn't work here...
*/
addr = ImageRvaToVa(img.FileHeader, img.MappedAddress,
code[ordinal[i]], NULL);
verbose_print("name=%s 0x%08x, ord=%d, code=0x%x -> 0x%08x\n",
string, string, ordinal[i], code[ordinal[i]], addr);
if (list_exports) {
print("ord %3d offs 0x%08x %s\n", ordinal[i],
addr - img.MappedAddress, string);
}
if (list_Ki && string[0] == 'K' && string[1] == 'i') {
print("\n==================================================\n");
print("%s\n\n", string);
check_Ki(string);
print("\ndisassembly:\n");
decode_function(dcontext, addr);
print( "==================================================\n");
}
/* forwarded export points inside exports section */
if (addr >= start_exports && addr < end_exports) {
if (list_forwards || verbose) {
/* I've had issues w/ forwards before, so avoid printing crap */
if (addr[0] > 0 && addr[0] < 127)
print("%s is forwarded to %.128s\n", string, addr);
else
print("ERROR identifying forwarded entry for %s\n", string);
}
} else if (list_syscalls) {
process_syscall_wrapper(dcontext, addr, string, "export");
}
}
if (list_syscalls)
process_symbols(dcontext, dllname, &img);
UnMapAndLoad(&img);
}
static void
usage(char *pgm)
{
print("Usage: %s [-syscalls <-sysenter | -int2e | -wow | -x64> [-ignore_Zw]] | "
"-Ki | -exports | -forwards | -v] <dll>\n", pgm);
exit(-1);
}
int
main(int argc, char *argv[])
{
void *dcontext = dr_standalone_init();
int res;
char *dll;
bool forced = false;
set_isa_mode(dcontext, DR_ISA_IA32);
for (res=1; res < argc; res++) {
if (strcmp(argv[res], "-sysenter") == 0) {
expect_sysenter = true;
forced = true;
} else if (strcmp(argv[res], "-int2e") == 0) {
expect_int2e = true;
forced = true;
} else if (strcmp(argv[res], "-wow") == 0) {
expect_wow = true;
forced = true;
} else if (strcmp(argv[res], "-x64") == 0) {
expect_x64 = true;
#ifdef X64
set_isa_mode(dcontext, DR_ISA_AMD64);
#else
/* For 32-bit builds we hack a fix for -syscalls (see
* decode_syscall_num()) but -Ki won't work.
*/
#endif
forced = true;
} else if (strcmp(argv[res], "-v") == 0) {
verbose = true;
} else if (strcmp(argv[res], "-exports") == 0) {
list_exports = true;
list_forwards = true; /* implied */
} else if (strcmp(argv[res], "-forwards") == 0) {
list_forwards = true;
} else if (strcmp(argv[res], "-Ki") == 0) {
list_Ki = true;
} else if (strcmp(argv[res], "-syscalls") == 0) {
list_syscalls = true;
} else if (strcmp(argv[res], "-ignore_Zw") == 0) {
ignore_Zw = true;
} else if (argv[res][0] == '-') {
usage(argv[0]);
assert(false); /* not reached */
} else {
break;
}
}
if (res >= argc ||
(!list_syscalls && !list_Ki && !list_forwards && !verbose)) {
usage(argv[0]);
assert(false); /* not reached */
}
dll = argv[res];
if (!forced && list_syscalls) {
usage(argv[0]);
assert(false); /* not reached */
}
process_exports(dcontext, dll);
return 0;
}