This allows client to query profiling states on the inferior.
llvm-svn: 168228
eBroadcastBitStateChanged = (1 << 0),
eBroadcastBitInterrupt = (1 << 1),
eBroadcastBitSTDOUT = (1 << 2),
- eBroadcastBitSTDERR = (1 << 3)
+ eBroadcastBitSTDERR = (1 << 3),
+ eBroadcastBitProfileData = (1 << 4)
};
SBProcess ();
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;
/// eBroadcastBitStateChanged = (1 << 0),
/// eBroadcastBitInterrupt = (1 << 1),
/// eBroadcastBitSTDOUT = (1 << 2),
-/// eBroadcastBitSTDERR = (1 << 3)
+/// eBroadcastBitSTDERR = (1 << 3),
+/// eBroadcastBitProfileData = (1 << 4)
/// };
/// \endcode
//----------------------------------------------------------------------
eBroadcastBitStateChanged = (1 << 0),
eBroadcastBitInterrupt = (1 << 1),
eBroadcastBitSTDOUT = (1 << 2),
- eBroadcastBitSTDERR = (1 << 3)
+ eBroadcastBitSTDERR = (1 << 3),
+ eBroadcastBitProfileData = (1 << 4)
};
enum
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.
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
//----------------------------------------------------------------------
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?
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);
eBroadcastBitStateChanged = (1 << 0),
eBroadcastBitInterrupt = (1 << 1),
eBroadcastBitSTDOUT = (1 << 2),
- eBroadcastBitSTDERR = (1 << 3)
+ eBroadcastBitSTDERR = (1 << 3),
+ eBroadcastBitProfileData = (1 << 4)
};
SBProcess ();
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;
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
{
}
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;
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),
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" );
eBroadcastBitStateChanged |
eBroadcastBitInterrupt |
eBroadcastBitSTDOUT |
- eBroadcastBitSTDERR);
+ eBroadcastBitSTDERR |
+ eBroadcastBitProfileData);
m_private_state_listener.StartListeningForEvents(&m_private_state_broadcaster,
eBroadcastBitStateChanged |
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,
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
//------------------------------------------------------------------
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
}
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;
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
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;
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
};
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),
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;
+ }
}
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)
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)
{
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
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
#include <mach/mach_vm.h>
// C++ Includes
+#include <sstream>
+
// Other libraries and framework includes
// Project includes
#include "CFUtils.h"
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<num_threads; i++) {
+ profile_data_stream << ',' << threads_id[i];
+ profile_data_stream << ',' << threads_used_usec[i];
+ }
+ profile_data_stream << ';';
+
+ profile_data_stream << "rprvt:" << rprvt << ';';
+ profile_data_stream << "rsize:" << rsize << ';';
+ profile_data_stream << "vprvt:" << vprvt << ';';
+ profile_data_stream << "vsize:" << vsize << ';';
+ profile_data_stream << "dirty:" << dirty_size << ';';
+ profile_data_stream << "$";
+
+ m_profile_data = profile_data_stream.str();
+ }
+ else
+ {
+ m_profile_data.clear();
+ }
+
+ free(threads_id);
+ free(threads_used_usec);
+
+ return m_profile_data.c_str();
+}
+
//----------------------------------------------------------------------
// MachTask::TaskPortForProcessID
#include <sys/socket.h>
// C++ Includes
#include <map>
+#include <string>
// Other libraries and framework includes
// Project includes
#include "MachException.h"
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);
typedef std::map <mach_vm_address_t, size_t> allocation_collection;
allocation_collection m_allocations;
+ std::string m_profile_data;
private:
MachTask(const MachTask&); // Outlaw
#include "MachVMRegion.h"
#include "DNBLog.h"
#include <mach/mach_vm.h>
+#include <mach/shared_region.h>
MachVMMemory::MachVMMemory() :
m_page_size (kInvalidPageSize),
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)
{
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;
};
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)
{
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))
{
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)
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,
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"));
}
}
}
+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)
{
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)
{
}
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.
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'
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);
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; }
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
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