| // 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()); |
| } |
| |
| // TODO(deanm): It seems in more recent updates the compiler is generating |
| // complicated sequences for padding / alignment. For example: |
| // 00000000 8DA42400000000 lea esp,[esp+0x0] |
| // 00000007 8D4900 lea ecx,[ecx+0x0] |
| // is used for a 16 byte alignment. We need a better way of handling this. |
| 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; |
| |
| // 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() { |
| if (options_.vista()) { |
| // TODO(deanm): Make PatchCreateThread work on Vista. |
| PatchThreadBeginVista(); |
| } else { |
| PatchCreateThread(); |
| PatchThreadBegin(); |
| } |
| |
| PatchThreadExit(); |
| PatchSetThreadName(); |
| PatchSyscall(); |
| |
| 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) { |
| 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); |
| |
| // 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) { |
| 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) { |
| // 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; |
| } |