1820 lines
61 KiB
C++
1820 lines
61 KiB
C++
//===-- ProcessMacOSXRemote.cpp ---------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// ProcessMacOSXRemote.cpp
|
|
// liblldb
|
|
//
|
|
// Created by Greg Clayton on 4/21/09.
|
|
//
|
|
//
|
|
//----------------------------------------------------------------------
|
|
|
|
// C Includes
|
|
#include <errno.h>
|
|
|
|
// C++ Includes
|
|
//#include <algorithm>
|
|
//#include <map>
|
|
|
|
// Other libraries and framework includes
|
|
|
|
// Project includes
|
|
#include "ProcessMacOSXRemote.h"
|
|
#include "ProcessMacOSXLog.h"
|
|
#include "ThreadMacOSX.h"
|
|
|
|
Process*
|
|
ProcessMacOSXRemote::CreateInstance (Target &target)
|
|
{
|
|
return new ProcessMacOSXRemote (target);
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::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;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// ProcessMacOSXRemote constructor
|
|
//----------------------------------------------------------------------
|
|
ProcessMacOSXRemote::ProcessMacOSXRemote(Target& target) :
|
|
Process (target),
|
|
m_flags (0),
|
|
m_arch_spec (),
|
|
m_dynamic_loader_ap (),
|
|
m_byte_order(eByteOrderInvalid)
|
|
{
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Destructor
|
|
//----------------------------------------------------------------------
|
|
ProcessMacOSXRemote::~DCProcessMacOSXRemote()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Process Control
|
|
//----------------------------------------------------------------------
|
|
lldb::pid_t
|
|
ProcessMacOSXRemote::DoLaunch
|
|
(
|
|
Module* module,
|
|
char const *argv[],
|
|
char const *envp[],
|
|
const char *stdin_path,
|
|
const char *stdout_path,
|
|
const char *stderr_path
|
|
)
|
|
{
|
|
// ::LogSetBitMask (PD_LOG_DEFAULT);
|
|
// ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD);
|
|
// ::LogSetLogFile ("/dev/stdout");
|
|
|
|
ObjectFile * object_file = module->GetObjectFile();
|
|
if (object_file)
|
|
{
|
|
char exec_file_path[PATH_MAX];
|
|
FileSpec* file_spec_ptr = object_file->GetFileSpec();
|
|
if (file_spec_ptr)
|
|
file_spec_ptr->GetPath(exec_file_path, sizeof(exec_file_path));
|
|
|
|
ArchSpec arch_spec(module->GetArchitecture());
|
|
|
|
switch (arch_spec.GetCPUType())
|
|
{
|
|
|
|
}
|
|
// Set our user ID to our process ID.
|
|
SetID(LaunchForDebug(exec_file_path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, eLaunchDefault, GetError()));
|
|
}
|
|
else
|
|
{
|
|
// Set our user ID to an invalid process ID.
|
|
SetID(LLDB_INVALID_PROCESS_ID);
|
|
GetError().SetErrorToGenericError ();
|
|
GetError().SetErrorStringWithFormat ("Failed to get object file from '%s' for arch %s.\n", module->GetFileSpec().GetFilename().AsCString(), module->GetArchitecture().AsCString());
|
|
}
|
|
|
|
// Return the process ID we have
|
|
return GetID();
|
|
}
|
|
|
|
lldb::pid_t
|
|
ProcessMacOSXRemote::DoAttach (lldb::pid_t attach_pid)
|
|
{
|
|
// Set our user ID to the attached process ID (which can be invalid if
|
|
// the attach fails
|
|
lldb::pid_t pid = AttachForDebug(attach_pid);
|
|
SetID(pid);
|
|
|
|
// if (pid != LLDB_INVALID_PROCESS_ID)
|
|
// {
|
|
// // Wait for a process stopped event, but don't consume it
|
|
// if (WaitForEvents(LLDB_EVENT_STOPPED, NULL, 30))
|
|
// {
|
|
// }
|
|
// }
|
|
//
|
|
// Return the process ID we have
|
|
return pid;
|
|
}
|
|
|
|
|
|
void
|
|
ProcessMacOSXRemote::DidLaunch ()
|
|
{
|
|
if (GetID() == LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
m_dynamic_loader_ap.reset();
|
|
}
|
|
else
|
|
{
|
|
Module * exe_module = GetTarget().GetExecutableModule ().get();
|
|
assert(exe_module);
|
|
ObjectFile *exe_objfile = exe_module->GetObjectFile();
|
|
assert(exe_objfile);
|
|
m_byte_order = exe_objfile->GetByteOrder();
|
|
assert(m_byte_order != eByteOrderInvalid);
|
|
// Install a signal handler so we can catch when our child process
|
|
// dies and set the exit status correctly.
|
|
m_wait_thread = Host::ThreadCreate (ProcessMacOSXRemote::WaitForChildProcessToExit, &m_uid, &m_error);
|
|
if (m_wait_thread != LLDB_INVALID_HOST_THREAD)
|
|
{
|
|
// Don't need to get the return value of this thread, so just let
|
|
// it clean up after itself when it dies.
|
|
Host::ThreadDetach (m_wait_thread, NULL);
|
|
}
|
|
m_dynamic_loader_ap.reset(DynamicLoader::FindPlugin(this, "macosx-dyld"));
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
ProcessMacOSXRemote::DidAttach ()
|
|
{
|
|
DidLaunch ();
|
|
m_need_to_run_did_attach = true;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::DoResume ()
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::Resume()");
|
|
State state = GetState();
|
|
|
|
if (CanResume(state))
|
|
{
|
|
PrivateResume(LLDB_INVALID_THREAD_ID);
|
|
}
|
|
else if (state == eStateRunning)
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x is running, ignoring...", m_task.TaskPort());
|
|
GetError().Clear();
|
|
|
|
}
|
|
else
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x can't continue, ignoring...", m_task.TaskPort());
|
|
GetError().SetError(UINT_MAX, Error::Generic);
|
|
}
|
|
|
|
return GetError().Success();
|
|
}
|
|
|
|
size_t
|
|
ProcessMacOSXRemote::GetSoftwareBreakpointTrapOpcode (BreakpointSite *bp_site)
|
|
{
|
|
ModuleSP exe_module_sp(GetTarget().GetExecutableModule());
|
|
if (exe_module_sp.get())
|
|
{
|
|
const ArchSpec &exe_arch = exe_module_sp->GetArchitecture();
|
|
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 };
|
|
|
|
switch (exe_arch.GetCPUType())
|
|
{
|
|
case CPU_TYPE_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 CPU_TYPE_POWERPC:
|
|
case CPU_TYPE_POWERPC64:
|
|
trap_opcode = g_ppc_breakpoint_opcode;
|
|
trap_opcode_size = sizeof(g_ppc_breakpoint_opcode);
|
|
break;
|
|
|
|
case CPU_TYPE_I386:
|
|
case CPU_TYPE_X86_64:
|
|
trap_opcode = g_i386_breakpoint_opcode;
|
|
trap_opcode_size = sizeof(g_i386_breakpoint_opcode);
|
|
break;
|
|
|
|
default:
|
|
assert(!"Unhandled architecture in ProcessMacOSXRemote::GetSoftwareBreakpointTrapOpcode()");
|
|
return 0;
|
|
}
|
|
|
|
if (trap_opcode && trap_opcode_size)
|
|
{
|
|
if (bp_loc->SetTrapOpcode(trap_opcode, trap_opcode_size))
|
|
return trap_opcode_size;
|
|
}
|
|
}
|
|
// No executable yet, so we can't tell what the breakpoint opcode will be.
|
|
return 0;
|
|
}
|
|
uint32_t
|
|
ProcessMacOSXRemote::UpdateThreadListIfNeeded ()
|
|
{
|
|
// locker will keep a mutex locked until it goes out of scope
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD);
|
|
if (log && log->GetMask().IsSet(PD_LOG_VERBOSE))
|
|
log->Printf ("ProcessMacOSXRemote::%s (pid = %4.4x)", __FUNCTION__, GetID());
|
|
|
|
const uint32_t stop_id = GetStopID();
|
|
if (m_thread_list.GetSize() == 0 || stop_id != m_thread_list.GetID())
|
|
{
|
|
m_thread_list.SetID (stop_id);
|
|
thread_array_t thread_list = NULL;
|
|
mach_msg_type_number_t thread_list_count = 0;
|
|
task_t task = Task().TaskPort();
|
|
Error err(::task_threads (task, &thread_list, &thread_list_count), Error::MachKernel);
|
|
|
|
if (log || err.Fail())
|
|
err.Log(log, "::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;
|
|
|
|
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(m_thread_list.FindThreadByID (tid));
|
|
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 m_thread_list.GetSize();
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::ShouldStop ()
|
|
{
|
|
// If we are attaching, let our dynamic loader plug-in know so it can get
|
|
// an initial list of shared libraries.
|
|
if (m_need_to_run_did_attach && m_dynamic_loader_ap.get())
|
|
{
|
|
m_need_to_run_did_attach = false;
|
|
m_dynamic_loader_ap->DidAttach();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
// Let all threads recover from stopping and do any clean up based
|
|
// on the previous thread state (if any).
|
|
UpdateThreadListIfNeeded ();
|
|
|
|
if (m_thread_list.ShouldStop())
|
|
{
|
|
// Let each thread know of any exceptions
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS);
|
|
task_t task = m_task.TaskPort();
|
|
size_t i;
|
|
for (i=0; i<m_exception_messages.size(); ++i)
|
|
{
|
|
// Let the thread list figure use the ProcessMacOSXRemote 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].Log(log);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::DoHalt ()
|
|
{
|
|
return Kill (SIGINT);
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::WillDetach ()
|
|
{
|
|
State state = GetState();
|
|
|
|
if (IsRunning(state))
|
|
{
|
|
m_error.SetErrorToGenericError();
|
|
m_error.SetErrorString("Process must be stopped in order to detach.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::DoDetach ()
|
|
{
|
|
m_use_public_queue = false;
|
|
bool success = Detach();
|
|
m_use_public_queue = true;
|
|
if (success)
|
|
SetState (eStateDetached);
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::DoKill (int signal)
|
|
{
|
|
return Kill (signal);
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------
|
|
// Thread Queries
|
|
//------------------------------------------------------------------
|
|
|
|
Thread *
|
|
ProcessMacOSXRemote::GetCurrentThread ()
|
|
{
|
|
return m_thread_list.GetCurrentThread().get();
|
|
}
|
|
|
|
ByteOrder
|
|
ProcessMacOSXRemote::GetByteOrder () const
|
|
{
|
|
return m_byte_order;
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------
|
|
// Process Queries
|
|
//------------------------------------------------------------------
|
|
|
|
bool
|
|
ProcessMacOSXRemote::IsAlive ()
|
|
{
|
|
return MachTask::IsValid (Task().TaskPort());
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::IsRunning ()
|
|
{
|
|
return LLDB_STATE_IS_RUNNING(GetState());
|
|
}
|
|
|
|
lldb::addr_t
|
|
ProcessMacOSXRemote::GetImageInfoAddress()
|
|
{
|
|
return Task().GetDYLDAllImageInfosAddress();
|
|
}
|
|
|
|
DynamicLoader *
|
|
ProcessMacOSXRemote::GetDynamicLoader()
|
|
{
|
|
return m_dynamic_loader_ap.get();
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Process Memory
|
|
//------------------------------------------------------------------
|
|
|
|
size_t
|
|
ProcessMacOSXRemote::DoReadMemory (lldb::addr_t addr, void *buf, size_t size)
|
|
{
|
|
return Task().ReadMemory(addr, buf, size);
|
|
}
|
|
|
|
size_t
|
|
ProcessMacOSXRemote::DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size)
|
|
{
|
|
return Task().WriteMemory(addr, buf, size);
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Process STDIO
|
|
//------------------------------------------------------------------
|
|
|
|
size_t
|
|
ProcessMacOSXRemote::GetSTDOUT (char *buf, size_t buf_size)
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size);
|
|
Mutex::Locker locker(m_stdio_mutex);
|
|
size_t bytes_available = m_stdout_data.size();
|
|
if (bytes_available > 0)
|
|
{
|
|
if (bytes_available > buf_size)
|
|
{
|
|
memcpy(buf, m_stdout_data.data(), buf_size);
|
|
m_stdout_data.erase(0, buf_size);
|
|
bytes_available = buf_size;
|
|
}
|
|
else
|
|
{
|
|
memcpy(buf, m_stdout_data.data(), bytes_available);
|
|
m_stdout_data.clear();
|
|
}
|
|
}
|
|
return bytes_available;
|
|
}
|
|
|
|
size_t
|
|
ProcessMacOSXRemote::GetSTDERR (char *buf, size_t buf_size)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::EnableBreakpoint (BreakpointLocation *bp)
|
|
{
|
|
assert (bp != NULL);
|
|
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS);
|
|
lldb::user_id_t breakID = bp->GetID();
|
|
lldb::addr_t addr = bp->GetAddress();
|
|
if (bp->IsEnabled())
|
|
{
|
|
if (log)
|
|
log->Printf("ProcessMacOSXRemote::EnableBreakpoint ( breakID = %d ) breakpoint already enabled.", breakID);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (bp->HardwarePreferred())
|
|
{
|
|
ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp->GetThreadID()).get();
|
|
if (thread)
|
|
{
|
|
bp->SetHardwareIndex (thread->EnableHardwareBreakpoint(bp));
|
|
if (bp->IsHardware())
|
|
{
|
|
bp->SetEnabled(true);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const size_t break_op_size = GetSoftwareBreakpointTrapOpcode (bp);
|
|
assert (break_op_size > 0);
|
|
const uint8_t * const break_op = bp->GetTrapOpcodeBytes();
|
|
|
|
if (break_op_size > 0)
|
|
{
|
|
// Save the original opcode by reading it
|
|
if (m_task.ReadMemory(addr, bp->GetSavedOpcodeBytes(), break_op_size) == break_op_size)
|
|
{
|
|
// Write a software breakpoint in place of the original opcode
|
|
if (m_task.WriteMemory(addr, break_op, break_op_size) == break_op_size)
|
|
{
|
|
uint8_t verify_break_op[4];
|
|
if (m_task.ReadMemory(addr, verify_break_op, break_op_size) == break_op_size)
|
|
{
|
|
if (memcmp(break_op, verify_break_op, break_op_size) == 0)
|
|
{
|
|
bp->SetEnabled(true);
|
|
if (log)
|
|
log->Printf("ProcessMacOSXRemote::EnableBreakpoint ( breakID = %d ) SUCCESS.", breakID, (uint64_t)addr);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Failed to verify the breakpoint trap in memory.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Unable to read memory to verify breakpoint trap.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Unable to write breakpoint trap to memory.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Unable to read memory at breakpoint address.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (log)
|
|
{
|
|
const char *err_string = GetError().AsCString();
|
|
log->Printf ("ProcessMacOSXRemote::EnableBreakpoint ( breakID = %d ) error: %s",
|
|
breakID, err_string ? err_string : "NULL");
|
|
}
|
|
GetError().SetErrorToGenericError();
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::DisableBreakpoint (BreakpointLocation *bp)
|
|
{
|
|
assert (bp != NULL);
|
|
lldb::addr_t addr = bp->GetAddress();
|
|
lldb::user_id_t breakID = bp->GetID();
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) addr = 0x%8.8llx", breakID, (uint64_t)addr);
|
|
|
|
if (bp->IsHardware())
|
|
{
|
|
ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp->GetThreadID()).get();
|
|
if (thread)
|
|
{
|
|
if (thread->DisableHardwareBreakpoint(bp))
|
|
{
|
|
bp->SetEnabled(false);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) (hardware) => success", breakID);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const size_t break_op_size = bp->GetByteSize();
|
|
assert (break_op_size > 0);
|
|
const uint8_t * const break_op = bp->GetTrapOpcodeBytes();
|
|
if (break_op_size > 0)
|
|
{
|
|
// Clear a software breakoint instruction
|
|
uint8_t curr_break_op[break_op_size];
|
|
bool break_op_found = false;
|
|
|
|
// Read the breakpoint opcode
|
|
if (m_task.ReadMemory(addr, curr_break_op, break_op_size) == break_op_size)
|
|
{
|
|
bool verify = false;
|
|
if (bp->IsEnabled())
|
|
{
|
|
// Make sure we have the a breakpoint opcode exists at this address
|
|
if (memcmp(curr_break_op, break_op, break_op_size) == 0)
|
|
{
|
|
break_op_found = true;
|
|
// We found a valid breakpoint opcode at this address, now restore
|
|
// the saved opcode.
|
|
if (m_task.WriteMemory(addr, bp->GetSavedOpcodeBytes(), break_op_size) == break_op_size)
|
|
{
|
|
verify = true;
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Memory write failed when restoring original opcode.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Original breakpoint trap is no longer in memory.");
|
|
// Set verify to true and so we can check if the original opcode has already been restored
|
|
verify = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) is already disabled", breakID);
|
|
// Set verify to true and so we can check if the original opcode is there
|
|
verify = true;
|
|
}
|
|
|
|
if (verify)
|
|
{
|
|
uint8_t verify_opcode[break_op_size];
|
|
// Verify that our original opcode made it back to the inferior
|
|
if (m_task.ReadMemory(addr, verify_opcode, break_op_size) == break_op_size)
|
|
{
|
|
// compare the memory we just read with the original opcode
|
|
if (memcmp(bp->GetSavedOpcodeBytes(), verify_opcode, break_op_size) == 0)
|
|
{
|
|
// SUCCESS
|
|
bp->SetEnabled(false);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) SUCCESS", breakID);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (break_op_found)
|
|
GetError().SetErrorString("Failed to restore original opcode.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Failed to read memory to verify that breakpoint trap was restored.");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Unable to read memory that should contain the breakpoint trap.");
|
|
}
|
|
}
|
|
|
|
GetError().SetErrorToGenericError();
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::EnableWatchpoint (WatchpointLocation *wp)
|
|
{
|
|
if (wp)
|
|
{
|
|
lldb::user_id_t watchID = wp->GetID();
|
|
lldb::addr_t addr = wp->GetAddress();
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::EnableWatchpoint(watchID = %d)", watchID);
|
|
if (wp->IsEnabled())
|
|
{
|
|
if (log)
|
|
log->Printf("ProcessMacOSXRemote::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(wp->GetThreadID()).get();
|
|
if (thread)
|
|
{
|
|
wp->SetHardwareIndex (thread->EnableHardwareWatchpoint (wp));
|
|
if (wp->IsHardware ())
|
|
{
|
|
wp->SetEnabled(true);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Watchpoints currently only support thread specific watchpoints.");
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::DisableWatchpoint (WatchpointLocation *wp)
|
|
{
|
|
if (wp)
|
|
{
|
|
lldb::user_id_t watchID = wp->GetID();
|
|
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS);
|
|
|
|
lldb::addr_t addr = wp->GetAddress();
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::DisableWatchpoint (watchID = %d) addr = 0x%8.8llx", watchID, (uint64_t)addr);
|
|
|
|
if (wp->IsHardware())
|
|
{
|
|
ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(wp->GetThreadID()).get();
|
|
if (thread)
|
|
{
|
|
if (thread->DisableHardwareWatchpoint (wp))
|
|
{
|
|
wp->SetEnabled(false);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::Disablewatchpoint (watchID = %d) addr = 0x%8.8llx (hardware) => success", watchID, (uint64_t)addr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// TODO: clear software watchpoints if we implement them
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorString("Watchpoint location argument was NULL.");
|
|
}
|
|
GetError().SetErrorToGenericError();
|
|
return false;
|
|
}
|
|
|
|
|
|
static ProcessMacOSXRemote::CreateArchCalback
|
|
ArchDCScriptInterpreter::TypeMap(const ArchSpec& arch_spec, ProcessMacOSXRemote::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, ProcessMacOSXRemote::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
|
|
ProcessMacOSXRemote::AddArchCreateDCScriptInterpreter::Type(const ArchSpec& arch_spec, CreateArchCalback callback)
|
|
{
|
|
ArchDCScriptInterpreter::TypeMap (arch_spec, callback, true);
|
|
}
|
|
|
|
ProcessMacOSXRemote::CreateArchCalback
|
|
ProcessMacOSXRemote::GetArchCreateDCScriptInterpreter::Type()
|
|
{
|
|
return ArchDCScriptInterpreter::TypeMap (m_arch_spec, NULL, false);
|
|
}
|
|
|
|
void
|
|
ProcessMacOSXRemote::Clear()
|
|
{
|
|
// Clear any cached thread list while the pid and task are still valid
|
|
|
|
m_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();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
bool
|
|
ProcessMacOSXRemote::Kill (int signal)
|
|
{
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::Kill(signal = %d)", signal);
|
|
State state = GetState();
|
|
|
|
if (IsRunning(state))
|
|
{
|
|
if (::kill (GetID(), signal) == 0)
|
|
{
|
|
GetError().Clear();
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorToErrno();
|
|
GetError().LogIfError(log, "ProcessMacOSXRemote::Kill(%d)", signal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::Kill(signal = %d) pid %u (task = 0x%4.4x) was't running, ignoring...", signal, GetID(), m_task.TaskPort());
|
|
GetError().Clear();
|
|
}
|
|
return GetError().Success();
|
|
|
|
}
|
|
|
|
|
|
bool
|
|
ProcessMacOSXRemote::Detach()
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::Detach()");
|
|
|
|
State state = GetState();
|
|
|
|
if (!IsRunning(state))
|
|
{
|
|
// Resume our process
|
|
PrivateResume(LLDB_INVALID_THREAD_ID);
|
|
|
|
// We have resumed and now we wait for that event to get posted
|
|
Event event;
|
|
if (WaitForPrivateEvents(LLDB_EVENT_RUNNING, &event, 2) == false)
|
|
return false;
|
|
|
|
|
|
// We need to be stopped in order to be able to detach, so we need
|
|
// to send ourselves a SIGSTOP
|
|
if (Kill(SIGSTOP))
|
|
{
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS);
|
|
|
|
lldb::pid_t pid = GetID();
|
|
// Wait for our process stop event to get posted
|
|
if (WaitForPrivateEvents(LLDB_EVENT_STOPPED, &event, 2) == false)
|
|
{
|
|
GetError().Log(log, "::kill (pid = %u, SIGSTOP)", pid);
|
|
return false;
|
|
}
|
|
|
|
// Shut down the exception thread and cleanup our exception remappings
|
|
m_task.ShutDownExceptionThread();
|
|
|
|
// Detach from our process while we are stopped.
|
|
errno = 0;
|
|
|
|
// Detach from our process
|
|
::ptrace (PT_DETACH, pid, (caddr_t)1, 0);
|
|
|
|
GetError().SetErrorToErrno();
|
|
|
|
if (log || GetError().Fail())
|
|
GetError().Log(log, "::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid);
|
|
|
|
// Resume our task
|
|
m_task.Resume();
|
|
|
|
// NULL our task out as we have already retored all exception ports
|
|
m_task.Clear();
|
|
|
|
// Clear out any notion of the process we once were
|
|
Clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::Detach() error: process must be stopped (SIGINT the process first).");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ProcessMacOSXRemote::ReplyToAllExceptions()
|
|
{
|
|
Mutex::Locker locker(m_exception_messages_mutex);
|
|
if (m_exception_messages.empty() == false)
|
|
{
|
|
Log *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)
|
|
{
|
|
if (log)
|
|
log->Printf ("Replying to exception %d...", std::distance(begin, pos));
|
|
int resume_signal = 0;
|
|
ThreadSP thread_sp = m_thread_list.FindThreadByID(pos->state.thread_port);
|
|
if (thread_sp.get())
|
|
resume_signal = thread_sp->GetResumeSignal();
|
|
GetError() = pos->Reply (Task().TaskPort(), GetID(), resume_signal);
|
|
GetError().LogIfError(log, "Error replying to exception");
|
|
}
|
|
|
|
// Erase all exception message as we should have used and replied
|
|
// to them all already.
|
|
m_exception_messages.clear();
|
|
}
|
|
}
|
|
void
|
|
ProcessMacOSXRemote::PrivateResume (lldb::tid_t tid)
|
|
{
|
|
Mutex::Locker locker(m_exception_messages_mutex);
|
|
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
|
|
SetState(eStateRunning);
|
|
|
|
// Now resume our task.
|
|
GetError() = m_task.Resume();
|
|
|
|
}
|
|
|
|
// 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
|
|
ProcessMacOSXRemote::ExceptionMessageReceived (const MachException::Message& exceptionMessage)
|
|
{
|
|
Mutex::Locker locker(m_exception_messages_mutex);
|
|
|
|
if (m_exception_messages.empty())
|
|
m_task.Suspend();
|
|
|
|
ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "ProcessMacOSXRemote::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
|
|
//ProcessMacOSXRemote::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
|
|
ProcessMacOSXRemote::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())
|
|
{
|
|
SetState (eStateStopped);
|
|
}
|
|
else
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s empty exception messages bundle.", __PRETTY_FUNCTION__, m_exception_messages.size());
|
|
}
|
|
}
|
|
|
|
bool
|
|
ProcessMacOSXRemote::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
|
|
ProcessMacOSXRemote::AppendSTDOUT (char* s, size_t len)
|
|
{
|
|
ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::%s (<%d> %s) ...", __FUNCTION__, len, s);
|
|
Mutex::Locker locker(m_stdio_mutex);
|
|
m_stdout_data.append(s, len);
|
|
AppendEvent (LLDB_EVENT_STDIO);
|
|
}
|
|
|
|
void *
|
|
ProcessMacOSXRemote::STDIOThread(void *arg)
|
|
{
|
|
ProcessMacOSXRemote *proc = (ProcessMacOSXRemote*) arg;
|
|
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("ProcessMacOSXRemote::%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 avaiable. 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);
|
|
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, Error::POSIX);
|
|
err.LogIfError(log, "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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (log)
|
|
log->Printf("ProcessMacOSXRemote::%s (%p): thread exiting...", __FUNCTION__, arg);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
lldb::pid_t
|
|
ProcessMacOSXRemote::AttachForDebug (lldb::pid_t pid)
|
|
{
|
|
// Clear out and clean up from any current state
|
|
Clear();
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
|
|
if (pid != 0)
|
|
{
|
|
SetState(eStateAttaching);
|
|
SetID(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
|
|
m_task.StartExceptionThread(GetError());
|
|
|
|
if (GetError().Success())
|
|
{
|
|
if (ptrace (PT_ATTACHEXC, pid, 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", pid);
|
|
return GetID();
|
|
}
|
|
else
|
|
{
|
|
GetError().SetErrorToErrno();
|
|
if (log)
|
|
log->Printf ("error: failed to attach to pid %d", pid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetError().Log(log, "ProcessMacOSXRemote::%s (pid = %i) failed to start exception thread", __FUNCTION__, pid);
|
|
}
|
|
}
|
|
return LLDB_INVALID_PROCESS_ID;
|
|
}
|
|
|
|
lldb::pid_t
|
|
ProcessMacOSXRemote::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,
|
|
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;
|
|
|
|
Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("%s( path = '%s', argv = %p, envp = %p, launch_type = %u )", __FUNCTION__, path, argv, envp, launch_type);
|
|
|
|
// Fork a child process for debugging
|
|
SetState(eStateLaunching);
|
|
switch (launch_type)
|
|
{
|
|
case eLaunchForkExec:
|
|
SetID(ProcessMacOSXRemote::ForkChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err));
|
|
break;
|
|
|
|
case eLaunchPosixSpawn:
|
|
SetID(ProcessMacOSXRemote::PosixSpawnChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, 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
|
|
m_task.TaskPortForProcessID (launch_err);
|
|
|
|
// If that goes well then kick off our exception thread
|
|
if (launch_err.Success())
|
|
m_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)
|
|
{
|
|
|
|
//SetState (eStateAttaching);
|
|
errno = 0;
|
|
if (::ptrace (PT_ATTACHEXC, pid, 0, 0) == 0)
|
|
launch_err.Clear();
|
|
else
|
|
launch_err.SetErrorToErrno();
|
|
|
|
if (launch_err.Fail() || log)
|
|
launch_err.Log(log, "::ptrace (PT_ATTACHEXC, pid = %i, 0, 0 )", pid);
|
|
|
|
if (launch_err.Success())
|
|
m_flags.Set (eFlagsAttached);
|
|
else
|
|
SetState (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);
|
|
::kill (pid, SIGCONT);
|
|
::kill (pid, SIGKILL);
|
|
pid = LLDB_INVALID_PROCESS_ID;
|
|
}
|
|
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
lldb::pid_t
|
|
ProcessMacOSXRemote::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,
|
|
ProcessMacOSXRemote* process,
|
|
Error &err
|
|
)
|
|
{
|
|
posix_spawnattr_t attr;
|
|
|
|
Log *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), Error::POSIX);
|
|
if (err.Fail() || log)
|
|
err.Log(log, "::posix_spawnattr_init ( &attr )");
|
|
if (err.Fail())
|
|
return LLDB_INVALID_PROCESS_ID;
|
|
|
|
err.SetError( ::posix_spawnattr_setflags (&attr, POSIX_SPAWN_START_SUSPENDED), Error::POSIX);
|
|
if (err.Fail() || log)
|
|
err.Log(log, "::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED )");
|
|
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.GetCPUType();
|
|
if (cpu != 0 && cpu != CPU_TYPE_ANY && cpu != LLDB_INVALID_CPUTYPE)
|
|
{
|
|
size_t ocount = 0;
|
|
err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu, &ocount), Error::POSIX);
|
|
if (err.Fail() || log)
|
|
err.Log(log, "::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
|
|
|
|
PseudoTerminal pty;
|
|
|
|
posix_spawn_file_actions_t file_actions;
|
|
err.SetError( ::posix_spawn_file_actions_init (&file_actions), Error::POSIX);
|
|
int file_actions_valid = err.Success();
|
|
if (!file_actions_valid || log)
|
|
err.Log(log, "::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), Error::POSIX);
|
|
if (stdio_err.Fail() || log)
|
|
stdio_err.Log(log, "::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), Error::POSIX);
|
|
if (stdio_err.Fail() || log)
|
|
stdio_err.Log(log, "::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), Error::POSIX);
|
|
if (stdio_err.Fail() || log)
|
|
stdio_err.Log(log, "::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
|
|
// ProcessMacOSXRemote::ReleaseChildFileDescriptors() function, otherwise
|
|
// this class will spawn a thread that tracks STDIO and buffers it.
|
|
process->SetSTDIOIsOurs(true);
|
|
if (pty.OpenFirstAvailableMaster(O_RDWR, &stdio_err))
|
|
{
|
|
const char* slave_name = pty.GetSlaveName(&stdio_err);
|
|
if (slave_name == NULL)
|
|
slave_name = "/dev/null";
|
|
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, slave_name, O_RDWR, 0), Error::POSIX);
|
|
if (stdio_err.Fail() || log)
|
|
stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", slave_name);
|
|
|
|
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, slave_name, O_RDONLY, 0), Error::POSIX);
|
|
if (stdio_err.Fail() || log)
|
|
stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", slave_name);
|
|
|
|
stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, slave_name, O_WRONLY, 0), Error::POSIX);
|
|
if (stdio_err.Fail() || log)
|
|
stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", slave_name);
|
|
}
|
|
}
|
|
err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), Error::POSIX);
|
|
if (err.Fail() || log)
|
|
err.Log(log, "::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), Error::POSIX);
|
|
if (err.Fail() || log)
|
|
err.Log(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, NULL, &attr, argv, envp);
|
|
}
|
|
|
|
// 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), Error::POSIX);
|
|
if (local_err.Fail() || log)
|
|
local_err.Log(log, "::posix_spawn_file_actions_destroy ( &file_actions )");
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
lldb::pid_t
|
|
ProcessMacOSXRemote::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,
|
|
ProcessMacOSXRemote* 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 ProcessMacOSXRemote::STDIOThread
|
|
// as unbuffered io.
|
|
PseudoTerminal pty;
|
|
pid = pty.Fork(&launch_err);
|
|
|
|
if (pid < 0)
|
|
{
|
|
//--------------------------------------------------------------
|
|
// 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
|
|
ProcessMacOSXRemote::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 = ProcessMacOSXRemote::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);
|
|
m_task.StartExceptionThread();
|
|
StartSTDIOThread();
|
|
SetState (eStateAttaching);
|
|
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
|
|
ProcessMacOSXRemote::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,
|
|
ProcessMacOSXRemote* 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);
|
|
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, Error::POSIX);
|
|
launch_err.SetErrorStringWithFormat ("%s: \"%s\".\n", launch_err.AsString(), app_bundle_path);
|
|
}
|
|
else
|
|
{
|
|
launch_err.SetError(-1, Error::Generic);
|
|
launch_err.SetErrorStringWithFormat ("Failed to extract CFBundleIdentifier from %s.\n", 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, Error::SpringBoard);
|
|
|
|
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__)
|
|
|