| Index: tools/traceline/traceline/main.cc
|
| diff --git a/tools/traceline/traceline/main.cc b/tools/traceline/traceline/main.cc
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..d0e220bdc17f8578a0bcba0476c015ab32d02ef4
|
| --- /dev/null
|
| +++ b/tools/traceline/traceline/main.cc
|
| @@ -0,0 +1,1335 @@
|
| +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// TODO
|
| +// - Make capturing system call arguments optional and the number configurable.
|
| +// - Lots of places depend on the ABI so that we can modify EAX or EDX, this
|
| +// is safe, but these could be moved to be saved and restored anyway.
|
| +// - Understand the loader better, and make some more meaningful hooks with
|
| +// proper data collection and durations. Right now it's just noise.
|
| +// - Get the returned pointer from AllocateHeap.
|
| +
|
| +#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 {
|
| + // Unescaped.
|
| + out.push_back(c);
|
| + }
|
| + break;
|
| + }
|
| + }
|
| + out.push_back('"');
|
| + return out;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +class Playground {
|
| + public:
|
| + static const int kPlaygroundSize = 64 * 1024 * 1024;
|
| +
|
| + // Encapsulate the configuration options to the playground.
|
| + class Options {
|
| + public:
|
| + Options()
|
| + : stack_unwind_depth_(0),
|
| + log_heap_(false),
|
| + log_lock_(false),
|
| + vista_(false) { }
|
| +
|
| +
|
| + // The maximum amount of frames we should unwind from the call stack.
|
| + int stack_unwind_depth() { return stack_unwind_depth_; }
|
| + void set_stack_unwind_depth(int depth) { stack_unwind_depth_ = depth; }
|
| +
|
| + // Whether we should log heap operations (alloc / free).
|
| + bool log_heap() { return log_heap_; }
|
| + void set_log_heap(bool x) { log_heap_ = x; }
|
| +
|
| + // Whether we should log lock (critical section) operations.
|
| + bool log_lock() { return log_lock_; }
|
| + void set_log_lock(bool x) { log_lock_ = x; }
|
| +
|
| + // Whether we are running on Vista.
|
| + 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) {
|
| + // We copy the entire playground into the remote process, and we have
|
| + // fields that we expect to be zero. TODO this could be a lot better.
|
| + memset(buf_, 0, sizeof(buf_));
|
| + }
|
| +
|
| + void AllocateInRemote() {
|
| + // Try to get something out of the way and easy to debug.
|
| + static void* kPlaygroundAddr = reinterpret_cast<void*>(0x66660000);
|
| + // Allocate our playground memory in the target process. This is a big
|
| + // slab of read/write/execute memory that we use for our code
|
| + // instrumentation, and the memory for writing out our logging events.
|
| + 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; // Looks like internal buffer is 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;
|
| +
|
| + // Given the address to the start of a function, patch the function to jump
|
| + // to a given offset into the playground. This function will try to take
|
| + // advantage of hotpatch code, if the function is prefixed with 5 0x90 bytes.
|
| + // Returns a std::string of any assembly instructions that must be relocated,
|
| + // as they were overwritten during patching.
|
| + 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) {
|
| + unsigned int instr_bytes = 0;
|
| +
|
| + // We might have a hotpatch no-op of mov edi, edi "\x8b\xff". It is a
|
| + // bit of a waste to relocate it, but it makes everything simpler.
|
| +
|
| + 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.");
|
| + }
|
| + // We only read 10 bytes worth of instructions.
|
| + CHECK(instr_bytes < 10);
|
| + }
|
| +
|
| + instrs.assign(buf + 5, instr_bytes);
|
| +
|
| + // We have a hotpatch prefix of 5 nop bytes. We can use this for our
|
| + // long jump, and then overwrite the first 2 bytes to jump back to there.
|
| + CodeBuffer patch(buf);
|
| + int off = stub_addr - func_addr;
|
| + patch.jmp_rel(off);
|
| + patch.jmp_rel_short(-2 - 5);
|
| + } else {
|
| + // We need a full 5 bytes for the jump.
|
| + 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.");
|
| + }
|
| + // We only read 10 bytes worth of instructions.
|
| + CHECK(instr_bytes < 10);
|
| + }
|
| +
|
| + instrs.assign(buf + 5, instr_bytes);
|
| +
|
| + // Overwrite the first 5 bytes with a relative jump to our stub.
|
| + CodeBuffer patch(buf + 5);
|
| + int off = stub_addr - (func_addr + 5);
|
| + patch.jmp_rel(off);
|
| + }
|
| +
|
| + // Write back the bytes, we are really probably writing more back than we
|
| + // need to, but it shouldn't really matter.
|
| + 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);
|
| + }
|
| +
|
| + // Restore any instructions that needed to be moved to make space for our
|
| + // patch and jump back to the original code.
|
| + 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);
|
| + }
|
| +
|
| + // Makes a call to NtQueryPerformanceCounter, writing the timestamp to the
|
| + // buffer pointed to by EDI. EDI it not incremented. EAX is not preserved.
|
| + void AssembleQueryPerformanceCounter(CodeBuffer* cb) {
|
| + // Make a call to NtQueryPerformanceCounter and write the result into
|
| + // the log area. The buffer we write to should be aligned, but we should
|
| + // garantee that anyway for the logging area for performance.
|
| + cb->push_imm(0); // PerformanceFrequency
|
| + cb->push(EDI); // PerformanceCounter
|
| + cb->mov_imm(EAX, reinterpret_cast<int>(
|
| + resolver_.Resolve("ntdll!NtQueryPerformanceCounter")));
|
| + cb->call(EAX);
|
| + }
|
| +
|
| + // This is the common log setup routine. It will allocate a new log entry,
|
| + // and write out the common log header to the event entry. The header is:
|
| + // is [ 64bit QPC ] [ 32bit cpu id ] [ 32bit thread id ] [ 32bit rec id ]
|
| + // EDI will be left pointing to the log entry, with |space| bytes left for
|
| + // type specific data. All other registers should not be clobbered.
|
| + 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();
|
| +
|
| + // Load EDI with the number of bytes we want for our log entry, this will
|
| + // be used in the atomic increment to allocate the log entry.
|
| + cb->mov_imm(EDI, kRecordHeaderSize + (unwind_depth * 4) + space);
|
| + // Do the increment and have EDI point to our log entry buffer space.
|
| + cb->mov_imm(EDX, reinterpret_cast<int>(remote_addr_ + kOffLogAreaPtr));
|
| + cb->inc_atomic(EDX, EDI);
|
| + // EDI is the buffer offset, make it a pointer to the record entry.
|
| + 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();
|
| +
|
| + // Stack unwinding, follow EBP to the maximum number of frames, and make
|
| + // sure that it stays on the stack (between ESP and TEB.StackBase).
|
| + if (unwind_depth > 0) {
|
| + cb->mov_imm(ECX, unwind_depth);
|
| + cb->fs(); cb->mov(EDX, Operand(0x04)); // get TEB.StackBase
|
| +
|
| + // Start at EBP.
|
| + cb->mov(EAX, EBP);
|
| +
|
| + Label unwind_loop, bail;
|
| + cb->bind(&unwind_loop);
|
| +
|
| + // Bail if (EAX < ESP) (below the stack)
|
| + cb->cmp(EAX, ESP);
|
| + cb->jcc(below, &bail);
|
| + // Bail if (EAX >= EDX) (above the stack)
|
| + cb->cmp(EAX, EDX);
|
| + cb->jcc(above_equal, &bail);
|
| +
|
| + // We have a valid stack pointer, it should point to something like:
|
| + // [ saved frame pointer ] [ return address ] [ arguments ... ]
|
| + cb->mov(ESI, EAX);
|
| + cb->lodsd(); // Get the new stack pointer to follow in EAX
|
| + cb->movsd(); // Copy the return address to the log area.
|
| +
|
| + cb->loop(&unwind_loop);
|
| +
|
| + cb->bind(&bail);
|
| + // If we did managed to unwind to the max, fill the rest with 0 (really
|
| + // we just want to inc EDI to the end, and this is an easy way).
|
| + cb->mov_imm(EAX, 0); // TODO use an xor
|
| + cb->rep(); cb->stosd();
|
| + }
|
| +
|
| + // Store the type for this record entry.
|
| + 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);
|
| +
|
| + // Set ShowSnaps to one to get the print routines to be called.
|
| + char enabled = 1;
|
| + WriteProcessMemory(
|
| + proc_, resolver_.Resolve("ntdll!ShowSnaps"), &enabled, 1, NULL);
|
| +
|
| + CodeBuffer cb(buf_ + kStubOffset);
|
| +
|
| + cb.pop(EDX); // return address
|
| + cb.pop(EAX); // First param in eax
|
| + cb.push(ESI);
|
| + cb.push(EDI);
|
| + cb.push(EDX);
|
| +
|
| + cb.mov(ESI, EAX); // ESI points at the string structure.
|
| +
|
| + // We used to do variable length based on the length supplied in the str
|
| + // structure, but it's easier (and sloppier) to just copy a fixed amount.
|
| + AssembleHeaderCode(&cb, kRecordType, kLdrBufSize);
|
| +
|
| + cb.lodsd(); // Load the character count
|
| + cb.lodsd(); // Load the char*
|
| + cb.mov(ESI, EAX);
|
| + cb.mov_imm(ECX, kLdrBufSize / 4); // load the char count as the rep count
|
| + cb.rep(); cb.movsb(); // Copy the string to the logging buffer
|
| +
|
| + // Return
|
| + cb.pop(EDX);
|
| + cb.pop(EDI);
|
| + cb.pop(ESI);
|
| + cb.pop(ECX); // don't care
|
| + cb.pop(ECX); // don't care
|
| + 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));
|
| +
|
| + // Super ugly hack. To coorrelate between creating a thread and the new
|
| + // thread running, we stash something to identify the creating event when
|
| + // we log the created event. We just use a pointer to the event log data
|
| + // since this will be unique and can tie the two events together. We pass
|
| + // it by writing into the context structure, so it will be passed in ESI.
|
| + cb.add_imm(EAX, 0xa0);
|
| + cb.push(EDI);
|
| + cb.mov(EDI, EAX);
|
| + cb.pop(EAX);
|
| + cb.push(EAX);
|
| + cb.stosd();
|
| +
|
| + // Get and save CONTEXT.Eip
|
| + cb.mov(ESI, EDI);
|
| + cb.add_imm(ESI, 20);
|
| + cb.pop(EDI);
|
| + cb.mov(EAX, EDI);
|
| + cb.stosd(); // Record the event identifier to tie together the events.
|
| + cb.movsd(); // write Eip to the log event
|
| +
|
| + 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); // We stashed the creator's eventid in the context ESI.
|
| + cb.stosd();
|
| +
|
| + // TODO(deanm): The pointer is going to point into the CRT or something,
|
| + // should we dig deeper to get more information about the real entry?
|
| + 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); // We stashed the creator's eventid in the context ESI.
|
| + cb.stosd();
|
| +
|
| + // TODO(deanm): The pointer is going to point into the CRT or something,
|
| + // should we dig deeper to get more information about the real entry?
|
| + //cb.mov(EAX, Operand(EBP, 0x8));
|
| + cb.mov_imm(EAX, 0);
|
| + cb.stosd();
|
| + cb.pop(EDI);
|
| +
|
| + ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
|
| + }
|
| +
|
| + // Intercept exception dispatching so we can catch when threads set a thread
|
| + // name (which is an exception with a special code). TODO it could be
|
| + // useful to log all exceptions.
|
| + 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); // return address
|
| + cb.pop(EAX); // ExceptionRecord
|
| + cb.push(EAX);
|
| + cb.push(EDX);
|
| +
|
| + cb.push(ESI);
|
| +
|
| + cb.mov(ESI, EAX);
|
| + cb.lodsd();
|
| +
|
| + Label bail;
|
| + // exception code
|
| + cb.cmp_imm(EAX, 0x406D1388);
|
| + cb.jcc(not_equal, &bail);
|
| +
|
| + cb.push(EDI);
|
| +
|
| + AssembleHeaderCode(&cb, kRecordType, kThreadNameBufSize);
|
| +
|
| + // Fetch the second parameter.
|
| + for (int i = 0; i < 6; ++i) {
|
| + cb.lodsd();
|
| + }
|
| +
|
| + // TODO This is sloppy and we could run into unmapped memory...
|
| + 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); // Skip over our saved and the return address
|
| + cb.movsd(); cb.movsd(); cb.movsd(); // Copy the 3 parameters
|
| +
|
| + 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); // Skip over our saved and the return address
|
| + cb.movsd(); cb.movsd(); cb.movsd(); // Copy the 3 parameters
|
| +
|
| + cb.pop(ESI);
|
| + cb.pop(EDI);
|
| +
|
| + ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
|
| + }
|
| +
|
| + // Don't even bother going back to the original code, just implement our
|
| + // own KiFastSystemCall. The original looks like:
|
| + // .text:7C90EB8B mov edx, esp
|
| + // .text:7C90EB8D sysenter
|
| + // .text:7C90EB8F nop
|
| + // .text:7C90EB90 nop
|
| + // .text:7C90EB91 nop
|
| + // .text:7C90EB92 nop
|
| + // .text:7C90EB93 nop
|
| + 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;
|
| +
|
| + // Skip 0xa5 which is QueryPerformanceCounter, to make sure we don't log
|
| + // our own logging's QPC. Disabled for now, using ret addr check...
|
| + // cb.cmp_imm(EAX, 0xa5);
|
| + // cb.jcc(equal, &skip);
|
| +
|
| + // Check if the return address is from 0x6666 (our code region).
|
| + // 66817C24066666 cmp word [esp+0x6],0x6666
|
| + 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);
|
| +
|
| + // This is all a bit shit. Originally I thought I could store some state
|
| + // on the stack above ESP, however, it seems that when APCs, etc are
|
| + // queued, they will use the stack above ESP. Well, not above ESP, above
|
| + // what was passed in as EDX into the systemcall, not matter if ESP was
|
| + // different than this :(. So we need to store our state in the event
|
| + // log record, and then we stick a pointer to that over a ret addr...
|
| +
|
| + // Our stack starts like:
|
| + // [ ret addr ] [ ret addr 2 ] [ arguments ]
|
| + // We will update it to look like
|
| + // [ ret stub addr ] [ event entry ptr ] [ arguments ]
|
| +
|
| + cb.push(EDI); // save EDI since we're using it
|
| + AssembleHeaderCode(&cb, kRecordType, 16 + 16 + 8);
|
| + cb.mov(EDX, EAX); // Save EAX...
|
| + cb.stosd(); // eax is the syscall number
|
| + cb.pop(EAX);
|
| + cb.stosd(); // store the saved EDI
|
| + cb.pop(EAX);
|
| + cb.stosd(); // store the real return address
|
| + cb.pop(EAX);
|
| + cb.stosd(); // store the real (secondary) return address;
|
| +
|
| + cb.push(ESI);
|
| + cb.mov(ESI, ESP);
|
| + cb.lodsd();
|
| + cb.movsd(); // argument 1
|
| + cb.movsd(); // argument 2
|
| + cb.movsd(); // argument 3
|
| + cb.pop(ESI);
|
| +
|
| + cb.push(EDI); // store our event ptr over the secondary ret addr.
|
| + cb.push_imm(reinterpret_cast<int>(remote_addr_ + kOffSyscallCode + 200));
|
| + cb.mov(EAX, EDX); // restore EAX
|
| +
|
| + 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);
|
| +
|
| + // TODO share the QPC code, this is a copy and paste...
|
| +
|
| + cb.pop(EDI); // get the log area
|
| +
|
| + cb.stosd(); // Log the system call return value.
|
| +
|
| + // QPC will clobber EAX, and it's very important to save it since it
|
| + // is the return value from the system call. TODO validate if there is
|
| + // anything else we need to save...
|
| + cb.push(EAX);
|
| + AssembleQueryPerformanceCounter(&cb);
|
| + cb.pop(EAX);
|
| +
|
| + // We need to:
|
| + // - Restore the original "seconary" return address
|
| + // - Restore the original value of the EDI register
|
| + // - Jump control flow to the original return address
|
| + // All 3 of these values are stored in the log record...
|
| + // [ syscall num ] [ saved edi ] [ real rets ] [ args ] [ retval ] [ ts ]
|
| + // currently edi points here ----^
|
| +
|
| + cb.push(Operand(EDI, -4 - 16)); // push the real 2nd ret
|
| + cb.push(Operand(EDI, -8 - 16)); // push the real ret
|
| + cb.push(Operand(EDI, -12 - 16)); // push the saved EDI
|
| +
|
| + cb.pop(EDI); // restore EDI that was saved in the record
|
| + cb.ret(); // jmp back to the real ret ...
|
| +
|
| + if (cb.size() > 56) {
|
| + NOTREACHED("ug");
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Patch lock (criticial section) holding.
|
| + 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);
|
| +
|
| + // We just want to capture the return address and original argument, so
|
| + // we know when EnterCriticalSection returned, we don't want to know when
|
| + // it entered because it could sit waiting. We want to know when the lock
|
| + // actually started being held. The compiler will sometimes generated code
|
| + // that overwrites arguments, so we'll keep a copy of the argument just in
|
| + // case code like this is ever generated in the future. TODO is it enough
|
| + // to just assume a LPCRITICAL_SECTION uniquely identifies the lock, or
|
| + // can the same lock have multiple different copies, I would assume not.
|
| + {
|
| + CodeBuffer cb(buf_ + kStubOffset);
|
| +
|
| + // Set up an additional frame so that we capture the return.
|
| + // TODO use memory instructions instead of using registers.
|
| + cb.pop(EAX); // return address
|
| + cb.pop(EDX); // first argument (critical section pointer)
|
| +
|
| + 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(); // Skip over our saved ESI
|
| + cb.lodsd(); // Skip over the return address
|
| + cb.movsd(); // Write the CRITICAL_SECTION* to the event record.
|
| +
|
| + 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);
|
| +
|
| + // Set up an additional frame so that we capture the return.
|
| + // TODO use memory instructions instead of using registers.
|
| + cb.pop(EAX); // return address
|
| + cb.pop(EDX); // first argument (critical section pointer)
|
| +
|
| + 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(); // Skip over our saved ESI
|
| + cb.lodsd(); // Skip over the return address
|
| + cb.movsd(); // Write the CRITICAL_SECTION* to the event record.
|
| +
|
| + cb.pop(EAX);
|
| + cb.stosd(); // Write the return value to the event record.
|
| +
|
| + 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);
|
| +
|
| + // TODO use memory instructions instead of using registers.
|
| + cb.pop(EDX); // return address
|
| + cb.pop(EAX); // first argument (critical section pointer)
|
| + cb.push(EAX);
|
| + cb.push(EDX);
|
| +
|
| + cb.push(EDI);
|
| + AssembleHeaderCode(&cb, kRecordType, 4);
|
| + cb.stosd(); // Write the CRITICAL_SECTION* to the event record.
|
| + cb.pop(EDI);
|
| +
|
| + ResumeOriginalFunction(kFuncName, moved_instructions, kStubOffset, &cb);
|
| + }
|
| +
|
| + // Patch APC dispatching. This is a bit hacky, since the return to kernel
|
| + // mode is done with NtContinue, we have to shim in a stub return address to
|
| + // catch when the callback is finished. It is probably a bit fragile.
|
| + 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);
|
| +
|
| + // We don't really need to preserve these since we're the first thing
|
| + // executing from the kernel dispatch, but yeah, it is good practice.
|
| + 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)); // Swap the callback address with ours.
|
| + cb.stosd(); // Store the original callback function address.
|
| +
|
| + // TODO for now we're lazy and depend that ESI will be preserved, and we
|
| + // use it to store the pointer into our log record. EDI isn't preserved.
|
| + 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);
|
| +
|
| + // This is our shim, we need to call the original callback function, then
|
| + // we can catch the return and log when it was completed.
|
| + cb.pop(EAX); // The real return address, safe to use EAX w/ the ABI?
|
| + cb.push(EDI);
|
| +
|
| + cb.mov(EDI, ESI);
|
| + cb.stosd(); // Store the real return address, we'll need it.
|
| +
|
| + cb.add_imm(ESI, -4);
|
| + cb.lodsd(); // Load the real callback address.
|
| +
|
| + cb.mov(ESI, EDI);
|
| + cb.pop(EDI);
|
| +
|
| + cb.call(EAX); // Call the original callback address.
|
| +
|
| + cb.push(EAX);
|
| + cb.push(EDI);
|
| +
|
| + cb.mov(EDI, ESI);
|
| + AssembleQueryPerformanceCounter(&cb);
|
| +
|
| + cb.pop(EDI);
|
| + cb.pop(EAX);
|
| +
|
| + cb.push(Operand(ESI, -4)); // Push the real return address.
|
| + cb.ret(); // Return back to the APC Dispatcher.
|
| +
|
| + CHECK(cb.size() < 50);
|
| + }
|
| + }
|
| +
|
| + // We need to hook into process shutdown for two reasons. Most importantly,
|
| + // we need to copy the playground back from the process before the address
|
| + // space goes away. We could avoid this with shared memory, however, there
|
| + // is a reason two. In order to capture symbols for all of the libraries
|
| + // loaded into arbitrary applications, on shutdown we do an instrusive load
|
| + // of symbols into the traced process.
|
| + //
|
| + // ntdll!LdrShutdownProcess
|
| + // - NtSetEvent(event, 0);
|
| + // - NtWaitForSingleObject(event, FALSE, NULL);
|
| + // - jmp back
|
| + 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);
|
| +
|
| + // NtSetEvent(exiting, 0);
|
| + 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);
|
| +
|
| + // NtWaitForSingleObject(exited, FALSE, INFINITE);
|
| + 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() {
|
| + //PatchCreateThread();
|
| +
|
| + if (options_.vista()) {
|
| + PatchThreadBeginVista();
|
| + } else {
|
| + PatchThreadBegin();
|
| + }
|
| +
|
| + PatchThreadExit();
|
| + PatchSetThreadName();
|
| +#if 0
|
| + // FIXME
|
| + PatchSyscall();
|
| +#endif
|
| +
|
| + PatchApcDispatcher();
|
| +
|
| + // The loader logging needs to be improved a bit to really be useful.
|
| + //PatchLoader();
|
| +
|
| + // These are interesting, but will collect a ton of data:
|
| + if (options_.log_heap()) {
|
| + PatchAllocateHeap();
|
| + PatchFreeHeap();
|
| + }
|
| + if (options_.log_lock()) {
|
| + PatchEnterCriticalSection();
|
| + PatchTryEnterCriticalSection();
|
| + PatchLeaveCriticalSection();
|
| + }
|
| + }
|
| +
|
| + // Dump the event records from the playground to stdout in a JSON format.
|
| + // TODO: Drop RDTSCNormalizer, it was from old code that tried to use the
|
| + // rdtsc counters from the CPU, and this required a bunch of normalization
|
| + // to account for non-syncronized timestamps across different cores, etc.
|
| + 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());
|
| + // Mark system calls that we should consider "waiting" system
|
| + // calls, where we are not actually active.
|
| + 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 the process we install into or read back from.
|
| + HANDLE proc_;
|
| + // The address where we will keep our playground in the remote process.
|
| + char* remote_addr_;
|
| + // Lookup addresses from symbol names for ntdll.dll.
|
| + SymResolver resolver_;
|
| + Options options_;
|
| + // A local copy of the playground data, we copy it into the remote process.
|
| + 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) {
|
| + STARTUPINFO 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);
|
| +
|
| + // The playground object is big (32MB), dynamically alloc.
|
| + 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);
|
| +
|
| + // Wait until we have been notified that it's exiting.
|
| + if (manual_quit) {
|
| + printf("Press enter when you want to collect.\n");
|
| + 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) {
|
| + // Break in and get the symbols...
|
| + SymResolver res(NULL, info.hProcess);
|
| + pg->DumpJSON(&rdn, &res);
|
| + } else {
|
| + pg->DumpJSON(&rdn, NULL);
|
| + }
|
| +
|
| + // Notify that it can exit now, we are done.
|
| + SetEvent(exited);
|
| +
|
| + CloseHandle(info.hProcess);
|
| + CloseHandle(info.hThread);
|
| +
|
| + delete pg;
|
| +}
|
|
|