#include #include #include #include #include #include #include #include #include #include "win32_debmod.h" //-------------------------------------------------------------------------- #ifndef TH32CS_SNAPNOHEAPS #define TH32CS_SNAPNOHEAPS 0x0 #endif #include "win32_debmod_impl.cpp" #define get_reg_class(reg_idx) x86_get_regclass(reg_idx) typedef HANDLE WINAPI OpenThread_t(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId); static OpenThread_t *_OpenThread = NULL; typedef HRESULT WINAPI GetThreadDescription_t(HANDLE hThread, PWSTR *threadDescription); static GetThreadDescription_t *_GetThreadDescription = NULL; #ifndef __X86__ typedef BOOL WINAPI Wow64GetThreadContext_t(HANDLE hThread, PWOW64_CONTEXT lpContext); typedef BOOL WINAPI Wow64SetThreadContext_t(HANDLE hThread, const WOW64_CONTEXT *lpContext); typedef BOOL WINAPI Wow64GetThreadSelectorEntry_t(HANDLE hThread, DWORD dwSelector, PWOW64_LDT_ENTRY lpSelectorEntry); static Wow64GetThreadContext_t *_Wow64GetThreadContext = NULL; static Wow64SetThreadContext_t *_Wow64SetThreadContext = NULL; static Wow64GetThreadSelectorEntry_t *_Wow64GetThreadSelectorEntry = NULL; #endif // Older SDKs (such as the one used for the ..._opt_s debug servers) // requires these defines #ifndef XSTATE_MASK_AVX # define XSTATE_MASK_AVX (XSTATE_MASK_GSSE) #endif // XSTATE_MASK_AVX #ifndef XSTATE_AVX # define XSTATE_AVX (XSTATE_GSSE) #endif // XSTATE_AVX #define I386_AVX_NREGS 8 #define CONTEXT_XSTATE_BIT 0x40 // https://docs.microsoft.com/en-us/windows/desktop/debug/working-with-xstate-context // The definition of CONTEXT_XSTATE changed across SDKs. On older SDKs, where it would // have another value, it wasn't usable anyway, so it's safe to undefine & re-define it. #ifdef CONTEXT_XSTATE # if ((CONTEXT_XSTATE & CONTEXT_XSTATE_BIT) == 0) # undef CONTEXT_XSTATE # if defined(_M_X64) # define CONTEXT_XSTATE (0x00100040) # else # define CONTEXT_XSTATE (0x00010040) # endif # endif #endif //------------------------------------------------------------------------- // https://docs.microsoft.com/en-us/windows/desktop/debug/working-with-xstate-context bool context_helper_t::create_context(context_holder_t *out, int *_ctxflags) { out->buffer.qclear(); out->buffer.clear(); int ctxsz = sizeof(CONTEXT); #define CONTEXT_REQUIRES_XSTATE() ((*_ctxflags & CONTEXT_XSTATE_BIT) == CONTEXT_XSTATE_BIT) if ( CONTEXT_REQUIRES_XSTATE() && !get_xstate_context_size(&ctxsz) ) *_ctxflags &= ~CONTEXT_XSTATE_BIT; out->buffer.resize(ctxsz); if ( CONTEXT_REQUIRES_XSTATE() ) { if ( pfnInitializeContext( out->buffer.begin(), *_ctxflags, &out->ptr, (DWORD *) &ctxsz) == FALSE ) { return false; } if ( pfnSetXStateFeaturesMask(out->ptr, XSTATE_MASK_AVX) == FALSE ) return false; } else { out->ptr = (PCONTEXT) out->buffer.begin(); out->ptr->ContextFlags = *_ctxflags; } return true; } //------------------------------------------------------------------------- bool context_helper_t::get_xstate_context_size(int *out_ctxsz) { if ( xstate_context_size < 0 ) { xstate_context_size = 0; const char *error = NULL; HINSTANCE h = GetModuleHandle(kernel32_dll); *(FARPROC*) &pfnGetEnabledXStateFeatures = GetProcAddress(h, "GetEnabledXStateFeatures"); *(FARPROC*) &pfnInitializeContext = GetProcAddress(h, "InitializeContext"); *(FARPROC*) &pfnGetXStateFeaturesMask = GetProcAddress(h, "GetXStateFeaturesMask"); *(FARPROC*) &pfnLocateXStateFeature = GetProcAddress(h, "LocateXStateFeature"); *(FARPROC*) &pfnSetXStateFeaturesMask = GetProcAddress(h, "SetXStateFeaturesMask"); *(FARPROC*) &pfnCopyContext = GetProcAddress(h, "CopyContext"); if ( pfnGetEnabledXStateFeatures != NULL && pfnInitializeContext != NULL && pfnGetXStateFeaturesMask != NULL && pfnLocateXStateFeature != NULL && pfnSetXStateFeaturesMask != NULL && pfnCopyContext != NULL ) { DWORD64 feature_mask; feature_mask = pfnGetEnabledXStateFeatures(); if ( (feature_mask & XSTATE_MASK_AVX) != 0 ) { DWORD context_size = 0; BOOL success = pfnInitializeContext( NULL, CONTEXT_ALL | CONTEXT_XSTATE, NULL, &context_size); if ( (success != FALSE) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER) ) error = "InitializeContext failed"; else xstate_context_size = context_size; } else { error = "AVX feature not enabled"; } } else { error = "Couldn't retrieve AVX functions"; } if ( error != NULL ) msg("%s\n", error); } int ctxsz = xstate_context_size; bool ok = ctxsz > 0; if ( ok ) *out_ctxsz = ctxsz; return ok; } //------------------------------------------------------------------------- void context_helper_t::clear() { memset(this, 0, sizeof(*this)); xstate_context_size = -1; } //-------------------------------------------------------------------------- // Macro to test the DBG_FLAG_DONT_DISTURB flag #if 0 #define NODISTURB_ASSERT(x) QASSERT(x) #else #define NODISTURB_ASSERT(x) #endif static int g_code = 0; //-------------------------------------------------------------------------- void win32_debmod_t::check_thread(bool must_be_main_thread) const { // remote debugger uses only one thread if ( rpc != NULL ) return; // someone turned off debthread? if ( (debugger_flags & DBG_FLAG_DEBTHREAD) == 0 ) return; // local debugger uses 2 threads and we must be in the correct one QASSERT(30191, is_main_thread() == must_be_main_thread); } //-------------------------------------------------------------------------- static int utf16_to_utf8(char *buf, size_t bufsize, LPCWSTR unicode) { qstring res; utf16_utf8(&res, unicode); qstrncpy(buf, res.c_str(), bufsize); size_t n = res.length(); if ( n > bufsize ) n = bufsize; return (int)n; } //-------------------------------------------------------------------------- // try to locate full path of a dll name without full path // for example, toolhelp.dll -> c:\windows\toolhelp.dll static bool find_full_path(char *fname, size_t fnamesize, const char *process_path) { if ( fname[0] != '\0' && !qisabspath(fname) ) { char path[QMAXPATH]; char dir[QMAXPATH]; // check system directory GetSystemDirectory(dir, sizeof(dir)); qmakepath(path, sizeof(path), dir, fname, NULL); if ( qfileexist(path) ) { FOUND: qstrncpy(fname, path, fnamesize); return true; } // check current process directory if ( process_path[0] != '\0' && !qisabspath(process_path) ) { qdirname(dir, sizeof(dir), process_path); qmakepath(path, sizeof(path), dir, fname, NULL); if ( qfileexist(path) ) goto FOUND; } // check current directory if ( GetCurrentDirectory(sizeof(dir), dir) != 0 ) { qmakepath(path, sizeof(path), dir, fname, NULL); if ( qfileexist(path) ) goto FOUND; } return false; } return true; } //-------------------------------------------------------------------------- ssize_t win32_debmod_t::access_memory(eanat_t ea, void *buffer, ssize_t size, bool do_write, bool suspend) { if ( process_handle == INVALID_HANDLE_VALUE ) return -1; NODISTURB_ASSERT(in_event != NULL || exiting); // stop all threads before accessing its memory if ( suspend ) suspend_all_threads(); ea = s0tops(ea); void *addr = (void *)ea; DWORD_PTR size_access = 0; const DWORD BADPROT = DWORD(-1); DWORD oldprotect = BADPROT; bool ok; while ( true ) { // try to access the memory ok = do_write ? WriteProcessMemory( process_handle, // handle of the process whose memory is accessed addr, // address to start access buffer, // address of buffer (DWORD)size, // number of bytes to access (PDWORD_PTR)&size_access) != 0// address of number of bytes accessed : ReadProcessMemory( process_handle, // handle of the process whose memory is accessed addr, // address to start access buffer, // address of buffer (DWORD)size, // number of bytes to access (PDWORD_PTR)&size_access) != 0;// address of number of bytes accessed // if we have changed the page protection, revert it if ( oldprotect != BADPROT ) { if ( !VirtualProtectEx( process_handle, // handle of the process whose memory is accessed addr, // address to start access (DWORD)size, // number of bytes to access oldprotect, &oldprotect) ) { deberr("VirtualProtectEx2(%p)", addr); } break; // do not attempt more than once } // bail out after a successful read/write if ( ok ) break; // bail out if it is not about "not enough access rights" // *or* ERROR_PARTIAL_COPY as, sometimes we may read/write // *only* parts of memory because of page breakpoints int code = GetLastError(); if ( code != ERROR_NOACCESS && code != ERROR_PARTIAL_COPY ) { deberr("%sProcessMemory(%p)", do_write ? "Write" : "Read", ea); break; } if ( code != ERROR_PARTIAL_COPY ) size_access = 0; // size_access may be spoiled after failed ReadProcessMemory // check if the address is valid MEMORY_BASIC_INFORMATION meminfo; if ( !VirtualQueryEx(process_handle, // handle of process addr, // address of region &meminfo, // address of information buffer sizeof(meminfo)) ) // size of buffer { size_access = 0; break; } // allow the desired access on the page if ( !VirtualProtectEx( process_handle, // handle of the process whose memory is accessed addr, // address to start access (DWORD)size, // number of bytes to access do_write ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ, &oldprotect) ) { deberr("VirtualProtectEx1(%08p, size=%d for %s)", ea, int(size), do_write ? "write" : "read"); break; } } if ( do_write && ok ) FlushInstructionCache( process_handle, // handle to process with cache to flush addr, // pointer to region to flush (DWORD)size); // length of region to flush if ( suspend ) resume_all_threads(); return size_access; } //-------------------------------------------------------------------------- ssize_t win32_debmod_t::_read_memory(eanat_t ea, void *buffer, size_t size, bool suspend) { return access_memory(ea, buffer, size, false, suspend); } ssize_t idaapi win32_debmod_t::dbg_read_memory(ea_t ea, void *buffer, size_t size, qstring * /*errbuf*/) { check_thread(false); return _read_memory(ea, buffer, size, true); } //-------------------------------------------------------------------------- // Make sure that the thread is suspended // by calling SuspendThread twice // If raw=true then SuspendThread() API will be called and we return directly // without doing any further logic static void _sure_suspend_thread(thread_info_t &ti, bool raw = false) { HANDLE h = ti.hThread; int count = SuspendThread(h); if ( raw ) return; if ( count != -1 ) ti.suspend_count++; count = SuspendThread(h); if ( count != -1 ) ti.suspend_count++; } //-------------------------------------------------------------------------- // Resume thread by calling ResumeThread as many times as required // Note: this function just reverts the actions of sure_suspend_thread // If the thread was already suspended before calling sure_suspend_thread // then it will stay in the suspended state // If raw=true then ResumeThread() will be called and we return directly // without doing any further logic static void _sure_resume_thread(thread_info_t &ti, bool raw = false) { HANDLE h = ti.hThread; if ( raw ) { ResumeThread(h); return; } while ( ti.suspend_count > 0 ) { ResumeThread(h); ti.suspend_count--; } } //-------------------------------------------------------------------------- inline void win32_debmod_t::suspend_all_threads(bool raw) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) _sure_suspend_thread(p->second, raw); } //-------------------------------------------------------------------------- inline void win32_debmod_t::resume_all_threads(bool raw) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) _sure_resume_thread(p->second, raw); } //-------------------------------------------------------------------------- static int get_thread_suspend_count(HANDLE hThread) { DWORD dwSuspendCount = SuspendThread(hThread); ResumeThread(hThread); return dwSuspendCount; } //-------------------------------------------------------------------------- inline void win32_debmod_t::suspend_running_threads(threadvec_t &suspended) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { thread_info_t t = p->second; t.suspend_count = get_thread_suspend_count(t.hThread); if ( t.suspend_count == 0 ) { _sure_suspend_thread(t); suspended.push_back(t); } } } //-------------------------------------------------------------------------- inline void win32_debmod_t::resume_suspended_threads(threadvec_t suspended) const { threadvec_t::iterator p; for ( p = suspended.begin(); p != suspended.end(); ++p ) _sure_resume_thread(*p); } //-------------------------------------------------------------------------- size_t win32_debmod_t::add_dll(image_info_t &ii) { dlls.insert(std::make_pair(ii.base, ii)); dlls_to_import.insert(ii.base); return (size_t)ii.imagesize; } //-------------------------------------------------------------------------- // is modname already present in the loaded module list? bool win32_debmod_t::module_present(const char *modname) { // host process is not added to dlls, so check it first if ( process_path == modname ) return true; // check DLLs which we already know about for ( const auto &p : dlls ) { const image_info_t &ii = p.second; if ( ii.name == modname ) return true; } return false; } //-------------------------------------------------------------------------- // iterate all modules of the specified process // until the callback returns != 0 int win32_debmod_t::for_each_module(DWORD _pid, module_cb_t module_cb, void *ud) { int code = 0; module_snapshot_t msnap(get_tool_help()); MODULEENTRY32 me; for ( bool ok = msnap.first(TH32CS_SNAPNOHEAPS, _pid, &me); ok; ok = msnap.next(&me) ) { code = module_cb(this, &me, ud); if ( code != 0 ) break; } return code; } //-------------------------------------------------------------------------- // callback: get info about the main module of the debugger process //lint -e{818} int win32_debmod_t::get_dmi_cb(debmod_t *sess, MODULEENTRY32 *me32, void *ud) { win32_debmod_t *_this = (win32_debmod_t *)sess; // if the module name doesn't correspond to the process name, // we continue to iterate char buf[QMAXPATH]; tchar_utf8(buf, me32->szModule, sizeof(buf)); if ( !_this->process_path.empty() && stricmp(buf, qbasename(_this->process_path.c_str())) != 0 ) return 0; // ok, this module corresponds to our debugged process modinfo_t &dmi = *(modinfo_t *)ud; dmi.name = _this->process_path; dmi.base = EA_T(me32->modBaseAddr); dmi.size = (asize_t)me32->modBaseSize; return 1; // we stop to iterate } //-------------------------------------------------------------------------- // Return module information on the currently debugged process void win32_debmod_t::get_debugged_module_info(modinfo_t *dmi) { dmi->name[0] = '\0'; dmi->base = BADADDR; dmi->size = 0; dmi->rebase_to = BADADDR; for_each_module(pid, get_dmi_cb, dmi); } //-------------------------------------------------------------------------- void idaapi win32_debmod_t::dbg_stopped_at_debug_event( import_infos_t *, bool dlls_added, thread_name_vec_t *thr_names) { if ( dlls_added ) { check_thread(true); // we will take advantage of this event to import information // about the exported functions from the loaded dlls and the // binary itself name_info_t &ni = *get_debug_names(); if ( !binary_to_import.name.empty() ) { const char *bin_path = binary_to_import.name.c_str(); linput_t *li = open_linput(bin_path, false); if ( li != NULL ) { get_pe_exports_from_path(bin_path, li, binary_to_import.base, ni); close_linput(li); } binary_to_import.name.clear(); } for ( easet_t::iterator p = dlls_to_import.begin(); p != dlls_to_import.end(); ) { get_dll_exports(dlls, *p, ni); p = dlls_to_import.erase(p); } } if ( thr_names != NULL ) update_thread_names(thr_names); } //-------------------------------------------------------------------------- static void update_thread_description(thread_info_t &ti) { if ( _GetThreadDescription == NULL ) return; PWSTR data; HRESULT hr = _GetThreadDescription(ti.hThread, &data); if ( SUCCEEDED(hr) ) { int data_sz = wcslen(data) * 2; // size in bytes qstring newname; if ( convert_encoding( (bytevec_t*)&newname, ENC_UTF16LE, ENC_UTF8, (const uchar *)data, data_sz) > 0 ) { if ( newname != ti.name ) { ti.name = newname.c_str(); // newname over allocated ti.set_new_name(); } } LocalFree(data); } } //-------------------------------------------------------------------------- void win32_debmod_t::update_thread_names(thread_name_vec_t *thr_names) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { thread_info_t &ti = p->second; update_thread_description(ti); if ( ti.is_new_name() ) { thread_name_t &tn = thr_names->push_back(); tn.tid = ti.tid; tn.name = ti.name; ti.clr_new_name(); } } } //-------------------------------------------------------------------------- // return the address of an exported name ea_t win32_debmod_t::get_dll_export( const images_t &_dlls, ea_t imagebase, const char *exported_name) { ea_t ret = BADADDR; name_info_t ni; if ( get_dll_exports(_dlls, imagebase, ni, exported_name) && !ni.addrs.empty() ) ret = ni.addrs[0]; return ret; } //-------------------------------------------------------------------------- win32_debmod_t::win32_debmod_t() : expecting_debug_break(0), stop_at_ntdll_bpts(false) { fake_suspend_event = false; pid = -1; // Reset handles process_handle = INVALID_HANDLE_VALUE; thread_handle = INVALID_HANDLE_VALUE; redirin_handle = INVALID_HANDLE_VALUE; redirout_handle = INVALID_HANDLE_VALUE; attach_evid = INVALID_HANDLE_VALUE; attach_status = as_none; memset(&cpdi, 0, sizeof(cpdi)); cpdi.hFile = INVALID_HANDLE_VALUE; // hFile cpdi.hProcess = INVALID_HANDLE_VALUE; // hProcess cpdi.hThread = INVALID_HANDLE_VALUE; // hThread winxp_step_thread = 0; memset(&in_event, 0, sizeof(in_event)); exiting = false; pause_requested = false; broken_event_handle = NULL; // we don't set platform name here because it will be inherited // from winbase_debmod_t binary_to_import.base = BADADDR; binary_to_import.size = 0; binary_to_import.rebase_to = BADADDR; } //---------------------------------------------------------------------- // return the handle associated with a thread id from the threads list HANDLE win32_debmod_t::get_thread_handle(thid_t tid) { thread_info_t *tinfo = threads.get(tid); return tinfo == NULL ? INVALID_HANDLE_VALUE : tinfo->hThread; } //-------------------------------------------------------------------------- bool win32_debmod_t::refresh_hwbpts(void) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) set_hwbpts(p->second.hThread); return true; } //-------------------------------------------------------------------------- // 1-ok, 0-failed int idaapi win32_debmod_t::dbg_del_bpt(bpttype_t type, ea_t ea, const uchar *orig_bytes, int len) { check_thread(false); if ( orig_bytes != NULL ) { bool ok = false; bpts.erase(ea); suspend_all_threads(); // write the old value only if our bpt is still present if ( !has_bpt_at(ea) ) { if ( !exiting ) dmsg("%a: breakpoint vanished from memory\n", ea); ok = true; } else { ok = _write_memory(ea, orig_bytes, len) == len; } resume_all_threads(); return ok; } // try to delete a page bpt first if ( del_page_bpt(ea, type) ) return true; return del_hwbpt(ea, type); } //-------------------------------------------------------------------------- ssize_t win32_debmod_t::_write_memory(eanat_t ea, const void *buffer, size_t size, bool suspend) { if ( !may_write(ea) ) return -1; return access_memory(ea, (void *)buffer, size, true, suspend); } //-------------------------------------------------------------------------- void idaapi win32_debmod_t::dbg_term(void) { check_thread(true); cleanup_hwbpts(); cleanup(); for ( size_t i = 0; i < pdb_remote_sessions.size(); ++i ) close_pdb_remote_session(pdb_remote_sessions[i]); inherited::dbg_term(); } //-------------------------------------------------------------------------- bool win32_debmod_t::has_bpt_at(ea_t ea) { uchar bytes[8]; int size = bpt_code.size(); return _read_memory(ea, bytes, size) == size && memcmp(bytes, bpt_code.begin(), size) == 0; } //-------------------------------------------------------------------------- // 2-ok(pagebpt), 1-ok, 0-failed, -2-read failed int idaapi win32_debmod_t::dbg_add_bpt( bytevec_t *orig_bytes, bpttype_t type, ea_t ea, int len) { check_thread(false); if ( type == BPT_SOFT ) { if ( len <= 0 ) len = bpt_code.size(); if ( orig_bytes != NULL && read_bpt_orgbytes(orig_bytes, ea, len) < 0 ) return -2; debmod_bpt_t dbpt(ea, len); if ( !dbg_read_memory(ea, dbpt.saved, len, NULL) ) return -2; int size = bpt_code.size(); if ( dbg_write_memory(ea, bpt_code.begin(), size, NULL) != size ) return 0; bpts[ea] = dbpt; return 1; } // try, first, to add a real hw breakpoint // if it fails, add a memory range type bpt // reason: the user may try to insert a 5th // correct hw bpt, however, it isn't possible // so, instead, we add a page breakpoint int ret = 0; if ( check_x86_hwbpt(type, ea, len) == BPT_OK ) ret = add_hwbpt(type, ea, len); if ( !ret ) ret = dbg_add_page_bpt(type, ea, len); return ret; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_get_memory_info(meminfo_vec_t &ranges, qstring * /*errbuf*/) { check_thread(false); NODISTURB_ASSERT(in_event != NULL); images.clear(); thread_ranges.clear(); class_ranges.clear(); for ( threads_t::iterator t=threads.begin(); t != threads.end(); ++t ) add_thread_ranges(t->first, thread_ranges, class_ranges); if ( process_handle != INVALID_HANDLE_VALUE ) { for ( ea_t ea=0; ea != BADADDR; ) { memory_info_t meminf; ea = get_region_info(ea, &meminf); if ( ea != BADADDR && meminf.start_ea != BADADDR ) ranges.push_back(meminf); } enable_page_bpts(true); } if ( same_as_oldmemcfg(ranges) ) return DRC_NOCHG; save_oldmemcfg(ranges); return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_thread_suspend(thid_t tid) { check_thread(false); NODISTURB_ASSERT(in_event != NULL); int count = SuspendThread(get_thread_handle(tid)); if ( count == -1 ) deberr("SuspendThread(%08X)", tid); if ( debug_debugger ) debdeb("SuspendThread(%08X) -> %d\n", tid, count); return count != -1 ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_thread_continue(thid_t tid) { check_thread(false); NODISTURB_ASSERT(in_event != NULL); int count = ResumeThread(get_thread_handle(tid)); if ( count == -1 ) deberr("ResumeThread(%08X)", tid); if ( debug_debugger ) debdeb("ResumeThread(%08X) -> %d\n", tid, count); return count != -1 ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- static int calc_ctxflags(int clsmask) { int ctxflags = CONTEXT_CONTROL|CONTEXT_INTEGER; if ( (clsmask & X86_RC_SEGMENTS) != 0 ) ctxflags |= CONTEXT_SEGMENTS; if ( (clsmask & (X86_RC_FPU|X86_RC_MMX)) != 0 ) ctxflags |= CONTEXT_FLOATING_POINT; if ( (clsmask & X86_RC_XMM) != 0 ) #ifndef __X86__ ctxflags |= CONTEXT_FLOATING_POINT; #else ctxflags |= CONTEXT_EXTENDED_REGISTERS; #endif if ( (clsmask & X86_RC_YMM) != 0 ) #ifndef __X86__ ctxflags |= CONTEXT_XSTATE|CONTEXT_FLOATING_POINT; #else ctxflags |= CONTEXT_XSTATE|CONTEXT_EXTENDED_REGISTERS; #endif return ctxflags; } //-------------------------------------------------------------------------- // assertions that ensure that the conversion between CONTEXT and WOW64_CONTEXT // is correct #ifdef __X86__ CASSERT(sizeof(FLOATING_SAVE_AREA) == 112); CASSERT(CONTEXT_CONTROL == 0x10001); CASSERT(CONTEXT_INTEGER == 0x10002); CASSERT(CONTEXT_SEGMENTS == 0x10004); CASSERT(CONTEXT_FLOATING_POINT == 0x10008); CASSERT(CONTEXT_DEBUG_REGISTERS == 0x10010); CASSERT(CONTEXT_EXTENDED_REGISTERS == 0x10020); #else CASSERT(sizeof(WOW64_FLOATING_SAVE_AREA) == 112); CASSERT(sizeof(XSAVE_FORMAT) == WOW64_MAXIMUM_SUPPORTED_EXTENSION); CASSERT(WOW64_CONTEXT_CONTROL == 0x10001); CASSERT(WOW64_CONTEXT_INTEGER == 0x10002); CASSERT(WOW64_CONTEXT_SEGMENTS == 0x10004); CASSERT(WOW64_CONTEXT_FLOATING_POINT == 0x10008); CASSERT(WOW64_CONTEXT_DEBUG_REGISTERS == 0x10010); CASSERT(WOW64_CONTEXT_EXTENDED_REGISTERS == 0x10020); #endif #ifndef __X86__ //-------------------------------------------------------------------------- inline int ctxflags_to_wow64(int ctxflags) { if ( (ctxflags & CONTEXT_FLOATING_POINT) != 0 ) ctxflags |= WOW64_CONTEXT_EXTENDED_REGISTERS; // XSTATE in WOW64 is handled in a separate context bool requires_xstate = (ctxflags & CONTEXT_XSTATE) == CONTEXT_XSTATE; if ( requires_xstate ) ctxflags |= WOW64_CONTEXT_FLOATING_POINT; ctxflags &= ~CONTEXT_XSTATE_BIT; ctxflags &= ~CONTEXT_AMD64; ctxflags |= WOW64_CONTEXT_i386; return ctxflags; // see CASSERTs anout CONTEXT_... bits above } //-------------------------------------------------------------------------- void thread_info_t::cvt_from_wow64( const WOW64_CONTEXT &wow64ctx, int clsmask, PCONTEXT xstate_ctx) const { CONTEXT &ctx = get_ctx(); ctx.ContextFlags = calc_ctxflags(clsmask); if ( xstate_ctx != NULL && debmod->context_helper.xstate_helpers_loaded() ) debmod->context_helper.pfnCopyContext(&ctx, CONTEXT_XSTATE, xstate_ctx); ctx.Dr0 = wow64ctx.Dr0; ctx.Dr1 = wow64ctx.Dr1; ctx.Dr2 = wow64ctx.Dr2; ctx.Dr3 = wow64ctx.Dr3; ctx.Dr6 = wow64ctx.Dr6; ctx.Dr7 = wow64ctx.Dr7; ctx.SegGs = wow64ctx.SegGs; ctx.SegFs = wow64ctx.SegFs; ctx.SegEs = wow64ctx.SegEs; ctx.SegDs = wow64ctx.SegDs; ctx.Rdi = wow64ctx.Edi; ctx.Rsi = wow64ctx.Esi; ctx.Rbx = wow64ctx.Ebx; ctx.Rdx = wow64ctx.Edx; ctx.Rcx = wow64ctx.Ecx; ctx.Rax = wow64ctx.Eax; ctx.Rbp = wow64ctx.Ebp; ctx.SegCs = wow64ctx.SegCs; ctx.EFlags = wow64ctx.EFlags; ctx.SegSs = wow64ctx.SegSs; ctx.Rip = wow64ctx.Eip; ctx.Rsp = wow64ctx.Esp; if ( (wow64ctx.ContextFlags & WOW64_CONTEXT_EXTENDED_REGISTERS) != 0 ) { memcpy(&ctx.FltSave, wow64ctx.ExtendedRegisters, sizeof(ctx.FltSave)); } else if ( (wow64ctx.ContextFlags & WOW64_CONTEXT_FLOATING_POINT) != 0 ) { ctx.FltSave.ControlWord = wow64ctx.FloatSave.ControlWord; ctx.FltSave.StatusWord = wow64ctx.FloatSave.StatusWord; ctx.FltSave.TagWord = wow64ctx.FloatSave.TagWord; ctx.FltSave.ErrorOffset = wow64ctx.FloatSave.ErrorOffset; ctx.FltSave.ErrorSelector = wow64ctx.FloatSave.ErrorSelector; ctx.FltSave.DataOffset = wow64ctx.FloatSave.DataOffset; ctx.FltSave.DataSelector = wow64ctx.FloatSave.DataSelector; memset(ctx.FltSave.FloatRegisters, 0, sizeof(ctx.FltSave.FloatRegisters)); for ( int i=0; i < 8; i++ ) memcpy(&ctx.FltSave.FloatRegisters[i], &wow64ctx.FloatSave.RegisterArea[i*10], 10); } } //-------------------------------------------------------------------------- void thread_info_t::cvt_to_wow64( WOW64_CONTEXT *wow64ctx, PCONTEXT xstate_ctx, int clsmask) const { int cflags = calc_ctxflags(clsmask); int wflags = ctxflags_to_wow64(cflags); wow64ctx->ContextFlags = wflags; CONTEXT &ctx = get_ctx(); if ( xstate_ctx != NULL && debmod->context_helper.xstate_helpers_loaded() ) debmod->context_helper.pfnCopyContext(xstate_ctx, CONTEXT_XSTATE, &ctx); wow64ctx->Dr0 = ctx.Dr0; wow64ctx->Dr1 = ctx.Dr1; wow64ctx->Dr2 = ctx.Dr2; wow64ctx->Dr3 = ctx.Dr3; wow64ctx->Dr6 = ctx.Dr6; wow64ctx->Dr7 = ctx.Dr7; wow64ctx->SegGs = ctx.SegGs; wow64ctx->SegFs = ctx.SegFs; wow64ctx->SegEs = ctx.SegEs; wow64ctx->SegDs = ctx.SegDs; wow64ctx->Edi = ctx.Rdi; wow64ctx->Esi = ctx.Rsi; wow64ctx->Ebx = ctx.Rbx; wow64ctx->Edx = ctx.Rdx; wow64ctx->Ecx = ctx.Rcx; wow64ctx->Eax = ctx.Rax; wow64ctx->Ebp = ctx.Rbp; wow64ctx->SegCs = ctx.SegCs; wow64ctx->EFlags = ctx.EFlags; wow64ctx->SegSs = ctx.SegSs; wow64ctx->Eip = ctx.Rip; wow64ctx->Esp = ctx.Rsp; if ( (wflags & WOW64_CONTEXT_FLOATING_POINT) != 0 ) { wow64ctx->FloatSave.ControlWord = ctx.FltSave.ControlWord; wow64ctx->FloatSave.StatusWord = ctx.FltSave.StatusWord; wow64ctx->FloatSave.TagWord = ctx.FltSave.TagWord; wow64ctx->FloatSave.ErrorOffset = ctx.FltSave.ErrorOffset; wow64ctx->FloatSave.ErrorSelector = ctx.FltSave.ErrorSelector; wow64ctx->FloatSave.DataOffset = ctx.FltSave.DataOffset; wow64ctx->FloatSave.DataSelector = ctx.FltSave.DataSelector; for ( int i=0; i < 8; i++ ) memcpy(&wow64ctx->FloatSave.RegisterArea[i*10], &ctx.FltSave.FloatRegisters[i], 10); } if ( (wflags & WOW64_CONTEXT_EXTENDED_REGISTERS) != 0 ) memcpy(wow64ctx->ExtendedRegisters, &ctx.FltSave, sizeof(ctx.FltSave)); } #define Eip Rip #endif //-------------------------------------------------------------------------- bool thread_info_t::read_context(int clsmask) { if ( (flags & clsmask) == clsmask ) return true; int ctxflags = calc_ctxflags(clsmask); if ( !debmod->context_helper.create_context(&ctx_holder, &ctxflags) ) return false; #ifndef __X86__ if ( (flags & THR_WOW64) != 0 && _Wow64GetThreadContext != NULL ) { WOW64_CONTEXT wow64ctx; wow64ctx.ContextFlags = ctxflags_to_wow64(ctxflags); if ( !_Wow64GetThreadContext(hThread, &wow64ctx) ) return false; context_holder_t xstate_ctx; if ( (clsmask & X86_RC_YMM) != 0 ) { int xstate_ctxflags = CONTEXT_XSTATE; if ( !debmod->context_helper.create_context(&xstate_ctx, &xstate_ctxflags) ) return false; if ( !GetThreadContext(hThread, xstate_ctx.ptr) ) return false; } invalidate_context(); cvt_from_wow64(wow64ctx, clsmask, xstate_ctx.ptr); } else #endif { CONTEXT &ctx = get_ctx(); ctx.ContextFlags = ctxflags; if ( !GetThreadContext(hThread, &ctx) ) return false; invalidate_context(); ctx.ContextFlags = ctxflags; // invalidate_context() zeroed it } flags |= (clsmask | X86_RC_GENERAL) & THR_CLSMASK; return true; } //-------------------------------------------------------------------------- bool thread_info_t::write_context(int clsmask) { #ifndef __X86__ if ( (flags & THR_WOW64) != 0 && _Wow64SetThreadContext != NULL ) { context_holder_t xstate_ctx; if ( (clsmask & X86_RC_YMM) != 0 ) { int xstate_ctxflags = CONTEXT_XSTATE; if ( !debmod->context_helper.create_context(&xstate_ctx, &xstate_ctxflags) ) return false; } WOW64_CONTEXT wow64ctx; cvt_to_wow64(&wow64ctx, xstate_ctx.ptr, clsmask); if ( xstate_ctx.ptr != NULL ) { if ( SetThreadContext(hThread, xstate_ctx.ptr) == 0 ) return false; } if ( _Wow64SetThreadContext(hThread, &wow64ctx) == 0 ) return false; return true; } #else qnotused(clsmask); #endif return SetThreadContext(hThread, &get_ctx()) != 0; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_prepare_to_pause_process(qstring * /*errbuf*/) { check_thread(false); bool ok = true; win_tool_help_t *wth = get_tool_help(); if ( wth->use_debug_break_process() ) // only possible on XP/2K3 or higher { ok = wth->debug_break_process(process_handle); if ( !stop_at_ntdll_bpts ) expecting_debug_break = ok; } else if ( threads.empty() ) { ok = false; } else { suspend_all_threads(); for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { thread_info_t &ti = p->second; if ( !ti.read_context(X86_RC_GENERAL) ) { ok = false; continue; } // we have a problem: it is possible that eip points past a temporary breakpoint // that will be removed and replaced by the original instruction opcode. // if we set a new breakpoint now, it will be at the second byte of the // instruction and will lead to a crash. eanat_t ip = ti.get_ctx().Eip; if ( bpts.find(ip-bpt_code.size()) != bpts.end() ) continue; // yes, there is a breakpoint just before us. it was just hit // but hopefully not processed yet if ( !set_thread_bpt(ti, ip) ) ok = false; } // send WM_NULL to the main thread, hopefully it will wake up the application thread_info_t &ti = threads.begin()->second; PostThreadMessageA(ti.tid, WM_NULL, 0, 0); resume_all_threads(); } pause_requested = true; return ok ? DRC_OK : DRC_FAILED; } //---------------------------------------------------------------------- // return the name associated with an existing image in 'images' list // containing a particular range static const char *get_range_name(const images_t &images, const range_t *range) { for ( images_t::const_iterator p=images.begin(); p != images.end(); ++p ) { const image_info_t &img = p->second; ea_t ea1 = (ea_t)img.base; ea_t ea2 = ea1 + img.imagesize; range_t b = range_t(ea1, ea2); b.intersect(*range); if ( !b.empty() ) return img.name.c_str(); } return NULL; } //-------------------------------------------------------------------------- void win32_debmod_t::restore_original_bytes(ea_t ea, bool really_restore) { bpt_info_t::iterator p = thread_bpts.find(ea); QASSERT(1488, p != thread_bpts.end()); if ( --p->second.count == 0 ) { uchar *obytes = p->second.orig_bytes; if ( really_restore ) { int size = bpt_code.size(); if ( _write_memory(ea, obytes, size) != size ) INTERR(1489); } thread_bpts.erase(p); } } //-------------------------------------------------------------------------- // returns: 0-error,1-ok,2-already had bpt, just increased the counter int win32_debmod_t::save_original_bytes(ea_t ea) { bpt_info_t::iterator p = thread_bpts.find(ea); if ( p == thread_bpts.end() ) { internal_bpt_info_t ibi; ibi.count = 1; int size = bpt_code.size(); if ( _read_memory(ea, ibi.orig_bytes, size) != size ) return 0; thread_bpts.insert(std::make_pair(ea, ibi)); return 1; } else { p->second.count++; return 2; } } //-------------------------------------------------------------------------- bool win32_debmod_t::del_thread_bpt(thread_info_t &ti, ea_t ea) { if ( ti.bpt_ea == BADADDR ) return false; if ( ti.bpt_ea == ea ) { if ( !ti.read_context(X86_RC_GENERAL) ) return false; CONTEXT &ctx = ti.get_ctx(); ctx.Eip = ti.bpt_ea; // reset EIP DWORD saved = ctx.ContextFlags; ctx.ContextFlags = CONTEXT_CONTROL; if ( !ti.write_context(X86_RC_GENERAL) ) deberr("del_thread_bpt: SetThreadContext"); ctx.ContextFlags = saved; } // restore old insn if necessary restore_original_bytes(ti.bpt_ea); ti.bpt_ea = BADADDR; return true; } //-------------------------------------------------------------------------- // delete all thread breakpoints // returns true if a breakpoint at which we stopped was removed bool win32_debmod_t::del_thread_bpts(ea_t ea) { bool found = false; for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) found |= del_thread_bpt(p->second, ea); return found; } //-------------------------------------------------------------------------- bool win32_debmod_t::set_thread_bpt(thread_info_t &ti, ea_t ea) { // delete old thread bpt if any existed before del_thread_bpt(ti, BADADDR); ti.bpt_ea = ea; int code = save_original_bytes(ti.bpt_ea); if ( code ) { if ( code == 2 ) // already have a bpt? return true; // yes, then everything ok int size = bpt_code.size(); code = _write_memory(ti.bpt_ea, bpt_code.begin(), size); if ( code > 0 ) { if ( code == size ) return true; // failed to write, forget the original byte restore_original_bytes(ti.bpt_ea, false); } } debdeb("%a: set_thread_bpt() failed to pause thread %d\n", ti.bpt_ea, ti.tid); ti.bpt_ea = BADADDR; return false; } //-------------------------------------------------------------------------- void win32_debmod_t::add_thread(const CREATE_THREAD_DEBUG_INFO &thr_info, thid_t tid) { wow64_state_t w = check_wow64_process(); thread_info_t ti(this, thr_info, tid, w); threads.insert(std::make_pair(tid, ti)); } //-------------------------------------------------------------------------- gdecode_t win32_debmod_t::get_debug_event(debug_event_t *event, int timeout_ms) { check_thread(false); if ( events.retrieve(event) ) return events.empty() ? GDE_ONE_EVENT : GDE_MANY_EVENTS; DEBUG_EVENT DebugEvent; // we have to wait infinitely if we just try to attach to a running process if ( attach_status == as_attaching ) timeout_ms = INFINITE; if ( !WaitForDebugEvent(&DebugEvent, timeout_ms) ) { // no event occurred if ( attach_status == as_detaching ) // if we were requested to detach, { // we generate a fake detach event event->set_eid(PROCESS_DETACHED); return GDE_ONE_EVENT; } // else, we don't return an event return GDE_NO_EVENT; } if ( attach_status == as_attaching ) { if ( DebugEvent.dwDebugEventCode != CREATE_PROCESS_DEBUG_EVENT ) return GDE_ERROR; // fill in starting information for the just attached process (we couldn't do it from CreateProcess() return values !) process_path = ""; pid = DebugEvent.dwProcessId; attach_status = as_breakpoint; } if ( debug_debugger ) show_debug_event(DebugEvent); // ignore events coming from other child processes if ( DebugEvent.dwProcessId != pid ) { debdeb("ignore: pid %x != %x\n", DebugEvent.dwProcessId, pid); bool handled = DebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT && (DebugEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT || DebugEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_WX86_BREAKPOINT); if ( !ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, handled ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED) ) { deberr("ContinueDebugEvent"); } invalidate_all_contexts(); return GDE_NO_EVENT; } event->pid = DebugEvent.dwProcessId; event->tid = DebugEvent.dwThreadId; event->handled = true; gdecode_t gdecode = GDE_ONE_EVENT; switch ( DebugEvent.dwDebugEventCode ) { case EXCEPTION_DEBUG_EVENT: { EXCEPTION_RECORD &er = DebugEvent.u.Exception.ExceptionRecord; // remove temporary breakpoints if any bool was_thread_bpt = del_thread_bpts(EA_T(er.ExceptionAddress)); bool firsttime = DebugEvent.u.Exception.dwFirstChance != 0; gdecode = handle_exception(event, er, was_thread_bpt, firsttime); } break; case CREATE_THREAD_DEBUG_EVENT: { // add this thread to our list add_thread(DebugEvent.u.CreateThread, event->tid); event->set_info(THREAD_STARTED); event->ea = EA_T(DebugEvent.u.CreateThread.lpStartAddress); // set hardware breakpoints if any set_hwbpts(DebugEvent.u.CreateThread.hThread); } break; case CREATE_PROCESS_DEBUG_EVENT: { // save information for later cpdi = DebugEvent.u.CreateProcessInfo; cpdi.lpBaseOfImage = correct_exe_image_base(cpdi.lpBaseOfImage); if ( process_handle != INVALID_HANDLE_VALUE && process_handle != cpdi.hProcess ) myCloseHandle(process_handle); // already have an open handle process_handle = cpdi.hProcess; create_start_event(event); curproc.insert(std::make_pair(event->modinfo().base, image_info_t(this, event->modinfo()))); // add record about the main thread into the list CREATE_THREAD_DEBUG_INFO ctdi; ctdi.hThread = cpdi.hThread; ctdi.lpThreadLocalBase = cpdi.lpThreadLocalBase; ctdi.lpStartAddress = cpdi.lpStartAddress; add_thread(ctdi, DebugEvent.dwThreadId); // set hardware breakpoints if any set_hwbpts(cpdi.hThread); // test hardware breakpoints: // add_hwbpt(HWBPT_WRITE, 0x0012FF68, 4); if ( highdlls.empty() && winver.is_DW32() ) // dw32 specific { HINSTANCE h = GetModuleHandle(kernel32_dll); eanat_t addr = eanat_t(h); uint32 size = calc_imagesize(addr); highdlls.add(addr, size); } break; } case EXIT_THREAD_DEBUG_EVENT: { threads.erase(event->tid); event->set_exit_code(THREAD_EXITED, DebugEvent.u.ExitThread.dwExitCode); // invalidate corresponding handles HANDLE h = get_thread_handle(event->tid); if ( h == thread_handle ) thread_handle = INVALID_HANDLE_VALUE; if ( h == cpdi.hThread ) cpdi.hThread = INVALID_HANDLE_VALUE; break; } case EXIT_PROCESS_DEBUG_EVENT: event->set_exit_code(PROCESS_EXITED, DebugEvent.u.ExitProcess.dwExitCode); exiting = true; break; case LOAD_DLL_DEBUG_EVENT: { const LOAD_DLL_DEBUG_INFO &dil = DebugEvent.u.LoadDll; eanat_t addr = eanat_t(dil.lpBaseOfDll); modinfo_t &mi_ll = event->set_modinfo(LIB_LOADED); event->ea = addr; mi_ll.base = event->ea; mi_ll.rebase_to = BADADDR; // this must be determined locally - see common_local.cpp char full_name[MAXSTR]; full_name[0] = '\0'; bool ok = get_filename_for(full_name, sizeof(full_name), eanat_t(dil.lpImageName), dil.fUnicode != 0, addr); // Win 7 may send a bogus DLL load event for the main module // ignore event if this module has already been mapped if ( !ok || module_present(full_name) ) { debdeb("%p: bogus DLL load event, skippping\n", addr); goto SILENTLY_RESUME; } mi_ll.name = full_name; uint32 size = calc_imagesize(addr); mi_ll.size = size; // does the address fit into ea_t? HANDLE ntdll_handle = dil.hFile; if ( highdlls.add_high_module(addr, size, ntdll_handle) ) { debdeb("%p: 64bit DLL loaded into high addresses has been detected\n", addr); goto SILENTLY_RESUME; } // we defer the import of the dll until the moment when ida stops // at a debug event. we do so to avoid unnecessary imports because // the dll might get unloaded before ida stops. image_info_t di(this, DebugEvent.u.LoadDll, size, full_name); add_dll(di); // determine the attach breakpoint if needed size_t max_ntdlls = check_wow64_process() == WOW64_YES ? 2 : 1; if ( highdlls.count_ntdlls() < max_ntdlls ) { if ( is_ntdll_name(full_name) ) highdlls.add_ntdll(addr, size); if ( attach_status == as_none && !stop_at_ntdll_bpts ) expecting_debug_break = highdlls.count_ntdlls(); } break; } SILENTLY_RESUME: // do not report this event to ida event->handled = true; dbg_continue_after_event(event); return GDE_NO_EVENT; case UNLOAD_DLL_DEBUG_EVENT: event->set_info(LIB_UNLOADED); { eanat_t addr = eanat_t(DebugEvent.u.UnloadDll.lpBaseOfDll); range_t range(addr, addr+MEMORY_PAGE_SIZE); // we assume DLL image is at least a PAGE size const char *name = get_range_name(dlls, &range); if ( name != NULL ) event->info() = name; else event->info().clear(); // remove ntdll from the list HANDLE ntdll_handle; if ( highdlls.del_high_module(&ntdll_handle, addr) ) { myCloseHandle(ntdll_handle); goto SILENTLY_RESUME; } // close the associated DLL handle images_t::iterator p = dlls.find(addr); if ( p != dlls.end() ) { myCloseHandle(p->second.dll_info.hFile); // Remove it from the list of dlls to import // (in the case it was never imported) dlls_to_import.erase(p->first); dlls.erase(p); } else { debdeb("Could not find dll to unload (base=%a)\n", EA_T(DebugEvent.u.UnloadDll.lpBaseOfDll)); } } break; case OUTPUT_DEBUG_STRING_EVENT: { char buf[MAXSTR]; get_debug_string(DebugEvent, buf, sizeof(buf)); event->set_info(INFORMATION) = buf; } break; case RIP_EVENT: debdeb("RIP_EVENT (system debugging error)"); break; default: debdeb("UNKNOWN_EVENT %d", DebugEvent.dwDebugEventCode); event->handled = false; // don't handle it break; } if ( gdecode > GDE_NO_EVENT && attach_status == as_breakpoint && event->eid() == EXCEPTION ) { // exception while attaching. apparently things went wrong // pretend that we attached successfully events.enqueue(*event, IN_BACK); create_attach_event(event, true); attach_status = as_none; } return gdecode; } //-------------------------------------------------------------------------- gdecode_t idaapi win32_debmod_t::dbg_get_debug_event(debug_event_t *event, int timeout_ms) { check_thread(false); gdecode_t gdecode = get_debug_event(event, timeout_ms); if ( gdecode >= GDE_ONE_EVENT ) { last_event = *event; in_event = &last_event; pause_requested = false; } return gdecode; } //-------------------------------------------------------------------------- bool win32_debmod_t::get_debug_string(const DEBUG_EVENT &ev, char *buf, size_t bufsize) { buf[0] = '\0'; size_t nullsize = ev.u.DebugString.fUnicode ? sizeof(wchar_t) : 1; size_t msize = qmin(ev.u.DebugString.nDebugStringLength, bufsize-nullsize); ea_t ea = EA_T(ev.u.DebugString.lpDebugStringData); ssize_t rsize = _read_memory(ea, buf, msize); if ( rsize == msize ) { buf[rsize] = '\0'; if ( ev.u.DebugString.fUnicode ) { *(wchar_t*)(buf + rsize) = 0; utf16_to_utf8(buf, bufsize, (LPCWSTR)buf); } return true; } return false; } //-------------------------------------------------------------------------- void win32_debmod_t::cleanup() { myCloseHandle(redirin_handle); myCloseHandle(redirout_handle); myCloseHandle(thread_handle); myCloseHandle(process_handle); myCloseHandle(cpdi.hFile); // Close handles of remaining DLLs for ( images_t::iterator p=dlls.begin(); p != dlls.end(); ++p ) myCloseHandle(p->second.dll_info.hFile); pid = -1; highdlls.clear(); stop_at_ntdll_bpts = qgetenv("IDA_SYSTEMBREAKPOINT"); expecting_debug_break = 0; in_event = NULL; memset(&cpdi, 0, sizeof(cpdi)); cpdi.hFile = INVALID_HANDLE_VALUE; cpdi.hProcess = INVALID_HANDLE_VALUE; cpdi.hThread = INVALID_HANDLE_VALUE; attach_status = as_none; attach_evid = INVALID_HANDLE_VALUE; old_ranges.clear(); threads.clear(); thread_bpts.clear(); bpts.clear(); curproc.clear(); dlls.clear(); dlls_to_import.clear(); images.clear(); thread_ranges.clear(); class_ranges.clear(); context_helper.clear(); inherited::cleanup(); } #define DRVBUFSIZE 512 //-------------------------------------------------------------------------- // Translate path with device name to drive letters. // e.g. \Device\HarddiskVolume4\Windows\System32\ntdll.dll -> C:\Windows\System32\ntdll.dll static void translate_nt_path(qstring *out, LPCWSTR pszFilename) { WCHAR szTemp[DRVBUFSIZE]; // get a list of all drive letters if ( GetLogicalDriveStringsW(qnumber(szTemp) - 1, szTemp) ) { WCHAR szName[MAX_PATH]; WCHAR szDrive[3] = L" :"; BOOL bFound = FALSE; WCHAR *p = szTemp; do { // Copy the drive letter to the template string *szDrive = *p; // Look up device name for this drive DWORD uNameLen = QueryDosDeviceW(szDrive, szName, qnumber(szName) - 1); if ( uNameLen != 0 ) { //for some reason return value is 2 chars longer, so get the actual length uNameLen = wcslen(szName); // do we have a match at the start of filename? bFound = _wcsnicmp(pszFilename, szName, uNameLen) == 0 && pszFilename[uNameLen] == L'\\'; if ( bFound ) { // Reconstruct filename // by replacing device path with DOS path (drive) qstring path; utf16_utf8(out, szDrive); utf16_utf8(&path, pszFilename + uNameLen); out->append(path); return; } } // Go to the next NULL character. while ( *p++ ) ; } while ( !bFound && *p ); // end of string } } //-------------------------------------------------------------------------- bool win32_debmod_t::get_filename_for( char *buf, size_t bufsize, eanat_t image_name_ea, bool use_unicode, eanat_t image_base) { buf[0] = '\0'; // if we have address of file name in the process image from the debug API, try to use it // remark: depending on the OS, NTDLL.DLL can return an empty string or only the DLL name! if ( image_name_ea != 0 ) { if ( get_filename_from_process(image_name_ea, use_unicode, buf, bufsize) ) return true; } // various fallbacks // first try GetMappedFileName if ( _GetMappedFileName != NULL ) { wchar16_t tbuf[MAXSTR]; HMODULE hmod = (HMODULE)(size_t)image_base; if ( _GetMappedFileName(process_handle, hmod, tbuf, qnumber(tbuf)) ) { qstring tmp; translate_nt_path(&tmp, tbuf); qstrncpy(buf, tmp.c_str(), bufsize); return true; } } // then try GetModuleFileNameEx if ( _GetModuleFileNameEx != NULL ) { wchar16_t tbuf[MAXSTR]; HMODULE hmod = (HMODULE)(size_t)image_base; if ( _GetModuleFileNameEx(process_handle, hmod, tbuf, qnumber(tbuf)) ) { qstring tmp; utf16_utf8(&tmp, tbuf); qstrncpy(buf, tmp.c_str(), bufsize); return true; } } // last: we try to get DLL name by looking at the export name from // the export directory in PE image in debugged process. // this is the least reliable way since this string may not match the actual dll filename get_pe_export_name_from_process(image_base, buf, bufsize); // for dlls without path, try to find it find_full_path(buf, bufsize, process_path.c_str()); // convert possible short path to long path qffblk64_t fb; if ( qfindfirst(buf, &fb, 0) == 0 ) { char *fptr = qbasename(buf); qstrncpy(fptr, fb.ff_name, bufsize-(fptr-buf)); } return buf[0] != '\0'; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_detach_process() { check_thread(false); if ( in_event != NULL ) dbg_continue_after_event(in_event); BOOL ret = get_tool_help()->debug_detach_process(pid); if ( ret ) { attach_status = as_detaching; exiting = true; } return ret ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- void idaapi win32_debmod_t::dbg_set_debugging(bool _debug_debugger) { debug_debugger = _debug_debugger; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_init(uint32_t *flags2, qstring * /*errbuf*/) { check_thread(true); cleanup(); cleanup_hwbpts(); if ( flags2 != nullptr ) *flags2 = g_code; return DRC_OK; } //-------------------------------------------------------------------------- image_info_t::image_info_t(win32_debmod_t *ses) : sess(ses), base(BADADDR), imagesize(0) { memset(&dll_info, 0, sizeof(dll_info)); } image_info_t::image_info_t( win32_debmod_t *ses, ea_t _base, uint32 _imagesize, const qstring &_name) : sess(ses), base(_base), imagesize(_imagesize), name(_name) { memset(&dll_info, 0, sizeof(dll_info)); } image_info_t::image_info_t( win32_debmod_t *ses, const LOAD_DLL_DEBUG_INFO &i, uint32 _imagesize, const char *_name) : sess(ses), name(_name), dll_info(i) { base = EA_T(i.lpBaseOfDll); imagesize = _imagesize; } image_info_t::image_info_t(win32_debmod_t *ses, const modinfo_t &m) : sess(ses), base(m.base), imagesize(m.size), name(m.name) { memset(&dll_info, 0, sizeof(dll_info)); } //-------------------------------------------------------------------------- // get (path+)name from debugged process // lpFileName - pointer to pointer to the file name // use_unicode - true if the filename is in unicode bool win32_debmod_t::get_filename_from_process( eanat_t name_ea, bool use_unicode, char *buf, size_t bufsize) { buf[0] = '\0'; if ( name_ea == 0 ) return false; eanat_t dll_addr; if ( _read_memory(name_ea, &dll_addr, sizeof(dll_addr)) != sizeof(dll_addr) ) return false; if ( dll_addr == NULL ) return false; name_ea = dll_addr; if ( _read_memory(name_ea, buf, bufsize) != bufsize ) return false; if ( use_unicode ) utf16_to_utf8(buf, bufsize, (LPCWSTR)buf); return true; } //-------------------------------------------------------------------------- ea_t win32_debmod_t::get_region_info(ea_t ea, memory_info_t *mi) { // okay to keep static, they won't change between clients static DWORD_PTR totalVirtual = 0; static DWORD granularity = 0; if ( totalVirtual == 0 ) { SYSTEM_INFO si; GetSystemInfo(&si); granularity = si.dwAllocationGranularity; totalVirtual = (DWORD_PTR)si.lpMaximumApplicationAddress; } void *addr = (void *)(size_t)ea; MEMORY_BASIC_INFORMATION meminfo; while ( !VirtualQueryEx(process_handle, // handle of process addr, // address of region &meminfo, // address of information buffer sizeof(meminfo)) ) // size of buffer { // On Windows CE VirtualQueryEx can fail when called with addr == 0, // so try to call it again with the next page (and end loop after 2d // iteration to prevent scanning of huge number of pages) // It's possible VirtualQueryEx fails on Windows CE not only for zero // address: perhaps we shouldn't limit the number of iterations and return // to using of a separate variable 'first' (as in win32_debmod.cpp#34) if ( ea != 0 || ea >= totalVirtual ) return BADADDR; // try to find next valid page ea += granularity; addr = (void *)(size_t)ea; } eanat_t startea = (eanat_t)meminfo.BaseAddress; eanat_t endea = startea + meminfo.RegionSize; if ( endea <= startea //overflow/empty? #if !defined(__X86__) && !defined(__EA64__) || endea >= BADADDR//crossed 4GB boundary #endif ) { if ( endea == startea ) { // ignore empty sections ... mi->start_ea = BADADDR; mi->end_ea = BADADDR; return endea + 1; } // signal end of enumeration endea = BADADDR; } // debdeb("VirtualQueryEx(%a): base = %a, end = %a, protect=0x%x, allocprotect=0x%x, state=0x%x\n", ea, startea, endea, meminfo.Protect, meminfo.AllocationProtect, meminfo.State); // hide the page bpts in this memory region from ida uint32 prot = meminfo.Protect; if ( mask_page_bpts(startea, endea, &prot) ) { debdeb(" masked protect=0x%x\n", prot); meminfo.Protect = prot; } if ( (meminfo.State & (MEM_FREE|MEM_RESERVE)) == MEM_FREE // if the range isn't interesting for/accessible by IDA || (meminfo.Protect & PAGE_NOACCESS) != 0 ) { // we simply return an invalid range, and a pointer to the next (eventual) range mi->start_ea = BADADDR; mi->end_ea = BADADDR; return endea; } mi->start_ea = startea; mi->end_ea = endea; #ifdef __EA64__ // we may be running a 32bit process in wow64 with ida64 mi->bitness = check_wow64_process() > 0 ? 1 : 2; #else mi->bitness = 1; // 32bit #endif // convert Windows protection modes to IDA protection modes mi->perm = win_prot_to_ida_perm(meminfo.Protect); // try to associate a segment name to the memory range const char *ptr; if ( (ptr=get_range_name(curproc, mi)) != NULL // first try with the current process || (ptr=get_range_name(dlls, mi)) != NULL // then try in DLLs || (ptr=get_range_name(images, mi)) != NULL // then try in previous images ranges || (ptr=get_range_name(thread_ranges, mi)) != NULL ) // and finally in thread ranges { // return the filename without the file path mi->name = qbasename(ptr); } else { char buf[MAXSTR]; buf[0] = '\0'; //check for a mapped filename get_filename_for(buf, sizeof(buf), 0, false, mi->start_ea); if ( buf[0] != '\0' ) { mi->name = qbasename(buf); } // if we found nothing, check if the segment is a PE file header, and we try to locate a name in it else if ( get_pe_export_name_from_process(mi->start_ea, buf, sizeof(buf)) ) { // we insert it in the image ranges list uint32 size = calc_imagesize(mi->start_ea); image_info_t ii(this, mi->start_ea, size, buf); images.insert(std::make_pair(ii.base, ii)); mi->name = buf; } } // try to associate a segment class name to the memory range mi->sclass = get_range_name(class_ranges, mi); return endea; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_attach_process(pid_t _pid, int event_id, int /*flags*/, qstring * /*errbuf*/) { check_thread(false); int addrsize = get_process_addrsize(_pid); #ifndef __EA64__ if ( addrsize > 4 ) { dwarning("AUTOHIDE NONE\nPlease use ida64 to debug 64-bit applications"); SetLastError(ERROR_NOT_SUPPORTED); return DRC_FAILED; } #endif if ( !DebugActiveProcess(_pid) ) { deberr("DebugActiveProcess %08lX", _pid); return DRC_FAILED; } debapp_attrs.addrsize = addrsize; attach_status = as_attaching; attach_evid = (HANDLE)(INT_PTR)(event_id); exiting = false; return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_start_process( const char *path, const char *args, const char *startdir, int flags, const char *input_path, uint32 input_file_crc32, qstring * /*errbuf*/) { check_thread(false); // input file specified in the database does not exist if ( input_path[0] != '\0' && !qfileexist(input_path) ) { dwarning("AUTOHIDE NONE\nInput file is missing: %s", input_path); return DRC_NOFILE; } input_file_path = input_path; is_dll = (flags & DBG_PROC_IS_DLL) != 0; char fullpath[QMAXPATH]; if ( !qfileexist(path) ) { if ( qisabspath(path) || !search_path(fullpath, sizeof(fullpath), path, false) ) { dwarning("AUTOHIDE NONE\nCannot find application file '%s'", path); return DRC_NETERR; } path = fullpath; } drc_t mismatch = DRC_OK; if ( !check_input_file_crc32(input_file_crc32) ) mismatch = DRC_CRC; exiting = false; // Build a full command line qstring args_buffer; // this vector must survive until create_process() if ( args != NULL && args[0] != '\0' ) { args_buffer += '"'; args_buffer += path; args_buffer += '"'; args_buffer += ' '; args_buffer += args; args = args_buffer.c_str(); } PROCESS_INFORMATION ProcessInformation; bool is_gui = (flags & DBG_PROC_IS_GUI) != 0; bool hide_window = (flags & DBG_HIDE_WINDOW) != 0; if ( !create_process(path, args, startdir, is_gui, hide_window, &ProcessInformation) ) return DRC_FAILED; pid = ProcessInformation.dwProcessId; process_handle = ProcessInformation.hProcess; thread_handle = ProcessInformation.hThread; process_path = path; debapp_attrs.addrsize = get_process_addrsize(pid); return mismatch; } //-------------------------------------------------------------------------- //lint -esym(1762,win32_debmod_t::myCloseHandle) could be made const bool win32_debmod_t::myCloseHandle(HANDLE &h) { bool ok = true; if ( h != INVALID_HANDLE_VALUE && h != NULL ) { DWORD code; __try { ok = CloseHandle(h) != 0; if ( !ok ) deberr("CloseHandle(%08X)", h); } __except ( code=GetExceptionCode() ) { debdeb("CloseHandle(%08X) exception code %08X\n", h, code); ok = false; } h = INVALID_HANDLE_VALUE; } return ok; } //-------------------------------------------------------------------------- void win32_debmod_t::install_callgate_workaround(thread_info_t *ti, const debug_event_t *event) { // add a breakpoint after the call statement ea_t bpt = event->ea + 7; ti->callgate_ea = bpt; if ( !set_thread_bpt(*ti, bpt) ) INTERR(637); // how can it be? } //-------------------------------------------------------------------------- // we do not use 'firsttime' argument anymore. we could use it to distinguish // the first chance and the second chance but it is more logical to // behave consistently. gdecode_t win32_debmod_t::handle_exception( debug_event_t *event, const EXCEPTION_RECORD &er, bool was_thread_bpt, bool /*firsttime*/) { int code = er.ExceptionCode; const exception_info_t *ei = find_exception(code); eanat_t addr = eanat_t(er.ExceptionAddress); excinfo_t &exc = event->set_exception(); event->ea = addr; exc.code = code; exc.can_cont = (er.ExceptionFlags == 0); exc.ea = BADADDR; event->handled = false; if ( exiting && ei == NULL ) { event->set_exit_code(PROCESS_EXITED, -1); return GDE_ONE_EVENT; } bool suspend = true; // we don't expect callgate breakpoints anymore ea_t was_callgate_ea = BADADDR; thread_info_t *ti = threads.get(event->tid); if ( ti != NULL ) { was_callgate_ea = ti->callgate_ea; ti->callgate_ea = BADADDR; } if ( ei != NULL ) { event->handled = ei->handle(); // if the user asked to suspend the process, do not resume if ( !was_thread_bpt ) suspend = ei->break_on() || pause_requested; } if ( !suspend ) suspend = should_suspend_at_exception(event, ei); exc.info.qclear(); int elc_flags = 0; switch ( uint32(code) ) { case EXCEPTION_BREAKPOINT: case STATUS_WX86_BREAKPOINT: if ( was_thread_bpt ) { QASSERT(638, ti != NULL); // is installed the workaround for the 'freely running after syscall' problem? if ( was_callgate_ea == event->ea ) event->set_eid(STEP); else event->set_eid(PROCESS_SUSPENDED); break; } if ( attach_status == as_breakpoint ) // the process was successfully suspended after an attachement { create_attach_event(event, true); break; } if ( expecting_debug_break > 0 && highdlls.has(addr) && get_kernel_bpt_ea(event->ea, event->tid) == BADADDR ) // not user-defined bpt { --expecting_debug_break; debdeb("%a: resuming after DbgBreakPoint(), expecting bpts: %d\n", event->ea, expecting_debug_break); event->handled = true; dbg_continue_after_event(event); return GDE_NO_EVENT; } // is this a breakpoint set by ida? { ea_t kea = get_kernel_bpt_ea(event->ea, event->tid); if ( kea != BADADDR ) { bptaddr_t &bpta = event->set_bpt(); bpta.hea = BADADDR; // no referenced address (only for hardware breakpoint) bpta.kea = kea == event->ea ? BADADDR : kea; event->handled = true; } } break; case EXCEPTION_SINGLE_STEP: case STATUS_WX86_SINGLE_STEP: { bool is_stepping = ti != NULL && ti->is_tracing(); // if this happened because of a hardware breakpoint // find out which one caused it if ( !check_for_hwbpt(event, is_stepping) ) { // if we have not asked for single step, do not convert it to STEP if ( is_stepping ) { event->set_eid(STEP); // Single-step breakpoint event->handled = true; ti->clr_tracing(); break; } } } break; case EXCEPTION_ACCESS_VIOLATION: { ea_t exc_ea = EA_T(er.ExceptionInformation[1]); // virtual address of the inaccessible data. exc.ea = exc_ea; // is this a page bpt? page_bpts_t::iterator p = find_page_bpt(exc_ea); if ( p == page_bpts.end() ) { exc_ea = event->ea; p = find_page_bpt(exc_ea); } if ( p != page_bpts.end() ) { // since on access violation the system does not update anything // there is no need to reset eip when handling lowcnd below. elc_flags |= ELC_KEEP_EIP; ea_t exc_eip = EA_T(er.ExceptionAddress); if ( !should_fire_page_bpt(p, exc_ea, er.ExceptionInformation[0], exc_eip, dep_policy) ) { // Silently step over the page breakpoint if ( ti != NULL && ti->is_tracing() ) elc_flags |= ELC_KEEP_SUSP; lowcnd_t lc; const pagebpt_data_t &bpt = p->second; lc.ea = bpt.ea; lc.type = bpt.type; lc.size = bpt.user_len; if ( !handling_lowcnds.has(bpt.ea) && handle_lowcnd(&lc, event, elc_flags) ) { if ( (elc_flags & ELC_KEEP_SUSP) != 0 ) { // if we were tracing, report a STEP event event->set_eid(STEP); event->handled = true; ti->clr_tracing(); return GDE_ONE_EVENT; } return GDE_NO_EVENT; } // failed to step over, return the exception } else { bptaddr_t &bpta = event->set_bpt(); bpta.hea = p->second.ea; bpta.kea = BADADDR; event->handled = true; break; } } if ( ei != NULL && event->eid() == EXCEPTION ) { int einfo = er.ExceptionInformation[0]; const char *verb = einfo == EXCEPTION_EXECUTE_FAULT ? "executed" : einfo == EXCEPTION_WRITE_FAULT ? "written" : "read"; exc.info.sprnt(ei->desc.c_str(), event->ea, exc.ea, verb); } } break; #define EXCEPTION_BCC_FATAL 0xEEFFACE #define EXCEPTION_BCC_NORMAL 0xEEDFAE6 case EXCEPTION_BCC_FATAL: case EXCEPTION_BCC_NORMAL: if ( er.NumberParameters == 5 && er.ExceptionInformation[0] == 2 // these numbers are highly hypothetic && er.ExceptionInformation[1] == 3 ) { EXCEPTION_RECORD r2; if ( dbg_read_memory(er.ExceptionInformation[3], &r2, sizeof(r2), NULL) == sizeof(r2) ) return handle_exception(event, r2, false, false); } break; #define MS_VC_EXCEPTION 0x406D1388L case MS_VC_EXCEPTION: // SetThreadName // https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx if ( er.ExceptionInformation[0] == 0x1000 && er.ExceptionInformation[3] == 0x00 ) { qstring name; name.resize(MAXSTR); ea_t nameaddr = er.ExceptionInformation[1]; if ( dbg_read_memory(nameaddr, name.begin(), name.length(), NULL) > 0 ) { thid_t tid = er.ExceptionInformation[2]; msg("Thread %d is named '%s'\n", tid, name.c_str()); thread_info_t *tin = threads.get(tid); if ( tin != NULL ) { tin->name = name.c_str(); tin->set_new_name(); } event->handled = true; } } break; } if ( !pause_requested && evaluate_and_handle_lowcnd(event, elc_flags) ) return GDE_NO_EVENT; if ( event->eid() == EXCEPTION ) { if ( ei == NULL ) { exc.info.sprnt("unknown exception code %X", code); } else if ( exc.info.empty() ) { exc.info.sprnt(ei->desc.c_str(), event->ea, ea_t(er.ExceptionInformation[0]), ea_t(er.ExceptionInformation[1])); } if ( !suspend ) { log_exception(event, ei); // if a single step was scheduled by the user if ( ti != NULL && ti->is_tracing() ) { clear_tbit(*ti); if ( event->handled ) { // since we mask the exception, we generate a STEP event event->set_eid(STEP); return GDE_ONE_EVENT; // got an event } } dbg_continue_after_event(event); return GDE_NO_EVENT; } } return GDE_ONE_EVENT; } //-------------------------------------------------------------------------- bool win32_debmod_t::check_for_hwbpt(debug_event_t *event, bool is_stepping) { ea_t ea = is_hwbpt_triggered(event->tid, is_stepping); if ( ea != BADADDR ) { bptaddr_t &addr = event->set_bpt(); addr.hea = ea; addr.kea = BADADDR; event->handled = true; return true; } return false; } //-------------------------------------------------------------------------- void win32_debmod_t::create_attach_event(debug_event_t *event, bool attached) { event->set_modinfo(PROCESS_ATTACHED); event->handled = true; if ( attached ) attach_status = as_attached; else attach_status = as_attaching; if ( attach_evid != INVALID_HANDLE_VALUE ) { SetEvent(attach_evid); attach_evid = INVALID_HANDLE_VALUE; } modinfo_t &mi_ps = event->modinfo(); get_debugged_module_info(&mi_ps); binary_to_import = mi_ps; } //-------------------------------------------------------------------------- void win32_debmod_t::create_start_event(debug_event_t *event) { modinfo_t &mi_ps = event->set_modinfo(PROCESS_STARTED); ea_t base = EA_T(cpdi.lpBaseOfImage); process_snapshot_t psnap(get_tool_help()); PROCESSENTRY32 pe32; for ( bool ok = psnap.first(TH32CS_SNAPNOHEAPS, &pe32); ok; ok = psnap.next(&pe32) ) { if ( pe32.th32ProcessID == event->pid ) { char exefile[QMAXPATH]; tchar_utf8(exefile, pe32.szExeFile, sizeof(exefile)); if ( !qisabspath(exefile) ) { char abspath[QMAXPATH]; get_filename_for(abspath, sizeof(abspath), /*image_name_ea=*/ 0, /*use_unicode=*/ false, // irrelevant base); if ( abspath[0] != '\0' ) qstrncpy(exefile, abspath, sizeof(exefile)); } if ( process_path.empty() || qisabspath(exefile) ) process_path = exefile; break; } } mi_ps.name = process_path; mi_ps.base = base; mi_ps.size = calc_imagesize(base); mi_ps.rebase_to = BADADDR; // this must be determined locally - see common_local.cpp binary_to_import = mi_ps; } //-------------------------------------------------------------------------- ea_t win32_debmod_t::get_kernel_bpt_ea(ea_t ea, thid_t tid) { if ( is_ida_bpt(ea, tid) ) return ea; return BADADDR; } ssize_t idaapi win32_debmod_t::dbg_write_memory(ea_t ea, const void *buffer, size_t size, qstring * /*errbuf*/) { check_thread(false); return _write_memory(ea, buffer, size, true); } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_thread_get_sreg_base( ea_t *pea, thid_t tid, int sreg_value, qstring * /*errbuf*/) { check_thread(false); NODISTURB_ASSERT(in_event != NULL); HANDLE h = get_thread_handle(tid); if ( h == INVALID_HANDLE_VALUE ) return DRC_FAILED; #ifndef __X86__ thread_info_t *ti = threads.get(tid); if ( ti != NULL && ti->read_context(X86_RC_SEGMENTS) ) { // is this a TLS base register? (FS for wow64 and GS for x64) CONTEXT &ctx = ti->get_ctx(); if ( sreg_value == ctx.SegGs && (ti->flags & THR_WOW64) == 0 || sreg_value == ctx.SegFs && (ti->flags & THR_WOW64) != 0 ) { // lpThreadLocalBase is the native (X64) TEB, or GS base if ( sreg_value == ctx.SegGs ) *pea = EA_T(ti->lpThreadLocalBase); else { // fs base is the WoW64 TEB // pointer to it is the first field in the native TEB LPVOID tib32; if ( _read_memory(EA_T(ti->lpThreadLocalBase), &tib32, sizeof(tib32)) != sizeof(tib32) ) return DRC_FAILED; *pea = EA_T(tib32); } return DRC_OK; } else if ( (ti->flags & THR_WOW64) != 0 ) { WOW64_LDT_ENTRY se; if ( !_Wow64GetThreadSelectorEntry(h, sreg_value, &se) ) { if ( GetLastError() == ERROR_NOT_SUPPORTED ) { // in x64 all selectors except fs/gs are 0-based *pea = 0; return DRC_OK; } deberr("GetThreadSelectorEntry"); return DRC_FAILED; } *pea = (se.HighWord.Bytes.BaseHi << 24) | (se.HighWord.Bytes.BaseMid << 16) | se.BaseLow; return DRC_OK; } } #endif // __X64__ //below code works for non-x64 LDT_ENTRY se; if ( !GetThreadSelectorEntry(h, sreg_value, &se) ) { if ( GetLastError() == ERROR_NOT_SUPPORTED ) { *pea = 0; return DRC_OK; } deberr("GetThreadSelectorEntry"); return DRC_FAILED; } *pea = (se.HighWord.Bytes.BaseHi << 24) | (se.HighWord.Bytes.BaseMid << 16) | se.BaseLow; return DRC_OK; } //-------------------------------------------------------------------------- #ifndef __X86__ #define FloatSave FltSave // FIXME: use XMM save area! #define RegisterArea FloatRegisters #define FPUREG_ENTRY_SIZE 16 #define XMMREG_PTR ((uchar *)&ctx.Xmm0) #define XMMREG_MXCSR (ctx.FltSave.MxCsr) #else #define XMMREG_PTR ((uchar *)&ctx.ExtendedRegisters[0xA0]) #define XMMREG_MXCSR (*(uint32 *)&ctx.ExtendedRegisters[0x18]) #define FPUREG_ENTRY_SIZE 10 #endif #define FPUREG_PTR ((uchar *)ctx.FloatSave.RegisterArea) //------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_write_register( thid_t tid, int reg_idx, const regval_t *value, qstring * /*errbuf*/) { check_thread(false); if ( value == NULL ) return DRC_FAILED; NODISTURB_ASSERT(in_event != NULL); int regclass = get_reg_class(reg_idx); if ( regclass == 0 ) return DRC_FAILED; thread_info_t *ti = threads.get(tid); if ( ti == NULL || !ti->read_context(regclass) ) return DRC_FAILED; CONTEXT &ctx = ti->get_ctx(); // Patch one field patch_context_struct(ctx, reg_idx, ti, value); bool ok = ti->write_context(regclass); if ( !ok ) deberr("SetThreadContext"); debdeb("write_register: %d\n", ok); return ok ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- bool idaapi win32_debmod_t::write_registers( thid_t tid, int start, int count, const regval_t *values) { thread_info_t *ti = threads.get(tid); if ( ti == NULL ) return false; if ( !ti->read_context(X86_RC_ALL) ) return false; CONTEXT &ctx = ti->get_ctx(); for ( int i=0; i < count; i++, values++ ) patch_context_struct(ctx, start + i, ti, values); bool ok = ti->write_context(X86_RC_ALL); if ( !ok ) deberr("SetThreadContext"); debdeb("write_registers: %d\n", ok); return ok; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_read_registers( thid_t tid, int clsmask, regval_t *values, qstring * /*errbuf*/) { check_thread(false); if ( values == NULL ) return DRC_FAILED; NODISTURB_ASSERT(in_event != NULL); thread_info_t *ti = threads.get(tid); if ( ti == NULL || !ti->read_context(clsmask) ) return DRC_FAILED; CONTEXT &ctx = ti->get_ctx(); if ( (clsmask & X86_RC_SEGMENTS) != 0 ) { values[R_CS].ival = ctx.SegCs; values[R_DS].ival = ctx.SegDs; values[R_ES].ival = ctx.SegEs; values[R_FS].ival = ctx.SegFs; values[R_GS].ival = ctx.SegGs; values[R_SS].ival = ctx.SegSs; } if ( (clsmask & X86_RC_GENERAL) != 0 ) { #ifndef __X86__ values[R_EAX].ival = ctx.Rax; values[R_EBX].ival = ctx.Rbx; values[R_ECX].ival = ctx.Rcx; values[R_EDX].ival = ctx.Rdx; values[R_ESI].ival = ctx.Rsi; values[R_EDI].ival = ctx.Rdi; values[R_EBP].ival = ctx.Rbp; values[R_ESP].ival = ctx.Rsp; values[R_EIP].ival = ctx.Rip; # ifdef __EA64__ values[R_R8 ].ival = ctx.R8; values[R_R9 ].ival = ctx.R9; values[R_R10].ival = ctx.R10; values[R_R11].ival = ctx.R11; values[R_R12].ival = ctx.R12; values[R_R13].ival = ctx.R13; values[R_R14].ival = ctx.R14; values[R_R15].ival = ctx.R15; # endif #else values[R_EAX].ival = ctx.Eax; values[R_EBX].ival = ctx.Ebx; values[R_ECX].ival = ctx.Ecx; values[R_EDX].ival = ctx.Edx; values[R_ESI].ival = ctx.Esi; values[R_EDI].ival = ctx.Edi; values[R_EBP].ival = ctx.Ebp; values[R_ESP].ival = ctx.Esp; values[R_EIP].ival = ctx.Eip; #endif values[R_EFLAGS].ival = ctx.EFlags; } if ( (clsmask & (X86_RC_FPU|X86_RC_MMX)) != 0 ) { if ( (clsmask & X86_RC_FPU) != 0 ) { values[R_CTRL].ival = ctx.FloatSave.ControlWord; values[R_STAT].ival = ctx.FloatSave.StatusWord; values[R_TAGS].ival = ctx.FloatSave.TagWord; } read_fpu_registers(values, clsmask, FPUREG_PTR, FPUREG_ENTRY_SIZE); } if ( (clsmask & X86_RC_XMM) != 0 ) { const uchar *xptr = XMMREG_PTR; for ( int i=R_XMM0; i < R_MXCSR; i++,xptr+=16 ) values[i].set_bytes(xptr, 16); values[R_MXCSR].ival = XMMREG_MXCSR; } if ( (clsmask & X86_RC_YMM) != 0 ) { int idx = 0; DWORD64 feature_mask = 0; if ( context_helper.xstate_helpers_loaded() && context_helper.pfnGetXStateFeaturesMask(&ctx, &feature_mask) != FALSE ) { DWORD xmm_blob_length = 0; PM128A Xmm = (PM128A) context_helper.pfnLocateXStateFeature(&ctx, XSTATE_LEGACY_SSE, &xmm_blob_length); PM128A Ymm = (PM128A) context_helper.pfnLocateXStateFeature(&ctx, XSTATE_AVX, NULL); const int nxmm_regs = xmm_blob_length / sizeof(Xmm[0]); const int nymm_regs = qmin(nxmm_regs, 16); for ( ; idx < nymm_regs; idx++ ) { #ifdef __X86__ if ( idx < I386_AVX_NREGS ) #else if ( idx < I386_AVX_NREGS || (ti->flags & THR_WOW64) == 0 ) #endif { bytevec_t &storage = values[R_YMM0+idx].set_bytes(); storage.resize(32, 0); if ( Xmm != NULL ) { CASSERT(sizeof(Xmm[0].Low) == 8); memcpy((void *) &storage[0], (void *) &Xmm[idx].Low, 8); memcpy((void *) &storage[8], (void *) &Xmm[idx].High, 8); } if ( Ymm != NULL && (feature_mask & XSTATE_MASK_AVX) != 0 ) { CASSERT(sizeof(Ymm[0].Low) == 8); memcpy((void *) &storage[16], (void *) &Ymm[idx].Low, 8); memcpy((void *) &storage[24], (void *) &Ymm[idx].High, 8); } } } } // All registers that were not set, will be marked as unavailable for ( ; idx < R_LAST_YMM - R_YMM0 + 1; ++idx ) values[R_YMM0+idx].set_unavailable(); } //? others registers return DRC_OK; } //-------------------------------------------------------------------------- bool thread_info_t::toggle_tbit(bool set_tbit) { if ( !read_context(X86_RC_GENERAL) ) return false; bool ok = true; CASSERT(EFLAGS_TRAP_FLAG == 0x100); // so we can shift set_tbit << 8 CONTEXT &ctx = get_ctx(); if ( (ctx.EFlags & EFLAGS_TRAP_FLAG) != (set_tbit << 8) ) //lint !e647 possible truncation before conversion from 'int' to 'unsigned long' { QASSERT(30117, (ctx.ContextFlags & CONTEXT_CONTROL) != 0); ctx.EFlags |= EFLAGS_TRAP_FLAG; int saved = ctx.ContextFlags; ctx.ContextFlags = CONTEXT_CONTROL; ok = write_context(X86_RC_GENERAL); ctx.ContextFlags = saved; if ( ok ) setflag(flags, THR_TRACING, set_tbit); else debmod->deberr("%d: SetThreadContext failed", tid); } return ok; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_set_resume_mode(thid_t tid, resume_mode_t resmod) { if ( resmod != RESMOD_INTO ) return DRC_FAILED; // not supported check_thread(false); NODISTURB_ASSERT(in_event != NULL); thread_info_t *ti = threads.get(tid); if ( ti == NULL ) return DRC_FAILED; bool ok = ti->toggle_tbit(true); if ( !ok ) deberr("%d: (set_step) SetThreadContext failed", tid); return ok ? DRC_OK : DRC_FAILED; } //-------------------------------------------------------------------------- bool win32_debmod_t::clear_tbit(thread_info_t &ti) { NODISTURB_ASSERT(in_event != NULL); bool ok = ti.toggle_tbit(false); if ( !ok ) deberr("%d: (clr_step) SetThreadContext failed", ti.tid); return ok; } //-------------------------------------------------------------------------- // invalidate registers of all threads void win32_debmod_t::invalidate_all_contexts(void) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) p->second.invalidate_context(); } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_continue_after_event(const debug_event_t *event) { check_thread(false); NODISTURB_ASSERT(in_event != NULL || exiting); if ( event == NULL ) return DRC_FAILED; if ( events.empty() ) { bool done = false; if ( !done ) //-V547 '!done' is always true { // check if we need to install the workaround for single stepping over callgates thread_info_t *ti = threads.get(event->tid); if ( ti != NULL && ti->is_tracing() ) { if ( check_for_call_large(event, process_handle) ) install_callgate_workaround(ti, event); } int flag = event->handled ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED; if ( !ContinueDebugEvent(event->pid, event->tid, flag) ) { deberr("ContinueDebugEvent"); return DRC_FAILED; } debdeb("ContinueDebugEvent: handled=%s\n", event->handled ? "yes" : "no"); if ( event->eid() == PROCESS_EXITED ) { // from WaitForDebugEvent help page: // If the system previously reported an EXIT_PROCESS_DEBUG_EVENT debugging event, // the system closes the handles to the process and thread when the debugger calls the ContinueDebugEvent function. // => we don't close these handles to avoid error messages cpdi.hProcess = INVALID_HANDLE_VALUE; cpdi.hThread = INVALID_HANDLE_VALUE; process_handle= INVALID_HANDLE_VALUE; thread_handle = INVALID_HANDLE_VALUE; cleanup(); } } invalidate_all_contexts(); } in_event = NULL; return DRC_OK; } //-------------------------------------------------------------------------- drc_t idaapi win32_debmod_t::dbg_exit_process(qstring * /*errbuf*/) { check_thread(false); // WindowsCE sometimes reports failure but terminates the application. // We ignore the return value. bool check_termination_code = prepare_to_stop_process(in_event, threads); bool terminated = TerminateProcess(process_handle, -1) != 0; if ( !terminated && check_termination_code ) { deberr("TerminateProcess"); return DRC_FAILED; } exiting = true; if ( in_event != NULL && dbg_continue_after_event(in_event) != DRC_OK ) { deberr("continue_after_event"); return DRC_FAILED; } return DRC_OK; } //-------------------------------------------------------------------------- void win32_debmod_t::show_exception_record(const EXCEPTION_RECORD &er, int level) { char name[MAXSTR]; get_exception_name(er.ExceptionCode, name, sizeof(name)); if ( level > 0 ) dmsg("%*c", level, ' '); dmsg("%s: fl=%X adr=%a #prm=%d\n", name, er.ExceptionFlags, EA_T(er.ExceptionAddress), er.NumberParameters); if ( er.NumberParameters > 0 ) { dmsg("%*c", level+2, ' '); int n = qmin(er.NumberParameters, EXCEPTION_MAXIMUM_PARAMETERS); for ( int i=0; i < n; i++ ) dmsg("%s0x%a", i == 0 ? "" : " ", ea_t(er.ExceptionInformation[i])); dmsg("\n"); } if ( er.ExceptionRecord != NULL ) show_exception_record(*er.ExceptionRecord, level+2); } //-------------------------------------------------------------------------- void win32_debmod_t::show_debug_event(const DEBUG_EVENT &ev) { if ( !debug_debugger ) return; dmsg("[%u %d] ", ev.dwProcessId, ev.dwThreadId); switch ( ev.dwDebugEventCode ) { case EXCEPTION_DEBUG_EVENT: { const EXCEPTION_RECORD &er = ev.u.Exception.ExceptionRecord; dmsg("EXCEPTION: ea=%p first: %d", // no \n intentionally er.ExceptionAddress, ev.u.Exception.dwFirstChance); show_exception_record(er); } break; case CREATE_THREAD_DEBUG_EVENT: dmsg("CREATE_THREAD: hThread=%X LocalBase=%p Entry=%p\n", ev.u.CreateThread.hThread, ev.u.CreateThread.lpThreadLocalBase, ev.u.CreateThread.lpStartAddress); break; case CREATE_PROCESS_DEBUG_EVENT: { const CREATE_PROCESS_DEBUG_INFO &cpinf = ev.u.CreateProcessInfo; char path[QMAXPATH]; if ( process_handle == INVALID_HANDLE_VALUE ) process_handle = cpinf.hProcess; get_filename_for( path, sizeof(path), eanat_t(cpinf.lpImageName), cpinf.fUnicode != 0, eanat_t(cpinf.lpBaseOfImage)); dmsg("CREATE_PROCESS: hFile=%X hProcess=%X hThread=%X " "base=%p\n dbgoff=%X dbgsiz=%X tlbase=%p start=%p name=%p '%s' \n", cpinf.hFile, cpinf.hProcess, cpinf.hThread, cpinf.lpBaseOfImage, cpinf.dwDebugInfoFileOffset, cpinf.nDebugInfoSize, cpinf.lpThreadLocalBase, cpinf.lpStartAddress, cpinf.lpImageName, path); } break; case EXIT_THREAD_DEBUG_EVENT: dmsg("EXIT_THREAD: code=%d\n", ev.u.ExitThread.dwExitCode); break; case EXIT_PROCESS_DEBUG_EVENT: dmsg("EXIT_PROCESS: code=%d\n", ev.u.ExitProcess.dwExitCode); break; case LOAD_DLL_DEBUG_EVENT: { char path[QMAXPATH]; const LOAD_DLL_DEBUG_INFO &di = ev.u.LoadDll; get_filename_for( path, sizeof(path), eanat_t(di.lpImageName), di.fUnicode != 0, eanat_t(di.lpBaseOfDll)); dmsg("LOAD_DLL: h=%X base=%p dbgoff=%X dbgsiz=%X name=%X '%s'\n", di.hFile, di.lpBaseOfDll, di.dwDebugInfoFileOffset, di.nDebugInfoSize, di.lpImageName, path); } break; case UNLOAD_DLL_DEBUG_EVENT: dmsg("UNLOAD_DLL: base=%p\n", ev.u.UnloadDll.lpBaseOfDll); break; case OUTPUT_DEBUG_STRING_EVENT: { char buf[MAXSTR]; get_debug_string(ev, buf, sizeof(buf)); dmsg("OUTPUT_DEBUG_STRING: str=\"%s\"\n", buf); } break; case RIP_EVENT: dmsg("RIP_EVENT (system debugging error)\n"); break; default: dmsg("UNKNOWN_DEBUG_EVENT %d\n", ev.dwDebugEventCode); break; } } //-------------------------------------------------------------------------- void win32_debmod_t::patch_context_struct( CONTEXT &ctx, int reg_idx, const thread_info_t *ti, const regval_t *value) const { switch ( reg_idx ) { case R_CS: ctx.SegCs = WORD(value->ival); break; case R_DS: ctx.SegDs = WORD(value->ival); break; case R_ES: ctx.SegEs = WORD(value->ival); break; case R_FS: ctx.SegFs = WORD(value->ival); break; case R_GS: ctx.SegGs = WORD(value->ival); break; case R_SS: ctx.SegSs = WORD(value->ival); break; #ifndef __X86__ case R_EAX: ctx.Rax = value->ival; break; case R_EBX: ctx.Rbx = value->ival; break; case R_ECX: ctx.Rcx = value->ival; break; case R_EDX: ctx.Rdx = value->ival; break; case R_ESI: ctx.Rsi = value->ival; break; case R_EDI: ctx.Rdi = value->ival; break; case R_EBP: ctx.Rbp = value->ival; break; case R_ESP: ctx.Rsp = value->ival; break; case R_EIP: ctx.Rip = value->ival; break; # ifdef __EA64__ case R_R8: ctx.R8 = value->ival; break; case R_R9 : ctx.R9 = value->ival; break; case R_R10: ctx.R10 = value->ival; break; case R_R11: ctx.R11 = value->ival; break; case R_R12: ctx.R12 = value->ival; break; case R_R13: ctx.R13 = value->ival; break; case R_R14: ctx.R14 = value->ival; break; case R_R15: ctx.R15 = value->ival; break; # endif #else case R_EAX: ctx.Eax = (size_t)value->ival; break; case R_EBX: ctx.Ebx = (size_t)value->ival; break; case R_ECX: ctx.Ecx = (size_t)value->ival; break; case R_EDX: ctx.Edx = (size_t)value->ival; break; case R_ESI: ctx.Esi = (size_t)value->ival; break; case R_EDI: ctx.Edi = (size_t)value->ival; break; case R_EBP: ctx.Ebp = (size_t)value->ival; break; case R_ESP: ctx.Esp = (size_t)value->ival; break; case R_EIP: ctx.Eip = (size_t)value->ival; break; #ifdef __EA64__ case R_R8: break; case R_R9 : break; case R_R10: break; case R_R11: break; case R_R12: break; case R_R13: break; case R_R14: break; case R_R15: break; #endif #endif case R_TAGS: ctx.FloatSave.TagWord = WORD(value->ival); break; case R_EFLAGS: ctx.EFlags = DWORD(value->ival); break; case R_CTRL: ctx.FloatSave.ControlWord = WORD(value->ival); break; case R_STAT: ctx.FloatSave.StatusWord = WORD(value->ival); break; case R_MXCSR: XMMREG_MXCSR = value->ival; break; default: { void *xptr = NULL; // uint8 ymm_storage[32]; int nbytes = 0; int regclass = get_reg_class(reg_idx); if ( (regclass & X86_RC_XMM) != 0 ) { // XMM registers xptr = XMMREG_PTR + (reg_idx - R_XMM0) * 16; nbytes = 16; } else if ( (regclass & X86_RC_YMM) != 0 ) { // YMM registers if ( context_helper.xstate_helpers_loaded() ) { if ( context_helper.pfnSetXStateFeaturesMask(&ctx, XSTATE_MASK_AVX) != FALSE ) { DWORD xmm_blob_length = 0; PM128A Xmm = (PM128A) context_helper.pfnLocateXStateFeature(&ctx, XSTATE_LEGACY_SSE, &xmm_blob_length); PM128A Ymm = (PM128A) context_helper.pfnLocateXStateFeature(&ctx, XSTATE_AVX, NULL); const int nxmm_regs = xmm_blob_length / sizeof(Xmm[0]); const int nymm_regs = qmin(nxmm_regs, 16); const int ymm_idx = reg_idx - R_YMM0; #ifdef __X86__ if ( ymm_idx < nymm_regs && ymm_idx < I386_AVX_NREGS ) #else if ( ymm_idx < nymm_regs && (ymm_idx < I386_AVX_NREGS || (ti->flags & THR_WOW64) == 0 ) ) #endif { struct ida_local writer_t { const uint8 *data; size_t bytes_left; writer_t(const regval_t *_value) { data = (const uint8 *) _value->get_data(); bytes_left = _value->get_data_size(); } void put8(uint64 *storage) { if ( storage != NULL ) { const size_t to_write = qmin(8, bytes_left); if ( to_write > 0 ) { memcpy((void *) storage, (void *) &data[0], to_write); bytes_left -= to_write; data += to_write; } } else { const size_t to_write = qmin(8, bytes_left); bytes_left -= to_write; data += to_write; } } void put16(PM128A storage) { if ( storage != NULL ) { put8((uint64 *) &storage->Low); put8((uint64 *) &storage->High); } else { const size_t to_write = qmin(16, bytes_left); bytes_left -= to_write; data += to_write; } } }; writer_t writer(value); writer.put16(&Xmm[ymm_idx]); writer.put16(&Ymm[ymm_idx]); } else { Xmm[ymm_idx].Low = 0; Xmm[ymm_idx].High = 0; Ymm[ymm_idx].Low = 0; Ymm[ymm_idx].High = 0; } } } break; // don't do the xptr copying thing } else if ( (regclass & X86_RC_FPU) != 0 ) { xptr = FPUREG_PTR + (reg_idx-R_ST0) * FPUREG_ENTRY_SIZE; nbytes = 10; } else if ( (regclass & X86_RC_MMX) != 0 ) { xptr = FPUREG_PTR + (reg_idx-R_MMX0) * FPUREG_ENTRY_SIZE; nbytes = 8; } else { INTERR(30118); } const void *vptr = value->get_data(); size_t size = value->get_data_size(); memcpy(xptr, vptr, qmin(size, nbytes)); //-V575 null pointer 'xptr' } break; } qnotused(ti); } //-------------------------------------------------------------------------- int win32_debmod_t::dbg_freeze_threads_except(thid_t tid) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) if ( p->first != tid ) _sure_suspend_thread(p->second, true); return 1; } //-------------------------------------------------------------------------- int win32_debmod_t::dbg_thaw_threads_except(thid_t tid) { for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) if ( p->first != tid ) _sure_resume_thread(p->second, true); return 1; } //-------------------------------------------------------------------------- // if we have to do something as soon as we noticed the connection // broke, this is the correct place bool idaapi win32_debmod_t::dbg_prepare_broken_connection(void) { broken_connection = true; bool ret = false; if ( restore_broken_breakpoints() ) { // create the required event for synchronization; we use it // to notify when the process was successfully detached broken_event_handle = CreateEvent(NULL, false, false, NULL); if ( broken_event_handle != NULL ) { int code = WAIT_TIMEOUT; while ( code == WAIT_TIMEOUT ) code = WaitForSingleObject(broken_event_handle, 100); if ( code == WAIT_OBJECT_0 ) { suspend_running_threads(_suspended_threads); if ( dbg_detach_process() == DRC_OK ) SetEvent(broken_event_handle); } } } return ret; } //-------------------------------------------------------------------------- // Continuing from a broken connection in win32 debugger consist in the // following step (if we're talking about a single threaded server): // // 1 - Notify the other thread that we want to reuse that connection // 2 - Wait for the previous thread to notify that finished his work // 3 - Reattach to the process and reopen thread's handles as, for a // reason, the handles we have are invalid (why?). // 4 - Resume the threads we suspended before. // bool idaapi win32_debmod_t::dbg_continue_broken_connection(pid_t _pid) { debmod_t::dbg_continue_broken_connection(_pid); QASSERT(676, broken_event_handle != NULL); // notify the broken thread we want to reuse the connection SetEvent(broken_event_handle); // and wait for the notification for a maximum of 15 seconds // as we don't want to wait forever (INFINITE) because the // other thread may fail int code = WaitForSingleObject(broken_event_handle, 15000); if ( code != WAIT_OBJECT_0 ) { msg("Error restoring broken connection"); return false; } if ( dbg_attach_process(_pid, -1, 0, NULL) == DRC_OK && reopen_threads() ) { resume_suspended_threads(_suspended_threads); return true; } return false; } //-------------------------------------------------------------------------- bool win32_debmod_t::reopen_threads(void) { if ( _OpenThread == NULL ) return false; for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p ) { HANDLE hThread; hThread = _OpenThread(THREAD_ALL_ACCESS, true, p->second.tid); if ( hThread != NULL ) p->second.hThread = hThread; else deberr("OpenThread"); p->second.suspend_count = get_thread_suspend_count(hThread); } return true; } //-------------------------------------------------------------------------- static bool enable_privilege(LPCTSTR privilege, bool enable); static bool g_subsys_inited = false; static bool g_got_debpriv = false; bool init_subsystem() { if ( g_subsys_inited ) return true; if ( !win32_debmod_t::winver.ok() ) return false; win_tool_help_t *wth = win32_debmod_t::get_tool_help(); if ( wth->ok() ) g_code |= DBG_HAS_GET_PROCESSES; // DebugActiveProcessStop() is only available on XP/2K3 if ( wth->use_debug_detach_process() ) g_code |= DBG_HAS_DETACH_PROCESS; g_got_debpriv = enable_privilege(SE_DEBUG_NAME, true); if ( !g_got_debpriv ) msg("Cannot set debug privilege: %s.\n" "Debugging of processes owned by another account won't be possible.\n", winerr(GetLastError())); win32_debmod_t::reuse_broken_connections = true; init_win32_subsystem(); HINSTANCE h = GetModuleHandle(kernel32_dll); *(FARPROC*)&_OpenThread = GetProcAddress(h, TEXT("OpenThread")); *(FARPROC*)&_GetThreadDescription = GetProcAddress(h, TEXT("GetThreadDescription")); #ifndef __X86__ *(FARPROC*)&_Wow64GetThreadContext = GetProcAddress(h, TEXT("Wow64GetThreadContext")); *(FARPROC*)&_Wow64SetThreadContext = GetProcAddress(h, TEXT("Wow64SetThreadContext")); *(FARPROC*)&_Wow64GetThreadSelectorEntry = GetProcAddress(h, TEXT("Wow64GetThreadSelectorEntry")); #endif g_subsys_inited = g_code != 0; return g_subsys_inited; } //-------------------------------------------------------------------------- bool term_subsystem() { if ( !g_subsys_inited ) return true; g_subsys_inited = false; if ( g_got_debpriv ) { enable_privilege(SE_DEBUG_NAME, false); g_got_debpriv = false; } term_win32_subsystem(); return true; } //-------------------------------------------------------------------------- debmod_t *create_debug_session(void *) { return new win32_debmod_t(); } //-------------------------------------------------------------------------- // // DEBUG PRIVILEGE // //-------------------------------------------------------------------------- // dynamic linking information for Advapi functions static HMODULE hAdvapi32 = NULL; // function prototypes typedef BOOL (WINAPI *OpenProcessToken_t)( HANDLE ProcessHandle, DWORD DesiredAccess, PHANDLE TokenHandle); typedef BOOL (WINAPI *LookupPrivilegeValue_t)( LPCTSTR lpSystemName, LPCTSTR lpName, PLUID lpLuid); typedef BOOL (WINAPI *AdjustTokenPrivileges_t)( HANDLE TokenHandle, BOOL DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, DWORD BufferLength, PTOKEN_PRIVILEGES PreviousState, PDWORD ReturnLength); // Function pointers static OpenProcessToken_t _OpenProcessToken = NULL; static LookupPrivilegeValue_t _LookupPrivilegeValue = NULL; static AdjustTokenPrivileges_t _AdjustTokenPrivileges = NULL; //-------------------------------------------------------------------------- static void term_advapi32(void) { if ( hAdvapi32 != NULL ) { DWORD code = GetLastError(); FreeLibrary(hAdvapi32); SetLastError(code); hAdvapi32 = NULL; } } //-------------------------------------------------------------------------- static bool init_advapi32(void) { // load the library hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); if ( hAdvapi32 == NULL ) return false; // find the needed functions *(FARPROC*)&_OpenProcessToken = GetProcAddress(hAdvapi32, TEXT("OpenProcessToken")); *(FARPROC*)&_LookupPrivilegeValue = GetProcAddress(hAdvapi32, TEXT(LookupPrivilegeValue_Name)); *(FARPROC*)&_AdjustTokenPrivileges = GetProcAddress(hAdvapi32, TEXT("AdjustTokenPrivileges")); bool ok = _OpenProcessToken != NULL && _LookupPrivilegeValue != NULL && _AdjustTokenPrivileges != NULL; if ( !ok ) term_advapi32(); return ok; } //-------------------------------------------------------------------------- // based on code from: // http://support.microsoft.com/support/kb/articles/Q131/0/65.asp static bool enable_privilege(LPCTSTR privilege, bool enable) { if ( !win32_debmod_t::winver.is_NT() ) // no privileges on 9X/ME return true; bool ok = false; if ( init_advapi32() ) { HANDLE hToken; DWORD tokens = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY; if ( _OpenProcessToken(GetCurrentProcess(), tokens, &hToken) ) { LUID luid; if ( _LookupPrivilegeValue(NULL, privilege, &luid) ) { TOKEN_PRIVILEGES tp; memset(&tp, 0, sizeof(tp)); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; ok = _AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL) != FALSE; } CloseHandle(hToken); } term_advapi32(); } return ok; }