This source file includes following definitions.
- JSONString
- vista_
- stack_unwind_depth
- set_stack_unwind_depth
- log_heap
- set_log_heap
- log_lock
- set_log_lock
- vista
- set_vista
- options_
- AllocateInRemote
- CopyToRemote
- CopyFromRemote
- PatchPreamble
- PatchPreamble
- ResumeOriginalFunction
- AssembleQueryPerformanceCounter
- AssembleHeaderCode
- PatchLoader
- PatchCreateThread
- PatchThreadBegin
- PatchThreadBeginVista
- PatchSetThreadName
- PatchThreadExit
- PatchAllocateHeap
- PatchFreeHeap
- PatchSyscall
- PatchEnterCriticalSection
- PatchTryEnterCriticalSection
- PatchLeaveCriticalSection
- PatchApcDispatcher
- PatchExit
- Patch
- DumpJSON
- IntAt
- Int64At
- main
#include <windows.h>
#include <stdio.h>
#include <map>
#include <string>
#include "assembler.h"
#include "logging.h"
#include "rdtsc.h"
#include "sym_resolver.h"
#include "syscall_map.h"
#include "sidestep/mini_disassembler.h"
namespace {
std::string JSONString(const std::string& str) {
static const char hextable[] = "0123456789abcdef";
std::string out;
out.push_back('"');
for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) {
unsigned char c = static_cast<unsigned char>(*it);
switch (c) {
case '\\':
case '"':
case '\'':
out.push_back('\\'); out.push_back(c);
break;
default:
if (c < 20 || c >= 127) {
out.push_back('\\'); out.push_back('x');
out.push_back(hextable[c >> 4]); out.push_back(hextable[c & 0xf]);
} else {
out.push_back(c);
}
break;
}
}
out.push_back('"');
return out;
}
}
class Playground {
public:
static const int kPlaygroundSize = 64 * 1024 * 1024;
class Options {
public:
Options()
: stack_unwind_depth_(0),
log_heap_(false),
log_lock_(false),
vista_(false) { }
int stack_unwind_depth() { return stack_unwind_depth_; }
void set_stack_unwind_depth(int depth) { stack_unwind_depth_ = depth; }
bool log_heap() { return log_heap_; }
void set_log_heap(bool x) { log_heap_ = x; }
bool log_lock() { return log_lock_; }
void set_log_lock(bool x) { log_lock_ = x; }
bool vista() { return vista_; }
void set_vista(bool x) { vista_ = x; }
private:
int stack_unwind_depth_;
bool log_heap_;
bool log_lock_;
bool vista_;
};
Playground(HANDLE proc, const Options& options)
: proc_(proc),
remote_addr_(NULL),
resolver_("ntdll.dll"),
options_(options) {
memset(buf_, 0, sizeof(buf_));
}
void AllocateInRemote() {
static void* kPlaygroundAddr = reinterpret_cast<void*>(0x66660000);
remote_addr_ = reinterpret_cast<char*>(
VirtualAllocEx(proc_,
kPlaygroundAddr,
kPlaygroundSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE));
if (remote_addr_ == NULL || remote_addr_ != kPlaygroundAddr) {
NOTREACHED("Falied to allocate playground: 0x%08x", remote_addr_);
}
}
void CopyToRemote() {
WriteProcessMemory(proc_,
remote_addr_,
buf_,
sizeof(buf_),
NULL);
}
void CopyFromRemote() {
SIZE_T size = 0;
ReadProcessMemory(proc_,
remote_addr_,
buf_,
sizeof(buf_),
&size);
}
enum EventRecordType {
EVENT_TYPE_LDR = 0,
EVENT_TYPE_THREADBEGIN = 1,
EVENT_TYPE_THREADNAME = 2,
EVENT_TYPE_EXCEPTION = 3,
EVENT_TYPE_PROCESSEXIT = 4,
EVENT_TYPE_CREATETHREAD = 5,
EVENT_TYPE_THREADEXIT = 6,
EVENT_TYPE_ALLOCHEAP = 7,
EVENT_TYPE_FREEHEAP = 8,
EVENT_TYPE_SYSCALL = 9,
EVENT_TYPE_ENTER_CS = 10,
EVENT_TYPE_TRYENTER_CS = 11,
EVENT_TYPE_LEAVE_CS = 12,
EVENT_TYPE_APC = 13
};
static const int kThreadNameBufSize = 64;
static const int kLdrBufSize = 512;
static const int kCodeBlockSize = 256;
static const int kOffLdrCode = 0 * kCodeBlockSize;
static const int kOffCreateThreadCode = 1 * kCodeBlockSize;
static const int kOffThreadCode = 2 * kCodeBlockSize;
static const int kOffExpCode = 3 * kCodeBlockSize;
static const int kOffExitCode = 4 * kCodeBlockSize;
static const int kOffThreadExitCode = 5 * kCodeBlockSize;
static const int kOffAllocHeapCode = 6 * kCodeBlockSize;
static const int kOffFreeHeapCode = 7 * kCodeBlockSize;
static const int kOffSyscallCode = 8 * kCodeBlockSize;
static const int kOffEnterCritSecCode = 9 * kCodeBlockSize;
static const int kOffTryEnterCritSecCode = 10 * kCodeBlockSize;
static const int kOffLeaveCritSecCode = 11 * kCodeBlockSize;
static const int kOffApcDispCode = 12 * kCodeBlockSize;
static const int kOffLogAreaPtr = 4096;
static const int kOffLogAreaData = 4096 + 4;
static const int kRecordHeaderSize = 8 + 4 + 4 + 4;
std::string PatchPreamble(int func_addr, int playground_off) {
sidestep::MiniDisassembler disas;
int stub_addr = reinterpret_cast<int>(remote_addr_ + playground_off);
std::string instrs;
char buf[15];
if (ReadProcessMemory(proc_,
reinterpret_cast<void*>(func_addr - 5),
buf,
sizeof(buf),
NULL) == 0) {
NOTREACHED("ReadProcessMemory(0x%08x) failed: %d",
func_addr - 5, GetLastError());
}
if (memcmp(buf, "\x90\x90\x90\x90\x90", 5) == 0 ||
memcmp(buf, "\x00\x8D\x64\x24\x00", 5) == 0 ||
memcmp(buf, "\x00\x00\x8D\x49\x00", 5) == 0) {
unsigned int instr_bytes = 0;
while (instr_bytes < 2) {
if (disas.Disassemble(
reinterpret_cast<unsigned char*>(buf + 5 + instr_bytes),
&instr_bytes) != sidestep::IT_GENERIC) {
NOTREACHED("Could not disassemble or relocate instruction.");
}
CHECK(instr_bytes < 10);
}
instrs.assign(buf + 5, instr_bytes);
CodeBuffer patch(buf);
int off = stub_addr - func_addr;
patch.jmp_rel(off);
patch.jmp_rel_short(-2 - 5);
} else {
unsigned int instr_bytes = 0;
while (instr_bytes < 5) {
if (disas.Disassemble(
reinterpret_cast<unsigned char*>(buf + 5 + instr_bytes),
&instr_bytes) != sidestep::IT_GENERIC) {
NOTREACHED("Could not disassemble or relocate instruction.");
}
CHECK(instr_bytes < 10);
}
instrs.assign(buf + 5, instr_bytes);
CodeBuffer patch(buf + 5);
int off = stub_addr - (func_addr + 5);
patch.jmp_rel(off);
}
if (WriteProcessMemory(proc_,
reinterpret_cast<void*>(func_addr - 5),
buf,
sizeof(buf),
NULL) == 0) {
NOTREACHED("WriteProcessMemory(0x%08x) failed: %d",
func_addr - 5, GetLastError());
}
return instrs;
}
std::string PatchPreamble(const char* func_name, int playground_off) {
return PatchPreamble(
reinterpret_cast<int>(resolver_.Resolve(func_name)), playground_off);
}
void ResumeOriginalFunction(const char* func_name,
const std::string& moved_instructions,
int stub_offset,
CodeBuffer* cb) {
cb->emit_bytes(moved_instructions);
int off = resolver_.Resolve(func_name) +
moved_instructions.size() -
(remote_addr_ + stub_offset + cb->size() + 5);
cb->jmp_rel(off);
}
void AssembleQueryPerformanceCounter(CodeBuffer* cb) {
cb->push_imm(0);
cb->push(EDI);
cb->mov_imm(EAX, reinterpret_cast<int>(
resolver_.Resolve("ntdll!NtQueryPerformanceCounter")));
cb->call(EAX);
}
void AssembleHeaderCode(CodeBuffer* cb, EventRecordType rt, int space) {
cb->push(EAX);
cb->push(EDX);
cb->push(ECX);
cb->push(ESI);
int unwind_depth = options_.stack_unwind_depth();
cb->mov_imm(EDI, kRecordHeaderSize + (unwind_depth * 4) + space);
cb->mov_imm(EDX, reinterpret_cast<int>(remote_addr_ + kOffLogAreaPtr));
cb->inc_atomic(EDX, EDI);
cb->add_imm(EDI, reinterpret_cast<int>(remote_addr_ + kOffLogAreaData));
AssembleQueryPerformanceCounter(cb);
cb->add_imm(EDI, 8);
cb->which_cpu();
cb->stosd();
cb->which_thread();
cb->stosd();
if (unwind_depth > 0) {
cb->mov_imm(ECX, unwind_depth);
cb->fs(); cb->mov(EDX, Operand(0x04));
cb->mov(EAX, EBP);
Label unwind_loop, bail;
cb->bind(&unwind_loop);
cb->cmp(EAX, ESP);
cb->jcc(below, &bail);
cb->cmp(EAX, EDX);
cb->jcc(above_equal, &bail);
cb->mov(ESI, EAX);
cb->lodsd();
cb->movsd();
cb->loop(&unwind_loop);
cb->bind(&bail);
cb->mov_imm(EAX, 0);
cb->rep(); cb->stosd();
}
cb->mov_imm(EAX, rt);
cb->stosd();
cb->pop(ESI);
cb->pop(ECX);
cb->pop(EDX);
cb->pop(EAX);
}
void PatchLoader() {
static const EventRecordType kRecordType = EVENT_TYPE_LDR;
static const char* kFuncName = "ntdll!DebugPrint";
static const int kStubOffset = kOffLdrCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
char enabled = 1;
WriteProcessMemory(
proc_, resolver_.Resolve("ntdll!ShowSnaps"), &enabled, 1, NULL);
CodeBuffer cb(buf_ + kStubOffset);
cb.pop(EDX);
cb.pop(EAX);
cb.push(ESI);
cb.push(EDI);
cb.push(EDX);
cb.mov(ESI, EAX);
AssembleHeaderCode(&cb, kRecordType, kLdrBufSize);
cb.lodsd();
cb.lodsd();
cb.mov(ESI, EAX);
cb.mov_imm(ECX, kLdrBufSize / 4);
cb.rep(); cb.movsb();
cb.pop(EDX);
cb.pop(EDI);
cb.pop(ESI);
cb.pop(ECX);
cb.pop(ECX);
cb.jmp(EDX);
}
void PatchCreateThread() {
static const EventRecordType kRecordType = EVENT_TYPE_CREATETHREAD;
static const char* kFuncName =
options_.vista() ? "ntdll!NtCreateThreadEx" : "ntdll!NtCreateThread";
static const int kStubOffset = kOffCreateThreadCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
cb.push(ESI);
AssembleHeaderCode(&cb, kRecordType, 8);
cb.mov(EAX, Operand(ESP, 0x18 + 8));
cb.add_imm(EAX, 0xa0);
cb.push(EDI);
cb.mov(EDI, EAX);
cb.pop(EAX);
cb.push(EAX);
cb.stosd();
cb.mov(ESI, EDI);
cb.add_imm(ESI, 20);
cb.pop(EDI);
cb.mov(EAX, EDI);
cb.stosd();
cb.movsd();
cb.pop(ESI);
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchThreadBegin() {
static const EventRecordType kRecordType = EVENT_TYPE_THREADBEGIN;
static const char* kFuncName = "ntdll!CsrNewThread";
static const int kStubOffset = kOffThreadCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 8);
cb.mov(EAX, ESI);
cb.stosd();
cb.mov(EAX, Operand(EBP, 0x8));
cb.stosd();
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchThreadBeginVista() {
static const EventRecordType kRecordType = EVENT_TYPE_THREADBEGIN;
static const char* kFuncName = "ntdll!_RtlUserThreadStart";
static const int kStubOffset = kOffThreadCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 8);
cb.mov(EAX, ESI);
cb.stosd();
cb.mov_imm(EAX, 0);
cb.stosd();
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchSetThreadName() {
static const EventRecordType kRecordType = EVENT_TYPE_THREADNAME;
static const char* kFuncName = "ntdll!RtlDispatchException";
static const int kStubOffset = kOffExpCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.pop(EDX);
cb.pop(EAX);
cb.push(EAX);
cb.push(EDX);
cb.push(ESI);
cb.mov(ESI, EAX);
cb.lodsd();
Label bail;
cb.cmp_imm(EAX, 0x406D1388);
cb.jcc(not_equal, &bail);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, kThreadNameBufSize);
for (int i = 0; i < 6; ++i) {
cb.lodsd();
}
cb.mov(ESI, EAX);
cb.mov_imm(ECX, kThreadNameBufSize / 4);
cb.rep(); cb.movsd();
cb.pop(EDI);
cb.bind(&bail);
cb.pop(ESI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchThreadExit() {
static const EventRecordType kRecordType = EVENT_TYPE_THREADEXIT;
static const char* kFuncName = "ntdll!LdrShutdownThread";
static const int kStubOffset = kOffThreadExitCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 0);
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchAllocateHeap() {
static const EventRecordType kRecordType = EVENT_TYPE_ALLOCHEAP;
static const char* kFuncName = "ntdll!RtlAllocateHeap";
static const int kStubOffset = kOffAllocHeapCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
cb.push(ESI);
AssembleHeaderCode(&cb, kRecordType, 12);
cb.mov(ESI, ESP);
cb.add_imm(ESI, 12);
cb.movsd(); cb.movsd(); cb.movsd();
cb.pop(ESI);
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchFreeHeap() {
static const EventRecordType kRecordType = EVENT_TYPE_FREEHEAP;
static const char* kFuncName = "ntdll!RtlFreeHeap";
static const int kStubOffset = kOffFreeHeapCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
cb.push(ESI);
AssembleHeaderCode(&cb, kRecordType, 12);
cb.mov(ESI, ESP);
cb.add_imm(ESI, 12);
cb.movsd(); cb.movsd(); cb.movsd();
cb.pop(ESI);
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchSyscall() {
static const EventRecordType kRecordType = EVENT_TYPE_SYSCALL;
static const char* kFuncName = "ntdll!KiFastSystemCall";
static const int kStubOffset = kOffSyscallCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
{
CodeBuffer cb(buf_ + kStubOffset);
Label skip;
cb.emit(0x66); cb.emit(0x81); cb.emit(0x7C);
cb.emit(0x24); cb.emit(0x06); cb.emit(0x66); cb.emit(0x66);
cb.jcc(equal, &skip);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 16 + 16 + 8);
cb.mov(EDX, EAX);
cb.stosd();
cb.pop(EAX);
cb.stosd();
cb.pop(EAX);
cb.stosd();
cb.pop(EAX);
cb.stosd();
cb.push(ESI);
cb.mov(ESI, ESP);
cb.lodsd();
cb.movsd();
cb.movsd();
cb.movsd();
cb.pop(ESI);
cb.push(EDI);
cb.push_imm(reinterpret_cast<int>(remote_addr_ + kOffSyscallCode + 200));
cb.mov(EAX, EDX);
cb.bind(&skip);
cb.mov(EDX, ESP);
cb.sysenter();
if (cb.size() > 200) {
NOTREACHED("code too big: %d", cb.size());
}
}
{
CodeBuffer cb(buf_ + kStubOffset + 200);
cb.pop(EDI);
cb.stosd();
cb.push(EAX);
AssembleQueryPerformanceCounter(&cb);
cb.pop(EAX);
cb.push(Operand(EDI, -4 - 16));
cb.push(Operand(EDI, -8 - 16));
cb.push(Operand(EDI, -12 - 16));
cb.pop(EDI);
cb.ret();
if (cb.size() > 56) {
NOTREACHED("ug");
}
}
}
void PatchEnterCriticalSection() {
static const EventRecordType kRecordType = EVENT_TYPE_ENTER_CS;
static const char* kFuncName = "ntdll!RtlEnterCriticalSection";
static const int kStubOffset = kOffEnterCritSecCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
{
CodeBuffer cb(buf_ + kStubOffset);
cb.pop(EAX);
cb.pop(EDX);
cb.push(EDX);
cb.push(EAX);
cb.push(EDX);
cb.push_imm(
reinterpret_cast<int>(remote_addr_ + kStubOffset + 40));
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
CHECK(cb.size() < 40);
}
{
CodeBuffer cb(buf_ + kStubOffset + 40);
cb.push(ESI);
cb.mov(ESI, ESP);
cb.push(EAX);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 4);
cb.lodsd();
cb.lodsd();
cb.movsd();
cb.pop(EDI);
cb.pop(EAX);
cb.pop(ESI);
cb.ret(0x04);
}
}
void PatchTryEnterCriticalSection() {
static const EventRecordType kRecordType = EVENT_TYPE_TRYENTER_CS;
static const char* kFuncName = "ntdll!RtlTryEnterCriticalSection";
static const int kStubOffset = kOffTryEnterCritSecCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
{
CodeBuffer cb(buf_ + kStubOffset);
cb.pop(EAX);
cb.pop(EDX);
cb.push(EDX);
cb.push(EAX);
cb.push(EDX);
cb.push_imm(reinterpret_cast<int>(remote_addr_ + kStubOffset + 40));
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
CHECK(cb.size() < 40);
}
{
CodeBuffer cb(buf_ + kStubOffset + 40);
cb.push(ESI);
cb.mov(ESI, ESP);
cb.push(EDI);
cb.push(EAX);
AssembleHeaderCode(&cb, kRecordType, 8);
cb.lodsd();
cb.lodsd();
cb.movsd();
cb.pop(EAX);
cb.stosd();
cb.pop(EDI);
cb.pop(ESI);
cb.ret(0x04);
}
}
void PatchLeaveCriticalSection() {
static const EventRecordType kRecordType = EVENT_TYPE_LEAVE_CS;
static const char* kFuncName = "ntdll!RtlLeaveCriticalSection";
static const int kStubOffset = kOffLeaveCritSecCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.pop(EDX);
cb.pop(EAX);
cb.push(EAX);
cb.push(EDX);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 4);
cb.stosd();
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void PatchApcDispatcher() {
static const EventRecordType kRecordType = EVENT_TYPE_APC;
static const char* kFuncName = "ntdll!KiUserApcDispatcher";
static const int kStubOffset = kOffApcDispCode;
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
{
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
cb.push(EAX);
AssembleHeaderCode(&cb, kRecordType, 4 + 4 + 8);
cb.mov_imm(EAX, reinterpret_cast<int>(remote_addr_ + kStubOffset + 140));
cb.xchg(EAX, Operand(ESP, 8));
cb.stosd();
cb.mov(ESI, EDI);
cb.pop(EAX);
cb.pop(EDI);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
CHECK(cb.size() < 140);
}
{
CodeBuffer cb(buf_ + kStubOffset + 140);
cb.pop(EAX);
cb.push(EDI);
cb.mov(EDI, ESI);
cb.stosd();
cb.add_imm(ESI, -4);
cb.lodsd();
cb.mov(ESI, EDI);
cb.pop(EDI);
cb.call(EAX);
cb.push(EAX);
cb.push(EDI);
cb.mov(EDI, ESI);
AssembleQueryPerformanceCounter(&cb);
cb.pop(EDI);
cb.pop(EAX);
cb.push(Operand(ESI, -4));
cb.ret();
CHECK(cb.size() < 50);
}
}
void PatchExit(HANDLE exiting, HANDLE exited) {
static const EventRecordType kRecordType = EVENT_TYPE_PROCESSEXIT;
static const char* kFuncName = "ntdll!LdrShutdownProcess";
static const int kStubOffset = kOffExitCode;
HANDLE rexiting, rexited;
if (!DuplicateHandle(::GetCurrentProcess(),
exiting,
proc_,
&rexiting,
0,
FALSE,
DUPLICATE_SAME_ACCESS)) {
NOTREACHED("");
}
if (!DuplicateHandle(::GetCurrentProcess(),
exited,
proc_,
&rexited,
0,
FALSE,
DUPLICATE_SAME_ACCESS)) {
NOTREACHED("");
}
std::string moved_instructions = PatchPreamble(kFuncName, kStubOffset);
CodeBuffer cb(buf_ + kStubOffset);
cb.push(EDI);
AssembleHeaderCode(&cb, kRecordType, 0);
cb.pop(EDI);
cb.push_imm(0);
cb.push_imm(reinterpret_cast<int>(rexiting));
cb.mov_imm(EAX, reinterpret_cast<int>(
resolver_.Resolve("ntdll!NtSetEvent")));
cb.call(EAX);
cb.push_imm(0);
cb.push_imm(0);
cb.push_imm(reinterpret_cast<int>(rexited));
cb.mov_imm(EAX, reinterpret_cast<int>(
resolver_.Resolve("ntdll!NtWaitForSingleObject")));
cb.call(EAX);
ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
}
void Patch() {
if (options_.vista()) {
PatchThreadBeginVista();
} else {
PatchCreateThread();
PatchThreadBegin();
}
PatchThreadExit();
PatchSetThreadName();
PatchSyscall();
PatchApcDispatcher();
if (options_.log_heap()) {
PatchAllocateHeap();
PatchFreeHeap();
}
if (options_.log_lock()) {
PatchEnterCriticalSection();
PatchTryEnterCriticalSection();
PatchLeaveCriticalSection();
}
}
void DumpJSON(RDTSCNormalizer* rdn, SymResolver* res) {
int pos = kOffLogAreaPtr;
int i = IntAt(pos);
pos += 4;
std::map<int, const char*> syscalls = CreateSyscallMap();
printf("parseEvents([\n");
for (int end = pos + i; pos < end; ) {
printf("{\n");
__int64 ts = Int64At(pos);
pos += 8;
void* cpuid = reinterpret_cast<void*>(IntAt(pos));
pos += 4;
printf("'ms': %f,\n", rdn->MsFromStart(cpuid, ts));
printf("'cpu': 0x%x,\n'thread': 0x%x,\n", cpuid, IntAt(pos));
pos += 4;
if (options_.stack_unwind_depth() > 0) {
printf("'stacktrace': [\n");
for (int i = 0; i < options_.stack_unwind_depth(); ++i) {
int retaddr = IntAt(pos + (i * 4));
if (!retaddr)
break;
printf(" [ 0x%x, %s ],\n",
retaddr,
res ? JSONString(res->Unresolve(retaddr)).c_str() : "\"\"");
}
printf("],\n");
pos += (options_.stack_unwind_depth() * 4);
}
EventRecordType rt = static_cast<EventRecordType>(IntAt(pos));
pos += 4;
switch (rt) {
case EVENT_TYPE_LDR:
{
printf("'eventtype': 'EVENT_TYPE_LDR',\n");
std::string str(&buf_[pos], kLdrBufSize);
str = str.substr(0, str.find('\0'));
printf("'ldrinfo': %s,\n", JSONString(str).c_str());
pos += kLdrBufSize;
break;
}
case EVENT_TYPE_CREATETHREAD:
{
printf("'eventtype': 'EVENT_TYPE_CREATETHREAD',\n"
"'eventid': 0x%x,\n"
"'startaddr': 0x%x,\n",
IntAt(pos), IntAt(pos+4));
pos += 8;
break;
}
case EVENT_TYPE_THREADBEGIN:
{
printf("'eventtype': 'EVENT_TYPE_THREADBEGIN',\n"
"'parenteventid': 0x%x,\n"
"'startaddr': 0x%x,\n",
IntAt(pos), IntAt(pos+4));
pos += 8;
break;
}
case EVENT_TYPE_THREADNAME:
{
std::string str(&buf_[pos], kThreadNameBufSize);
str = str.substr(0, str.find('\0'));
printf("'eventtype': 'EVENT_TYPE_THREADNAME',\n"
"'threadname': %s,\n",
JSONString(str).c_str());
pos += kThreadNameBufSize;
break;
}
case EVENT_TYPE_PROCESSEXIT:
{
printf("'eventtype': 'EVENT_TYPE_PROCESSEXIT',\n");
break;
}
case EVENT_TYPE_THREADEXIT:
{
printf("'eventtype': 'EVENT_TYPE_THREADEXIT',\n");
break;
}
case EVENT_TYPE_ALLOCHEAP:
{
printf("'eventtype': 'EVENT_TYPE_ALLOCHEAP',\n"
"'heaphandle': 0x%x,\n"
"'heapflags': 0x%x,\n"
"'heapsize': %d,\n",
IntAt(pos), IntAt(pos+4), IntAt(pos+8));
pos += 12;
break;
}
case EVENT_TYPE_FREEHEAP:
{
printf("'eventtype': 'EVENT_TYPE_FREEHEAP',\n"
"'heaphandle': 0x%x,\n"
"'heapflags': 0x%x,\n"
"'heapptr': %d,\n",
IntAt(pos), IntAt(pos+4), IntAt(pos+8));
pos += 12;
break;
}
case EVENT_TYPE_SYSCALL:
{
int syscall = IntAt(pos);
printf("'eventtype': 'EVENT_TYPE_SYSCALL',\n"
"'syscall': 0x%x,\n", syscall);
pos += 16;
printf("'syscallargs': [\n");
for (int i = 0; i < 3; ++i) {
printf(" 0x%x,\n", IntAt(pos));
pos += 4;
}
printf("],\n");
printf("'retval': 0x%x,\n"
"'done': %f,\n",
IntAt(pos), rdn->MsFromStart(0, Int64At(pos+4)));
pos += 12;
if (syscalls.count(syscall) == 1) {
std::string sname = syscalls[syscall];
printf("'syscallname': %s,\n",
JSONString(sname).c_str());
if (sname.find("WaitFor") != std::string::npos ||
sname.find("RemoveIoCompletion") != std::string::npos) {
printf("'waiting': 1,\n");
}
}
break;
}
case EVENT_TYPE_ENTER_CS:
{
printf("'eventtype': 'EVENT_TYPE_ENTER_CS',\n"
"'critical_section': 0x%x,\n", IntAt(pos));
pos += 4;
break;
}
case EVENT_TYPE_TRYENTER_CS:
{
printf("'eventtype': 'EVENT_TYPE_TRYENTER_CS',\n"
"'critical_section': 0x%x,\n"
"'retval': 0x%x,\n",
IntAt(pos), IntAt(pos+4));
pos += 8;
break;
}
case EVENT_TYPE_LEAVE_CS:
{
printf("'eventtype': 'EVENT_TYPE_LEAVE_CS',\n"
"'critical_section': 0x%x,\n", IntAt(pos));
pos += 4;
break;
}
case EVENT_TYPE_APC:
{
int func_addr = IntAt(pos);
printf("'eventtype': 'EVENT_TYPE_APC',\n"
"'func_addr': 0x%x,\n"
"'func_addr_name': %s,\n"
"'ret_addr': 0x%x,\n"
"'done': %f,\n",
func_addr,
res ? JSONString(res->Unresolve(func_addr)).c_str() : "\"\"",
IntAt(pos+4), rdn->MsFromStart(0, Int64At(pos+8)));
pos += 16;
break;
}
default:
NOTREACHED("Unknown event type: %d", rt);
break;
}
printf("},\n");
}
printf("]);");
}
int IntAt(int pos) { return *reinterpret_cast<int*>(&buf_[pos]); }
__int64 Int64At(int pos) { return *reinterpret_cast<__int64*>(&buf_[pos]); }
private:
HANDLE proc_;
char* remote_addr_;
SymResolver resolver_;
Options options_;
char buf_[kPlaygroundSize];
};
int main(int argc, char** argv) {
std::string command_line;
bool use_symbols = false;
bool attaching = false;
bool launched = false;
bool manual_quit = false;
Playground::Options options;
PROCESS_INFORMATION info = {0};
argc--; argv++;
while (argc > 0) {
if (std::string("--symbols") == argv[0]) {
use_symbols = true;
} else if (std::string("--vista") == argv[0]) {
options.set_vista(true);
} else if (std::string("--log-heap") == argv[0]) {
options.set_log_heap(true);
} else if (std::string("--log-lock") == argv[0]) {
options.set_log_lock(true);
} else if (std::string("--manual-quit") == argv[0]) {
manual_quit = true;
} else if (argc >= 2 && std::string("--unwind") == argv[0]) {
options.set_stack_unwind_depth(atoi(argv[1]));
argc--; argv++;
} else if (argc >= 2 && !launched && std::string("--attach") == argv[0]) {
attaching = true;
info.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, atoi(argv[1]));
launched = true;
argc--; argv++;
} else if (!launched) {
STARTUPINFOA start_info = {0};
start_info.cb = sizeof(start_info);
if (!CreateProcessA(NULL,
argv[0],
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&start_info,
&info)) {
NOTREACHED("Failed to launch \"%s\": %d\n", argv[0], GetLastError());
return 1;
}
launched = true;
} else {
NOTREACHED("error parsing command line.");
}
argc--; argv++;
}
if (!launched) {
printf("usage: traceline.exe \"app.exe my arguments\"\n"
" --attach 123: buggy support for attaching to a process\n"
" --unwind 16: unwind the stack to the specified max depth\n"
" --symbols: use symbols for stacktraces\n"
" --log-heap: log heap operations (alloc / free).\n"
" --log-lock: log lock (critical section) operations.\n");
return 1;
}
HANDLE exiting = CreateEvent(NULL, FALSE, FALSE, NULL);
HANDLE exited = CreateEvent(NULL, FALSE, FALSE, NULL);
Playground* pg = new Playground(info.hProcess, options);
pg->AllocateInRemote();
pg->Patch();
pg->PatchExit(exiting, exited);
pg->CopyToRemote();
RDTSCNormalizer rdn;
rdn.Start();
if (!attaching)
ResumeThread(info.hThread);
if (manual_quit) {
fprintf(stderr, "Press enter when you want stop tracing and collect.\n");
fflush(stderr);
getchar();
} else {
HANDLE whs[] = {exiting, info.hProcess};
if (WaitForMultipleObjects(2, whs, FALSE, INFINITE) != WAIT_OBJECT_0) {
NOTREACHED("Failed to correctly capture process shutdown.");
}
}
pg->CopyFromRemote();
if (use_symbols) {
SymResolver res(NULL, info.hProcess);
pg->DumpJSON(&rdn, &res);
} else {
pg->DumpJSON(&rdn, NULL);
}
SetEvent(exited);
CloseHandle(info.hProcess);
CloseHandle(info.hThread);
delete pg;
}