# This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # # Licence is LGPL, see LICENCE in the top-level directory require 'metasm/os/main' require 'metasm/debug' require 'metasm/dynldr' module Metasm class WinAPI < DynLdr def self.api_not_found(lib, func) puts "could not find symbol #{func.name.inspect} in #{lib.inspect}" if $VERBOSE and not func.attributes.to_a.include?('optional') end new_api_c < 0 prot = { WinAPI::PAGE_NOACCESS => '---', WinAPI::PAGE_READONLY => 'r--', WinAPI::PAGE_READWRITE => 'rw-', WinAPI::PAGE_WRITECOPY => 'rw-', WinAPI::PAGE_EXECUTE => '--x', WinAPI::PAGE_EXECUTE_READ => 'r-x', WinAPI::PAGE_EXECUTE_READWRITE => 'rwx', WinAPI::PAGE_EXECUTE_WRITECOPY => 'rwx' }[info[:protect] & 0xff] prot = prot.sub('r', '-') + 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0 prot << 'p' if info[:type] & WinAPI::MEM_PRIVATE > 0 if h = hcache[info.baseaddress] a = [] a << 'default' if h[:default] a << 'shared' if h[:shared] a << 'heap' #a << h[:flags].to_s(16) cmt = '[' + a.join(' ') + ']' elsif WinAPI.ntqueryvirtualmemory(handle, info.baseaddress, WinAPI::MEMORYMAPFILENAME, path, path.length, 0) == 0 us = WinAPI.decode_c_struct('UNICODE_STRING', path) s = WinAPI.decode_c_ary('USHORT', us['Length']/2, WinAPI.memory_read(us['Buffer'], us['MaximumLength'])) cmt = s.to_strz else cmt = '' end list << [info.baseaddress, info.regionsize, prot, cmt] end list end def peb_base @peb_base ||= if WinAPI.respond_to?(:ntqueryinformationprocess) pinfo = WinAPI.alloc_c_struct('PROCESS_BASIC_INFORMATION') if WinAPI.ntqueryinformationprocess(handle, WinAPI::PROCESSBASICINFORMATION, pinfo, pinfo.sizeof, 0) == 0 pinfo.pebbaseaddress end else # pre-NT: all pebs should have the same addr WinAPI.new_func_asm('unsigned get_peb(void)', 'mov eax, fs:[30h] ret') { WinAPI.get_peb } end end attr_writer :peb_base def terminate(exitcode=0) WinAPI.terminateprocess(handle, exitcode) end end class Thread attr_accessor :tid attr_accessor :process def initialize(tid, handle=nil, process=nil) @tid = tid @handle = handle @process = process end def handle @handle ||= WinAPI.openthread(WinAPI::THREAD_ALL_ACCESS, 0, @tid) end attr_writer :handle # return the address of the TEB for the target thread def teb_base @teb_base ||= if WinAPI.respond_to?(:ntqueryinformationthread) tinfo = WinAPI.alloc_c_struct('THREAD_BASIC_INFORMATION') if WinAPI.ntqueryinformationthread(handle, WinAPI::THREADBASICINFORMATION, tinfo, tinfo.sizeof, 0) == 0 tinfo.tebbaseaddress end else fs = context { |c| c[:fs] } ldte = WinAPI.alloc_c_struct('LDT_ENTRY') if WinAPI.getthreadselectorentry(handle, fs, ldte) ldte.baselow | (ldte.basemid << 16) | (ldte.basehi << 24) end end end attr_writer :teb_base # increment the suspend count of the target thread - stop at >0 def suspend if WinAPI.host_cpu.size == 64 and process and process.addrsz == 32 WinAPI.wow64suspendthread(handle) else WinAPI.suspendthread(handle) end end # decrease the suspend count of the target thread - runs at 0 def resume WinAPI.resumethread(handle) end def terminate(exitcode=0) WinAPI.terminatethread(handle, exitcode) end # returns a Context object. Can be reused, refresh the values with #update (target thread must be suspended) # if a block is given, suspend the thread, update the context, yield it, and resume the thread def context @context ||= Context.new(self, :all) if block_given? suspend begin @context.update yield @context ensure resume end else @context end end attr_writer :context class Context def initialize(thread, kind=:all) @handle = thread.handle tg = (thread.process ? thread.process.addrsz : 32) hcpu = WinAPI.host_cpu.shortname case hcpu when 'ia32', 'x64' else raise "unsupported architecture #{tg}" end @getcontext = :getthreadcontext @setcontext = :setthreadcontext case tg when 32 @context = WinAPI.alloc_c_struct('_CONTEXT_I386') @context.contextflags = WinAPI::CONTEXT_I386_ALL if hcpu == 'x64' @getcontext = :wow64getthreadcontext @setcontext = :wow64setthreadcontext end when 64 @context = WinAPI.alloc_c_struct('_CONTEXT_AMD64') @context.contextflags = WinAPI::CONTEXT_AMD64_ALL end end # retrieve the actual context structure (so we can pass to API's like StackWalk64) def c_struct @context end # update the context to reflect the current thread reg values # call only when the thread is suspended def update WinAPI.send(@getcontext, @handle, @context) end def [](k) case k.to_s when /^[cdefgs]s$/i @context["seg#{k}"] when /^st(\d?)$/i v = @context['st'][$1.to_i] buf = v.str[v.stroff, 10] # TODO check this, 'D' is 8byte wide buf.unpack('D')[0] # TODO when /^ymm(\d+)$/i when /^xmm(\d+)$/i v = @context['xmm'][$1.to_i] (v.hi << 64) | v.lo when /^mmx?(\d)$/i # XXX probably in st(0/7) @context['xmm'][$1.to_i].lo else @context[k] end end def []=(k, v) case k.to_s when /^[cdefgs]s$/i @context["seg#{k}"] = v when /^st(\d?)$/i # TODO check this, 'D' is 8byte wide buf = [v, 0, 0].pack('DCC') @context['st'][$1.to_i][0, 10] = buf # TODO when /^ymm(\d+)$/i when /^xmm(\d+)$/i kk = @context['xmm'][$1.to_i] kk.lo = v & ((1<<64)-1) kk.hi = (v>>64) & ((1<<64)-1) when /^mmx?(\d)$/i # XXX st(7-$1) ? @context['xmm'][$1.to_i].lo = v else @context[k] = v end WinAPI.send(@setcontext, @handle, @context) end def method_missing(m, *a) if m.to_s[-1] == ?= return super(m, *a) if a.length != 1 send '[]=', m.to_s[0...-1], a[0] else return super(m, *a) if a.length != 0 send '[]', m end end end end class << self # try to enable debug privilege in current process def get_debug_privilege # TODO use real structs / new_func_c htok = [0].pack('L') return if not WinAPI.openprocesstoken(WinAPI.getcurrentprocess(), WinAPI::TOKEN_ADJUST_PRIVILEGES | WinAPI::TOKEN_QUERY, htok) luid = [0, 0].pack('LL') return if not WinAPI.lookupprivilegevaluea(nil, WinAPI::SE_DEBUG_NAME, luid) # priv.PrivilegeCount = 1; # priv.Privileges[0].Luid = luid; # priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; priv = luid.unpack('LL').unshift(1).push(WinAPI::SE_PRIVILEGE_ENABLED).pack('LLLL') return if not WinAPI.adjusttokenprivileges(htok.unpack('L').first, 0, priv, 0, nil, nil) true end # returns an array of Processes with pid/ppid/path filled def list_processes h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPPROCESS, 0) list = [] pe = WinAPI.alloc_c_struct('PROCESSENTRY32', :dwsize => :size) return if not WinAPI.process32first(h, pe) loop do p = Process.new(pe.th32processid) p.ppid = pe.th32parentprocessid p.path = pe.szexefile.to_strz list << p if p.pid != 0 break if WinAPI.process32next(h, pe) == 0 end WinAPI.closehandle(h) list end # retrieve the list of Modules for a process with addr/size/path filled def list_modules(pid) h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPMODULE, pid) return [] if h == WinAPI::INVALID_HANDLE_VALUE list = [] me = WinAPI.alloc_c_struct('MODULEENTRY32', :dwsize => :size) return [] if not WinAPI.module32first(h, me) loop do m = Process::Module.new m.addr = me.modbaseaddr m.size = me.modbasesize m.path = me.szexepath.to_strz list << m break if WinAPI.module32next(h, me) == 0 end WinAPI.closehandle(h) list end # returns the list of thread ids of the system, optionally filtering by pid def list_threads(pid=nil) h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPTHREAD, 0) list = [] te = WinAPI.alloc_c_struct('THREADENTRY32', :dwsize => :size) return [] if not WinAPI.thread32first(h, te) loop do list << te.th32threadid if not pid or te.th32ownerprocessid == pid break if WinAPI.thread32next(h, te) == 0 end WinAPI.closehandle(h) list end # returns the heaps of the process, from a toolhelp snapshot SNAPHEAPLIST # this is a hash # heap_addr => { :flags => integer (heap flags) # :shared => bool (from flags) # :default => bool (from flags) } def list_heaps(pid) h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPHEAPLIST, pid) return [] if h == WinAPI::INVALID_HANDLE_VALUE ret = {} he = WinAPI.alloc_c_struct('HEAPLIST32', :dwsize => :size) return [] if not WinAPI.heap32listfirst(h, he) loop do hash = ret[he.th32heapid] = { :flags => he.dwflags } hash[:default] = true if hash[:flags] & WinAPI::HF32_DEFAULT == WinAPI::HF32_DEFAULT hash[:shared] = true if hash[:flags] & WinAPI::HF32_SHARED == WinAPI::HF32_SHARED # TODO there are lots of other flags in there ! like 0x1000 / 0x8000 break if WinAPI.heap32listnext(h, he) == 0 end WinAPI.closehandle(h) ret end # create a debugger for the target pid/path def create_debugger(path) WinDebugger.new(path) end # Injects a shellcode into the memory space of targetproc # target is a WinOS::Process # shellcode may be a String (raw shellcode) or an EncodedData # With an EncodedData, unresolved relocations are solved using # exports of modules from the target address space ; also the # shellcode need not be position-independant. def inject_shellcode(target, shellcode) raise 'cannot open target memory' if not remote_mem = target.memory return if not injectaddr = WinAPI.virtualallocex(target.handle, 0, shellcode.length, WinAPI::MEM_COMMIT | WinAPI::MEM_RESERVE, WinAPI::PAGE_EXECUTE_READWRITE) puts 'remote buffer at %x' % injectaddr if $VERBOSE if shellcode.kind_of? EncodedData fixup_shellcode_relocs(shellcode, target, remote_mem) shellcode.fixup! shellcode.binding(injectaddr) r = shellcode.reloc.values.map { |r_| r_.target } raise "unresolved shellcode relocs #{r.join(', ')}" if not r.empty? shellcode = shellcode.data end # inject the shellcode remote_mem[injectaddr, shellcode.length] = shellcode injectaddr end def fixup_shellcode_relocs(shellcode, target, remote_mem) ext = shellcode.reloc_externals binding = {} while e = ext.pop next if binding[e] next if not lib = WindowsExports::EXPORT[e] # XXX could scan all exports... LoadLibrary ftw next if not m = target.modules.find { |m_| m_.path.downcase.include? lib.downcase } lib = LoadedPE.load(remote_mem[m.addr, 0x1000_0000]) lib.decode_header lib.decode_exports lib.export.exports.each { |e_| next if not e_.name or not e_.target binding[e_.name] = m.addr + lib.label_rva(e_.target) } shellcode.fixup! binding end end # creates a new thread in the target process, with the specified start address def createthread(target, startaddr) WinAPI.createremotethread(target.handle, 0, 0, startaddr, 0, 0, 0) end # calls inject_shellcode and createthread def inject_run_shellcode(target, shellcode) raise "failed to inject shellcode" if not addr = inject_shellcode(target, shellcode) createthread(target, addr) end # returns a Process associated to the process handle def open_process_handle(h) pid = WinAPI.getprocessid(h) rescue 0 Process.new(pid, h) end # returns the Process associated to pid if it is alive def open_process(pid) Process.new(pid) if check_process(pid) end # returns true if the process pid exists and we can open it with QUERY_INFORMATION def check_process(pid) if h = WinAPI.openprocess(WinAPI::PROCESS_QUERY_INFORMATION, 0, pid) WinAPI.closehandle(h) true end end # returns the Thread associated to a tid if it is alive def open_thread(tid) Thread.new(tid) if check_tid(tid) end # check if the thread is alive and can be read with QUERY_INFO # and optionally if it belongs to pid def check_tid(tid, pid=nil) if h = WinAPI.openthread(WinAPI::THREAD_QUERY_INFORMATION, 0, tid) WinAPI.closehandle(h) not pid or list_threads(pid).include?(tid) end end # returns the [major, minor] version of the windows os def version v = WinAPI.getversion [v & 0xff, (v>>8) & 0xff] end end # class << self end class WindowsRemoteString < VirtualString def self.open_pid(pid, access = nil) if access handle = WinAPI.openprocess(access, 0, pid) else handle = WinAPI.openprocess(WinAPI::PROCESS_ALL_ACCESS, 0, pid) if not handle puts "cannot openprocess ALL_ACCESS pid #{pid}, try ro" if $VERBOSE handle = WinAPI.openprocess(WinAPI::PROCESS_VM_READ, 0, pid) end end raise "OpenProcess(#{pid}): #{WinAPI.last_error_msg}" if not handle new(handle) end attr_accessor :handle # returns a virtual string proxying the specified process memory range # reads are cached (4096 aligned bytes read at once) # writes are done directly (if handle has appropriate privileges) def initialize(handle, addr_start=0, length=nil) @handle = handle length ||= 1 << (WinOS.open_process_handle(@handle).addrsz rescue 32) super(addr_start, length) end def dup(addr = @addr_start, len = @length) self.class.new(@handle, addr, len) end def rewrite_at(addr, data) WinAPI.writeprocessmemory(@handle, addr, data, data.length, nil) end def get_page(addr, len=@pagelength) page = [0].pack('C')*len return if WinAPI.readprocessmemory(@handle, addr, page, len, 0) == 0 page end end # this class implements a high-level API over the Windows debugging primitives class WinDebugger < Debugger attr_accessor :os_process, :os_thread, :auto_fix_fs_bug, # is current exception handled? (arg to pass to continuedbgevt) # if it has the special value :suspended, it means that the thread # is to be restarted through resume and not continuedbgevt :continuecode attr_accessor :callback_unloadlibrary, :callback_debugstring, :callback_ripevent def initialize(pidpath=nil) super() @pid_stuff_list << :os_process @tid_stuff_list << :os_thread << :ctx << :continuecode @auto_fix_fs_bug = false return if not pidpath begin npid = Integer(pidpath) attach(npid) rescue ArgumentError create_process(pidpath) end check_target until pid end def shortname; 'windbg'; end def attach(npid) WinAPI.debugactiveprocess(npid) WinAPI.debugsetprocesskillonexit(0) if WinAPI.respond_to?(:debugsetprocesskillonexit) 100.times { check_target break if pid } raise "attach failed" if not pid end def create_process(target) startupinfo = WinAPI.alloc_c_struct('STARTUPINFOA', :cb => :size) processinfo = WinAPI.alloc_c_struct('PROCESS_INFORMATION') flags = WinAPI::DEBUG_PROCESS flags |= WinAPI::DEBUG_ONLY_THIS_PROCESS if not trace_children target = target.dup if target.frozen? # eg ARGV h = WinAPI.createprocessa(nil, target, nil, nil, 0, flags, nil, nil, startupinfo, processinfo) raise "CreateProcess: #{WinAPI.last_error_msg}" if not h set_context(processinfo.dwprocessid, processinfo.dwthreadid) @os_process = WinOS::Process.new(processinfo.dwprocessid, processinfo.hprocess) @os_thread = WinOS::Thread.new(processinfo.dwthreadid, processinfo.hthread, @os_process) initialize_osprocess check_target end # called whenever we receive a handle to a new process being debugged, after initialisation of @os_process def initialize_osprocess initialize_cpu initialize_memory initialize_disassembler end def initialize_newpid raise "non-existing pid #@pid" if pid and not WinOS.check_process(@pid) super() # os_process etc wait for CREATE_THREAD_DBGEVT end def initialize_newtid super() # os_thread etc wait for CREATE_THREAD_DBGEVT @continuecode = WinAPI::DBG_CONTINUE #WinAPI::DBG_EXCEPTION_NOT_HANDLED end def initialize_cpu # wait until we receive the CREATE_PROCESS_DBGEVT message return if not @os_process case WinAPI.host_cpu.shortname when 'ia32', 'x64' @cpu = Ia32.new(os_process.addrsz) else raise 'unsupported architecture' end end def initialize_memory return if not @os_process @memory = os_process.memory end def mappings os_process.mappings end def modules os_process.modules end def list_processes WinOS.list_processes end def list_threads os_process.threads end def check_pid(pid) WinOS.check_process(pid) end def check_tid(tid) # dont raise() on the first set_context when os_proc is not set yet return true if not os_process super(tid) end def ctx if not @ctx # swapin_tid => gui.swapin_tid => getreg before we init os_thread in EventCreateThread return Hash.new(0) if not os_thread @ctx = os_thread.context @ctx.update end @ctx end def invalidate @ctx = nil super() end def get_reg_value(r) ctx[r] end def set_reg_value(r, v) if @state == :running suspend ctx[r] = v resume else ctx[r] = v end end def do_continue(*a) @cpu.dbg_disable_singlestep(self) if @continuecode == :suspended resume else @state = :running WinAPI.continuedebugevent(@pid, @tid, @continuecode) end end def do_singlestep(*a) @cpu.dbg_enable_singlestep(self) if @continuecode == :suspended resume else @state = :running WinAPI.continuedebugevent(@pid, @tid, @continuecode) end end def do_enable_bpm(bp) @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}") WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof) # TODO save original page perms, check bpm type (:w -> vprotect(PAGE_READONLY)), handle multiple bpm on same page WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] | WinAPI::PAGE_GUARD, @bpm_info) end def do_disable_bpm(bp) @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}") WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof) WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] & ~WinAPI::PAGE_GUARD, @bpm_info) end def update_dbgev(ev) # XXX ev is static, copy all necessary values before yielding to something that may call check_target set_context ev.dwprocessid, ev.dwthreadid invalidate @continuecode = WinAPI::DBG_CONTINUE # XXX reinterpret ev as struct32/64 depending on os_process.addrsz ? case ev.dwdebugeventcode when WinAPI::EXCEPTION_DEBUG_EVENT st = ev.exception str = st.exceptionrecord stf = st.dwfirstchance # non-zero = first chance @state = :stopped @info = "exception" # DWORD ExceptionCode; # DWORD ExceptionFlags; # struct _EXCEPTION_RECORD *ExceptionRecord; # PVOID ExceptionAddress; # DWORD NumberParameters; # ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; case str.exceptioncode when WinAPI::STATUS_ACCESS_VIOLATION, WinAPI::STATUS_GUARD_PAGE_VIOLATION if @auto_fix_fs_bug and ctx.fs != 0x3b # fix bug in xpsp1 where fs would get a random value in a debugee log "wdbg: #{pid}:#{tid} fix fs bug" if $DEBUG ctx.fs = 0x3b resume_badbreak return end mode = case str.exceptioninformation[0] when 0; :r when 1; :w when 8; :x end addr = str.exceptioninformation[1] evt_exception(:type => 'access violation', :st => str, :firstchance => stf, :fault_addr => addr, :fault_access => mode) when WinAPI::STATUS_BREAKPOINT, WinAPI::STATUS_WX86_BREAKPOINT # we must ack ntdll interrupts on process start # but we should not mask process-generated exceptions by default.. evt_bpx when WinAPI::STATUS_SINGLE_STEP, WinAPI::STATUS_WX86_SINGLE_STEP evt_hwbp_singlestep else @status_name ||= WinAPI.cp.lexer.definition.keys.grep(/^STATUS_/). sort.inject({}) { |h, c| h.update WinAPI.const_get(c) => c } type = @status_name[str.exceptioncode] || str.exceptioncode.to_s(16) evt_exception(:type => type, :st => str, :firstchance => stf) end when WinAPI::CREATE_THREAD_DEBUG_EVENT st = ev.createthread @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) @os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0 evt_newthread(:st => st) when WinAPI::CREATE_PROCESS_DEBUG_EVENT # XXX 32 vs 64 struct undecidable before we get hprocess.. st = ev.createprocess if not @os_process @os_process = WinOS::Process.new(@pid, st.hprocess) @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) initialize_osprocess else @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) end @os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0 hfile = st.hfile evt_newprocess(:st => st) WinAPI.closehandle(hfile) when WinAPI::EXIT_THREAD_DEBUG_EVENT st = ev.exitthread evt_endthread(:exitcode => st.dwexitcode) when WinAPI::EXIT_PROCESS_DEBUG_EVENT st = ev.exitprocess evt_endprocess(:exitcode => st.dwexitcode) when WinAPI::LOAD_DLL_DEBUG_EVENT st = ev.loaddll hfile = st.hfile evt_loadlibrary(:address => st.lpbaseofdll, :st => st) WinAPI.closehandle(hfile) when WinAPI::UNLOAD_DLL_DEBUG_EVENT st = ev.unloaddll evt_unloadlibrary(:address => st.lpbaseofdll) when WinAPI::OUTPUT_DEBUG_STRING_EVENT st = ev.debugstring str = WinAPI.decode_c_ary("__int#{st.funicode != 0 ? 16 : 8}", st.ndebugstringlength, @memory, st.lpdebugstringdata) if st.lpdebugstringdata str = str.to_array.pack('C*') rescue str.to_array.pack('v*') evt_debugstring(:string => str, :st => str) when WinAPI::RIP_EVENT st = ev.ripinfo evt_ripevent(:st => st) end end def evt_debugstring(info={}) @state = :stopped @info = "debugstring" log "Debugstring: #{info[:string].inspect}" callback_debugstring[info] if callback_debugstring # allow callback to skip this call to continue() by setting info[:nocontinue] = true continue unless info[:nocontinue] end def evt_unloadlibrary(info={}) @state = :stopped @info = "unload library" callback_unloadlibrary[info] if callback_unloadlibrary continue unless info[:nocontinue] end def evt_ripevent(info={}) @state = :stopped @info = "rip_event" # wtf? callback_ripevent[info] if callback_ripevent continue unless info[:nocontinue] end def do_check_target do_waitfordebug(0) end def do_wait_target do_waitfordebug(WinAPI::INFINITE) end def do_waitfordebug(timeout) @dbg_eventstruct ||= WinAPI.alloc_c_struct('_DEBUG_EVENT') if WinAPI.waitfordebugevent(@dbg_eventstruct, timeout) != 0 update_dbgev(@dbg_eventstruct) true end end def del_tid # tell Windows to release the THREAD object WinAPI.continuedebugevent(@pid, @tid, @continuecode) super() end # do nothing, windows will send us a EXIT_PROCESS event def del_tid_notid nil while do_waitfordebug(10) and !@tid end def del_pid # tell Windows to release the PROCESS object WinAPI.debugactiveprocessstop(@pid) if WinAPI.respond_to?(:debugactiveprocessstop) super() end def break return if @state != :running # debugbreak() will create a new thread to 0xcc, but wont touch existing threads suspend end def suspend os_thread.suspend invalidate @state = :stopped @info = 'thread suspended' @continuecode = :suspended end def resume @state = :running @info = nil os_thread.resume end def detach del_all_breakpoints if not WinAPI.respond_to? :debugactiveprocessstop raise 'detach not supported' end # handle pending bp events # TODO check_target needs the Breakpoint objects... #pid = @pid ; 50.times { check_target } ; self.pid = pid # if we detach after a dbgevt and before calling continuedbgevent, the thread # may receive unhandled exceptions (eg BPX) and crash the process right after detach each_tid { do_continue if @state == :stopped } del_pid end def kill(exitcode=0) os_process.terminate(exitcode) end def pass_current_exception(doit = true) return if @continuecode == :suspended @continuecode = (doit ? WinAPI::DBG_EXCEPTION_NOT_HANDLED : WinAPI::DBG_CONTINUE) end end end