From ab3b8b22a157f98bab6260ddf2b88b05dcb4cd1e Mon Sep 17 00:00:00 2001 From: Han Ming Ong Date: Sat, 17 Nov 2012 00:21:04 +0000 Subject: [PATCH] Sub-TLF: Provide service to profile the inferior This allows client to query profiling states on the inferior. llvm-svn: 168228 --- lldb/include/lldb/API/SBProcess.h | 6 +- lldb/include/lldb/Core/Broadcaster.h | 3 +- lldb/include/lldb/Target/Process.h | 55 +++++- lldb/scripts/Python/interface/SBProcess.i | 6 +- lldb/source/API/SBProcess.cpp | 23 +++ .../gdb-remote/GDBRemoteCommunicationClient.cpp | 10 + lldb/source/Target/Process.cpp | 46 ++++- lldb/tools/debugserver/source/DNB.cpp | 41 +++++ lldb/tools/debugserver/source/DNB.h | 3 + lldb/tools/debugserver/source/DNBDefs.h | 4 +- .../debugserver/source/MacOSX/MachProcess.cpp | 98 ++++++++++ lldb/tools/debugserver/source/MacOSX/MachProcess.h | 18 ++ lldb/tools/debugserver/source/MacOSX/MachTask.cpp | 128 +++++++++++++ lldb/tools/debugserver/source/MacOSX/MachTask.h | 3 + .../debugserver/source/MacOSX/MachVMMemory.cpp | 201 +++++++++++++++++++++ .../tools/debugserver/source/MacOSX/MachVMMemory.h | 3 +- lldb/tools/debugserver/source/RNBContext.cpp | 15 +- lldb/tools/debugserver/source/RNBContext.h | 8 +- lldb/tools/debugserver/source/RNBRemote.cpp | 85 +++++++++ lldb/tools/debugserver/source/RNBRemote.h | 6 + lldb/tools/debugserver/source/debugserver.cpp | 8 +- 21 files changed, 756 insertions(+), 14 deletions(-) diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h index 0664d7f..6ef29fa 100644 --- a/lldb/include/lldb/API/SBProcess.h +++ b/lldb/include/lldb/API/SBProcess.h @@ -30,7 +30,8 @@ public: eBroadcastBitStateChanged = (1 << 0), eBroadcastBitInterrupt = (1 << 1), eBroadcastBitSTDOUT = (1 << 2), - eBroadcastBitSTDERR = (1 << 3) + eBroadcastBitSTDERR = (1 << 3), + eBroadcastBitProfileData = (1 << 4) }; SBProcess (); @@ -74,6 +75,9 @@ public: size_t GetSTDERR (char *dst, size_t dst_len) const; + size_t + GetAsyncProfileData(char *dst, size_t dst_len) const; + void ReportEventState (const lldb::SBEvent &event, FILE *out) const; diff --git a/lldb/include/lldb/Core/Broadcaster.h b/lldb/include/lldb/Core/Broadcaster.h index 907132b..b9bc130 100644 --- a/lldb/include/lldb/Core/Broadcaster.h +++ b/lldb/include/lldb/Core/Broadcaster.h @@ -233,7 +233,8 @@ private: /// eBroadcastBitStateChanged = (1 << 0), /// eBroadcastBitInterrupt = (1 << 1), /// eBroadcastBitSTDOUT = (1 << 2), -/// eBroadcastBitSTDERR = (1 << 3) +/// eBroadcastBitSTDERR = (1 << 3), +/// eBroadcastBitProfileData = (1 << 4) /// }; /// \endcode //---------------------------------------------------------------------- diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 528943c..2fb41ed 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -1344,7 +1344,8 @@ public: eBroadcastBitStateChanged = (1 << 0), eBroadcastBitInterrupt = (1 << 1), eBroadcastBitSTDOUT = (1 << 2), - eBroadcastBitSTDERR = (1 << 3) + eBroadcastBitSTDERR = (1 << 3), + eBroadcastBitProfileData = (1 << 4) }; enum @@ -2942,6 +2943,35 @@ public: Error DeallocateMemory (lldb::addr_t ptr); + +// virtual Error +// DoGetProfileData (uint64_t &elapsed_usec, +// uint64_t &task_used_usec, +// int &num_threads, +// uint64_t **threads_id, +// uint64_t **threads_used_usec, +// mach_vm_size_t &rprvt, +// mach_vm_size_t &rsize, +// mach_vm_size_t &vprvt, +// mach_vm_size_t &vsize, +// mach_vm_size_t &dirty_size) +// { +// Error error; +// error.SetErrorStringWithFormat("error: wrong method called."); +// return error; +// } +// +// Error +// GetProfileData (uint64_t &elapsed_usec, +// uint64_t &task_used_usec, +// int &num_threads, +// uint64_t **threads_id, +// uint64_t **threads_used_usec, +// mach_vm_size_t &rprvt, +// mach_vm_size_t &rsize, +// mach_vm_size_t &vprvt, +// mach_vm_size_t &vsize, +// mach_vm_size_t &dirty_size); //------------------------------------------------------------------ /// Get any available STDOUT. @@ -2998,6 +3028,24 @@ public: return 0; } + //------------------------------------------------------------------ + /// Get any available profile data. + /// + /// @param[out] buf + /// A buffer that will receive any profile data bytes that are + /// currently available. + /// + /// @param[out] buf_size + /// The size in bytes for the buffer \a buf. + /// + /// @return + /// The number of bytes written into \a buf. If this value is + /// equal to \a buf_size, another call to this function should + /// be made to retrieve more profile data. + //------------------------------------------------------------------ + virtual size_t + GetAsyncProfileData (char *buf, size_t buf_size, Error &error); + //---------------------------------------------------------------------- // Process Breakpoints //---------------------------------------------------------------------- @@ -3443,6 +3491,8 @@ protected: Mutex m_stdio_communication_mutex; std::string m_stdout_data; std::string m_stderr_data; + Mutex m_profile_data_comm_mutex; + std::string m_profile_data; MemoryCache m_memory_cache; AllocatedMemoryCache m_allocated_memory_cache; bool m_should_detach; /// Should we detach if the process object goes away with an explicit call to Kill or Detach? @@ -3518,6 +3568,9 @@ protected: void AppendSTDERR (const char *s, size_t len); + void + BroadcastAsyncProfileData(const char *s, size_t len); + static void STDIOReadThreadBytesReceived (void *baton, const void *src, size_t src_len); diff --git a/lldb/scripts/Python/interface/SBProcess.i b/lldb/scripts/Python/interface/SBProcess.i index 2c48692..99ba6cf 100644 --- a/lldb/scripts/Python/interface/SBProcess.i +++ b/lldb/scripts/Python/interface/SBProcess.i @@ -43,7 +43,8 @@ public: eBroadcastBitStateChanged = (1 << 0), eBroadcastBitInterrupt = (1 << 1), eBroadcastBitSTDOUT = (1 << 2), - eBroadcastBitSTDERR = (1 << 3) + eBroadcastBitSTDERR = (1 << 3), + eBroadcastBitProfileData = (1 << 4) }; SBProcess (); @@ -96,6 +97,9 @@ public: size_t GetSTDERR (char *dst, size_t dst_len) const; + size_t + GetAsyncProfileData(char *dst, size_t dst_len) const; + void ReportEventState (const lldb::SBEvent &event, FILE *out) const; diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp index 51cc340..65adbee 100644 --- a/lldb/source/API/SBProcess.cpp +++ b/lldb/source/API/SBProcess.cpp @@ -366,6 +366,29 @@ SBProcess::GetSTDERR (char *dst, size_t dst_len) const return bytes_read; } +size_t +SBProcess::GetAsyncProfileData(char *dst, size_t dst_len) const +{ + size_t bytes_read = 0; + ProcessSP process_sp(GetSP()); + if (process_sp) + { + Error error; + bytes_read = process_sp->GetAsyncProfileData (dst, dst_len, error); + } + + LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_API)); + if (log) + log->Printf ("SBProcess(%p)::GetProfileData (dst=\"%.*s\", dst_len=%llu) => %llu", + process_sp.get(), + (int) bytes_read, + dst, + (uint64_t)dst_len, + (uint64_t)bytes_read); + + return bytes_read; +} + void SBProcess::ReportEventState (const SBEvent &event, FILE *out) const { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp index c1560e4..e9f1abc 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -647,6 +647,16 @@ GDBRemoteCommunicationClient::SendContinuePacketAndWaitForResponse } break; + case 'A': + // Async miscellaneous reply. Right now, only profile data is coming through this channel. + { + const std::string& profile_data = response.GetStringRef(); + const char *data_cstr = profile_data.c_str(); + data_cstr++; // Move beyond 'A' + process->BroadcastAsyncProfileData (data_cstr, profile_data.size()-1); + } + break; + case 'E': // ERROR state = eStateInvalid; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index ee6fd27..6ece319 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -953,6 +953,8 @@ Process::Process(Target &target, Listener &listener) : m_stdio_communication_mutex (Mutex::eMutexTypeRecursive), m_stdout_data (), m_stderr_data (), + m_profile_data_comm_mutex (Mutex::eMutexTypeRecursive), + m_profile_data (), m_memory_cache (*this), m_allocated_memory_cache (*this), m_should_detach (false), @@ -972,6 +974,7 @@ Process::Process(Target &target, Listener &listener) : SetEventName (eBroadcastBitInterrupt, "interrupt"); SetEventName (eBroadcastBitSTDOUT, "stdout-available"); SetEventName (eBroadcastBitSTDERR, "stderr-available"); + SetEventName (eBroadcastBitProfileData, "profile-data-available"); m_private_state_control_broadcaster.SetEventName (eBroadcastInternalStateControlStop , "control-stop" ); m_private_state_control_broadcaster.SetEventName (eBroadcastInternalStateControlPause , "control-pause" ); @@ -981,7 +984,8 @@ Process::Process(Target &target, Listener &listener) : eBroadcastBitStateChanged | eBroadcastBitInterrupt | eBroadcastBitSTDOUT | - eBroadcastBitSTDERR); + eBroadcastBitSTDERR | + eBroadcastBitProfileData); m_private_state_listener.StartListeningForEvents(&m_private_state_broadcaster, eBroadcastBitStateChanged | @@ -2546,6 +2550,12 @@ Process::DeallocateMemory (addr_t ptr) return error; } +//Error +//Process::GetProfileData (uint64_t &elapsed_usec, uint64_t &task_used_usec, int &num_threads, uint64_t **threads_id, uint64_t **threads_used_usec, mach_vm_size_t &rprvt, mach_vm_size_t &rsize, mach_vm_size_t &vprvt, mach_vm_size_t &vsize, mach_vm_size_t &dirty_size) +//{ +// return DoGetProfileData(elapsed_usec, task_used_usec, num_threads, threads_id, threads_used_usec, rprvt, rsize, vprvt, vsize, dirty_size); +//} +// ModuleSP Process::ReadModuleFromMemory (const FileSpec& file_spec, lldb::addr_t header_addr, @@ -3999,6 +4009,40 @@ Process::AppendSTDERR (const char * s, size_t len) BroadcastEventIfUnique (eBroadcastBitSTDERR, new ProcessEventData (shared_from_this(), GetState())); } +void +Process::BroadcastAsyncProfileData(const char *s, size_t len) +{ + Mutex::Locker locker (m_profile_data_comm_mutex); + m_profile_data.append (s, len); + BroadcastEventIfUnique (eBroadcastBitProfileData, new ProcessEventData (shared_from_this(), GetState())); +} + +size_t +Process::GetAsyncProfileData (char *buf, size_t buf_size, Error &error) +{ + Mutex::Locker locker(m_profile_data_comm_mutex); + size_t bytes_available = m_profile_data.size(); + if (bytes_available > 0) + { + LogSP log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf ("Process::GetProfileData (buf = %p, size = %llu)", buf, (uint64_t)buf_size); + if (bytes_available > buf_size) + { + memcpy(buf, m_profile_data.c_str(), buf_size); + m_profile_data.erase(0, buf_size); + bytes_available = buf_size; + } + else + { + memcpy(buf, m_profile_data.c_str(), bytes_available); + m_profile_data.clear(); + } + } + return bytes_available; +} + + //------------------------------------------------------------------ // Process STDIO //------------------------------------------------------------------ diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index a9d3645..9b6d98b 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -1217,6 +1217,38 @@ DNBProcessMemoryRegionInfo (nub_process_t pid, nub_addr_t addr, DNBRegionInfo *r return -1; } +const char * +DNBProcessGetProfileDataAsCString (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->Task().GetProfileDataAsCString(); + + return NULL; +} + +//nub_bool_t +//DNBProcessGetProfileData (nub_process_t pid, uint64_t &elapsed_usec, uint64_t &task_used_usec, int &num_threads, uint64_t **threads_id, uint64_t **threads_used_usec, mach_vm_size_t &rprvt, mach_vm_size_t &rsize, mach_vm_size_t &vprvt, mach_vm_size_t &vsize, mach_vm_size_t &dirty_size) +//{ +// MachProcessSP procSP; +// if (GetProcessSP (pid, procSP)) +// return procSP->Task().GetProfileData(elapsed_usec, task_used_usec, num_threads, threads_id, threads_used_usec, rprvt, rsize, vprvt, vsize, dirty_size); +// +// return false; +//} +// +nub_bool_t +DNBProcessSetAsyncEnableProfiling (nub_process_t pid, nub_bool_t enable, uint64_t interval_usec) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + procSP->SetAsyncEnableProfiling(enable, interval_usec); + return true; + } + + return false; +} //---------------------------------------------------------------------- // Formatted output that uses memory and registers from process and @@ -2072,6 +2104,15 @@ DNBProcessGetAvailableSTDERR (nub_process_t pid, char *buf, nub_size_t buf_size) } nub_size_t +DNBProcessGetAvailableProfileData (nub_process_t pid, char *buf, nub_size_t buf_size) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetAsyncProfileData (buf, buf_size); + return 0; +} + +nub_size_t DNBProcessGetStopCount (nub_process_t pid) { MachProcessSP procSP; diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h index d394397..332dd18 100644 --- a/lldb/tools/debugserver/source/DNB.h +++ b/lldb/tools/debugserver/source/DNB.h @@ -67,6 +67,8 @@ nub_size_t DNBProcessMemoryWrite (nub_process_t pid, nub_addr_t addr, nub nub_addr_t DNBProcessMemoryAllocate (nub_process_t pid, nub_size_t size, uint32_t permissions) DNB_EXPORT; nub_bool_t DNBProcessMemoryDeallocate (nub_process_t pid, nub_addr_t addr) DNB_EXPORT; int DNBProcessMemoryRegionInfo (nub_process_t pid, nub_addr_t addr, DNBRegionInfo *region_info) DNB_EXPORT; +const char * DNBProcessGetProfileDataAsCString (nub_process_t pid) DNB_EXPORT; // Process owns the returned string. Do not free. +nub_bool_t DNBProcessSetAsyncEnableProfiling (nub_process_t pid, nub_bool_t enable, uint64_t interval_usec) DNB_EXPORT; //---------------------------------------------------------------------- // Process status @@ -88,6 +90,7 @@ nub_bool_t DNBProcessSetSharedLibraryInfoCallback (nub_process_t pid, DNBC nub_addr_t DNBProcessLookupAddress (nub_process_t pid, const char *name, const char *shlib) DNB_EXPORT; nub_size_t DNBProcessGetAvailableSTDOUT (nub_process_t pid, char *buf, nub_size_t buf_size) DNB_EXPORT; nub_size_t DNBProcessGetAvailableSTDERR (nub_process_t pid, char *buf, nub_size_t buf_size) DNB_EXPORT; +nub_size_t DNBProcessGetAvailableProfileData (nub_process_t pid, char *buf, nub_size_t buf_size) DNB_EXPORT; nub_size_t DNBProcessGetStopCount (nub_process_t pid) DNB_EXPORT; uint32_t DNBProcessGetCPUType (nub_process_t pid) DNB_EXPORT; diff --git a/lldb/tools/debugserver/source/DNBDefs.h b/lldb/tools/debugserver/source/DNBDefs.h index b061ef2..9c07c1a 100644 --- a/lldb/tools/debugserver/source/DNBDefs.h +++ b/lldb/tools/debugserver/source/DNBDefs.h @@ -134,11 +134,13 @@ enum eEventProcessStoppedStateChanged = 1 << 1, // The process has changed state to stopped eEventSharedLibsStateChange = 1 << 2, // Shared libraries loaded/unloaded state has changed eEventStdioAvailable = 1 << 3, // Something is available on stdout/stderr - eEventProcessAsyncInterrupt = 1 << 4, // Gives the ability for any infinite wait calls to be interrupted + eEventProfileDataAvailable = 1 << 4, // Profile data ready for retrieval + eEventProcessAsyncInterrupt = 1 << 5, // Gives the ability for any infinite wait calls to be interrupted kAllEventsMask = eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventSharedLibsStateChange | eEventStdioAvailable | + eEventProfileDataAvailable | eEventProcessAsyncInterrupt }; diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp b/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp index c20be15..30e48fa 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp @@ -83,6 +83,11 @@ MachProcess::MachProcess() : m_stdio_mutex (PTHREAD_MUTEX_RECURSIVE), m_stdout_data (), m_thread_actions (), + m_profile_enabled (false), + m_profile_interval_usec (0), + m_profile_thread (0), + m_profile_data_mutex(PTHREAD_MUTEX_RECURSIVE), + m_profile_data (), m_thread_list (), m_exception_messages (), m_exception_messages_mutex (PTHREAD_MUTEX_RECURSIVE), @@ -286,6 +291,11 @@ MachProcess::Clear() PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex); m_exception_messages.clear(); } + if (m_profile_thread) + { + pthread_join(m_profile_thread, NULL); + m_profile_thread = NULL; + } } @@ -297,6 +307,26 @@ MachProcess::StartSTDIOThread() return ::pthread_create (&m_stdio_thread, NULL, MachProcess::STDIOThread, this) == 0; } +void +MachProcess::SetAsyncEnableProfiling(bool enable, uint64_t interval_usec) +{ + m_profile_enabled = enable; + m_profile_interval_usec = interval_usec; + + if (m_profile_enabled && (m_profile_thread == 0)) + { + StartProfileThread(); + } +} + +bool +MachProcess::StartProfileThread() +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__); + // Create the thread that profiles the inferior and reports back if enabled + return ::pthread_create (&m_profile_thread, NULL, MachProcess::ProfileThread, this) == 0; +} + nub_addr_t MachProcess::LookupSymbol(const char *name, const char *shlib) @@ -1311,6 +1341,74 @@ MachProcess::STDIOThread(void *arg) return NULL; } + +void +MachProcess::SignalAsyncProfileData (const char *info) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%s) ...", __FUNCTION__, info); + PTHREAD_MUTEX_LOCKER (locker, m_profile_data_mutex); + m_profile_data.append(info); + m_events.SetEvents(eEventProfileDataAvailable); + + // Wait for the event bit to reset if a reset ACK is requested + m_events.WaitForResetAck(eEventProfileDataAvailable); +} + + +size_t +MachProcess::GetAsyncProfileData (char *buf, size_t buf_size) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__, buf, (uint64_t)buf_size); + PTHREAD_MUTEX_LOCKER (locker, m_profile_data_mutex); + size_t bytes_available = m_profile_data.size(); + if (bytes_available > 0) + { + if (bytes_available > buf_size) + { + memcpy(buf, m_profile_data.data(), buf_size); + m_profile_data.erase(0, buf_size); + bytes_available = buf_size; + } + else + { + memcpy(buf, m_profile_data.data(), bytes_available); + m_profile_data.clear(); + } + } + return bytes_available; +} + + +void * +MachProcess::ProfileThread(void *arg) +{ + MachProcess *proc = (MachProcess*) arg; + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( arg = %p ) thread starting...", __FUNCTION__, arg); + + while (proc->IsProfilingEnabled()) + { + nub_state_t state = proc->GetState(); + if (state == eStateRunning) + { + const char *data = proc->Task().GetProfileDataAsCString(); + if (data) + { + proc->SignalAsyncProfileData(data); + } + } + else if ((state == eStateUnloaded) || (state == eStateDetached) || (state == eStateUnloaded)) + { + // Done. Get out of this thread. + break; + } + + // A simple way to set up the profile interval. We can also use select() or dispatch timer source if necessary. + usleep(proc->ProfileInterval()); + } + return NULL; +} + + pid_t MachProcess::AttachForDebug (pid_t pid, char *err_str, size_t err_len) { diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h index 7755315..fabcc46 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -144,6 +144,17 @@ public: void ExceptionMessageBundleComplete (); void SharedLibrariesUpdated (); nub_size_t CopyImageInfos (struct DNBExecutableImageInfo **image_infos, bool only_changed); + + //---------------------------------------------------------------------- + // Profile functions + //---------------------------------------------------------------------- + void SetAsyncEnableProfiling (bool enable, uint64_t internal_usec); + bool IsProfilingEnabled () { return m_profile_enabled; } + uint64_t ProfileInterval () { return m_profile_interval_usec; } + bool StartProfileThread (); + static void * ProfileThread (void *arg); + void SignalAsyncProfileData (const char *info); + size_t GetAsyncProfileData (char *buf, size_t buf_size); //---------------------------------------------------------------------- // Accessors @@ -266,6 +277,13 @@ private: pthread_t m_stdio_thread; // Thread ID for the thread that watches for child process stdio PThreadMutex m_stdio_mutex; // Multithreaded protection for stdio std::string m_stdout_data; + + bool m_profile_enabled; // A flag to indicate if profiling is enabled + uint64_t m_profile_interval_usec; // If enable, the profiling interval in microseconds + pthread_t m_profile_thread; // Thread ID for the thread that profiles the inferior + PThreadMutex m_profile_data_mutex; // Multithreaded protection for profile info data + std::string m_profile_data; // Profile data, must be protected by m_profile_data_mutex + DNBThreadResumeActions m_thread_actions; // The thread actions for the current MachProcess::Resume() call MachException::Message::collection m_exception_messages; // A collection of exception messages caught when listening to the exception port diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.cpp b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp index f8fda64..bd8783f 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp @@ -23,6 +23,8 @@ #include // C++ Includes +#include + // Other libraries and framework includes // Project includes #include "CFUtils.h" @@ -224,6 +226,132 @@ MachTask::GetMemoryRegionInfo (nub_addr_t addr, DNBRegionInfo *region_info) return ret; } +#define TIME_VALUE_TO_TIMEVAL(a, r) do { \ +(r)->tv_sec = (a)->seconds; \ +(r)->tv_usec = (a)->microseconds; \ +} while (0) + +// todo: make use of existing MachThread, if there is already one? +static void update_used_time(task_t task, int &num_threads, uint64_t **threads_id, uint64_t **threads_used_usec, struct timeval ¤t_used_time) +{ + kern_return_t kr; + thread_act_array_t threads; + mach_msg_type_number_t tcnt; + + kr = task_threads(task, &threads, &tcnt); + if (kr != KERN_SUCCESS) + return; + + num_threads = tcnt; + *threads_id = (uint64_t *)malloc(num_threads * sizeof(uint64_t)); + *threads_used_usec = (uint64_t *)malloc(num_threads * sizeof(uint64_t)); + + for (int i = 0; i < tcnt; i++) { + thread_identifier_info_data_t identifier_info; + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + kr = thread_info(threads[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&identifier_info, &count); + if (kr != KERN_SUCCESS) continue; + + thread_basic_info_data_t basic_info; + count = THREAD_BASIC_INFO_COUNT; + kr = thread_info(threads[i], THREAD_BASIC_INFO, (thread_info_t)&basic_info, &count); + if (kr != KERN_SUCCESS) continue; + + if ((basic_info.flags & TH_FLAGS_IDLE) == 0) { + (*threads_id)[i] = identifier_info.thread_id; + + struct timeval tv; + struct timeval thread_tv; + TIME_VALUE_TO_TIMEVAL(&basic_info.user_time, &tv); + TIME_VALUE_TO_TIMEVAL(&basic_info.user_time, &thread_tv); + timeradd(¤t_used_time, &tv, ¤t_used_time); + TIME_VALUE_TO_TIMEVAL(&basic_info.system_time, &tv); + timeradd(&thread_tv, &tv, &thread_tv); + timeradd(¤t_used_time, &tv, ¤t_used_time); + uint64_t used_usec = thread_tv.tv_sec * 1000000ULL + thread_tv.tv_usec; + (*threads_used_usec)[i] = used_usec; + } + + kr = mach_port_deallocate(mach_task_self(), threads[i]); + } + kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)(uintptr_t)threads, tcnt * sizeof(*threads)); +} + +const char * +MachTask::GetProfileDataAsCString () +{ + task_t task = TaskPort(); + if (task == TASK_NULL) + return NULL; + + struct task_basic_info task_info; + DNBError err; + err = BasicInfo(task, &task_info); + + if (!err.Success()) + return NULL; + + uint64_t elapsed_usec = 0; + uint64_t task_used_usec = 0; + int num_threads = 0; + uint64_t *threads_used_usec = NULL; + uint64_t *threads_id = NULL; + mach_vm_size_t rprvt = 0; + mach_vm_size_t rsize = 0; + mach_vm_size_t vprvt = 0; + mach_vm_size_t vsize = 0; + mach_vm_size_t dirty_size = 0; + + // Get current used time. + struct timeval current_used_time; + struct timeval tv; + TIME_VALUE_TO_TIMEVAL(&task_info.user_time, ¤t_used_time); + TIME_VALUE_TO_TIMEVAL(&task_info.system_time, &tv); + timeradd(¤t_used_time, &tv, ¤t_used_time); + task_used_usec = current_used_time.tv_sec * 1000000ULL + current_used_time.tv_usec; + update_used_time(task, num_threads, &threads_id, &threads_used_usec, current_used_time); + + struct timeval current_elapsed_time; + int res = gettimeofday(¤t_elapsed_time, NULL); + if (res == 0) + { + elapsed_usec = current_elapsed_time.tv_sec * 1000000ULL + current_elapsed_time.tv_usec; + } + + if (m_vm_memory.GetMemoryProfile(task, task_info, m_process->GetCPUType(), m_process->ProcessID(), rprvt, rsize, vprvt, vsize, dirty_size)) + { + std::ostringstream profile_data_stream; + + profile_data_stream << "elapsed_usec:" << elapsed_usec << ';'; + profile_data_stream << "task_used_usec:" << task_used_usec << ';'; + + profile_data_stream << "threads_info:" << num_threads; + for (int i=0; i // C++ Includes #include +#include // Other libraries and framework includes // Project includes #include "MachException.h" @@ -65,6 +66,7 @@ public: nub_size_t ReadMemory (nub_addr_t addr, nub_size_t size, void *buf); nub_size_t WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf); int GetMemoryRegionInfo (nub_addr_t addr, DNBRegionInfo *region_info); + const char * GetProfileDataAsCString (); nub_addr_t AllocateMemory (nub_size_t size, uint32_t permissions); nub_bool_t DeallocateMemory (nub_addr_t addr); @@ -121,6 +123,7 @@ protected: typedef std::map allocation_collection; allocation_collection m_allocations; + std::string m_profile_data; private: MachTask(const MachTask&); // Outlaw diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp index 1fe9716..23973b3 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp @@ -15,6 +15,7 @@ #include "MachVMRegion.h" #include "DNBLog.h" #include +#include MachVMMemory::MachVMMemory() : m_page_size (kInvalidPageSize), @@ -88,6 +89,206 @@ MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address, DNBRegionInfo return true; } +// rsize and dirty_size is not adjusted for dyld shared cache and multiple __LINKEDIT segment, as in vmmap. In practice, dirty_size doesn't differ much but rsize may. There is performance penalty for the adjustment. Right now, only use the dirty_size. +static void GetRegionSizes(task_t task, mach_vm_size_t &rsize, mach_vm_size_t &dirty_size) +{ + mach_vm_address_t address = 0; + mach_vm_size_t size; + kern_return_t err = 0; + unsigned nestingDepth = 0; + mach_vm_size_t pages_resident = 0; + mach_vm_size_t pages_dirtied = 0; + + while (1) + { + mach_msg_type_number_t count; + struct vm_region_submap_info_64 info; + + count = VM_REGION_SUBMAP_INFO_COUNT_64; + err = mach_vm_region_recurse(task, &address, &size, &nestingDepth, (vm_region_info_t)&info, &count); + if (err == KERN_INVALID_ADDRESS) + { + // It seems like this is a good break too. + break; + } + else if (err) + { + mach_error("vm_region",err); + break; // reached last region + } + + bool should_count = true; + if (info.is_submap) + { // is it a submap? + nestingDepth++; + should_count = false; + } + else + { + // Don't count malloc stack logging data in the TOTAL VM usage lines. + if (info.user_tag == VM_MEMORY_ANALYSIS_TOOL) + should_count = false; + // Don't count system shared library region not used by this process. + if (address >= SHARED_REGION_BASE && address < (SHARED_REGION_BASE + SHARED_REGION_SIZE)) + should_count = false; + + address = address+size; + } + + if (should_count) + { + pages_resident += info.pages_resident; + pages_dirtied += info.pages_dirtied; + } + } + + rsize = pages_resident * vm_page_size; + dirty_size = pages_dirtied * vm_page_size; +} + +// Test whether the virtual address is within the architecture's shared region. +static bool InSharedRegion(mach_vm_address_t addr, cpu_type_t type) +{ + mach_vm_address_t base = 0, size = 0; + + switch(type) { + case CPU_TYPE_ARM: + base = SHARED_REGION_BASE_ARM; + size = SHARED_REGION_SIZE_ARM; + break; + + case CPU_TYPE_X86_64: + base = SHARED_REGION_BASE_X86_64; + size = SHARED_REGION_SIZE_X86_64; + break; + + case CPU_TYPE_I386: + base = SHARED_REGION_BASE_I386; + size = SHARED_REGION_SIZE_I386; + break; + + default: { + // Log error abut unknown CPU type + break; + } + } + + + return(addr >= base && addr < (base + size)); +} + +static void GetMemorySizes(task_t task, cpu_type_t cputype, nub_process_t pid, mach_vm_size_t &rprvt, mach_vm_size_t &vprvt) +{ + // Collecting some other info cheaply but not reporting for now. + mach_vm_size_t empty = 0; + mach_vm_size_t fw_private = 0; + + mach_vm_size_t aliased = 0; + mach_vm_size_t pagesize = vm_page_size; + bool global_shared_text_data_mapped = false; + + for (mach_vm_address_t addr=0, size=0; ; addr += size) + { + vm_region_top_info_data_t info; + mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT; + mach_port_t object_name; + + kern_return_t kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name); + if (kr != KERN_SUCCESS) break; + + if (InSharedRegion(addr, cputype)) + { + // Private Shared + fw_private += info.private_pages_resident * pagesize; + + // Check if this process has the globally shared text and data regions mapped in. If so, set global_shared_text_data_mapped to TRUE and avoid checking again. + if (global_shared_text_data_mapped == FALSE && info.share_mode == SM_EMPTY) { + vm_region_basic_info_data_64_t b_info; + mach_vm_address_t b_addr = addr; + mach_vm_size_t b_size = size; + count = VM_REGION_BASIC_INFO_COUNT_64; + + kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO, (vm_region_info_t)&b_info, &count, &object_name); + if (kr != KERN_SUCCESS) break; + + if (b_info.reserved) { + global_shared_text_data_mapped = TRUE; + } + } + + // Short circuit the loop if this isn't a shared private region, since that's the only region type we care about within the current address range. + if (info.share_mode != SM_PRIVATE) + { + continue; + } + } + + // Update counters according to the region type. + if (info.share_mode == SM_COW && info.ref_count == 1) + { + // Treat single reference SM_COW as SM_PRIVATE + info.share_mode = SM_PRIVATE; + } + + switch (info.share_mode) + { + case SM_LARGE_PAGE: + // Treat SM_LARGE_PAGE the same as SM_PRIVATE + // since they are not shareable and are wired. + case SM_PRIVATE: + rprvt += info.private_pages_resident * pagesize; + rprvt += info.shared_pages_resident * pagesize; + vprvt += size; + break; + + case SM_EMPTY: + empty += size; + break; + + case SM_COW: + case SM_SHARED: + { + if (pid == 0) + { + // Treat kernel_task specially + if (info.share_mode == SM_COW) + { + rprvt += info.private_pages_resident * pagesize; + vprvt += size; + } + break; + } + + if (info.share_mode == SM_COW) + { + rprvt += info.private_pages_resident * pagesize; + vprvt += info.private_pages_resident * pagesize; + } + break; + } + default: + // log that something is really bad. + break; + } + } + + rprvt += aliased; +} + +nub_bool_t +MachVMMemory::GetMemoryProfile(task_t task, struct task_basic_info ti, cpu_type_t cputype, nub_process_t pid, mach_vm_size_t &rprvt, mach_vm_size_t &rsize, mach_vm_size_t &vprvt, mach_vm_size_t &vsize, mach_vm_size_t &dirty_size) +{ + // This uses vmmap strategy. We don't use the returned rsize for now. We prefer to match top's version since that's what we do for the rest of the metrics. + GetRegionSizes(task, rsize, dirty_size); + + GetMemorySizes(task, cputype, pid, rprvt, vprvt); + + rsize = ti.resident_size; + vsize = ti.virtual_size; + + return true; +} + nub_size_t MachVMMemory::Read(task_t task, nub_addr_t address, void *data, nub_size_t data_count) { diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h index d7162d0..cbb0224 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h @@ -28,12 +28,13 @@ public: nub_size_t Write(task_t task, nub_addr_t address, const void *data, nub_size_t data_count); nub_size_t PageSize(); nub_bool_t GetMemoryRegionInfo(task_t task, nub_addr_t address, DNBRegionInfo *region_info); + nub_bool_t GetMemoryProfile(task_t task, struct task_basic_info ti, cpu_type_t cputype, nub_process_t pid, mach_vm_size_t &rprvt, mach_vm_size_t &rsize, mach_vm_size_t &vprvt, mach_vm_size_t &vsize, mach_vm_size_t &dirty_size); protected: nub_size_t MaxBytesLeftInPage(nub_addr_t addr, nub_size_t count); nub_size_t WriteRegion(task_t task, const nub_addr_t address, const void *data, const nub_size_t data_count); - vm_size_t m_page_size; + vm_size_t m_page_size; DNBError m_err; }; diff --git a/lldb/tools/debugserver/source/RNBContext.cpp b/lldb/tools/debugserver/source/RNBContext.cpp index 629bdef..b9189df 100644 --- a/lldb/tools/debugserver/source/RNBContext.cpp +++ b/lldb/tools/debugserver/source/RNBContext.cpp @@ -148,9 +148,9 @@ RNBContext::ThreadFunctionProcessStatus(void *arg) bool done = false; while (!done) { - DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s calling DNBProcessWaitForEvent(pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable, true)...", __FUNCTION__); - nub_event_t pid_status_event = DNBProcessWaitForEvents (pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable, true, NULL); - DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s calling DNBProcessWaitForEvent(pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable, true) => 0x%8.8x", __FUNCTION__, pid_status_event); + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s calling DNBProcessWaitForEvent(pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable | eEventProfileDataAvailable, true)...", __FUNCTION__); + nub_event_t pid_status_event = DNBProcessWaitForEvents (pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable | eEventProfileDataAvailable, true, NULL); + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s calling DNBProcessWaitForEvent(pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable | eEventProfileDataAvailable, true) => 0x%8.8x", __FUNCTION__, pid_status_event); if (pid_status_event == 0) { @@ -167,6 +167,13 @@ RNBContext::ThreadFunctionProcessStatus(void *arg) ctx.Events().WaitForResetAck(RNBContext::event_proc_stdio_available); } + if (pid_status_event & eEventProfileDataAvailable) + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got profile data event....", __FUNCTION__, pid); + ctx.Events().SetEvents (RNBContext::event_proc_profile_data); + // Wait for the main thread to consume this notification if it requested we wait for it + ctx.Events().WaitForResetAck(RNBContext::event_proc_profile_data); + } if (pid_status_event & (eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged)) { @@ -217,6 +224,8 @@ RNBContext::EventsAsString (nub_event_t events, std::string& s) s += "proc_thread_exiting "; if (events & event_proc_stdio_available) s += "proc_stdio_available "; + if (events & event_proc_profile_data) + s += "proc_profile_data "; if (events & event_read_packet_available) s += "read_packet_available "; if (events & event_read_thread_running) diff --git a/lldb/tools/debugserver/source/RNBContext.h b/lldb/tools/debugserver/source/RNBContext.h index 0044d19..e8292bd 100644 --- a/lldb/tools/debugserver/source/RNBContext.h +++ b/lldb/tools/debugserver/source/RNBContext.h @@ -29,13 +29,15 @@ public: event_proc_thread_running = 0x02, // Sticky event_proc_thread_exiting = 0x04, event_proc_stdio_available = 0x08, - event_read_packet_available = 0x10, - event_read_thread_running = 0x20, // Sticky - event_read_thread_exiting = 0x40, + event_proc_profile_data = 0x10, + event_read_packet_available = 0x20, + event_read_thread_running = 0x40, // Sticky + event_read_thread_exiting = 0x80, normal_event_bits = event_proc_state_changed | event_proc_thread_exiting | event_proc_stdio_available | + event_proc_profile_data | event_read_packet_available | event_read_thread_exiting, diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index 1951dfe..f852b68 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -193,6 +193,8 @@ RNBRemote::CreatePacketTable () t.push_back (Packet (allocate_memory, &RNBRemote::HandlePacket_AllocateMemory, NULL, "_M", "Allocate memory in the inferior process.")); t.push_back (Packet (deallocate_memory, &RNBRemote::HandlePacket_DeallocateMemory, NULL, "_m", "Deallocate memory in the inferior process.")); t.push_back (Packet (memory_region_info, &RNBRemote::HandlePacket_MemoryRegionInfo, NULL, "qMemoryRegionInfo", "Return size and attributes of a memory region that contains the given address")); + t.push_back (Packet (get_profile_data, &RNBRemote::HandlePacket_GetProfileData, NULL, "qGetProfileData", "Return profiling data of the current target.")); + t.push_back (Packet (set_enable_profiling, &RNBRemote::HandlePacket_SetAsyncEnableProfiling, NULL, "QSetAsyncEnableProfiling", "Enable or disable the profiling of current target.")); t.push_back (Packet (watchpoint_support_info, &RNBRemote::HandlePacket_WatchpointSupportInfo, NULL, "qWatchpointSupportInfo", "Return the number of supported hardware watchpoints")); } @@ -226,6 +228,25 @@ RNBRemote::FlushSTDIO () } } +void +RNBRemote::SendAsyncProfileData () +{ + if (m_ctx.HasValidProcessID()) + { + nub_process_t pid = m_ctx.ProcessID(); + char buf[256]; + nub_size_t count; + do + { + count = DNBProcessGetAvailableProfileData(pid, buf, sizeof(buf)); + if (count > 0) + { + SendAsyncProfileDataPacket (buf, count); + } + } while (count > 0); + } +} + rnb_err_t RNBRemote::SendHexEncodedBytePacket (const char *header, const void *buf, size_t buf_len, const char *footer) { @@ -262,6 +283,18 @@ RNBRemote::SendSTDERRPacket (char *buf, nub_size_t buf_size) return SendHexEncodedBytePacket("O", buf, buf_size, NULL); } +// This makes use of asynchronous bit 'A' in the gdb remote protocol. +rnb_err_t +RNBRemote::SendAsyncProfileDataPacket (char *buf, nub_size_t buf_size) +{ + if (buf_size == 0) + return rnb_success; + + std::string packet("A"); + packet.append(buf, buf_size); + return SendPacket(packet); +} + rnb_err_t RNBRemote::SendPacket (const std::string &s) { @@ -3428,6 +3461,58 @@ RNBRemote::HandlePacket_MemoryRegionInfo (const char *p) } rnb_err_t +RNBRemote::HandlePacket_GetProfileData (const char *p) +{ + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + const char *data = DNBProcessGetProfileDataAsCString(pid); + if (data) + { + return SendPacket (data); + } + else + { + return SendPacket ("OK"); + } +} + + +// QSetAsyncEnableProfiling;enable:[0|1]:interval_usec:XXXXXX; +rnb_err_t +RNBRemote::HandlePacket_SetAsyncEnableProfiling (const char *p) +{ + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket (""); + + StringExtractor packet(p += sizeof ("QSetAsyncEnableProfiling:") - 1); + bool enable = false; + uint64_t interval_usec = 0; + std::string name; + std::string value; + while (packet.GetNameColonValue(name, value)) + { + if (name.compare ("enable") == 0) + { + enable = strtoul(value.c_str(), NULL, 10) > 0; + } + else if (name.compare ("interval_usec") == 0) + { + interval_usec = strtoul(value.c_str(), NULL, 10); + } + } + + if (interval_usec == 0) + { + enable = 0; + } + DNBProcessSetAsyncEnableProfiling(pid, enable, interval_usec); + return SendPacket ("OK"); +} + +rnb_err_t RNBRemote::HandlePacket_WatchpointSupportInfo (const char *p) { /* This packet simply returns the number of supported hardware watchpoints. diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h index 1b1b0a6..0255e7f 100644 --- a/lldb/tools/debugserver/source/RNBRemote.h +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -112,6 +112,8 @@ public: set_list_threads_in_stop_reply, // 'QListThreadsInStopReply:' sync_thread_state, // 'QSyncThreadState:' memory_region_info, // 'qMemoryRegionInfo:' + get_profile_data, // 'qGetProfileData' + set_enable_profiling, // 'QSetAsyncEnableProfiling' watchpoint_support_info, // 'qWatchpointSupportInfo:' allocate_memory, // '_M' deallocate_memory, // '_m' @@ -210,6 +212,8 @@ public: rnb_err_t HandlePacket_AllocateMemory (const char *p); rnb_err_t HandlePacket_DeallocateMemory (const char *p); rnb_err_t HandlePacket_MemoryRegionInfo (const char *p); + rnb_err_t HandlePacket_GetProfileData(const char *p); + rnb_err_t HandlePacket_SetAsyncEnableProfiling(const char *p); rnb_err_t HandlePacket_WatchpointSupportInfo (const char *p); rnb_err_t HandlePacket_stop_process (const char *p); @@ -219,6 +223,8 @@ public: rnb_err_t SendSTDOUTPacket (char *buf, nub_size_t buf_size); rnb_err_t SendSTDERRPacket (char *buf, nub_size_t buf_size); void FlushSTDIO (); + void SendAsyncProfileData (); + rnb_err_t SendAsyncProfileDataPacket (char *buf, nub_size_t buf_size); RNBContext& Context() { return m_ctx; } RNBSocket& Comm() { return m_comm; } diff --git a/lldb/tools/debugserver/source/debugserver.cpp b/lldb/tools/debugserver/source/debugserver.cpp index 69c27d4..cb835ef 100644 --- a/lldb/tools/debugserver/source/debugserver.cpp +++ b/lldb/tools/debugserver/source/debugserver.cpp @@ -498,8 +498,9 @@ RNBRunLoopInferiorExecuting (RNBRemote *remote) if (!ctx.ProcessStateRunning()) { - // Clear the stdio bits if we are not running so we don't send any async packets + // Clear some bits if we are not running so we don't send any async packets event_mask &= ~RNBContext::event_proc_stdio_available; + event_mask &= ~RNBContext::event_proc_profile_data; } // We want to make sure we consume all process state changes and have @@ -519,6 +520,11 @@ RNBRunLoopInferiorExecuting (RNBRemote *remote) remote->FlushSTDIO(); } + if (set_events & RNBContext::event_proc_profile_data) + { + remote->SendAsyncProfileData(); + } + if (set_events & RNBContext::event_read_packet_available) { // handleReceivedPacket will take care of resetting the -- 2.7.4