nub_size_t MachVMMemory::PageSize(task_t task) { if (m_page_size == kInvalidPageSize) { #if defined (TASK_VM_INFO) && TASK_VM_INFO >= 22 if (task != TASK_NULL) { kern_return_t kr; mach_msg_type_number_t info_count = TASK_VM_INFO_COUNT; task_vm_info_data_t vm_info; kr = task_info (task, TASK_VM_INFO, (task_info_t) &vm_info, &info_count); if (kr == KERN_SUCCESS) { DNBLogThreadedIf(LOG_TASK, "MachVMMemory::PageSize task_info returned page size of 0x%x", (int) vm_info.page_size); m_page_size = vm_info.page_size; return m_page_size; } else { DNBLogThreadedIf(LOG_TASK, "MachVMMemory::PageSize task_info call failed to get page size, TASK_VM_INFO %d, TASK_VM_INFO_COUNT %d, kern return %d", TASK_VM_INFO, TASK_VM_INFO_COUNT, kr); } } #endif m_err = ::host_page_size( ::mach_host_self(), &m_page_size); if (m_err.Fail()) m_page_size = 0; } return m_page_size; }
rnb_err_t RNBSocket::Write (const void *buffer, size_t length) { if (m_fd == -1) return rnb_err; DNBError err; int bytessent = send (m_fd, buffer, length, 0); if (bytessent < 0) err.SetError(errno, DNBError::POSIX); if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) err.LogThreaded("::send ( socket = %i, buffer = %p, length = %zu, flags = 0 ) => %i", m_fd, buffer, length, bytessent); if (bytessent < 0) return rnb_err; if (bytessent != length) return rnb_err; DNBLogThreadedIf(LOG_RNB_PACKETS, "putpkt: %*s", (int)length, (char *)buffer); // All data is string based in debugserver, so this is safe DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s", (int)length, (char *)buffer); return rnb_success; }
nub_size_t MachVMMemory::Write(task_t task, nub_addr_t address, const void *data, nub_size_t data_count) { MachVMRegion vmRegion(task); nub_size_t total_bytes_written = 0; nub_addr_t curr_addr = address; const uint8_t *curr_data = (const uint8_t*)data; while (total_bytes_written < data_count) { if (vmRegion.GetRegionForAddress(curr_addr)) { mach_vm_size_t curr_data_count = data_count - total_bytes_written; mach_vm_size_t region_bytes_left = vmRegion.BytesRemaining(curr_addr); if (region_bytes_left == 0) { break; } if (curr_data_count > region_bytes_left) curr_data_count = region_bytes_left; if (vmRegion.SetProtections(curr_addr, curr_data_count, VM_PROT_READ | VM_PROT_WRITE)) { nub_size_t bytes_written = WriteRegion(task, curr_addr, curr_data, curr_data_count); if (bytes_written <= 0) { // Error should have already be posted by WriteRegion... break; } else { total_bytes_written += bytes_written; curr_addr += bytes_written; curr_data += bytes_written; } } else { DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to set read/write protections on region for address: [0x%8.8llx-0x%8.8llx)", (uint64_t)curr_addr, (uint64_t)(curr_addr + curr_data_count)); break; } } else { DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to get region for address: 0x%8.8llx", (uint64_t)address); break; } } return total_bytes_written; }
MachThread::MachThread(MachProcess *process, bool is_64_bit, uint64_t unique_thread_id, thread_t mach_port_num) : m_process(process), m_unique_id(unique_thread_id), m_mach_port_number(mach_port_num), m_seq_id(GetSequenceID()), m_state(eStateUnloaded), m_state_mutex(PTHREAD_MUTEX_RECURSIVE), m_suspend_count(0), m_stop_exception(), m_arch_ap(DNBArchProtocol::Create(this)), m_reg_sets(NULL), m_num_reg_sets(0), m_ident_info(), m_proc_threadinfo(), m_dispatch_queue_name(), m_is_64_bit(is_64_bit), m_pthread_qos_class_decode(nullptr) { nub_size_t num_reg_sets = 0; m_reg_sets = m_arch_ap->GetRegisterSetInfo(&num_reg_sets); m_num_reg_sets = num_reg_sets; m_pthread_qos_class_decode = (unsigned int (*)(unsigned long, int *, unsigned long *))dlsym( RTLD_DEFAULT, "_pthread_qos_class_decode"); // Get the thread state so we know if a thread is in a state where we can't // muck with it and also so we get the suspend count correct in case it was // already suspended GetBasicInfo(); DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::MachThread ( process = %p, tid = 0x%8.8" PRIx64 ", seq_id = %u )", reinterpret_cast<void *>(&m_process), m_unique_id, m_seq_id); }
bool MachThread::RestoreSuspendCount() { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); DNBError err; if (ThreadIDIsValid(m_tid) == false) return false; else if (m_suspendCount > m_basicInfo.suspend_count) { while (m_suspendCount > m_basicInfo.suspend_count) { err = ::thread_resume (m_tid); if (err.Success()) --m_suspendCount; if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_resume (%4.4x)", m_tid); } } else if (m_suspendCount < m_basicInfo.suspend_count) { while (m_suspendCount < m_basicInfo.suspend_count) { err = ::thread_suspend (m_tid); if (err.Success()) --m_suspendCount; if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_suspend (%4.4x)", m_tid); } } return m_suspendCount == m_basicInfo.suspend_count; }
void MachThread::SetState(nub_state_t state) { PTHREAD_MUTEX_LOCKER (locker, m_state_mutex); m_state = state; DNBLogThreadedIf(LOG_THREAD, "MachThread::SetState ( %s ) for tid = 0x%4.4x", DNBStateAsString(state), m_tid); }
void MachThread::Resume(bool others_stopped) { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); if (MachPortNumberIsValid(m_mach_port_number)) { SetSuspendCountBeforeResume(others_stopped); } }
MachThread::MachThread (MachProcess *process, thread_t tid) : m_process (process), m_tid (tid), m_seq_id (GetSequenceID()), m_state (eStateUnloaded), m_state_mutex (PTHREAD_MUTEX_RECURSIVE), m_break_id (INVALID_NUB_BREAK_ID), m_suspend_count (0), m_stop_exception (), m_arch_ap (DNBArchProtocol::Create (this)), m_reg_sets (NULL), m_num_reg_sets (0) #ifdef THREAD_IDENTIFIER_INFO_COUNT , m_ident_info(), m_proc_threadinfo(), m_dispatch_queue_name() #endif { nub_size_t num_reg_sets = 0; m_reg_sets = m_arch_ap->GetRegisterSetInfo (&num_reg_sets); m_num_reg_sets = num_reg_sets; // Get the thread state so we know if a thread is in a state where we can't // muck with it and also so we get the suspend count correct in case it was // already suspended GetBasicInfo(); DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::MachThread ( process = %p, tid = 0x%4.4x, seq_id = %u )", &m_process, m_tid, m_seq_id); }
kern_return_t MachException::PortInfo::Save (task_t task) { DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Save ( task = 0x%4.4x )", task); // Be careful to be able to have debugserver built on a newer OS than what // it is currently running on by being able to start with all exceptions // and back off to just what is supported on the current system DNBError err; mask = EXC_MASK_ALL; count = (sizeof (ports) / sizeof (ports[0])); err = ::task_get_exception_ports (task, mask, masks, &count, ports, behaviors, flavors); if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, mask, count); if (err.Error() == KERN_INVALID_ARGUMENT && mask != PREV_EXC_MASK_ALL) { mask = PREV_EXC_MASK_ALL; count = (sizeof (ports) / sizeof (ports[0])); err = ::task_get_exception_ports (task, mask, masks, &count, ports, behaviors, flavors); if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, mask, count); } if (err.Fail()) { mask = 0; count = 0; } return err.Error(); }
bool DNBArchProtocol::SetArchitecture (uint32_t cpu_type) { g_current_cpu_type = cpu_type; bool result = g_arch_plugins.find(g_current_cpu_type) != g_arch_plugins.end(); DNBLogThreadedIf (LOG_PROCESS, "DNBArchProtocol::SetDefaultArchitecture (cpu_type=0x%8.8x) => %i", cpu_type, result); return result; }
bool LogFilterRegex::DoesMatch(const LogMessage &message) const { switch (m_filter_target) { case eFilterTargetActivity: // Empty fields never match a condition. if (!message.HasActivity()) return false; return ::regexec(&m_regex, message.GetActivity(), 0, nullptr, 0) == 0; case eFilterTargetActivityChain: // Empty fields never match a condition. if (!message.HasActivity()) return false; return ::regexec(&m_regex, message.GetActivityChain().c_str(), 0, nullptr, 0) == 0; case eFilterTargetCategory: // Empty fields never match a condition. if (!message.HasCategory()) return false; return ::regexec(&m_regex, message.GetCategory(), 0, nullptr, 0) == 0; case eFilterTargetMessage: { const char *message_text = message.GetMessage(); if (!message_text) { DNBLogThreadedIf(LOG_DARWIN_LOG, "LogFilterRegex: regex " "\"%s\" no match due to nullptr message.", m_regex_text.c_str()); return false; } bool match = ::regexec(&m_regex, message_text, 0, nullptr, 0) == 0; DNBLogThreadedIf(LOG_DARWIN_LOG, "LogFilterRegex: regex " "\"%s\" %s message \"%s\".", m_regex_text.c_str(), match ? "matches" : "does not match", message_text); return match; } case eFilterTargetSubsystem: // Empty fields never match a condition. if (!message.HasSubsystem()) return false; return ::regexec(&m_regex, message.GetSubsystem(), 0, nullptr, 0) == 0; default: // We don't know this type. return false; } }
void MachThread::Resume(bool others_stopped) { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); if (ThreadIDIsValid(m_tid)) { SetSuspendCountBeforeResume(others_stopped); } }
uint32_t MachThread::Resume() { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); if (ThreadIDIsValid(m_tid)) { RestoreSuspendCount(); } return SuspendCount(); }
bool MachTask::StartExceptionThread(DNBError &err) { DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__); task_t task = TaskPortForProcessID(err); if (MachTask::IsValid(task)) { // Got the mach port for the current process mach_port_t task_self = mach_task_self (); // Allocate an exception port that we will use to track our child process err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port); if (err.Fail()) return false; // Add the ability to send messages on the new exception port err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND); if (err.Fail()) return false; // Save the original state of the exception ports for our child process SaveExceptionPortInfo(); // We weren't able to save the info for our exception ports, we must stop... if (m_exc_port_info.mask == 0) { err.SetErrorString("failed to get exception port info"); return false; } // Set the ability to get all exceptions on this port err = ::task_set_exception_ports (task, m_exc_port_info.mask, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) { err.LogThreaded("::task_set_exception_ports ( task = 0x%4.4x, exception_mask = 0x%8.8x, new_port = 0x%4.4x, behavior = 0x%8.8x, new_flavor = 0x%8.8x )", task, m_exc_port_info.mask, m_exception_port, (EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES), THREAD_STATE_NONE); } if (err.Fail()) return false; // Create the exception thread err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this); return err.Success(); } else { DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__); } return false; }
void MachThread::Suspend() { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); if (MachPortNumberIsValid(m_mach_port_number)) { DNBError err(::thread_suspend(m_mach_port_number), DNBError::MachKernel); if (err.Success()) m_suspend_count++; if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_suspend (%4.4" PRIx32 ")", m_mach_port_number); } }
void RNBContext::StartProcessStatusThread() { DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__); if ((m_events.GetEventBits() & event_proc_thread_running) == 0) { int err = ::pthread_create(&m_pid_pthread, NULL, ThreadFunctionProcessStatus, this); if (err == 0) { // Our thread was successfully kicked off, wait for it to // set the started event so we can safely continue m_events.WaitForSetEvents(event_proc_thread_running); DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread got started!", __FUNCTION__); } else { DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread failed to start: err = %i", __FUNCTION__, err); m_events.ResetEvents(event_proc_thread_running); m_events.SetEvents(event_proc_thread_exiting); } } }
void RNBContext::StopProcessStatusThread() { DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__); if ((m_events.GetEventBits() & event_proc_thread_running) == event_proc_thread_running) { struct timespec timeout_abstime; DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0); // Wait for 2 seconds for the rx thread to exit if (m_events.WaitForSetEvents(RNBContext::event_proc_thread_exiting, &timeout_abstime) == RNBContext::event_proc_thread_exiting) { DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread stopped as requeseted", __FUNCTION__); } else { DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread did not stop in 2 seconds...", __FUNCTION__); // Kill the RX thread??? } } }
rnb_err_t RNBSocket::useFD(int fd) { if (fd < 0) { DNBLogThreadedIf(LOG_RNB_COMM, "Bad file descriptor passed in."); return rnb_err; } m_fd = fd; return rnb_success; }
rnb_err_t RNBSocket::ConnectToService() { DNBLog("Connecting to com.apple.%s service...", DEBUGSERVER_PROGRAM_NAME); // Disconnect from any previous connections Disconnect(false); if (::secure_lockdown_checkin (&m_ld_conn, NULL, NULL) != kLDESuccess) { DNBLogThreadedIf(LOG_RNB_COMM, "::secure_lockdown_checkin(&m_fd, NULL, NULL) failed"); m_fd = -1; return rnb_not_connected; } m_fd = ::lockdown_get_socket (m_ld_conn); if (m_fd == -1) { DNBLogThreadedIf(LOG_RNB_COMM, "::lockdown_get_socket() failed"); return rnb_not_connected; } m_fd_from_lockdown = true; return rnb_success; }
void MachThread::Suspend() { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); if (ThreadIDIsValid(m_tid)) { DNBError err(::thread_suspend (m_tid), DNBError::MachKernel); if (err.Success()) m_suspend_count++; if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_suspend (%4.4x)", m_tid); } }
kern_return_t MachException::PortInfo::Save (task_t task) { count = (sizeof (ports) / sizeof (ports[0])); DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Save ( task = 0x%4.4x )", task); DNBError err; err = ::task_get_exception_ports (task, EXC_MASK_ALL, masks, &count, ports, behaviors, flavors); if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, EXC_MASK_ALL, count); if (err.Fail()) count = 0; return err.Error(); }
void MachException::Message::Dump() const { DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_msg { bits = 0x%8.8lx size = 0x%8.8lx remote-port = 0x%8.8lx local-port = 0x%8.8lx reserved = 0x%8.8lx id = 0x%8.8lx } ", exc_msg.hdr.msgh_bits, exc_msg.hdr.msgh_size, exc_msg.hdr.msgh_remote_port, exc_msg.hdr.msgh_local_port, exc_msg.hdr.msgh_reserved, exc_msg.hdr.msgh_id); DNBLogThreadedIf(LOG_EXCEPTIONS, "reply_msg { bits = 0x%8.8lx size = 0x%8.8lx remote-port = 0x%8.8lx local-port = 0x%8.8lx reserved = 0x%8.8lx id = 0x%8.8lx }", reply_msg.hdr.msgh_bits, reply_msg.hdr.msgh_size, reply_msg.hdr.msgh_remote_port, reply_msg.hdr.msgh_local_port, reply_msg.hdr.msgh_reserved, reply_msg.hdr.msgh_id); state.Dump(); }
void MachException::Data::Dump() const { const char *exc_type_name = MachException::Name(exc_type); DNBLogThreadedIf(LOG_EXCEPTIONS, " state { task_port = 0x%4.4x, thread_port = 0x%4.4x, exc_type = %i (%s) ...", task_port, thread_port, exc_type, exc_type_name ? exc_type_name : "???"); const size_t exc_data_count = exc_data.size(); // Dump any special exception data contents int soft_signal = SoftSignal(); if (soft_signal != 0) { const char *sig_str = SysSignal::Name(soft_signal); DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data: EXC_SOFT_SIGNAL (%i (%s))", soft_signal, sig_str ? sig_str : "unknown signal"); } else { // No special disassembly for this data, just dump the data size_t idx; for (idx = 0; idx < exc_data_count; ++idx) { DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data[%u]: " MACH_EXCEPTION_DATA_FMT_HEX, idx, exc_data[idx]); } } }
//---------------------------------------------------------------------- // MachTask::MemoryRegionInfo //---------------------------------------------------------------------- int MachTask::GetMemoryRegionInfo (nub_addr_t addr, DNBRegionInfo *region_info) { task_t task = TaskPort(); if (task == TASK_NULL) return -1; int ret = m_vm_memory.GetMemoryRegionInfo(task, addr, region_info); DNBLogThreadedIf(LOG_MEMORY, "MachTask::MemoryRegionInfo ( addr = 0x%8.8llx ) => %i (start = 0x%8.8llx, size = 0x%8.8llx, permissions = %u)", (uint64_t)addr, ret, (uint64_t)region_info->addr, (uint64_t)region_info->size, region_info->permissions); return ret; }
void signal_handler(int signo) { DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (%s)", __FUNCTION__, SysSignal::Name(signo)); switch (signo) { // case SIGINT: // DNBProcessKill (g_pid, signo); // break; case SIGPIPE: g_sigpipe_received = 1; break; } }
uint32_t MachThread::Resume() { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); if (ThreadIDIsValid(m_tid)) { while (m_suspendCount > 0) { DNBError err(::thread_resume (m_tid), DNBError::MachKernel); if (err.Success()) m_suspendCount--; if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_resume (%4.4x)", m_tid); } } return SuspendCount(); }
//---------------------------------------------------------------------- // MachTask::WriteMemory //---------------------------------------------------------------------- nub_size_t MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf) { nub_size_t n = 0; task_t task = TaskPort(); if (task != TASK_NULL) { n = m_vm_memory.Write(task, addr, buf, size); DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes written", (uint64_t)addr, size, buf, n); if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) { DNBDataRef data((uint8_t*)buf, n, false); data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); } } return n; }
MachThread::MachThread (MachProcess *process, thread_t thread) : m_process (process), m_tid (thread), m_seq_id (GetSequenceID()), m_state (eStateUnloaded), m_state_mutex (PTHREAD_MUTEX_RECURSIVE), m_breakID (INVALID_NUB_BREAK_ID), m_suspendCount (0), m_arch_ap (DNBArchProtocol::Create (this)), m_reg_sets (m_arch_ap->GetRegisterSetInfo (&n_num_reg_sets)) { // Get the thread state so we know if a thread is in a state where we can't // muck with it and also so we get the suspend count correct in case it was // already suspended GetBasicInfo(); DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::MachThread ( process = %p, tid = 0x%4.4x, seq_id = %u )", &m_process, m_tid, m_seq_id); }
bool MachThread::SetSuspendCountBeforeResume(bool others_stopped) { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); DNBError err; if (ThreadIDIsValid(m_tid) == false) return false; size_t times_to_resume; if (others_stopped) { if (GetBasicInfo()) { times_to_resume = m_basic_info.suspend_count; m_suspend_count = - (times_to_resume - m_suspend_count); } else times_to_resume = 0; } else { times_to_resume = m_suspend_count; m_suspend_count = 0; } if (times_to_resume > 0) { while (times_to_resume > 0) { err = ::thread_resume (m_tid); if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_resume (%4.4x)", m_tid); if (err.Success()) --times_to_resume; else { if (GetBasicInfo()) times_to_resume = m_basic_info.suspend_count; else times_to_resume = 0; } } } return true; }
bool MachThread::RestoreSuspendCountAfterStop () { DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); DNBError err; if (ThreadIDIsValid(m_tid) == false) return false; if (m_suspend_count > 0) { while (m_suspend_count > 0) { err = ::thread_resume (m_tid); if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) err.LogThreaded("::thread_resume (%4.4x)", m_tid); if (err.Success()) --m_suspend_count; else { if (GetBasicInfo()) m_suspend_count = m_basic_info.suspend_count; else m_suspend_count = 0; return false; // ??? } } } else if (m_suspend_count < 0) { while (m_suspend_count < 0) { err = ::thread_suspend (m_tid); if (err.Success()) ++m_suspend_count; if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) { err.LogThreaded("::thread_suspend (%4.4x)", m_tid); return false; } } } return true; }