blob: 464500cf6b3fcd34ad120d84ec8a268a716cf319 [file] [log] [blame]
//===-- ProcessMacOSX.cpp ---------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// C Includes
#include <errno.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <spawn.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <unistd.h>
// C++ Includes
#include <algorithm>
#include <map>
// Other libraries and framework includes
#include "lldb/Breakpoint/WatchpointLocation.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/State.h"
#include "lldb/Core/Timer.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/TimeValue.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/TargetList.h"
#include "lldb/Utility/PseudoTerminal.h"
#if defined (__arm__)
#include <CoreFoundation/CoreFoundation.h>
#include <SpringBoardServices/SpringBoardServer.h>
#include <SpringBoardServices/SBSWatchdogAssertion.h>
#endif // #if defined (__arm__)
// Project includes
#include "ProcessMacOSX.h"
#include "ProcessMacOSXLog.h"
#include "ThreadMacOSX.h"
#if 0
#define DEBUG_LOG(fmt, ...) printf(fmt, ## __VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif
#ifndef MACH_PROCESS_USE_POSIX_SPAWN
#define MACH_PROCESS_USE_POSIX_SPAWN 1
#endif
#ifndef _POSIX_SPAWN_DISABLE_ASLR
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
#endif
#if defined (__arm__)
static bool
IsSBProcess (lldb::pid_t pid)
{
bool opt_runningApps = true;
bool opt_debuggable = false;
CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable));
if (sbsAppIDs.get() != NULL)
{
CFIndex count = ::CFArrayGetCount (sbsAppIDs.get());
CFIndex i = 0;
for (i = 0; i < count; i++)
{
CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i);
// Get the process id for the app (if there is one)
lldb::pid_t sbs_pid = LLDB_INVALID_PROCESS_ID;
if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &sbs_pid) == TRUE)
{
if (sbs_pid == pid)
return true;
}
}
}
return false;
}
#endif // #if defined (__arm__)
using namespace lldb;
using namespace lldb_private;
//
//void *
//ProcessMacOSX::WaitForChildProcessToExit (void *pid_ptr)
//{
// const lldb::pid_t pid = *((lldb::user_id_t *)pid_ptr);
//
// LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD));
//
// if (log)
// log->Printf ("ProcessMacOSX::%s (arg = %p) thread starting...", __FUNCTION__, pid_ptr);
//
// int status = -1;
//
// while (1)
// {
// if (log)
// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0)...", pid, &status);
//
// lldb::pid_t return_pid = ::waitpid (pid, &status, 0);
//
// if (return_pid < 0)
// {
// if (log)
// log->Printf("%s ::waitpid (pid = %i, stat_loc = %p, options = 0) => errno = %i, status = 0x%8.8x", pid, &status, errno, status);
// break;
// }
//
// bool set_exit_status = false;
// if (WIFSTOPPED(status))
// {
// if (log)
// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (STOPPED)", pid, &status, return_pid, status);
// }
// else if (WIFEXITED(status))
// {
// set_exit_status = true;
// if (log)
// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (EXITED)", pid, &status, return_pid, status);
// }
// else if (WIFSIGNALED(status))
// {
// set_exit_status = true;
// if (log)
// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (SIGNALED)", pid, &status, return_pid, status);
// }
// else
// {
// if (log)
// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x", pid, &status, return_pid, status);
// }
//
// if (set_exit_status)
// {
// // Try and deliver the news to the process if it is still around
// TargetSP target_sp(TargetList::SharedList().FindTargetWithProcessID (return_pid));
// if (target_sp.get())
// {
// ProcessMacOSX *process = dynamic_cast<ProcessMacOSX*>(target_sp->GetProcess().get());
// if (process)
// {
// process->SetExitStatus (status);
// if (log)
// log->Printf("Setting exit status of %i to 0x%8.8x", pid, status);
// process->Task().ShutDownExceptionThread();
// }
// }
// // Break out of the loop and return.
// break;
// }
// }
//
// if (log)
// log->Printf ("ProcessMacOSX::%s (arg = %p) thread exiting...", __FUNCTION__, pid_ptr);
//
// return NULL;
//}
//
const char *
ProcessMacOSX::GetPluginNameStatic()
{
return "process.macosx";
}
const char *
ProcessMacOSX::GetPluginDescriptionStatic()
{
return "Native MacOSX user process debugging plug-in.";
}
void
ProcessMacOSX::Terminate()
{
PluginManager::UnregisterPlugin (ProcessMacOSX::CreateInstance);
}
Process*
ProcessMacOSX::CreateInstance (Target &target, Listener &listener)
{
ProcessMacOSX::Initialize();
return new ProcessMacOSX (target, listener);
}
bool
ProcessMacOSX::CanDebug(Target &target)
{
// For now we are just making sure the file exists for a given module
ModuleSP exe_module_sp(target.GetExecutableModule());
if (exe_module_sp.get())
return exe_module_sp->GetFileSpec().Exists();
return false;
}
//----------------------------------------------------------------------
// ProcessMacOSX constructor
//----------------------------------------------------------------------
ProcessMacOSX::ProcessMacOSX(Target& target, Listener &listener) :
Process (target, listener),
m_stdio_ours (false),
m_child_stdin (-1),
m_child_stdout (-1),
m_child_stderr (-1),
m_task (this),
m_flags (eFlagsNone),
m_stdio_thread (LLDB_INVALID_HOST_THREAD),
m_monitor_thread (LLDB_INVALID_HOST_THREAD),
m_stdio_mutex (Mutex::eMutexTypeRecursive),
m_stdout_data (),
m_exception_messages (),
m_exception_messages_mutex (Mutex::eMutexTypeRecursive),
m_arch_spec ()
{
}
//----------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------
ProcessMacOSX::~ProcessMacOSX()
{
// m_mach_process.UnregisterNotificationCallbacks (this);
Clear();
}
//----------------------------------------------------------------------
// PluginInterface
//----------------------------------------------------------------------
const char *
ProcessMacOSX::GetPluginName()
{
return "Process debugging plug-in for MacOSX";
}
const char *
ProcessMacOSX::GetShortPluginName()
{
return GetPluginNameStatic();
}
uint32_t
ProcessMacOSX::GetPluginVersion()
{
return 1;
}
void
ProcessMacOSX::GetPluginCommandHelp (const char *command, Stream *strm)
{
strm->Printf("The following arguments can be supplied to the 'log %s' command:\n", GetShortPluginName());
strm->PutCString("\tverbose - enable verbose logging\n");
strm->PutCString("\tprocess - enable process logging\n");
strm->PutCString("\tthread - enable thread logging\n");
strm->PutCString("\texceptions - enable exception logging\n");
strm->PutCString("\tdynamic - enable DynamicLoader logging\n");
strm->PutCString("\tmemory-calls - enable memory read and write call logging\n");
strm->PutCString("\tmemory-data-short - log short memory read and write byte data\n");
strm->PutCString("\tmemory-data-long - log all memory read and write byte data\n");
strm->PutCString("\tmemory-protections - log memory protection calls\n");
strm->PutCString("\tbreakpoints - log breakpoint calls\n");
strm->PutCString("\twatchpoints - log watchpoint calls\n");
strm->PutCString("\tevents - log event and event queue status\n");
strm->PutCString("\tstep - log step related activity\n");
strm->PutCString("\ttask - log task functions\n");
}
Error
ProcessMacOSX::ExecutePluginCommand (Args &command, Stream *strm)
{
Error error;
error.SetErrorString("No plug-in command are currently supported.");
return error;
}
Log *
ProcessMacOSX::EnablePluginLogging (Stream *strm, Args &command)
{
return NULL;
}
//----------------------------------------------------------------------
// Process Control
//----------------------------------------------------------------------
Error
ProcessMacOSX::DoLaunch
(
Module* module,
char const *argv[],
char const *envp[],
uint32_t flags,
const char *stdin_path,
const char *stdout_path,
const char *stderr_path,
const char *working_dir
)
{
// ::LogSetBitMask (PD_LOG_DEFAULT);
// ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD);
// ::LogSetLogFile ("/dev/stdout");
Error error;
ObjectFile * object_file = module->GetObjectFile();
if (object_file)
{
ArchSpec arch_spec(module->GetArchitecture());
// Set our user ID to our process ID.
SetID (LaunchForDebug(argv[0], argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, eLaunchDefault, flags, error));
}
else
{
// Set our user ID to an invalid process ID.
SetID (LLDB_INVALID_PROCESS_ID);
error.SetErrorToGenericError ();
error.SetErrorStringWithFormat("Failed to get object file from '%s' for arch %s.\n",
module->GetFileSpec().GetFilename().AsCString(),
module->GetArchitecture().GetArchitectureName());
}
// Return the process ID we have
return error;
}
Error
ProcessMacOSX::DoAttachToProcessWithID (lldb::pid_t attach_pid)
{
Error error;
// Clear out and clean up from any current state
Clear();
// HACK: require arch be set correctly at the target level until we can
// figure out a good way to determine the arch of what we are attaching to
m_arch_spec = m_target.GetArchitecture();
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
if (attach_pid != LLDB_INVALID_PROCESS_ID)
{
SetID(attach_pid);
// Let ourselves know we are going to be using SBS if the correct flag bit is set...
#if defined (__arm__)
if (IsSBProcess(pid))
m_flags |= eFlagsUsingSBS;
#endif
if (Task().GetTaskPortForProcessID(error) == TASK_NULL)
{
if (log)
log->Printf ("error attaching to pid %i: %s", GetID(), error.AsCString());
}
else
{
Task().StartExceptionThread(error);
if (error.Success())
{
errno = 0;
if (::ptrace (PT_ATTACHEXC, GetID(), 0, 0) == 0)
{
m_flags.Set (eFlagsAttached);
// Sleep a bit to let the exception get received and set our process status
// to stopped.
::usleep(250000);
if (log)
log->Printf ("successfully attached to pid %d", GetID());
return error;
}
else
{
error.SetErrorToErrno();
if (log)
log->Printf ("error: failed to attach to pid %d", GetID());
}
}
else
{
if (log)
log->Printf ("error: failed to start exception thread for pid %d: %s", GetID(), error.AsCString());
}
}
}
SetID (LLDB_INVALID_PROCESS_ID);
if (error.Success())
error.SetErrorStringWithFormat ("failed to attach to pid %d", attach_pid);
return error;
}
Error
ProcessMacOSX::WillLaunchOrAttach ()
{
return Error();
}
Error
ProcessMacOSX::WillLaunch (Module* module)
{
return WillLaunchOrAttach ();
}
void
ProcessMacOSX::DidLaunchOrAttach ()
{
if (GetID() != LLDB_INVALID_PROCESS_ID)
{
Module * exe_module = GetTarget().GetExecutableModule ().get();
assert (exe_module);
// Install a signal handler so we can catch when our child process
// dies and set the exit status correctly.
m_monitor_thread = Host::StartMonitoringChildProcess (Process::SetProcessExitStatus, NULL, GetID(), false);
}
}
void
ProcessMacOSX::DidLaunch ()
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::DidLaunch()");
DidLaunchOrAttach ();
}
void
ProcessMacOSX::DidAttach ()
{
DidLaunchOrAttach ();
}
Error
ProcessMacOSX::WillAttachToProcessWithID (lldb::pid_t pid)
{
return WillLaunchOrAttach ();
}
Error
ProcessMacOSX::WillAttachToProcessWithName (const char *process_name, bool wait_for_launch)
{
return WillLaunchOrAttach ();
}
Error
ProcessMacOSX::DoResume ()
{
Error error;
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::Resume()");
const StateType state = m_private_state.GetValue();
if (CanResume(state))
{
error = PrivateResume(LLDB_INVALID_THREAD_ID);
}
else if (state == eStateRunning)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x is running, ignoring...", Task().GetTaskPort());
}
else
{
error.SetErrorStringWithFormat("task 0x%x can't continue, ignoring...", Task().GetTaskPort());
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x can't continue, ignoring...", Task().GetTaskPort());
}
return error;
}
size_t
ProcessMacOSX::GetSoftwareBreakpointTrapOpcode (BreakpointSite* bp_site)
{
const uint8_t *trap_opcode = NULL;
uint32_t trap_opcode_size = 0;
static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 };
//static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE };
static const uint8_t g_ppc_breakpoint_opcode[] = { 0x7F, 0xC0, 0x00, 0x08 };
static const uint8_t g_i386_breakpoint_opcode[] = { 0xCC };
llvm::Triple::ArchType machine = m_arch_spec.GetMachine();
switch (machine)
{
case llvm::Triple::x86:
case llvm::Triple::x86_64:
trap_opcode = g_i386_breakpoint_opcode;
trap_opcode_size = sizeof(g_i386_breakpoint_opcode);
break;
case llvm::Triple::arm:
// TODO: fill this in for ARM. We need to dig up the symbol for
// the address in the breakpoint locaiton and figure out if it is
// an ARM or Thumb breakpoint.
trap_opcode = g_arm_breakpoint_opcode;
trap_opcode_size = sizeof(g_arm_breakpoint_opcode);
break;
case llvm::Triple::ppc:
case llvm::Triple::ppc64:
trap_opcode = g_ppc_breakpoint_opcode;
trap_opcode_size = sizeof(g_ppc_breakpoint_opcode);
break;
default:
assert(!"Unhandled architecture in ProcessMacOSX::GetSoftwareBreakpointTrapOpcode()");
break;
}
if (trap_opcode && trap_opcode_size)
{
if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size))
return trap_opcode_size;
}
return 0;
}
uint32_t
ProcessMacOSX::UpdateThreadListIfNeeded ()
{
// locker will keep a mutex locked until it goes out of scope
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD));
if (log && log->GetMask().Test(PD_LOG_VERBOSE))
log->Printf ("ProcessMacOSX::%s (pid = %4.4x)", __FUNCTION__, GetID());
const uint32_t stop_id = GetStopID();
if (m_thread_list.GetSize(false) == 0 || stop_id != m_thread_list.GetStopID())
{
// Update the thread list's stop id immediately so we don't recurse into this function.
thread_array_t thread_list = NULL;
mach_msg_type_number_t thread_list_count = 0;
task_t task = Task().GetTaskPort();
Error err(::task_threads (task, &thread_list, &thread_list_count), eErrorTypeMachKernel);
if (log || err.Fail())
err.PutToLog(log.get(), "::task_threads ( task = 0x%4.4x, thread_list => %p, thread_list_count => %u )", task, thread_list, thread_list_count);
if (err.GetError() == KERN_SUCCESS && thread_list_count > 0)
{
ThreadList curr_thread_list (this);
curr_thread_list.SetStopID(stop_id);
size_t idx;
// Iterator through the current thread list and see which threads
// we already have in our list (keep them), which ones we don't
// (add them), and which ones are not around anymore (remove them).
for (idx = 0; idx < thread_list_count; ++idx)
{
const lldb::tid_t tid = thread_list[idx];
ThreadSP thread_sp(GetThreadList().FindThreadByID (tid, false));
if (thread_sp.get() == NULL)
thread_sp.reset (new ThreadMacOSX (*this, tid));
curr_thread_list.AddThread(thread_sp);
}
m_thread_list = curr_thread_list;
// Free the vm memory given to us by ::task_threads()
vm_size_t thread_list_size = (vm_size_t) (thread_list_count * sizeof (lldb::tid_t));
::vm_deallocate (::mach_task_self(),
(vm_address_t)thread_list,
thread_list_size);
}
}
return GetThreadList().GetSize(false);
}
void
ProcessMacOSX::RefreshStateAfterStop ()
{
// If we are attaching, let our dynamic loader plug-in know so it can get
// an initial list of shared libraries.
// We must be attaching if we don't already have a valid architecture
if (!m_arch_spec.IsValid())
{
Module *exe_module = GetTarget().GetExecutableModule().get();
if (exe_module)
m_arch_spec = exe_module->GetArchitecture();
}
// Discover new threads:
UpdateThreadListIfNeeded ();
// Let all threads recover from stopping and do any clean up based
// on the previous thread state (if any).
m_thread_list.RefreshStateAfterStop();
// Let each thread know of any exceptions
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS));
task_t task = Task().GetTaskPort();
size_t i;
for (i=0; i<m_exception_messages.size(); ++i)
{
// Let the thread list figure use the ProcessMacOSX to forward all exceptions
// on down to each thread.
if (m_exception_messages[i].state.task_port == task)
{
ThreadSP thread_sp(m_thread_list.FindThreadByID(m_exception_messages[i].state.thread_port));
if (thread_sp.get())
{
ThreadMacOSX *macosx_thread = (ThreadMacOSX *)thread_sp.get();
macosx_thread->NotifyException (m_exception_messages[i].state);
}
}
if (log)
m_exception_messages[i].PutToLog(log.get());
}
}
Error
ProcessMacOSX::DoHalt (bool &caused_stop)
{
caused_stop = true;
return Signal (SIGSTOP);
}
Error
ProcessMacOSX::WillDetach ()
{
Error error;
const StateType state = m_private_state.GetValue();
if (IsRunning(state))
{
error.SetErrorToGenericError();
error.SetErrorString("Process must be stopped in order to detach.");
}
return error;
}
Error
ProcessMacOSX::DoSIGSTOP (bool clear_all_breakpoints)
{
Error error;
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
if (log)
log->Printf ("ProcessMacOSX::DoSIGSTOP()");
EventSP event_sp;
TimeValue timeout_time;
StateType state = m_private_state.GetValue();
lldb::pid_t pid = GetID();
if (IsRunning(state))
{
// If our process is running, we need to SIGSTOP it so we can detach.
if (log)
log->Printf ("ProcessMacOSX::DoDestroy() - kill (%i, SIGSTOP)", pid);
// Send the SIGSTOP and wait a few seconds for it to stop
// Pause the Private State Thread so it doesn't intercept the events we need to wait for.
PausePrivateStateThread();
// I don't think this is right. Halt should just stop the process, and then whoever called halt should
// arrange whatever they need to with the thread plans.
//m_thread_list.DiscardThreadPlans();
// First jettison all the current thread plans, since we want to make sure it
// really just stops.
if (::kill (pid, SIGSTOP) == 0)
error.Clear();
else
error.SetErrorToErrno();
if (error.Fail())
error.PutToLog(log.get(), "::kill (pid = %i, SIGSTOP)", pid);
timeout_time = TimeValue::Now();
timeout_time.OffsetWithSeconds(2);
state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp);
// Resume the private state thread at this point.
ResumePrivateStateThread();
if (!StateIsStoppedState (state))
{
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
if (log)
log->Printf("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP");
return error;
}
if (clear_all_breakpoints)
GetTarget().DisableAllBreakpoints();
}
else if (!HasExited(state))
{
if (clear_all_breakpoints)
GetTarget().DisableAllBreakpoints();
// const uint32_t num_threads = GetNumThreads();
// for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx)
// {
// Thread *thread = GetThreadAtIndex(thread_idx);
// thread->SetResumeState(eStateRunning);
// if (thread_idx == 0)
// thread->SetResumeSignal(SIGSTOP);
// }
// Our process was stopped, so resume it and then SIGSTOP it so we can
// detach.
// But discard all the thread plans first, so we don't keep going because we
// are in mid-plan.
// Pause the Private State Thread so it doesn't intercept the events we need to wait for.
PausePrivateStateThread();
m_thread_list.DiscardThreadPlans();
if (::kill (pid, SIGSTOP) == 0)
error.Clear();
else
error.SetErrorToErrno();
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
if (log || error.Fail())
error.PutToLog(log.get(), "ProcessMacOSX::DoSIGSTOP() ::kill (pid = %i, SIGSTOP)", pid);
error = PrivateResume(LLDB_INVALID_THREAD_ID);
// Wait a few seconds for our process to resume
timeout_time = TimeValue::Now();
timeout_time.OffsetWithSeconds(2);
state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp);
// Make sure the process resumed
if (StateIsStoppedState (state))
{
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
if (log)
log->Printf ("ProcessMacOSX::DoSIGSTOP() couldn't resume process, state = %s", StateAsCString(state));
error.SetErrorStringWithFormat("ProcessMacOSX::DoSIGSTOP() couldn't resume process, state = %s", StateAsCString(state));
}
else
{
// Send the SIGSTOP and wait a few seconds for it to stop
timeout_time = TimeValue::Now();
timeout_time.OffsetWithSeconds(2);
state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp);
if (!StateIsStoppedState (state))
{
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
if (log)
log->Printf("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP");
error.SetErrorString("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP");
}
}
// Resume the private state thread at this point.
ResumePrivateStateThread();
}
return error;
}
Error
ProcessMacOSX::DoDestroy ()
{
Error error;
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
if (log)
log->Printf ("ProcessMacOSX::DoDestroy()");
error = DoSIGSTOP (true);
if (error.Success())
{
StopSTDIOThread(true);
if (log)
log->Printf ("ProcessMacOSX::DoDestroy() DoSIGSTOP succeeded");
const StateType state = m_private_state.GetValue();
// Scope for "locker" so we can reply to all of our exceptions (the SIGSTOP
// exception).
{
Mutex::Locker locker(m_exception_messages_mutex);
ReplyToAllExceptions();
}
if (log)
log->Printf ("ProcessMacOSX::DoDestroy() replied to all exceptions");
// Shut down the exception thread and cleanup our exception remappings
Task().ShutDownExceptionThread();
if (log)
log->Printf ("ProcessMacOSX::DoDestroy() exception thread has been shutdown");
if (!HasExited(state))
{
lldb::pid_t pid = GetID();
// Detach from our process while we are stopped.
errno = 0;
// Detach from our process
::ptrace (PT_KILL, pid, 0, 0);
error.SetErrorToErrno();
if (log || error.Fail())
error.PutToLog (log.get(), "::ptrace (PT_KILL, %u, 0, 0)", pid);
// Resume our task and let the SIGKILL do its thing. The thread named
// "ProcessMacOSX::WaitForChildProcessToExit(void*)" will catch the
// process exiting, so we don't need to set our state to exited in this
// function.
Task().Resume();
}
// NULL our task out as we have already restored all exception ports
Task().Clear();
// Clear out any notion of the process we once were
Clear();
}
return error;
}
//------------------------------------------------------------------
// Process Queries
//------------------------------------------------------------------
bool
ProcessMacOSX::IsAlive ()
{
return MachTask::IsValid (Task().GetTaskPort());
}
lldb::addr_t
ProcessMacOSX::GetImageInfoAddress()
{
return Task().GetDYLDAllImageInfosAddress();
}
//------------------------------------------------------------------
// Process Memory
//------------------------------------------------------------------
size_t
ProcessMacOSX::DoReadMemory (lldb::addr_t addr, void *buf, size_t size, Error& error)
{
return Task().ReadMemory(addr, buf, size, error);
}
size_t
ProcessMacOSX::DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size, Error& error)
{
return Task().WriteMemory(addr, buf, size, error);
}
lldb::addr_t
ProcessMacOSX::DoAllocateMemory (size_t size, uint32_t permissions, Error& error)
{
return Task().AllocateMemory (size, permissions, error);
}
Error
ProcessMacOSX::DoDeallocateMemory (lldb::addr_t ptr)
{
return Task().DeallocateMemory (ptr);
}
//------------------------------------------------------------------
// Process STDIO
//------------------------------------------------------------------
size_t
ProcessMacOSX::GetSTDOUT (char *buf, size_t buf_size, Error &error)
{
error.Clear();
Mutex::Locker locker(m_stdio_mutex);
size_t bytes_available = m_stdout_data.size();
if (bytes_available > 0)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size);
if (bytes_available > buf_size)
{
memcpy(buf, m_stdout_data.c_str(), buf_size);
m_stdout_data.erase(0, buf_size);
bytes_available = buf_size;
}
else
{
memcpy(buf, m_stdout_data.c_str(), bytes_available);
m_stdout_data.clear();
//ResetEventBits(eBroadcastBitSTDOUT);
}
}
return bytes_available;
}
size_t
ProcessMacOSX::GetSTDERR (char *buf, size_t buf_size, Error &error)
{
error.Clear();
return 0;
}
size_t
ProcessMacOSX::PutSTDIN (const char *buf, size_t buf_size, Error &error)
{
if (m_child_stdin == -1)
{
error.SetErrorString ("Invalid child stdin handle.");
}
else
{
ssize_t bytes_written = ::write (m_child_stdin, buf, buf_size);
if (bytes_written == -1)
error.SetErrorToErrno();
else
{
error.Clear();
return bytes_written;
}
}
return 0;
}
Error
ProcessMacOSX::EnableBreakpoint (BreakpointSite *bp_site)
{
Error error;
assert (bp_site != NULL);
const lldb::addr_t addr = bp_site->GetLoadAddress();
const lldb::user_id_t site_id = bp_site->GetID();
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS));
if (log)
log->Printf ("ProcessMacOSX::EnableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr);
if (bp_site->IsEnabled())
{
if (log)
log->Printf ("ProcessMacOSX::EnableBreakpoint (site_id = %d) addr = 0x%8.8llx -- SUCCESS (already enabled)", site_id, (uint64_t)addr);
return error;
}
if (bp_site->HardwarePreferred())
{
// FIXME: This code doesn't make sense. Breakpoint sites don't really have single ThreadID's, since one site could be
// owned by a number of Locations, each with a different Thread ID. So either this should run over all the Locations and
// set it for all threads owned by those locations, or set it for all threads, and let the thread specific code sort it out.
// ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp_site->GetThreadID()).get();
// if (thread)
// {
// bp_site->SetHardwareIndex (thread->SetHardwareBreakpoint(bp_site));
// if (bp_site->IsHardware())
// {
// bp_site->SetEnabled(true);
// return error;
// }
// }
}
// Just let lldb::Process::EnableSoftwareBreakpoint() handle everything...
return EnableSoftwareBreakpoint (bp_site);
}
Error
ProcessMacOSX::DisableBreakpoint (BreakpointSite *bp_site)
{
Error error;
assert (bp_site != NULL);
const lldb::addr_t addr = bp_site->GetLoadAddress();
const lldb::user_id_t site_id = bp_site->GetID();
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS));
if (log)
log->Printf ("ProcessMacOSX::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr);
if (bp_site->IsHardware())
{
error.SetErrorString("hardware breakpoints are no supported");
return error;
}
// Just let lldb::Process::EnableSoftwareBreakpoint() handle everything...
return DisableSoftwareBreakpoint (bp_site);
}
Error
ProcessMacOSX::EnableWatchpoint (WatchpointLocation *wp)
{
Error error;
if (wp)
{
lldb::user_id_t watchID = wp->GetID();
lldb::addr_t addr = wp->GetLoadAddress();
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS));
if (log)
log->Printf ("ProcessMacOSX::EnableWatchpoint(watchID = %d)", watchID);
if (wp->IsEnabled())
{
if (log)
log->Printf("ProcessMacOSX::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr);
return error;
}
else
{
// Watchpoints aren't supported at present.
error.SetErrorString("Watchpoints aren't currently supported.");
}
}
return error;
}
Error
ProcessMacOSX::DisableWatchpoint (WatchpointLocation *wp)
{
Error error;
if (wp)
{
lldb::user_id_t watchID = wp->GetID();
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS));
lldb::addr_t addr = wp->GetLoadAddress();
if (log)
log->Printf ("ProcessMacOSX::DisableWatchpoint (watchID = %d) addr = 0x%8.8llx", watchID, (uint64_t)addr);
if (wp->IsHardware())
{
error.SetErrorString("Watchpoints aren't currently supported.");
}
// TODO: clear software watchpoints if we implement them
error.SetErrorToGenericError();
}
else
{
error.SetErrorString("Watchpoint location argument was NULL.");
}
return error;
}
static ProcessMacOSX::CreateArchCalback
ArchCallbackMap(const ArchSpec& arch_spec, ProcessMacOSX::CreateArchCalback callback, bool add )
{
// We must wrap the "g_arch_map" file static in a function to avoid
// any global constructors so we don't get a build verification error
typedef std::multimap<ArchSpec, ProcessMacOSX::CreateArchCalback> ArchToProtocolMap;
static ArchToProtocolMap g_arch_map;
if (add)
{
g_arch_map.insert(std::make_pair(arch_spec, callback));
return callback;
}
else
{
ArchToProtocolMap::const_iterator pos = g_arch_map.find(arch_spec);
if (pos != g_arch_map.end())
{
return pos->second;
}
}
return NULL;
}
void
ProcessMacOSX::AddArchCreateCallback(const ArchSpec& arch_spec, CreateArchCalback callback)
{
ArchCallbackMap (arch_spec, callback, true);
}
ProcessMacOSX::CreateArchCalback
ProcessMacOSX::GetArchCreateCallback()
{
return ArchCallbackMap (m_arch_spec, NULL, false);
}
void
ProcessMacOSX::Clear()
{
// Clear any cached thread list while the pid and task are still valid
Task().Clear();
// Now clear out all member variables
CloseChildFileDescriptors();
m_flags = eFlagsNone;
m_thread_list.Clear();
{
Mutex::Locker locker(m_exception_messages_mutex);
m_exception_messages.clear();
}
if (IS_VALID_LLDB_HOST_THREAD(m_monitor_thread))
{
Host::ThreadCancel (m_monitor_thread, NULL);
thread_result_t thread_result;
Host::ThreadJoin (m_monitor_thread, &thread_result, NULL);
m_monitor_thread = LLDB_INVALID_HOST_THREAD;
}
}
bool
ProcessMacOSX::StartSTDIOThread()
{
if (IS_VALID_LLDB_HOST_THREAD(m_stdio_thread))
return true;
// If we created and own the child STDIO file handles, then we track the
// STDIO ourselves, else we let whomever owns these file handles track
// the IO themselves.
if (m_stdio_ours)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s ( )", __FUNCTION__);
// Create the thread that watches for the child STDIO
m_stdio_thread = Host::ThreadCreate ("<lldb.process.process-macosx.stdio>", ProcessMacOSX::STDIOThread, this, NULL);
return IS_VALID_LLDB_HOST_THREAD(m_stdio_thread);
}
return false;
}
void
ProcessMacOSX::StopSTDIOThread(bool close_child_fds)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s ( )", __FUNCTION__);
// Stop the stdio thread
if (IS_VALID_LLDB_HOST_THREAD(m_stdio_thread))
{
Host::ThreadCancel (m_stdio_thread, NULL);
thread_result_t result = NULL;
Host::ThreadJoin (m_stdio_thread, &result, NULL);
if (close_child_fds)
CloseChildFileDescriptors();
else
{
// We may have given up control of these file handles, so just
// set them to invalid values so the STDIO thread can exit when
// we interrupt it with pthread_cancel...
m_child_stdin = -1;
m_child_stdout = -1;
m_child_stderr = -1;
}
}
}
void *
ProcessMacOSX::STDIOThread(void *arg)
{
ProcessMacOSX *proc = (ProcessMacOSX*) arg;
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
if (log)
log->Printf ("ProcessMacOSX::%s (arg = %p) thread starting...", __FUNCTION__, arg);
// We start use a base and more options so we can control if we
// are currently using a timeout on the mach_msg. We do this to get a
// bunch of related exceptions on our exception port so we can process
// then together. When we have multiple threads, we can get an exception
// per thread and they will come in consecutively. The main thread loop
// will start by calling mach_msg to without having the MACH_RCV_TIMEOUT
// flag set in the options, so we will wait forever for an exception on
// our exception port. After we get one exception, we then will use the
// MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
// exceptions for our process. After we have received the last pending
// exception, we will get a timeout which enables us to then notify
// our main thread that we have an exception bundle available. We then wait
// for the main thread to tell this exception thread to start trying to get
// exceptions messages again and we start again with a mach_msg read with
// infinite timeout.
Error err;
int stdout_fd = proc->GetStdoutFileDescriptor();
int stderr_fd = proc->GetStderrFileDescriptor();
if (stdout_fd == stderr_fd)
stderr_fd = -1;
while (stdout_fd >= 0 || stderr_fd >= 0)
{
//::pthread_testcancel ();
fd_set read_fds;
FD_ZERO (&read_fds);
if (stdout_fd >= 0)
FD_SET (stdout_fd, &read_fds);
if (stderr_fd >= 0)
FD_SET (stderr_fd, &read_fds);
int nfds = std::max<int>(stdout_fd, stderr_fd) + 1;
int num_set_fds = select (nfds, &read_fds, NULL, NULL, NULL);
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
if (log)
log->Printf("select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
if (num_set_fds < 0)
{
int select_errno = errno;
if (log)
{
err.SetError (select_errno, eErrorTypePOSIX);
err.LogIfError(log.get(), "select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
}
switch (select_errno)
{
case EAGAIN: // The kernel was (perhaps temporarily) unable to allocate the requested number of file descriptors, or we have non-blocking IO
break;
case EBADF: // One of the descriptor sets specified an invalid descriptor.
return NULL;
break;
case EINTR: // A signal was delivered before the time limit expired and before any of the selected events occurred.
case EINVAL: // The specified time limit is invalid. One of its components is negative or too large.
default: // Other unknown error
break;
}
}
else if (num_set_fds == 0)
{
}
else
{
char s[1024];
s[sizeof(s)-1] = '\0'; // Ensure we have NULL termination
int bytes_read = 0;
if (stdout_fd >= 0 && FD_ISSET (stdout_fd, &read_fds))
{
do
{
bytes_read = ::read (stdout_fd, s, sizeof(s)-1);
if (bytes_read < 0)
{
int read_errno = errno;
if (log)
log->Printf("read (stdout_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno));
}
else if (bytes_read == 0)
{
// EOF...
if (log)
log->Printf("read (stdout_fd, ) => %d (reached EOF for child STDOUT)", bytes_read);
stdout_fd = -1;
}
else if (bytes_read > 0)
{
proc->AppendSTDOUT(s, bytes_read);
}
} while (bytes_read > 0);
}
if (stderr_fd >= 0 && FD_ISSET (stderr_fd, &read_fds))
{
do
{
bytes_read = ::read (stderr_fd, s, sizeof(s)-1);
if (bytes_read < 0)
{
int read_errno = errno;
if (log)
log->Printf("read (stderr_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno));
}
else if (bytes_read == 0)
{
// EOF...
if (log)
log->Printf("read (stderr_fd, ) => %d (reached EOF for child STDERR)", bytes_read);
stderr_fd = -1;
}
else if (bytes_read > 0)
{
proc->AppendSTDOUT(s, bytes_read);
}
} while (bytes_read > 0);
}
}
}
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
if (log)
log->Printf("ProcessMacOSX::%s (%p): thread exiting...", __FUNCTION__, arg);
return NULL;
}
Error
ProcessMacOSX::DoSignal (int signal)
{
Error error;
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
if (log)
log->Printf ("ProcessMacOSX::DoSignal (signal = %d)", signal);
if (::kill (GetID(), signal) != 0)
{
error.SetErrorToErrno();
error.LogIfError(log.get(), "ProcessMacOSX::DoSignal (%d)", signal);
}
return error;
}
Error
ProcessMacOSX::DoDetach()
{
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS));
if (log)
log->Printf ("ProcessMacOSX::DoDetach()");
Error error (DoSIGSTOP (true));
if (error.Success())
{
CloseChildFileDescriptors ();
// Scope for "locker" so we can reply to all of our exceptions (the SIGSTOP
// exception).
{
Mutex::Locker locker(m_exception_messages_mutex);
ReplyToAllExceptions();
}
// Shut down the exception thread and cleanup our exception remappings
Task().ShutDownExceptionThread();
lldb::pid_t pid = GetID();
// Detach from our process while we are stopped.
errno = 0;
// Detach from our process
::ptrace (PT_DETACH, pid, (caddr_t)1, 0);
error.SetErrorToErrno();
if (log || error.Fail())
error.PutToLog(log.get(), "::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid);
// Resume our task
Task().Resume();
// NULL our task out as we have already restored all exception ports
Task().Clear();
// Clear out any notion of the process we once were
Clear();
SetPrivateState (eStateDetached);
}
return error;
}
Error
ProcessMacOSX::ReplyToAllExceptions()
{
Error error;
Mutex::Locker locker(m_exception_messages_mutex);
if (m_exception_messages.empty() == false)
{
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS));
MachException::Message::iterator pos;
MachException::Message::iterator begin = m_exception_messages.begin();
MachException::Message::iterator end = m_exception_messages.end();
for (pos = begin; pos != end; ++pos)
{
int resume_signal = -1;
ThreadSP thread_sp = m_thread_list.FindThreadByID(pos->state.thread_port);
if (thread_sp.get())
resume_signal = thread_sp->GetResumeSignal();
if (log)
log->Printf ("Replying to exception %d, tid = 0x%4.4x, resume_signal = %i", std::distance(begin, pos), thread_sp->GetID(), resume_signal);
Error curr_error (pos->Reply (Task().GetTaskPort(), GetID(), resume_signal));
// Only report the first error
if (curr_error.Fail() && error.Success())
error = curr_error;
error.LogIfError(log.get(), "Error replying to exception");
}
// Erase all exception message as we should have used and replied
// to them all already.
m_exception_messages.clear();
}
return error;
}
Error
ProcessMacOSX::PrivateResume (lldb::tid_t tid)
{
Mutex::Locker locker(m_exception_messages_mutex);
Error error (ReplyToAllExceptions());
// Let the thread prepare to resume and see if any threads want us to
// step over a breakpoint instruction (ProcessWillResume will modify
// the value of stepOverBreakInstruction).
//StateType process_state = m_thread_list.ProcessWillResume(this);
// Set our state accordingly
SetPrivateState (eStateRunning);
// Now resume our task.
error = Task().Resume();
return error;
}
// Called by the exception thread when an exception has been received from
// our process. The exception message is completely filled and the exception
// data has already been copied.
void
ProcessMacOSX::ExceptionMessageReceived (const MachException::Message& exceptionMessage)
{
Mutex::Locker locker(m_exception_messages_mutex);
if (m_exception_messages.empty())
Task().Suspend();
ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "ProcessMacOSX::ExceptionMessageReceived ( )");
// Use a locker to automatically unlock our mutex in case of exceptions
// Add the exception to our internal exception stack
m_exception_messages.push_back(exceptionMessage);
}
//bool
//ProcessMacOSX::GetProcessInfo (struct kinfo_proc* proc_info)
//{
// int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, GetID() };
// size_t buf_size = sizeof(struct kinfo_proc);
//
// if (::sysctl (mib, (unsigned)(sizeof(mib)/sizeof(int)), &proc_info, &buf_size, NULL, 0) == 0)
// return buf_size > 0;
//
// return false;
//}
//
//
void
ProcessMacOSX::ExceptionMessageBundleComplete()
{
// We have a complete bundle of exceptions for our child process.
Mutex::Locker locker(m_exception_messages_mutex);
ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s: %d exception messages.", __PRETTY_FUNCTION__, m_exception_messages.size());
if (!m_exception_messages.empty())
{
SetPrivateState (eStateStopped);
}
else
{
ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s empty exception messages bundle.", __PRETTY_FUNCTION__, m_exception_messages.size());
}
}
bool
ProcessMacOSX::ReleaseChildFileDescriptors ( int *stdin_fileno, int *stdout_fileno, int *stderr_fileno )
{
if (stdin_fileno)
*stdin_fileno = m_child_stdin;
if (stdout_fileno)
*stdout_fileno = m_child_stdout;
if (stderr_fileno)
*stderr_fileno = m_child_stderr;
// Stop the stdio thread if we have one, but don't have it close the child
// file descriptors since we are giving control of these descriptors to the
// caller
bool close_child_fds = false;
StopSTDIOThread(close_child_fds);
return true;
}
void
ProcessMacOSX::AppendSTDOUT (const char* s, size_t len)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s (<%d> %s) ...", __FUNCTION__, len, s);
Mutex::Locker locker(m_stdio_mutex);
m_stdout_data.append(s, len);
// FIXME: Make a real data object for this and put it out.
BroadcastEventIfUnique (eBroadcastBitSTDOUT);
}
lldb::pid_t
ProcessMacOSX::LaunchForDebug
(
const char *path,
char const *argv[],
char const *envp[],
ArchSpec& arch_spec,
const char *stdin_path,
const char *stdout_path,
const char *stderr_path,
PDLaunchType launch_type,
uint32_t flags,
Error &launch_err)
{
// Clear out and clean up from any current state
Clear();
m_arch_spec = arch_spec;
if (launch_type == eLaunchDefault)
launch_type = eLaunchPosixSpawn;
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
if (log)
log->Printf ("%s( path = '%s', argv = %p, envp = %p, launch_type = %u, flags = %x )", __FUNCTION__, path, argv, envp, launch_type, flags);
// Fork a child process for debugging
SetPrivateState (eStateLaunching);
switch (launch_type)
{
case eLaunchForkExec:
SetID(ProcessMacOSX::ForkChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err));
break;
case eLaunchPosixSpawn:
SetID(ProcessMacOSX::PosixSpawnChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, flags & eLaunchFlagDisableASLR ? 1 : 0, launch_err));
break;
#if defined (__arm__)
case eLaunchSpringBoard:
{
const char *app_ext = strstr(path, ".app");
if (app_ext != NULL)
{
std::string app_bundle_path(path, app_ext + strlen(".app"));
return SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, launch_err);
}
}
break;
#endif
default:
// Invalid launch
launch_err.SetErrorToGenericError ();
return LLDB_INVALID_PROCESS_ID;
}
lldb::pid_t pid = GetID();
if (pid == LLDB_INVALID_PROCESS_ID)
{
// If we don't have a valid process ID and no one has set the error,
// then return a generic error
if (launch_err.Success())
launch_err.SetErrorToGenericError ();
}
else
{
// Make sure we can get our task port before going any further
Task().GetTaskPortForProcessID (launch_err);
// If that goes well then kick off our exception thread
if (launch_err.Success())
Task().StartExceptionThread(launch_err);
if (launch_err.Success())
{
//m_path = path;
// size_t i;
// if (argv)
// {
// char const *arg;
// for (i=0; (arg = argv[i]) != NULL; i++)
// m_args.push_back(arg);
// }
StartSTDIOThread();
if (launch_type == eLaunchPosixSpawn)
{
errno = 0;
if (::ptrace (PT_ATTACHEXC, pid, 0, 0) == 0)
launch_err.Clear();
else
launch_err.SetErrorToErrno();
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
if (launch_err.Fail() || log)
launch_err.PutToLog(log.get(), "::ptrace (PT_ATTACHEXC, pid = %i, 0, 0 )", pid);
if (launch_err.Success())
m_flags.Set (eFlagsAttached);
else
SetPrivateState (eStateExited);
}
else
{
launch_err.Clear();
}
}
else
{
// We were able to launch the process, but not get its task port
// so now we need to make it sleep with da fishes.
SetID(LLDB_INVALID_PROCESS_ID);
::ptrace (PT_KILL, pid, 0, 0 );
::kill (pid, SIGCONT);
pid = LLDB_INVALID_PROCESS_ID;
}
}
return pid;
}
lldb::pid_t
ProcessMacOSX::PosixSpawnChildForPTraceDebugging
(
const char *path,
char const *argv[],
char const *envp[],
ArchSpec& arch_spec,
const char *stdin_path,
const char *stdout_path,
const char *stderr_path,
ProcessMacOSX* process,
int disable_aslr,
Error &err
)
{
posix_spawnattr_t attr;
short flags;
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS));
Error local_err; // Errors that don't affect the spawning.
if (log)
log->Printf ("%s ( path='%s', argv=%p, envp=%p, process )", __FUNCTION__, path, argv, envp);
err.SetError( ::posix_spawnattr_init (&attr), eErrorTypePOSIX);
if (err.Fail() || log)
err.PutToLog(log.get(), "::posix_spawnattr_init ( &attr )");
if (err.Fail())
return LLDB_INVALID_PROCESS_ID;
flags = POSIX_SPAWN_START_SUSPENDED;
if (disable_aslr)
flags |= _POSIX_SPAWN_DISABLE_ASLR;
err.SetError( ::posix_spawnattr_setflags (&attr, flags), eErrorTypePOSIX);
if (err.Fail() || log)
err.PutToLog(log.get(), "::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED%s )", disable_aslr ? " | _POSIX_SPAWN_DISABLE_ASLR" : "");
if (err.Fail())
return LLDB_INVALID_PROCESS_ID;
#if !defined(__arm__)
// We don't need to do this for ARM, and we really shouldn't now that we
// have multiple CPU subtypes and no posix_spawnattr call that allows us
// to set which CPU subtype to launch...
cpu_type_t cpu = arch_spec.GetMachOCPUType();
if (cpu != 0 && cpu != UINT32_MAX && cpu != LLDB_INVALID_CPUTYPE)
{
size_t ocount = 0;
err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu, &ocount), eErrorTypePOSIX);
if (err.Fail() || log)
err.PutToLog(log.get(), "::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %zu )", cpu, ocount);
if (err.Fail() != 0 || ocount != 1)
return LLDB_INVALID_PROCESS_ID;
}
#endif
lldb_utility::PseudoTerminal pty;
posix_spawn_file_actions_t file_actions;
err.SetError( ::posix_spawn_file_actions_init (&file_actions), eErrorTypePOSIX);
int file_actions_valid = err.Success();
if (!file_actions_valid || log)
err.PutToLog(log.get(), "::posix_spawn_file_actions_init ( &file_actions )");
Error stdio_err;
lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
if (file_actions_valid)
{
// If the user specified any STDIO files, then use those
if (stdin_path || stdout_path || stderr_path)
{
process->SetSTDIOIsOurs(false);
if (stderr_path != NULL && stderr_path[0])
{
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, stderr_path, O_RDWR, 0), eErrorTypePOSIX);
if (stdio_err.Fail() || log)
stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", stderr_path);
}
if (stdin_path != NULL && stdin_path[0])
{
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, stdin_path, O_RDONLY, 0), eErrorTypePOSIX);
if (stdio_err.Fail() || log)
stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", stdin_path);
}
if (stdout_path != NULL && stdout_path[0])
{
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, stdout_path, O_WRONLY, 0), eErrorTypePOSIX);
if (stdio_err.Fail() || log)
stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", stdout_path);
}
}
else
{
// The user did not specify any STDIO files, use a pseudo terminal.
// Callers can then access the file handles using the
// ProcessMacOSX::ReleaseChildFileDescriptors() function, otherwise
// this class will spawn a thread that tracks STDIO and buffers it.
process->SetSTDIOIsOurs(true);
char error_str[1024];
if (pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY, error_str, sizeof(error_str)))
{
const char* slave_name = pty.GetSlaveName(error_str, sizeof(error_str));
if (slave_name == NULL)
slave_name = "/dev/null";
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, slave_name, O_RDWR|O_NOCTTY, 0), eErrorTypePOSIX);
if (stdio_err.Fail() || log)
stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR|O_NOCTTY, mode = 0 )", slave_name);
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, slave_name, O_RDONLY|O_NOCTTY, 0), eErrorTypePOSIX);
if (stdio_err.Fail() || log)
stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY|O_NOCTTY, mode = 0 )", slave_name);
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, slave_name, O_WRONLY|O_NOCTTY, 0), eErrorTypePOSIX);
if (stdio_err.Fail() || log)
stdio_err.PutToLog(log.get(), "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY|O_NOCTTY, mode = 0 )", slave_name);
}
else
{
if (error_str[0])
stdio_err.SetErrorString(error_str);
else
stdio_err.SetErrorString("Unable to open master side of pty for inferior.");
}
}
err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), eErrorTypePOSIX);
if (err.Fail() || log)
err.PutToLog(log.get(), "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, &file_actions, &attr, argv, envp);
if (stdio_err.Success())
{
// If we have a valid process and we created the STDIO file handles,
// then remember them on our process class so we can spawn a STDIO
// thread and close them when we are done with them.
if (process != NULL && process->STDIOIsOurs())
{
int master_fd = pty.ReleaseMasterFileDescriptor ();
process->SetChildFileDescriptors (master_fd, master_fd, master_fd);
}
}
}
else
{
err.SetError( ::posix_spawnp (&pid, path, NULL, &attr, (char * const*)argv, (char * const*)envp), eErrorTypePOSIX);
if (err.Fail() || log)
err.PutToLog(log.get(), "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, NULL, &attr, argv, envp);
}
::posix_spawnattr_destroy (&attr);
// We have seen some cases where posix_spawnp was returning a valid
// looking pid even when an error was returned, so clear it out
if (err.Fail())
pid = LLDB_INVALID_PROCESS_ID;
if (file_actions_valid)
{
local_err.SetError( ::posix_spawn_file_actions_destroy (&file_actions), eErrorTypePOSIX);
if (local_err.Fail() || log)
local_err.PutToLog(log.get(), "::posix_spawn_file_actions_destroy ( &file_actions )");
}
return pid;
}
lldb::pid_t
ProcessMacOSX::ForkChildForPTraceDebugging
(
const char *path,
char const *argv[],
char const *envp[],
ArchSpec& arch_spec,
const char *stdin_path,
const char *stdout_path,
const char *stderr_path,
ProcessMacOSX* process,
Error &launch_err
)
{
lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
if (stdin_path || stdout_path || stderr_path)
{
assert(!"TODO: ForkChildForPTraceDebugging doesn't currently support fork/exec with user file handles...");
}
else
{
// Use a fork that ties the child process's stdin/out/err to a pseudo
// terminal so we can read it in our ProcessMacOSX::STDIOThread
// as unbuffered io.
lldb_utility::PseudoTerminal pty;
char error_str[1024];
pid = pty.Fork(error_str, sizeof(error_str));
if (pid < 0)
{
launch_err.SetErrorString (error_str);
//--------------------------------------------------------------
// Error during fork.
//--------------------------------------------------------------
return pid;
}
else if (pid == 0)
{
//--------------------------------------------------------------
// Child process
//--------------------------------------------------------------
::ptrace (PT_TRACE_ME, 0, 0, 0); // Debug this process
::ptrace (PT_SIGEXC, 0, 0, 0); // Get BSD signals as mach exceptions
// If our parent is setgid, lets make sure we don't inherit those
// extra powers due to nepotism.
::setgid (getgid ());
// Let the child have its own process group. We need to execute
// this call in both the child and parent to avoid a race condition
// between the two processes.
::setpgid (0, 0); // Set the child process group to match its pid
// Sleep a bit to before the exec call
::sleep (1);
// Turn this process into
::execv (path, (char * const *)argv);
// Exit with error code. Child process should have taken
// over in above exec call and if the exec fails it will
// exit the child process below.
::exit (127);
}
else
{
//--------------------------------------------------------------
// Parent process
//--------------------------------------------------------------
// Let the child have its own process group. We need to execute
// this call in both the child and parent to avoid a race condition
// between the two processes.
::setpgid (pid, pid); // Set the child process group to match its pid
if (process != NULL)
{
// Release our master pty file descriptor so the pty class doesn't
// close it and so we can continue to use it in our STDIO thread
int master_fd = pty.ReleaseMasterFileDescriptor ();
process->SetChildFileDescriptors (master_fd, master_fd, master_fd);
}
}
}
return pid;
}
#if defined (__arm__)
lldb::pid_t
ProcessMacOSX::SBLaunchForDebug
(
const char *path,
char const *argv[],
char const *envp[],
ArchSpec& arch_spec,
const char *stdin_path,
const char *stdout_path,
const char *stderr_path,
Error &launch_err
)
{
// Clear out and clean up from any current state
Clear();
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
// Fork a child process for debugging
SetState(eStateLaunching);
m_pid = ProcessMacOSX::SBLaunchForDebug(path, argv, envp, this, launch_err);
if (m_pid != 0)
{
m_flags |= eFlagsUsingSBS;
//m_path = path;
// size_t i;
// char const *arg;
// for (i=0; (arg = argv[i]) != NULL; i++)
// m_args.push_back(arg);
Task().StartExceptionThread();
StartSTDIOThread();
int err = ptrace (PT_ATTACHEXC, m_pid, 0, 0);
if (err == 0)
{
m_flags |= eFlagsAttached;
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "successfully attached to pid %d", m_pid);
}
else
{
SetState (eStateExited);
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
}
}
return m_pid;
}
#include <servers/bootstrap.h>
#include "CFBundle.h"
#include "CFData.h"
#include "CFString.h"
lldb::pid_t
ProcessMacOSX::SBLaunchForDebug
(
const char *app_bundle_path,
char const *argv[],
char const *envp[],
ArchSpec& arch_spec,
const char *stdin_path,
const char *stdout_path,
const char *stderr_path,
ProcessMacOSX* process,
Error &launch_err
)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, process);
CFAllocatorRef alloc = kCFAllocatorDefault;
if (argv[0] == NULL)
return LLDB_INVALID_PROCESS_ID;
size_t argc = 0;
// Count the number of arguments
while (argv[argc] != NULL)
argc++;
// Enumerate the arguments
size_t first_launch_arg_idx = 1;
CFReleaser<CFMutableArrayRef> launch_argv;
if (argv[first_launch_arg_idx])
{
size_t launch_argc = argc > 0 ? argc - 1 : 0;
launch_argv.reset (::CFArrayCreateMutable (alloc, launch_argc, &kCFTypeArrayCallBacks));
size_t i;
char const *arg;
CFString launch_arg;
for (i=first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL); i++)
{
launch_arg.reset(::CFStringCreateWithCString (alloc, arg, kCFStringEncodingUTF8));
if (launch_arg.get() != NULL)
CFArrayAppendValue(launch_argv.get(), launch_arg.get());
else
break;
}
}
// Next fill in the arguments dictionary. Note, the envp array is of the form
// Variable=value but SpringBoard wants a CF dictionary. So we have to convert
// this here.
CFReleaser<CFMutableDictionaryRef> launch_envp;
if (envp[0])
{
launch_envp.reset(::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
const char *value;
int name_len;
CFString name_string, value_string;
for (int i = 0; envp[i] != NULL; i++)
{
value = strstr (envp[i], "=");
// If the name field is empty or there's no =, skip it. Somebody's messing with us.
if (value == NULL || value == envp[i])
continue;
name_len = value - envp[i];
// Now move value over the "="
value++;
name_string.reset(::CFStringCreateWithBytes(alloc, (const UInt8 *) envp[i], name_len, kCFStringEncodingUTF8, false));
value_string.reset(::CFStringCreateWithCString(alloc, value, kCFStringEncodingUTF8));
CFDictionarySetValue (launch_envp.get(), name_string.get(), value_string.get());
}
}
CFString stdout_cf_path;
CFString stderr_cf_path;
PseudoTerminal pty;
if (stdin_path || stdout_path || stderr_path)
{
process->SetSTDIOIsOurs(false);
if (stdout_path)
stdout_cf_path.SetFileSystemRepresentation (stdout_path);
if (stderr_path)
stderr_cf_path.SetFileSystemRepresentation (stderr_path);
}
else
{
process->SetSTDIOIsOurs(true);
PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY);
if (pty_err == PseudoTerminal::success)
{
const char* slave_name = pty.SlaveName();
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() successfully opened master pty, slave is %s", __FUNCTION__, slave_name);
if (slave_name && slave_name[0])
{
::chmod (slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
stdout_cf_path.SetFileSystemRepresentation (slave_name);
stderr_cf_path.(stdout_cf_path);
}
}
}
if (stdout_cf_path.get() == NULL)
stdout_cf_path.SetFileSystemRepresentation ("/dev/null");
if (stderr_cf_path.get() == NULL)
stderr_cf_path.SetFileSystemRepresentation ("/dev/null");
CFBundle bundle(app_bundle_path);
CFStringRef bundleIDCFStr = bundle.GetIdentifier();
std::string bundleID;
if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL)
{
struct stat app_bundle_stat;
if (::stat (app_bundle_path, &app_bundle_stat) < 0)
{
launch_err.SetError(errno, eErrorTypePOSIX);
launch_err.SetErrorStringWithFormat("%s: \"%s\".\n", launch_err.AsString(), app_bundle_path);
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: %s", __FUNCTION__, launch_err.AsCString());
}
else
{
launch_err.SetError(-1, eErrorTypeGeneric);
launch_err.SetErrorStringWithFormat("Failed to extract CFBundleIdentifier from %s.\n", app_bundle_path);
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to extract CFBundleIdentifier from '%s'", __FUNCTION__, app_bundle_path);
}
return LLDB_INVALID_PROCESS_ID;
}
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s", __FUNCTION__, bundleID.c_str());
CFData argv_data(NULL);
if (launch_argv.get())
{
if (argv_data.Serialize(launch_argv.get(), kCFPropertyListBinaryFormat_v1_0) == NULL)
{
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to serialize launch arg array...", __FUNCTION__);
return LLDB_INVALID_PROCESS_ID;
}
}
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() serialized launch arg array", __FUNCTION__);
// Find SpringBoard
SBSApplicationLaunchError sbs_error = 0;
sbs_error = SBSLaunchApplication ( bundleIDCFStr,
(CFURLRef)NULL, // openURL
launch_argv.get(),
launch_envp.get(), // CFDictionaryRef environment
stdout_cf_path.get(),
stderr_cf_path.get(),
SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice);
launch_err.SetError(sbs_error, eErrorTypeSpringBoard);
if (sbs_error == SBSApplicationLaunchErrorSuccess)
{
static const useconds_t pid_poll_interval = 200000;
static const useconds_t pid_poll_timeout = 30000000;
useconds_t pid_poll_total = 0;
lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
Boolean pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
// Poll until the process is running, as long as we are getting valid responses and the timeout hasn't expired
// A return PID of 0 means the process is not running, which may be because it hasn't been (asynchronously) started
// yet, or that it died very quickly (if you weren't using waitForDebugger).
while (!pid_found && pid_poll_total < pid_poll_timeout)
{
usleep (pid_poll_interval);
pid_poll_total += pid_poll_interval;
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() polling Springboard for pid for %s...", __FUNCTION__, bundleID.c_str());
pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
}
if (pid_found)
{
// If we have a valid process and we created the STDIO file handles,
// then remember them on our process class so we can spawn a STDIO
// thread and close them when we are done with them.
if (process != NULL && process->STDIOIsOurs())
{
// Release our master pty file descriptor so the pty class doesn't
// close it and so we can continue to use it in our STDIO thread
int master_fd = pty.ReleaseMasterFD();
process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
}
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid);
}
else
{
LogError("failed to lookup the process ID for CFBundleIdentifier %s.", bundleID.c_str());
}
return pid;
}
LogError("unable to launch the application with CFBundleIdentifier '%s' sbs_error = %u", bundleID.c_str(), sbs_error);
return LLDB_INVALID_PROCESS_ID;
}
#endif // #if defined (__arm__)
#include "MachThreadContext_x86_64.h"
#include "MachThreadContext_i386.h"
#include "MachThreadContext_arm.h"
void
ProcessMacOSX::Initialize()
{
static bool g_initialized = false;
if (g_initialized == false)
{
g_initialized = true;
MachThreadContext_x86_64::Initialize();
MachThreadContext_i386::Initialize();
MachThreadContext_arm::Initialize();
PluginManager::RegisterPlugin (GetPluginNameStatic(),
GetPluginDescriptionStatic(),
CreateInstance);
Log::Callbacks log_callbacks = {
ProcessMacOSXLog::DisableLog,
ProcessMacOSXLog::EnableLog,
ProcessMacOSXLog::ListLogCategories
};
Log::RegisterLogChannel (ProcessMacOSX::GetPluginNameStatic(), log_callbacks);
}
}
uint32_t
ProcessMacOSX::ListProcessesMatchingName (const char *name, lldb_private::StringList &matches, std::vector<lldb::pid_t> &pids)
{
return Host::ListProcessesMatchingName (name, matches, pids);
}