| /* ********************************************************** |
| * Copyright (c) 2011-2012 Google, Inc. All rights reserved. |
| * Copyright (c) 2004-2007 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| #ifdef X64 |
| # error 64-bit not yet supported (issue #118) |
| #endif |
| |
| #include <windows.h> |
| #include <stdio.h> |
| #include <assert.h> |
| |
| #define RC1_HACK 1 |
| |
| static int verbose = 1; |
| |
| /* Avoid flushing issues on Windows, and keep warnings and info messages |
| * all in the same order, by always going to STDERR |
| */ |
| #define PRINT(...) fprintf(stderr, __VA_ARGS__) |
| |
| #define INFO(level, ...) do { \ |
| if (verbose >= (level)) { \ |
| PRINT(__VA_ARGS__); \ |
| } \ |
| } while (0) |
| |
| #define WARN(...) INFO(0, __VA_ARGS__) |
| |
| /* alignment helpers */ |
| #define ALIGNED(x, alignment) ((((uint)x) & ((alignment)-1)) == 0) |
| #define ALIGN_FORWARD(x, alignment) ((((uint)x) + ((alignment)-1)) & (~((alignment)-1))) |
| #define ALIGN_BACKWARD(x, alignment) (((uint)x) & (~((alignment)-1))) |
| #define PAD(length, alignment) (ALIGN_FORWARD((length), (alignment)) - (length)) |
| |
| /* check if all bits in mask are set in var */ |
| #define TESTALL(mask, var) (((mask) & (var)) == (mask)) |
| /* check if any bit in mask is set in var */ |
| #define TESTANY(mask, var) (((mask) & (var)) != 0) |
| /* check if a single bit is set in var */ |
| #define TEST TESTANY |
| |
| #define BUFFER_SIZE_BYTES(buf) sizeof(buf) |
| #define BUFFER_SIZE_ELEMENTS(buf) (BUFFER_SIZE_BYTES(buf) / sizeof(buf[0])) |
| #define BUFFER_LAST_ELEMENT(buf) buf[BUFFER_SIZE_ELEMENTS(buf) - 1] |
| #define NULL_TERMINATE_BUFFER(buf) BUFFER_LAST_ELEMENT(buf) = 0 |
| |
| typedef int bool; |
| typedef unsigned int uint; |
| typedef LONG NTSTATUS; |
| #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) |
| #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) |
| |
| #define false FALSE |
| #define true TRUE |
| #define PAGE_SIZE 0x1000 |
| #define ALLOCATION_GRANULARITY 0x10000 |
| #define INVALID_File (NULL) |
| |
| #define GET_NTDLL(NtFunction, signature) \ |
| NTSYSAPI NTSTATUS NTAPI NtFunction signature |
| |
| typedef struct _UNICODE_STRING { |
| /* Length field is size in bytes not counting final 0 */ |
| USHORT Length; |
| USHORT MaximumLength; |
| PWSTR Buffer; |
| } UNICODE_STRING; |
| typedef UNICODE_STRING *PUNICODE_STRING; |
| |
| typedef struct _OBJECT_ATTRIBUTES { |
| ULONG Length; |
| HANDLE RootDirectory; |
| PUNICODE_STRING ObjectName; |
| ULONG Attributes; |
| PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR |
| PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE |
| } OBJECT_ATTRIBUTES; |
| typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; |
| |
| #define InitializeObjectAttributes( p, n, a, r, s ) { \ |
| (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ |
| (p)->RootDirectory = r; \ |
| (p)->Attributes = a; \ |
| (p)->ObjectName = n; \ |
| (p)->SecurityDescriptor = s; \ |
| (p)->SecurityQualityOfService = NULL; \ |
| } |
| |
| #define OBJ_CASE_INSENSITIVE 0x00000040L |
| #define OBJ_KERNEL_HANDLE 0x00000200L |
| typedef ULONG ACCESS_MASK; |
| |
| #ifndef X64 |
| # ifndef _W64 |
| # define _W64 |
| # endif |
| typedef _W64 long LONG_PTR, *PLONG_PTR; |
| typedef _W64 unsigned long ULONG_PTR, *PULONG_PTR; |
| #endif |
| typedef LONG KPRIORITY; |
| typedef ULONG_PTR KAFFINITY; |
| |
| typedef struct _PROCESS_BASIC_INFORMATION { |
| NTSTATUS ExitStatus; |
| char *PebBaseAddress; |
| ULONG_PTR AffinityMask; |
| KPRIORITY BasePriority; |
| ULONG_PTR UniqueProcessId; |
| ULONG_PTR InheritedFromUniqueProcessId; |
| } PROCESS_BASIC_INFORMATION; |
| typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION; |
| |
| typedef struct _CLIENT_ID { |
| HANDLE UniqueProcess; |
| HANDLE UniqueThread; |
| } CLIENT_ID; |
| typedef CLIENT_ID *PCLIENT_ID; |
| |
| typedef struct _THREAD_BASIC_INFORMATION { // Information Class 0 |
| NTSTATUS ExitStatus; |
| PNT_TIB TebBaseAddress; |
| CLIENT_ID ClientId; |
| KAFFINITY AffinityMask; |
| KPRIORITY Priority; |
| KPRIORITY BasePriority; |
| } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; |
| |
| typedef enum {MEMORY_RESERVE_ONLY = MEM_RESERVE, |
| MEMORY_COMMIT = MEM_RESERVE|MEM_COMMIT |
| } memory_commit_status_t; |
| |
| typedef struct _IO_STATUS_BLOCK { |
| NTSTATUS Status; |
| // PVOID Pointer - Reserved, supposedly in a union with Status. see MSDN |
| ULONG Information; // MSDN says it is ULONG_PTR |
| } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; |
| |
| typedef struct _USER_STACK { |
| PVOID FixedStackBase; |
| PVOID FixedStackLimit; |
| PVOID ExpandableStackBase; |
| PVOID ExpandableStackLimit; |
| PVOID ExpandableStackBottom; |
| } USER_STACK, *PUSER_STACK; |
| |
| #define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 |
| |
| typedef enum _THREADINFOCLASS { |
| ThreadBasicInformation, |
| ThreadTimes, |
| ThreadPriority, |
| ThreadBasePriority, |
| ThreadAffinityMask, |
| ThreadImpersonationToken, |
| ThreadDescriptorTableEntry, |
| ThreadEnableAlignmentFaultFixup, |
| ThreadEventPair_Reusable, |
| ThreadQuerySetWin32StartAddress, |
| ThreadZeroTlsCell, |
| ThreadPerformanceCount, |
| ThreadAmILastThread, |
| ThreadIdealProcessor, |
| ThreadPriorityBoost, |
| ThreadSetTlsArrayAddress, |
| ThreadIsIoPending, |
| ThreadHideFromDebugger, |
| MaxThreadInfoClass |
| } THREADINFOCLASS; |
| |
| typedef enum _PROCESSINFOCLASS { |
| ProcessBasicInformation, |
| ProcessQuotaLimits, |
| ProcessIoCounters, |
| ProcessVmCounters, |
| ProcessTimes, |
| ProcessBasePriority, |
| ProcessRaisePriority, |
| ProcessDebugPort, |
| ProcessExceptionPort, |
| ProcessAccessToken, |
| ProcessLdtInformation, |
| ProcessLdtSize, |
| ProcessDefaultHardErrorMode, |
| ProcessIoPortHandlers, // Note: this is kernel mode only |
| ProcessPooledUsageAndLimits, |
| ProcessWorkingSetWatch, |
| ProcessUserModeIOPL, |
| ProcessEnableAlignmentFaultFixup, |
| ProcessPriorityClass, |
| ProcessWx86Information, |
| ProcessHandleCount, |
| ProcessAffinityMask, |
| ProcessPriorityBoost, |
| ProcessDeviceMap, |
| ProcessSessionInformation, |
| ProcessForegroundInformation, |
| ProcessWow64Information, |
| MaxProcessInfoClass |
| } PROCESSINFOCLASS; |
| |
| typedef struct _DESCRIPTOR_TABLE_ENTRY { |
| ULONG Selector; |
| LDT_ENTRY Descriptor; |
| } DESCRIPTOR_TABLE_ENTRY, *PDESCRIPTOR_TABLE_ENTRY; |
| |
| |
| /************************************************************************/ |
| static bool |
| nt_remote_allocate_virtual_memory(HANDLE process, void **base, uint size, |
| uint prot, memory_commit_status_t commit) |
| { |
| NTSTATUS res; |
| GET_NTDLL(NtAllocateVirtualMemory, (IN HANDLE ProcessHandle, |
| IN OUT PVOID *BaseAddress, |
| IN ULONG ZeroBits, |
| IN OUT PULONG AllocationSize, |
| IN ULONG AllocationType, |
| IN ULONG Protect)); |
| uint sz = size; |
| assert(ALIGNED(*base, PAGE_SIZE)); |
| res = NtAllocateVirtualMemory(process, base, 0 /* zero bits */, &sz, |
| commit, prot); |
| assert(sz >= size); |
| return NT_SUCCESS(res); |
| } |
| |
| static bool |
| query_thread_info(HANDLE h, THREAD_BASIC_INFORMATION *info) |
| { |
| NTSTATUS res; |
| GET_NTDLL(NtQueryInformationThread, (IN HANDLE ThreadHandle, |
| IN THREADINFOCLASS ThreadInformationClass, |
| OUT PVOID ThreadInformation, |
| IN ULONG ThreadInformationLength, |
| OUT PULONG ReturnLength OPTIONAL)); |
| int got; |
| memset(info, 0, sizeof(THREAD_BASIC_INFORMATION)); |
| res = NtQueryInformationThread(h, ThreadBasicInformation, |
| info, sizeof(THREAD_BASIC_INFORMATION), &got); |
| assert(!NT_SUCCESS(res) || got == sizeof(THREAD_BASIC_INFORMATION)); |
| return NT_SUCCESS(res); |
| } |
| |
| static bool |
| query_process_info(HANDLE h, PROCESS_BASIC_INFORMATION *info) |
| { |
| NTSTATUS res; |
| GET_NTDLL(NtQueryInformationProcess, (IN HANDLE ProcessHandle, |
| IN PROCESSINFOCLASS ProcessInformationClass, |
| OUT PVOID ProcessInformation, |
| IN ULONG ProcessInformationLength, |
| OUT PULONG ReturnLength OPTIONAL)); |
| int got; |
| memset(info, 0, sizeof(PROCESS_BASIC_INFORMATION)); |
| res = NtQueryInformationProcess(h, ProcessBasicInformation, info, |
| sizeof(PROCESS_BASIC_INFORMATION), &got); |
| assert(!NT_SUCCESS(res) || got == sizeof(PROCESS_BASIC_INFORMATION)); |
| return NT_SUCCESS(res); |
| } |
| |
| static bool |
| set_win32_start_addr(HANDLE h, uint *start_addr) |
| { |
| NTSTATUS res; |
| GET_NTDLL(NtSetInformationThread, (IN HANDLE ThreadHandle, |
| IN THREADINFOCLASS ThreadInfoClass, |
| IN PVOID ThreadInformation, |
| IN ULONG ThreadInformationLength)); |
| res = NtSetInformationThread(h, ThreadQuerySetWin32StartAddress, |
| start_addr, sizeof(*start_addr)); |
| if (!NT_SUCCESS(res)) { |
| INFO(1, "setting thread start addr failed with 0x%08x\n", res); |
| } |
| return NT_SUCCESS(res); |
| } |
| |
| static bool |
| wchar_to_unicode(PUNICODE_STRING dst, PCWSTR src) |
| { |
| NTSTATUS res; |
| GET_NTDLL(RtlInitUnicodeString, (IN OUT PUNICODE_STRING DestinationString, |
| IN PCWSTR SourceString)); |
| res = RtlInitUnicodeString(dst, src); |
| return NT_SUCCESS(res); |
| } |
| |
| static uint |
| remove_writecopy(uint prot) |
| { |
| uint other = prot & (PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE); |
| prot &= ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE); |
| |
| switch (prot) { |
| case PAGE_READONLY: |
| case PAGE_READWRITE: |
| case PAGE_EXECUTE: |
| case PAGE_EXECUTE_READ: |
| case PAGE_EXECUTE_READWRITE: |
| break; |
| case PAGE_WRITECOPY: |
| prot = PAGE_READWRITE; |
| break; |
| case PAGE_EXECUTE_WRITECOPY: |
| prot = PAGE_EXECUTE_READWRITE; |
| break; |
| } |
| return (prot | other); |
| } |
| |
| static bool |
| prot_is_readable(uint prot) |
| { |
| prot &= ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE); |
| |
| switch (prot) { |
| case PAGE_READONLY: |
| case PAGE_READWRITE: |
| case PAGE_WRITECOPY: |
| case PAGE_EXECUTE: |
| case PAGE_EXECUTE_READ: |
| case PAGE_EXECUTE_READWRITE: |
| case PAGE_EXECUTE_WRITECOPY: return true; |
| } |
| return false; |
| } |
| |
| static bool |
| prot_is_writable(uint prot) |
| { |
| prot &= ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE); |
| return (prot == PAGE_READWRITE || prot == PAGE_WRITECOPY || |
| prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY); |
| } |
| |
| static char * |
| mem_state_string(DWORD state) |
| { |
| switch (state) { |
| case 0: return "none"; |
| case MEM_COMMIT: return "COMMIT"; |
| case MEM_FREE: return "FREE"; |
| case MEM_RESERVE: return "RESERVE"; |
| } |
| return "<error>"; |
| } |
| |
| static char * |
| mem_type_string(DWORD type) |
| { |
| switch (type) { |
| case 0: return "none"; |
| case MEM_IMAGE: return "IMAGE"; |
| case MEM_MAPPED: return "MAPPED"; |
| case MEM_PRIVATE: return "PRIVATE"; |
| } |
| return "<error>"; |
| } |
| |
| static char * |
| prot_string(DWORD prot) |
| { |
| DWORD ignore_extras = prot & ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE); |
| switch (ignore_extras) { |
| case PAGE_NOACCESS: return "----"; |
| case PAGE_READONLY: return "r---"; |
| case PAGE_READWRITE: return "rw--"; |
| case PAGE_WRITECOPY: return "rw-c"; |
| case PAGE_EXECUTE: return "--x-"; |
| case PAGE_EXECUTE_READ: return "r-x-"; |
| case PAGE_EXECUTE_READWRITE: return "rwx-"; |
| case PAGE_EXECUTE_WRITECOPY: return "rwxc"; |
| } |
| return "<error>"; |
| } |
| |
| void |
| dump_mbi(MEMORY_BASIC_INFORMATION *mbi) |
| { |
| PRINT( |
| "BaseAddress: %08x\n" |
| "AllocationBase: %08x\n" |
| "AllocationProtect: %08x %s\n" |
| "RegionSize: %08x\n" |
| "State: %08x %s\n" |
| "Protect: %08x %s\n" |
| "Type: %08x %s\n", |
| mbi->BaseAddress, |
| mbi->AllocationBase, |
| mbi->AllocationProtect, prot_string(mbi->AllocationProtect), |
| mbi->RegionSize, |
| mbi->State, mem_state_string(mbi->State), |
| mbi->Protect, prot_string(mbi->Protect), |
| mbi->Type, mem_type_string(mbi->Type)); |
| } |
| |
| /********************************************************************************/ |
| |
| typedef struct _mem_map { |
| char *old; |
| char *new; |
| } mem_map; |
| |
| static int map_count = 0; |
| static mem_map map[4097]; |
| static int pending_map_count = 0; |
| static char * pending_map[4097]; |
| |
| static void |
| add_mapped_addr(char *old, char *new) |
| { |
| assert(map_count < BUFFER_SIZE_ELEMENTS(map)); |
| map[map_count].new = new; |
| map[map_count].old = old; |
| map_count++; |
| } |
| |
| static void |
| add_pending_mapped_addr(char *addr) |
| { |
| assert(pending_map_count < BUFFER_SIZE_ELEMENTS(map)); |
| pending_map[pending_map_count++] = addr; |
| } |
| |
| static bool |
| is_pending_mapped_addr(char *addr) |
| { |
| int i; |
| for (i = 0; i < pending_map_count; i++) { |
| if (pending_map[i] == addr) |
| return true; |
| } |
| return false; |
| } |
| |
| /* we can get NULL from a bad TEB so use this instead */ |
| #define FAIL ((char *)-1) |
| |
| static char * |
| get_mapped_addr(char *old) |
| { |
| int i; |
| for (i = 0; i < map_count; i++) { |
| if (map[i].old == old) |
| return map[i].new; |
| } |
| return FAIL; |
| } |
| |
| static char * |
| get_original_addr(char *new) |
| { |
| int i; |
| for (i = 0; i < map_count; i++) { |
| if (map[i].new == new) |
| return map[i].old; |
| } |
| return FAIL; |
| } |
| |
| /* NOTE - sum users implicitly assume this is exactly 1 page */ |
| static char buf[4096]; |
| |
| /* Note, because of my sloppy file format, type may or may not have a |
| * description string, so we skip it and just eat the line, we can't do |
| * anything with the type information anyways */ |
| static bool |
| read_mbi(FILE *file, MEMORY_BASIC_INFORMATION *mbi) |
| { |
| int res; |
| char *resc; |
| char dummy_buf[128]; |
| memset(mbi, 0, sizeof(MEMORY_BASIC_INFORMATION)); |
| res = fscanf(file, "\nBaseAddress=0x%08x\nAllocationBase=0x%08x\n" |
| "AllocationProtect=0x%08x %*s\nRegionSize=0x%08x\n" |
| "State=0x%08x %*s\nProtect=0x%08x %*s\n", |
| &(mbi->BaseAddress), &(mbi->AllocationBase), |
| &(mbi->AllocationProtect), &(mbi->RegionSize), |
| &(mbi->State), &(mbi->Protect)); |
| if (res != 6) |
| return false; |
| /* eat the type line */ |
| resc = fgets(dummy_buf, sizeof(dummy_buf), file); |
| assert(resc != NULL); |
| return true; |
| } |
| |
| static void |
| print_descriptor(DESCRIPTOR_TABLE_ENTRY *entry) |
| { |
| /* IA-32 SDM Volume 3A, section 3.4.5, "Segment Descriptors". |
| * The segment type is determined by the S bit (0=system, |
| * 1=code or data) and the 4-bit type field: |
| */ |
| static const char *types[] = {"Data RO ", |
| "Data RO, acc ", |
| "Data R/W, ", |
| "Data R/W, acc ", |
| "Data RO, down ", |
| "Data RO, down, acc ", |
| "Data R/W, down ", |
| "Data R/W, down, acc ", |
| "Code EO ", |
| "Code EO, acc ", |
| "Code E/R ", |
| "Code E/R, acc ", |
| "Code EO, conf ", |
| "Code EO, conf, acc ", |
| "Code E/RO, conf ", |
| "Code E/RO, conf, acc"}; |
| |
| LDT_ENTRY *descr = &entry->Descriptor; |
| |
| uint base = (descr->BaseLow | |
| (descr->HighWord.Bits.BaseMid << 16) | |
| (descr->HighWord.Bits.BaseHi << 24)); |
| |
| uint limit = descr->LimitLow | (descr->HighWord.Bits.LimitHi << 16); |
| if (descr->HighWord.Bits.Granularity == 1) { |
| limit = limit << 12 | 0xfff; |
| } |
| |
| PRINT("\t%04x ", entry->Selector); |
| PRINT("%08x ", base); |
| PRINT("%08x ", limit); |
| |
| /* The definition for LDT_ENTRY combines the S and type fields |
| * into a five bit field. */ |
| if (TEST(0x10, descr->HighWord.Bits.Type)) { |
| PRINT("%s ", types[descr->HighWord.Bits.Type & 0xf]); |
| } else { |
| PRINT("System "); |
| } |
| |
| PRINT(" %x ", descr->HighWord.Bits.Dpl); |
| PRINT(" %x ", descr->HighWord.Bits.Default_Big); |
| PRINT("%s ", descr->HighWord.Bits.Granularity == 1 ? "4kb" : " 1b"); |
| PRINT(" %x ", descr->HighWord.Bits.Pres); |
| PRINT("%x ", descr->HighWord.Bits.Reserved_0); |
| PRINT(" %x\n", descr->HighWord.Bits.Sys); |
| } |
| |
| static void |
| print_descriptors(DESCRIPTOR_TABLE_ENTRY entries[6]) |
| { |
| int i; |
| PRINT("\n\tSel Base Limit Type Dpl D/B Gran Pres L Sys\n" |
| "\t---- -------- -------- -------------------- --- --- ---- ---- - ---\n"); |
| |
| for (i=0; i<6; i++) { |
| /* Intel SDM vol. 3A, section 3.4.2: the first entry in the LDT |
| * is null and not used by the processor */ |
| if (entries[i].Selector != 0) { |
| print_descriptor(&entries[i]); |
| } |
| } |
| PRINT("\n"); |
| } |
| |
| static void |
| insert_entry(uint selector, uint word1, uint word2, DESCRIPTOR_TABLE_ENTRY entries[6]) |
| { |
| int i; |
| |
| /* Intel SDM vol. 3A, section 3.4.2: the first entry in the LDT |
| * is null and not used by the processor */ |
| if (selector != 0) { |
| for (i=0; i<6; i++) { |
| /* don't insert duplicates */ |
| if (entries[i].Selector == selector) |
| return; |
| |
| /* 0 indicates empty slot in entries[] */ |
| if (entries[i].Selector == 0) { |
| entries[i].Selector = selector; |
| *((PULONG)&entries[i].Descriptor) = word1; |
| *((PULONG)&entries[i].Descriptor.HighWord) = word2; |
| return; |
| } |
| } |
| } |
| } |
| |
| /* macro to read descriptor info and insert it in a table */ |
| #define read_descriptor_and_insert(name, reg) { \ |
| fgets(line, sizeof(line), file); \ |
| res = sscanf(line, name "=0x%04x (0x%08x 0x%08x)\n", ®, &word1, &word2); \ |
| assert(res == 3 || res == 1); \ |
| if (res == 3) \ |
| insert_entry(reg, word1, word2, entries); \ |
| } |
| |
| static void |
| read_threads(FILE *file, bool create, HANDLE hProc) { |
| HANDLE hThread; |
| DWORD thread_id, new_id, res; |
| THREAD_BASIC_INFORMATION thread_info; |
| CLIENT_ID cid; |
| OBJECT_ATTRIBUTES oa; |
| USER_STACK stack = {0}; |
| CONTEXT cxt; |
| GET_NTDLL(NtCreateThread, (OUT PHANDLE ThreadHandle, |
| IN ACCESS_MASK DesiredAccess, |
| IN POBJECT_ATTRIBUTES ObjectAttributes, |
| IN HANDLE ProcessHandle, |
| OUT PCLIENT_ID ClientId, |
| IN PCONTEXT ThreadContext, |
| IN PUSER_STACK UserStack, |
| IN BOOLEAN CreateSuspended)); |
| |
| if (create) { |
| cxt.ContextFlags = CONTEXT_INTEGER|CONTEXT_CONTROL; |
| /* initialize, unsafe but should be ok right? */ |
| GetThreadContext(GetCurrentThread(), &cxt); |
| InitializeObjectAttributes(&oa, NULL, OBJ_CASE_INSENSITIVE, |
| NULL, NULL); |
| } |
| |
| while (fscanf(file, "Thread=0x%08x\n", &thread_id)) { |
| bool valid_state = true, valid_handle_state = false; |
| uint handle_rights; |
| char *teb; |
| bool valid_selectors = false; |
| uint Cs, Ss, Ds, Es, Fs, Gs; |
| DESCRIPTOR_TABLE_ENTRY entries[6] = {0,}; |
| uint word1, word2; |
| char line[64]; |
| long pos; |
| uint win32_start_addr = 0; |
| |
| res = fscanf(file, "TEB=0x%08x\n", &teb); |
| assert(res == 1); |
| |
| res = fscanf(file, "HandleRights=0x%08x\n", &handle_rights); |
| if (res == 1) { |
| valid_handle_state = true; |
| } |
| |
| res = fscanf(file, "Eax=0x%08x, Ebx=0x%08x, Ecx=0x%08x, Edx=0x%08x\n", |
| &cxt.Eax, &cxt.Ebx, &cxt.Ecx, &cxt.Edx); |
| if (res == 4) { |
| res = fscanf(file, "Esi=0x%08x, Edi=0x%08x, Esp=0x%08x, Ebp=0x%08x\n", |
| &cxt.Esi, &cxt.Edi, &cxt.Esp, &cxt.Ebp); |
| assert(res == 4); |
| res = fscanf(file, "EFlags=0x%08x, Eip=0x%08x\n\n", |
| &cxt.EFlags, &cxt.Eip); |
| assert(res == 2); |
| |
| /* look for segment registers and associated descriptors */ |
| pos = ftell(file); |
| fgets(line, sizeof(line), file); |
| res = sscanf(line, "Cs=0x%04x (0x%08x 0x%08x)\n", &Cs, &word1, &word2); |
| if (res == 3 || res == 1) { |
| valid_selectors = true; |
| if (res == 3) |
| insert_entry(Cs, word1, word2, entries); |
| |
| read_descriptor_and_insert("Ss", Ss); |
| read_descriptor_and_insert("Ds", Ds); |
| read_descriptor_and_insert("Es", Es); |
| read_descriptor_and_insert("Fs", Fs); |
| read_descriptor_and_insert("Gs", Gs); |
| } else { |
| /* put file position back a line */ |
| fseek(file, pos, SEEK_SET); |
| } |
| |
| /* look for the win32 start address */ |
| pos = ftell(file); |
| res = fscanf(file, "Win32StartAddr=0x%08x\n", &win32_start_addr); |
| if (res != 1) { |
| /* set start addr to 0 so we know right away if the thread |
| * doesn't have a start address in the ldmp. |
| */ |
| win32_start_addr = 0; |
| |
| /* put file position back a line */ |
| fseek(file, pos, SEEK_SET); |
| } |
| } else { |
| valid_state = false; |
| fgets(buf, sizeof(buf), file); |
| /* only show error when creating to avoid duplicate messages */ |
| if (create) { |
| WARN("\nError reading thread state for original thread tid=0x%04x\n", |
| thread_id); |
| WARN("%s", buf); |
| } |
| fgets(buf, sizeof(buf), file); /* read in the newline */ |
| /* clear context integer registers */ |
| cxt.Eax = 0; cxt.Ebx = 0; cxt.Ecx = 0; cxt.Edx = 0; cxt.EFlags = 0; |
| cxt.Edi = 0; cxt.Esi = 0; cxt.Esp = 0; cxt.Ebp = 0; cxt.Eip = 0; |
| } |
| |
| if (create) { |
| res = NtCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProc, |
| &cid, &cxt, &stack, TRUE); |
| assert(NT_SUCCESS(res)); |
| res = set_win32_start_addr(hThread, &win32_start_addr); |
| if (!res) |
| WARN("unable to set thread start address to %p\n", win32_start_addr); |
| res = query_thread_info(hThread, &thread_info); |
| assert(res); |
| new_id = (uint)thread_info.ClientId.UniqueThread; |
| INFO(1, "created thread tid=0x%04x with TEB=0x%08x original tid=0x%04x with TEB=0x%08x\n", |
| new_id, thread_info.TebBaseAddress, thread_id, teb); |
| if (valid_selectors) { |
| INFO(1, "\tcs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n", |
| Cs, Ss, Ds, Es, Fs, Gs); |
| } |
| if (entries[0].Selector != 0) { |
| print_descriptors(entries); |
| } |
| if (valid_handle_state) { |
| INFO(1, "\tHandleRights=0x%08x\n", handle_rights); |
| } |
| if (teb == NULL) { |
| WARN("\twill be unable to copy over TEB\n"); |
| } else { |
| add_mapped_addr(teb, (char *)thread_info.TebBaseAddress); |
| } |
| if (!valid_state) |
| WARN("\tnew thread's register state is invalid\n\n"); |
| CloseHandle(hThread); |
| } else { |
| /* prevent this region from being copied over till we know |
| * where to put it */ |
| add_pending_mapped_addr(teb); |
| } |
| } |
| } |
| |
| static HANDLE |
| create_process(char *path) |
| { |
| HANDLE hProc, hSection, hFile; |
| UNICODE_STRING uexe; |
| OBJECT_ATTRIBUTES oa; |
| IO_STATUS_BLOCK iosb; |
| NTSTATUS res; |
| wchar_t abs_path[MAX_PATH]; |
| wchar_t wpath[MAX_PATH]; |
| GET_NTDLL(NtCreateProcess, (OUT PHANDLE ProcessHandle, |
| IN ACCESS_MASK DesiredAccess, |
| IN POBJECT_ATTRIBUTES ObjectAttributes, |
| IN HANDLE InheritFromProcessHandle, |
| IN BOOLEAN InheritHandles, |
| IN HANDLE SectionHandle OPTIONAL, |
| IN HANDLE DebugPort OPTIONAL, |
| IN HANDLE ExceptionPort OPTIONAL)); |
| GET_NTDLL(NtOpenFile, (OUT PHANDLE FileHandle, |
| IN ACCESS_MASK DesiredAccess, |
| IN POBJECT_ATTRIBUTES ObjectAttributes, |
| OUT PIO_STATUS_BLOCK IoStatusBlock, |
| IN ULONG ShareAccess, |
| IN ULONG OpenOptions)); |
| GET_NTDLL(NtCreateSection, (OUT PHANDLE SectionHandle, |
| IN ACCESS_MASK DesiredAccess, |
| IN POBJECT_ATTRIBUTES ObjectAttributes, |
| IN PLARGE_INTEGER SectionSize OPTIONAL, |
| IN ULONG Protect, |
| IN ULONG Attributes, |
| IN HANDLE FileHandle)); |
| |
| /* convert to absolute path which is required for create_process() */ |
| _snwprintf(wpath, BUFFER_SIZE_ELEMENTS(wpath), L"%hs", path); |
| NULL_TERMINATE_BUFFER(wpath); |
| GetFullPathName(wpath, BUFFER_SIZE_ELEMENTS(abs_path), abs_path, NULL); |
| NULL_TERMINATE_BUFFER(abs_path); |
| /* create dummy process */ |
| _snwprintf(wpath, BUFFER_SIZE_ELEMENTS(wpath), L"\\??\\%s", abs_path); |
| NULL_TERMINATE_BUFFER(wpath); |
| INFO(2, "dummy exe path = %ls\n", wpath); |
| wchar_to_unicode(&uexe, wpath); |
| InitializeObjectAttributes(&oa, &uexe, OBJ_CASE_INSENSITIVE, NULL, NULL); |
| if (!NT_SUCCESS(NtOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb, |
| FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT))) { |
| WARN("failed to open dummy process exe file\n"); |
| exit(0); |
| } |
| oa.ObjectName = 0; |
| res = NtCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, |
| 0, PAGE_EXECUTE, SEC_IMAGE, hFile); |
| if (!NT_SUCCESS(res)) { |
| WARN("failed to create section with error=0x%08x\n", res); |
| } |
| res = NtCreateProcess(&hProc, PROCESS_ALL_ACCESS, &oa, |
| GetCurrentProcess(), true, hSection, 0, 0); |
| if (!NT_SUCCESS(res)) { |
| WARN("failed to create dummy process with error=0x%08x\n", res); |
| exit(0); |
| } |
| return hProc; |
| } |
| |
| static void *highest_address_copied = NULL; |
| static bool reached_vsyscall_page = false; |
| |
| /* if just_mapped then only copies over mapped regions, otherwise copies over |
| * non-mapped regions only */ |
| static void |
| copy_memory(FILE *file, bool just_mapped, HANDLE hProc) |
| { |
| MEMORY_BASIC_INFORMATION mbi; |
| DWORD res; |
| fpos_t pos; |
| bool do_copy; |
| while (true) { |
| res = fgetpos(file, &pos); |
| assert(res == 0); |
| res = read_mbi(file, &mbi); |
| if (!res) |
| break; |
| if (mbi.State != MEM_FREE) { |
| char *allocation_base = (char *)mbi.AllocationBase; |
| uint allocation_protect = mbi.AllocationProtect; |
| uint allocation_size = 0; |
| char *target; |
| fpos_t last_mbi_pos; /* for unknown TEB backing out */ |
| |
| /* we can't handle write copy flag! remove it */ |
| allocation_protect = remove_writecopy(allocation_protect); |
| INFO(2, "allocation base = 0x%08x, protect = 0x%08x\n", |
| allocation_base, allocation_protect); |
| |
| do { |
| allocation_size += mbi.RegionSize; |
| /* scan past data */ |
| if (prot_is_readable(mbi.Protect)) { |
| /* FIXME : hack, rc1 core doesn't check for guard page so |
| * sometimes memory is copied and sometimes it isn't if |
| * is guard! (why?), post rc1 should check |
| * MEM_COMMIT && !guard && is_readable */ |
| assert(mbi.State == MEM_COMMIT); |
| if (!TEST(PAGE_GUARD, mbi.Protect)) { |
| res = fseek(file, mbi.RegionSize, SEEK_CUR); |
| assert(res == 0); |
| } else { |
| #if RC1_HACK |
| fpos_t hack_pos; |
| MEMORY_BASIC_INFORMATION hack_mbi; |
| res = fgetpos(file, &hack_pos); |
| assert(res == 0); |
| if (!read_mbi(file, &hack_mbi)) { |
| res = fsetpos(file, &hack_pos); |
| assert(res == 0); |
| res = fseek(file, mbi.RegionSize, SEEK_CUR); |
| assert(res == 0); |
| } else { |
| res = fsetpos(file, &hack_pos); |
| assert(res == 0); |
| } |
| #endif |
| } |
| } |
| res = fgetpos(file, &last_mbi_pos); |
| assert(res == 0); |
| } while (read_mbi(file, &mbi) && |
| mbi.State != MEM_FREE && |
| mbi.AllocationBase == allocation_base); |
| |
| /* see if we should copy over this allocation */ |
| target = get_mapped_addr(allocation_base); |
| do_copy = (just_mapped && target != FAIL) || |
| (!just_mapped && target == FAIL && |
| !is_pending_mapped_addr(allocation_base)); |
| if (!do_copy) { |
| res = fsetpos(file, &last_mbi_pos); |
| assert(res == 0); |
| continue; |
| } |
| |
| /* get allocation */ |
| if (target == FAIL) { |
| if (!ALIGNED(allocation_base, ALLOCATION_GRANULARITY)) { |
| /* probably a TEB for a thread that wasn't in the all |
| * threads list at time of ldmp */ |
| WARN("Probable TEB for unknown thread region (or x64 PEB/TEB?) " |
| "addr 0x%08x size 0x%08x\n", |
| allocation_base, allocation_size); |
| } |
| target = allocation_base; |
| /* FIXME : why can't we use VirtualAllocEx? it fails with |
| * code 487 == INVALID_ADDRESS */ |
| res = nt_remote_allocate_virtual_memory(hProc, &target, |
| allocation_size, |
| allocation_protect, |
| MEMORY_COMMIT); |
| if (!res) { |
| target = NULL; |
| res = nt_remote_allocate_virtual_memory(hProc, &target, |
| allocation_size, |
| allocation_protect, |
| MEMORY_COMMIT); |
| } |
| if (!res) { |
| WARN("ERROR: unable to allocate memory at 0x%08x size 0x%08x, SKIPPING\n", |
| allocation_base, allocation_size); |
| res = fsetpos(file, &last_mbi_pos); |
| assert(res == 0); |
| continue; |
| } |
| |
| assert(target != NULL); |
| if (target != allocation_base) { |
| WARN("ERROR: unable to allocate memory at 0x%08x size 0x%08x\n\t will be copied to 0x%08x instead\n", |
| allocation_base, allocation_size, target); |
| } |
| INFO(2, "target = 0x%08x, base = 0x%08x\n", target, allocation_base); |
| } |
| INFO(2, "size=0x%08x\n", allocation_size); |
| INFO(2, "target=0x%08x\n", target); |
| |
| /* reset file pointer */ |
| res = fsetpos(file, &pos); |
| assert(res == 0); |
| |
| /* walk memory */ |
| #define TARGET_ADDR (target + ((char *)mbi.BaseAddress - allocation_base)) |
| res = fgetpos(file, &pos); |
| assert(res == 0); |
| while (read_mbi(file, &mbi) && mbi.State != MEM_FREE && |
| mbi.AllocationBase == allocation_base) { |
| if ((uint)mbi.BaseAddress > (uint)highest_address_copied) |
| highest_address_copied = mbi.BaseAddress; |
| if (mbi.State == MEM_RESERVE) { |
| res = VirtualFreeEx(hProc, TARGET_ADDR, mbi.RegionSize, |
| MEM_DECOMMIT); |
| if (!res && TARGET_ADDR == (char *)0x7ffe1000) { |
| WARN("unable to make post vsyscall/shared user data page 0x7ffe1000 reserve, skipping\n"); |
| /* in case break out, save position */ |
| res = fgetpos(file, &pos); |
| assert(res == 0); |
| continue; |
| } |
| assert(res); |
| } else { |
| uint old_prot; |
| assert(mbi.State = MEM_COMMIT); |
| if (!TEST(PAGE_GUARD, mbi.Protect) && |
| prot_is_readable(mbi.Protect)) { |
| uint i; |
| /* copy over memory */ |
| res = VirtualProtectEx(hProc, TARGET_ADDR, |
| mbi.RegionSize, PAGE_READWRITE, |
| &old_prot); |
| if (!res && TARGET_ADDR == (char *)0x7ffe0000) { |
| WARN("unable to copy over vsyscall/shared user data page 0x7ffe0000, skipping\n"); |
| reached_vsyscall_page = true; |
| res = fseek(file, mbi.RegionSize, SEEK_CUR); |
| assert(res == 0); |
| /* in case break out, save position */ |
| res = fgetpos(file, &pos); |
| assert(res == 0); |
| continue; |
| } |
| assert(res); |
| assert(mbi.RegionSize % sizeof(buf) == 0); |
| for (i = 0; i < mbi.RegionSize; i += sizeof(buf)) { |
| uint written; |
| res = fread(buf, 1, sizeof(buf), file); |
| assert(res == sizeof(buf)); |
| res = WriteProcessMemory(hProc, TARGET_ADDR+i, buf, |
| sizeof(buf), &written); |
| assert(res && written == sizeof(buf)); |
| } |
| } else { |
| #if RC1_HACK |
| /* FIXME : rc1 hack, might have memory from guard page |
| * somehow */ |
| fpos_t hack_pos; |
| MEMORY_BASIC_INFORMATION hack_mbi; |
| res = fgetpos(file, &hack_pos); |
| assert(res == 0); |
| if (!read_mbi(file, &hack_mbi)) { |
| res = fsetpos(file, &hack_pos); |
| assert(res == 0); |
| res = fseek(file, mbi.RegionSize, SEEK_CUR); |
| assert(res == 0); |
| } else { |
| res = fsetpos(file, &hack_pos); |
| assert(res == 0); |
| } |
| #endif |
| } |
| res = VirtualProtectEx(hProc, TARGET_ADDR, mbi.RegionSize, |
| remove_writecopy(mbi.Protect), |
| &old_prot); |
| assert(res); |
| } |
| /* in case break out, save position */ |
| res = fgetpos(file, &pos); |
| assert(res == 0); |
| } |
| /* roll back last mbi */ |
| res = fsetpos(file, &pos); |
| assert(res == 0); |
| #undef TARGET_ADDR |
| } |
| } |
| } |
| |
| static void |
| usage(const char *msg) |
| { |
| PRINT("%s\nusage: ldmp [-verbose <N>] <.ldmp file> <dummy executable>\n", |
| msg); |
| PRINT("example: bin32/ldmp logs/hello.exe.5124.0000000.ldmp bin32/dummy.exe\n"); |
| exit(-1); |
| } |
| |
| /* creates a debuggable process that matches (roughly) the process that was |
| * used to generate the .ldmp file, prints out a mapping of thread_ids from |
| * the dump to the new process. |
| * ldmp.exe <.ldmp file> <windows path to dummy.exe (absolute, local drive)> */ |
| DWORD __cdecl |
| main(DWORD argc, char *argv[], char *envp[]) |
| { |
| FILE *file; |
| HANDLE hProc; |
| PROCESS_BASIC_INFORMATION info; |
| MEMORY_BASIC_INFORMATION mbi; |
| char *pb; |
| uint drbase = 0; |
| |
| DWORD res; |
| fpos_t thread_start_pos; |
| DWORD i; |
| |
| for (i = 1; i < argc; i++) { |
| if (argv[i][0] == '-') { |
| if (strcmp(argv[i], "-verbose") == 0) { |
| if (i >= argc - 1) |
| usage("-verbose takes an integer"); |
| verbose = atoi(argv[++i]); |
| } else |
| usage("unknown option"); |
| } else |
| break; |
| } |
| if (i >= argc -1) |
| usage("missing arguments"); |
| INFO(1, "opening ldump file %s\n", argv[i]); |
| file = fopen(argv[i], "rb"); |
| if (file == NULL) { |
| WARN("unable to find file %s\n", argv[i]); |
| exit(-1); |
| } |
| |
| hProc = create_process(argv[i+1]); |
| |
| if (GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx")) { |
| /* XXX i#397: on Vista in the past we saw NtCreateThread fail |
| * with STATUS_INVALID_IMAGE_FORMAT, but it seems to be |
| * working now on win7 so we'll stick with it rather than |
| * cobbling together an approach that uses NtCreateThreadEx. |
| */ |
| WARN("ldmp.exe may not work fully on Vista+ (i#397)\n"); |
| } |
| |
| /* read peb (& message if there is one) */ |
| res = fscanf(file, "PEB=0x%08x\n", &pb); |
| if (res != 1) { |
| uint length = 0; |
| /* newer format, starts with a message prefaced with number of bytes |
| * in message */ |
| /* case 8912: don't read a newline here as it will gobble the next newline |
| * if the msg is empty; instead we add 1 to the length and read the newline |
| * w/ the msg. |
| */ |
| res = fscanf(file, "0x%08x", &length); |
| assert(res == 1); |
| length = length + 1; /* newline after length, see comment above */ |
| /* FIXME - should never happen, but we could handle by doing this in |
| * pieces */ |
| assert(length < sizeof(buf)); |
| if (length >= sizeof(buf)) |
| length = sizeof(buf)-1; |
| res = fread(buf, 1, length, file); |
| assert(res == length); |
| buf[length] = '\0'; |
| /* Remember that buf has a newline as its first char */ |
| INFO(1, "\n**************************************************\n" |
| "Message:\n%s\n**************************************************\n", buf); |
| res = fscanf(file, "PEB=0x%08x\n", &pb); |
| } |
| assert(res == 1); |
| res = query_process_info(hProc, &info); |
| assert(res); |
| INFO(1, "\ncreated dummy process pid=%d with PEB=0x%08x original PEB=0x%08x\n", |
| info.UniqueProcessId, info.PebBaseAddress, pb); |
| |
| /* read dynamorio.dll base, present only in format after case 5366 */ |
| if (fscanf(file, "dynamorio.dll=0x%08x\n", &drbase) == 1) { |
| /* for custom scripting */ |
| INFO(1, "\ndynamorio.dll=0x%08x\n", drbase); |
| /* or more likely */ |
| INFO(1, "\nRun this command, or attach non-invasively from an existing windbg:\n" |
| "windbg -pv -p %d -c '.reload dynamorio.dll=0x%08x'\n\n", |
| info.UniqueProcessId, drbase); |
| } |
| |
| add_mapped_addr(pb, (char *)info.PebBaseAddress); |
| /* add shared user data (win2k) / syscall page (xp/03) to map list */ |
| add_mapped_addr((char *)0x7ffe0000, (char *)0x7ffe0000); |
| |
| /* iterate through threads without creating to build up pending |
| * teb mappings */ |
| res = fgetpos(file, &thread_start_pos); |
| assert(res == 0); |
| read_threads(file, false, hProc); |
| |
| INFO(1, "\n"); |
| /* free memory in dummy process */ |
| pb = NULL; |
| while(VirtualQueryEx(hProc, pb, &mbi, sizeof(mbi)) == sizeof(mbi)) { |
| if (mbi.State == MEM_FREE || get_original_addr(mbi.AllocationBase) != FAIL) { |
| if ((uint)mbi.BaseAddress + mbi.RegionSize < (uint)mbi.BaseAddress) |
| break; |
| pb = (char *)mbi.BaseAddress + mbi.RegionSize; |
| } else { |
| if (!VirtualFreeEx(hProc, mbi.AllocationBase, 0, MEM_RELEASE)) { |
| /* try an unmap */ |
| GET_NTDLL(NtUnmapViewOfSection, (IN HANDLE ProcessHandle, |
| IN PVOID BaseAddress)); |
| if (!NT_SUCCESS(NtUnmapViewOfSection(hProc, mbi.AllocationBase))) { |
| if (get_original_addr(mbi.AllocationBase) == FAIL) { |
| /* this happens in wow64 w/ the x64 PEB */ |
| WARN("Unable to free memory region (x64 PEB?):\n"); |
| dump_mbi(&mbi); |
| } |
| pb += mbi.RegionSize; |
| } else { |
| INFO(2, "unmapped allocation at 0x%08x\n", mbi.AllocationBase); |
| } |
| } else { |
| INFO(2, "freed memory at 0x%08x\n", mbi.AllocationBase); |
| } |
| } |
| } |
| INFO(1, "finished freeing memory, starting copy over\n"); |
| |
| /* chomp any error line (such as all threads list freed) */ |
| if (fscanf(file, "<%512[a-zA-Z1-9 ]>", &buf)) |
| WARN("%s\n", buf); |
| |
| /* copy over directly corresponding (non-mapped) memory regions */ |
| copy_memory(file, false, hProc); |
| |
| /* now create threads (can't do this before since we have no control over |
| * where the teb's are placed and they might conflict with one of the |
| * copied over regions above */ |
| res = fsetpos(file, &thread_start_pos); |
| assert(res == 0); |
| read_threads(file, true, hProc); |
| INFO(1, "\n"); |
| |
| /* now copy over mapped allocations */ |
| /* first chomp off any error line, we already printed it once above */ |
| fscanf(file, "<%512[a-zA-Z1-9 ]>", &buf); |
| copy_memory(file, true, hProc); |
| |
| if (!reached_vsyscall_page) { |
| WARN("ERROR: failed to reach shared_user_data/vsyscall page, ldmp likely truncated.\n" |
| " Memory above 0x%08x is likely unavailable or incorrect.\n\n", |
| highest_address_copied); |
| } |
| |
| INFO(1, "finished\n"); |
| CloseHandle(hProc); |
| return 0; |
| } |