2010-06-09 00:52:24 +08:00
|
|
|
//===-- DNB.cpp -------------------------------------------------*- C++ -*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// Created by Greg Clayton on 3/23/07.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "DNB.h"
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <map>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "MacOSX/MachProcess.h"
|
|
|
|
#include "MacOSX/MachTask.h"
|
|
|
|
#include "CFString.h"
|
|
|
|
#include "DNBLog.h"
|
|
|
|
#include "DNBDataRef.h"
|
|
|
|
#include "DNBThreadResumeActions.h"
|
|
|
|
#include "DNBTimer.h"
|
|
|
|
|
|
|
|
typedef std::tr1::shared_ptr<MachProcess> MachProcessSP;
|
|
|
|
typedef std::map<nub_process_t, MachProcessSP> ProcessMap;
|
|
|
|
typedef ProcessMap::iterator ProcessMapIter;
|
|
|
|
typedef ProcessMap::const_iterator ProcessMapConstIter;
|
|
|
|
|
|
|
|
static size_t GetAllInfos (std::vector<struct kinfo_proc>& proc_infos);
|
|
|
|
static size_t GetAllInfosMatchingName (const char *process_name, std::vector<struct kinfo_proc>& matching_proc_infos);
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// A Thread safe singleton to get a process map pointer.
|
|
|
|
//
|
|
|
|
// Returns a pointer to the existing process map, or a pointer to a
|
|
|
|
// newly created process map if CAN_CREATE is non-zero.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
static ProcessMap*
|
|
|
|
GetProcessMap(bool can_create)
|
|
|
|
{
|
|
|
|
static ProcessMap* g_process_map_ptr = NULL;
|
|
|
|
|
|
|
|
if (can_create && g_process_map_ptr == NULL)
|
|
|
|
{
|
|
|
|
static pthread_mutex_t g_process_map_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
PTHREAD_MUTEX_LOCKER (locker, &g_process_map_mutex);
|
|
|
|
if (g_process_map_ptr == NULL)
|
|
|
|
g_process_map_ptr = new ProcessMap;
|
|
|
|
}
|
|
|
|
return g_process_map_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Add PID to the shared process pointer map.
|
|
|
|
//
|
|
|
|
// Return non-zero value if we succeed in adding the process to the map.
|
|
|
|
// The only time this should fail is if we run out of memory and can't
|
|
|
|
// allocate a ProcessMap.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
static nub_bool_t
|
|
|
|
AddProcessToMap (nub_process_t pid, MachProcessSP& procSP)
|
|
|
|
{
|
|
|
|
ProcessMap* process_map = GetProcessMap(true);
|
|
|
|
if (process_map)
|
|
|
|
{
|
|
|
|
process_map->insert(std::make_pair(pid, procSP));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Remove the shared pointer for PID from the process map.
|
|
|
|
//
|
|
|
|
// Returns the number of items removed from the process map.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
static size_t
|
|
|
|
RemoveProcessFromMap (nub_process_t pid)
|
|
|
|
{
|
|
|
|
ProcessMap* process_map = GetProcessMap(false);
|
|
|
|
if (process_map)
|
|
|
|
{
|
|
|
|
return process_map->erase(pid);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Get the shared pointer for PID from the existing process map.
|
|
|
|
//
|
|
|
|
// Returns true if we successfully find a shared pointer to a
|
|
|
|
// MachProcess object.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
static nub_bool_t
|
|
|
|
GetProcessSP (nub_process_t pid, MachProcessSP& procSP)
|
|
|
|
{
|
|
|
|
ProcessMap* process_map = GetProcessMap(false);
|
|
|
|
if (process_map != NULL)
|
|
|
|
{
|
|
|
|
ProcessMapIter pos = process_map->find(pid);
|
|
|
|
if (pos != process_map->end())
|
|
|
|
{
|
|
|
|
procSP = pos->second;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
procSP.reset();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void *
|
|
|
|
waitpid_thread (void *arg)
|
|
|
|
{
|
|
|
|
const pid_t pid = (pid_t)(intptr_t)arg;
|
|
|
|
int status;
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
pid_t child_pid = waitpid(pid, &status, 0);
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "waitpid_process_thread (): waitpid (pid = %i, &status, 0) => %i, status = %i, errno = %i", pid, child_pid, status, errno);
|
|
|
|
|
|
|
|
if (child_pid < 0)
|
|
|
|
{
|
|
|
|
if (errno == EINTR)
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (WIFSTOPPED(status))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else// if (WIFEXITED(status) || WIFSIGNALED(status))
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "waitpid_process_thread (): setting exit status for pid = %i to %i", child_pid, status);
|
|
|
|
DNBProcessSetExitStatus (child_pid, status);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should never exit as long as our child process is alive, so if we
|
|
|
|
// do something else went wrong and we should exit...
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "waitpid_process_thread (): main loop exited, setting exit status to an invalid value (-1) for pid %i", pid);
|
|
|
|
DNBProcessSetExitStatus (pid, -1);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
spawn_waitpid_thread (pid_t pid)
|
|
|
|
{
|
|
|
|
pthread_t thread = THREAD_NULL;
|
|
|
|
::pthread_create (&thread, NULL, waitpid_thread, (void *)(intptr_t)pid);
|
|
|
|
if (thread != THREAD_NULL)
|
|
|
|
{
|
|
|
|
::pthread_detach (thread);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_process_t
|
|
|
|
DNBProcessLaunch (const char *path,
|
|
|
|
char const *argv[],
|
|
|
|
const char *envp[],
|
2011-01-23 07:43:18 +08:00
|
|
|
const char *working_directory, // NULL => dont' change, non-NULL => set working directory for inferior to this
|
|
|
|
const char *stdin_path,
|
|
|
|
const char *stdout_path,
|
|
|
|
const char *stderr_path,
|
2010-12-04 02:46:09 +08:00
|
|
|
bool no_stdio,
|
2010-06-09 00:52:24 +08:00
|
|
|
nub_launch_flavor_t launch_flavor,
|
2010-09-01 02:35:14 +08:00
|
|
|
int disable_aslr,
|
2010-06-09 00:52:24 +08:00
|
|
|
char *err_str,
|
|
|
|
size_t err_len)
|
|
|
|
{
|
Added a new variant of SBTarget::Launch() that deprectates the old one that
takes separate file handles for stdin, stdout, and stder and also allows for
the working directory to be specified.
Added support to "process launch" to a new option: --working-dir=PATH. We
can now set the working directory. If this is not set, it defaults to that
of the process that has LLDB loaded. Added the working directory to the
host LaunchInNewTerminal function to allows the current working directory
to be set in processes that are spawned in their own terminal. Also hooked this
up to the lldb_private::Process and all mac plug-ins. The linux plug-in had its
API changed, but nothing is making use of it yet. Modfied "debugserver" and
"darwin-debug" to also handle the current working directory options and modified
the code in LLDB that spawns these tools to pass the info along.
Fixed ProcessGDBRemote to properly pass along all file handles for stdin, stdout
and stderr.
After clearing the default values for the stdin/out/err file handles for
process to be NULL, we had a crasher in UserSettingsController::UpdateStringVariable
which is now fixed. Also fixed the setting of boolean values to be able to
be set as "true", "yes", "on", "1" for true (case insensitive) and "false", "no",
"off", or "0" for false.
Fixed debugserver to properly handle files for STDIN, STDOUT and STDERR that are not
already opened. Previous to this fix debugserver would only correctly open and dupe
file handles for the slave side of a pseudo terminal. It now correctly handles
getting STDIN for the inferior from a file, and spitting STDOUT and STDERR out to
files. Also made sure the file handles were correctly opened with the NOCTTY flag
for terminals.
llvm-svn: 124060
2011-01-23 13:56:20 +08:00
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv = %p, envp = %p, working_dir=%s, stdin=%s, stdout=%s, stderr=%s, no-stdio=%i, launch_flavor = %u, disable_aslr = %d, err = %p, err_len = %zu) called...",
|
|
|
|
__FUNCTION__,
|
|
|
|
path,
|
|
|
|
argv,
|
|
|
|
envp,
|
|
|
|
working_directory,
|
|
|
|
stdin_path,
|
|
|
|
stdout_path,
|
|
|
|
stderr_path,
|
|
|
|
no_stdio,
|
|
|
|
launch_flavor,
|
|
|
|
disable_aslr,
|
|
|
|
err_str,
|
|
|
|
err_len);
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
if (err_str && err_len > 0)
|
|
|
|
err_str[0] = '\0';
|
|
|
|
struct stat path_stat;
|
|
|
|
if (::stat(path, &path_stat) == -1)
|
|
|
|
{
|
|
|
|
char stat_error[256];
|
|
|
|
::strerror_r (errno, stat_error, sizeof(stat_error));
|
|
|
|
snprintf(err_str, err_len, "%s (%s)", stat_error, path);
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
MachProcessSP processSP (new MachProcess);
|
|
|
|
if (processSP.get())
|
|
|
|
{
|
|
|
|
DNBError launch_err;
|
2011-01-23 07:43:18 +08:00
|
|
|
pid_t pid = processSP->LaunchForDebug (path,
|
|
|
|
argv,
|
|
|
|
envp,
|
|
|
|
working_directory,
|
|
|
|
stdin_path,
|
|
|
|
stdout_path,
|
|
|
|
stderr_path,
|
|
|
|
no_stdio,
|
|
|
|
launch_flavor,
|
|
|
|
disable_aslr,
|
|
|
|
launch_err);
|
2010-06-09 00:52:24 +08:00
|
|
|
if (err_str)
|
|
|
|
{
|
|
|
|
*err_str = '\0';
|
|
|
|
if (launch_err.Fail())
|
|
|
|
{
|
|
|
|
const char *launch_err_str = launch_err.AsString();
|
|
|
|
if (launch_err_str)
|
|
|
|
{
|
|
|
|
strncpy(err_str, launch_err_str, err_len-1);
|
|
|
|
err_str[err_len-1] = '\0'; // Make sure the error string is terminated
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) new pid is %d...", pid);
|
|
|
|
|
|
|
|
if (pid != INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
// Spawn a thread to reap our child inferior process...
|
|
|
|
spawn_waitpid_thread (pid);
|
|
|
|
|
|
|
|
if (processSP->Task().TaskPortForProcessID (launch_err) == TASK_NULL)
|
|
|
|
{
|
|
|
|
// We failed to get the task for our process ID which is bad.
|
|
|
|
if (err_str && err_len > 0)
|
|
|
|
{
|
|
|
|
if (launch_err.AsString())
|
|
|
|
{
|
|
|
|
::snprintf (err_str, err_len, "failed to get the task for process %i (%s)", pid, launch_err.AsString());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
::snprintf (err_str, err_len, "failed to get the task for process %i", pid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
assert(AddProcessToMap(pid, processSP));
|
|
|
|
return pid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_process_t
|
|
|
|
DNBProcessAttachByName (const char *name, struct timespec *timeout, char *err_str, size_t err_len)
|
|
|
|
{
|
|
|
|
if (err_str && err_len > 0)
|
|
|
|
err_str[0] = '\0';
|
|
|
|
std::vector<struct kinfo_proc> matching_proc_infos;
|
|
|
|
size_t num_matching_proc_infos = GetAllInfosMatchingName(name, matching_proc_infos);
|
|
|
|
if (num_matching_proc_infos == 0)
|
|
|
|
{
|
|
|
|
DNBLogError ("error: no processes match '%s'\n", name);
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
else if (num_matching_proc_infos > 1)
|
|
|
|
{
|
|
|
|
DNBLogError ("error: %u processes match '%s':\n", num_matching_proc_infos, name);
|
|
|
|
size_t i;
|
|
|
|
for (i=0; i<num_matching_proc_infos; ++i)
|
|
|
|
DNBLogError ("%6u - %s\n", matching_proc_infos[i].kp_proc.p_pid, matching_proc_infos[i].kp_proc.p_comm);
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
2010-11-18 13:57:03 +08:00
|
|
|
|
|
|
|
return DNBProcessAttach (matching_proc_infos[0].kp_proc.p_pid, timeout, err_str, err_len);
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
nub_process_t
|
|
|
|
DNBProcessAttach (nub_process_t attach_pid, struct timespec *timeout, char *err_str, size_t err_len)
|
|
|
|
{
|
|
|
|
if (err_str && err_len > 0)
|
|
|
|
err_str[0] = '\0';
|
|
|
|
|
2011-08-12 03:03:44 +08:00
|
|
|
pid_t pid = INVALID_NUB_PROCESS;
|
2010-06-09 00:52:24 +08:00
|
|
|
MachProcessSP processSP(new MachProcess);
|
|
|
|
if (processSP.get())
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) attaching to pid %d...", attach_pid);
|
|
|
|
pid = processSP->AttachForDebug (attach_pid, err_str, err_len);
|
|
|
|
|
|
|
|
if (pid != INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
assert(AddProcessToMap(pid, processSP));
|
|
|
|
spawn_waitpid_thread(pid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (pid != INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
// Wait for process to start up and hit entry point
|
2010-11-18 13:57:03 +08:00
|
|
|
DNBLogThreadedIf (LOG_PROCESS,
|
|
|
|
"%s DNBProcessWaitForEvent (%4.4x, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged, true, INFINITE)...",
|
|
|
|
__FUNCTION__,
|
|
|
|
pid);
|
2010-06-09 00:52:24 +08:00
|
|
|
nub_event_t set_events = DNBProcessWaitForEvents (pid,
|
2010-11-18 13:57:03 +08:00
|
|
|
eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged,
|
|
|
|
true,
|
|
|
|
timeout);
|
|
|
|
|
|
|
|
DNBLogThreadedIf (LOG_PROCESS,
|
|
|
|
"%s DNBProcessWaitForEvent (%4.4x, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged, true, INFINITE) => 0x%8.8x",
|
|
|
|
__FUNCTION__,
|
|
|
|
pid,
|
|
|
|
set_events);
|
2010-06-09 00:52:24 +08:00
|
|
|
|
|
|
|
if (set_events == 0)
|
|
|
|
{
|
|
|
|
if (err_str && err_len > 0)
|
|
|
|
snprintf(err_str, err_len, "operation timed out");
|
|
|
|
pid = INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (set_events & (eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged))
|
|
|
|
{
|
|
|
|
nub_state_t pid_state = DNBProcessGetState (pid);
|
|
|
|
DNBLogThreadedIf (LOG_PROCESS, "%s process %4.4x state changed (eEventProcessStateChanged): %s",
|
|
|
|
__FUNCTION__, pid, DNBStateAsString(pid_state));
|
|
|
|
|
|
|
|
switch (pid_state)
|
|
|
|
{
|
2010-11-18 13:57:03 +08:00
|
|
|
default:
|
|
|
|
case eStateInvalid:
|
|
|
|
case eStateUnloaded:
|
|
|
|
case eStateAttaching:
|
|
|
|
case eStateLaunching:
|
|
|
|
case eStateSuspended:
|
|
|
|
break; // Ignore
|
|
|
|
|
|
|
|
case eStateRunning:
|
|
|
|
case eStateStepping:
|
|
|
|
// Still waiting to stop at entry point...
|
|
|
|
break;
|
|
|
|
|
|
|
|
case eStateStopped:
|
|
|
|
case eStateCrashed:
|
|
|
|
return pid;
|
|
|
|
|
|
|
|
case eStateDetached:
|
|
|
|
case eStateExited:
|
|
|
|
if (err_str && err_len > 0)
|
|
|
|
snprintf(err_str, err_len, "process exited");
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
}
|
2010-06-09 00:52:24 +08:00
|
|
|
|
|
|
|
DNBProcessResetEvents(pid, set_events);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
2010-11-18 13:57:03 +08:00
|
|
|
GetAllInfos (std::vector<struct kinfo_proc>& proc_infos)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
|
|
|
size_t size;
|
|
|
|
int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
|
|
|
|
u_int namelen = sizeof(name)/sizeof(int);
|
|
|
|
int err;
|
|
|
|
|
|
|
|
// Try to find out how many processes are around so we can
|
|
|
|
// size the buffer appropriately. sysctl's man page specifically suggests
|
|
|
|
// this approach, and says it returns a bit larger size than needed to
|
|
|
|
// handle any new processes created between then and now.
|
|
|
|
|
|
|
|
err = ::sysctl (name, namelen, NULL, &size, NULL, 0);
|
|
|
|
|
|
|
|
if ((err < 0) && (err != ENOMEM))
|
|
|
|
{
|
|
|
|
proc_infos.clear();
|
|
|
|
perror("sysctl (mib, miblen, NULL, &num_processes, NULL, 0)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Increase the size of the buffer by a few processes in case more have
|
|
|
|
// been spawned
|
|
|
|
proc_infos.resize (size / sizeof(struct kinfo_proc));
|
|
|
|
size = proc_infos.size() * sizeof(struct kinfo_proc); // Make sure we don't exceed our resize...
|
|
|
|
err = ::sysctl (name, namelen, &proc_infos[0], &size, NULL, 0);
|
|
|
|
if (err < 0)
|
|
|
|
{
|
|
|
|
proc_infos.clear();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trim down our array to fit what we actually got back
|
|
|
|
proc_infos.resize(size / sizeof(struct kinfo_proc));
|
|
|
|
return proc_infos.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
GetAllInfosMatchingName(const char *full_process_name, std::vector<struct kinfo_proc>& matching_proc_infos)
|
|
|
|
{
|
|
|
|
|
|
|
|
matching_proc_infos.clear();
|
|
|
|
if (full_process_name && full_process_name[0])
|
|
|
|
{
|
|
|
|
// We only get the process name, not the full path, from the proc_info. So just take the
|
|
|
|
// base name of the process name...
|
|
|
|
const char *process_name;
|
|
|
|
process_name = strrchr (full_process_name, '/');
|
|
|
|
if (process_name == NULL)
|
|
|
|
process_name = full_process_name;
|
|
|
|
else
|
|
|
|
process_name++;
|
|
|
|
|
|
|
|
std::vector<struct kinfo_proc> proc_infos;
|
|
|
|
const size_t num_proc_infos = GetAllInfos(proc_infos);
|
|
|
|
if (num_proc_infos > 0)
|
|
|
|
{
|
|
|
|
uint32_t i;
|
|
|
|
for (i=0; i<num_proc_infos; i++)
|
|
|
|
{
|
|
|
|
// Skip zombie processes and processes with unset status
|
|
|
|
if (proc_infos[i].kp_proc.p_stat == 0 || proc_infos[i].kp_proc.p_stat == SZOMB)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Check for process by name. We only check the first MAXCOMLEN
|
|
|
|
// chars as that is all that kp_proc.p_comm holds.
|
|
|
|
if (::strncasecmp(proc_infos[i].kp_proc.p_comm, process_name, MAXCOMLEN) == 0)
|
|
|
|
{
|
|
|
|
// We found a matching process, add it to our list
|
|
|
|
matching_proc_infos.push_back(proc_infos[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// return the newly added matches.
|
|
|
|
return matching_proc_infos.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_process_t
|
2010-10-18 09:45:30 +08:00
|
|
|
DNBProcessAttachWait (const char *waitfor_process_name,
|
|
|
|
nub_launch_flavor_t launch_flavor,
|
|
|
|
struct timespec *timeout_abstime,
|
|
|
|
useconds_t waitfor_interval,
|
|
|
|
char *err_str,
|
|
|
|
size_t err_len,
|
2010-06-09 00:52:24 +08:00
|
|
|
DNBShouldCancelCallback should_cancel_callback,
|
|
|
|
void *callback_data)
|
|
|
|
{
|
|
|
|
DNBError prepare_error;
|
|
|
|
std::vector<struct kinfo_proc> exclude_proc_infos;
|
|
|
|
size_t num_exclude_proc_infos;
|
|
|
|
|
|
|
|
// If the PrepareForAttach returns a valid token, use MachProcess to check
|
|
|
|
// for the process, otherwise scan the process table.
|
|
|
|
|
|
|
|
const void *attach_token = MachProcess::PrepareForAttach (waitfor_process_name, launch_flavor, true, prepare_error);
|
|
|
|
|
|
|
|
if (prepare_error.Fail())
|
|
|
|
{
|
|
|
|
DNBLogError ("Error in PrepareForAttach: %s", prepare_error.AsString());
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attach_token == NULL)
|
|
|
|
num_exclude_proc_infos = GetAllInfosMatchingName (waitfor_process_name, exclude_proc_infos);
|
|
|
|
|
|
|
|
DNBLogThreadedIf (LOG_PROCESS, "Waiting for '%s' to appear...\n", waitfor_process_name);
|
|
|
|
|
|
|
|
// Loop and try to find the process by name
|
|
|
|
nub_process_t waitfor_pid = INVALID_NUB_PROCESS;
|
|
|
|
|
|
|
|
while (waitfor_pid == INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
if (attach_token != NULL)
|
|
|
|
{
|
|
|
|
nub_process_t pid;
|
|
|
|
pid = MachProcess::CheckForProcess(attach_token);
|
|
|
|
if (pid != INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
waitfor_pid = pid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
// Get the current process list, and check for matches that
|
|
|
|
// aren't in our original list. If anyone wants to attach
|
|
|
|
// to an existing process by name, they should do it with
|
|
|
|
// --attach=PROCNAME. Else we will wait for the first matching
|
|
|
|
// process that wasn't in our exclusion list.
|
|
|
|
std::vector<struct kinfo_proc> proc_infos;
|
|
|
|
const size_t num_proc_infos = GetAllInfosMatchingName (waitfor_process_name, proc_infos);
|
|
|
|
for (size_t i=0; i<num_proc_infos; i++)
|
|
|
|
{
|
|
|
|
nub_process_t curr_pid = proc_infos[i].kp_proc.p_pid;
|
|
|
|
for (size_t j=0; j<num_exclude_proc_infos; j++)
|
|
|
|
{
|
|
|
|
if (curr_pid == exclude_proc_infos[j].kp_proc.p_pid)
|
|
|
|
{
|
|
|
|
// This process was in our exclusion list, don't use it.
|
|
|
|
curr_pid = INVALID_NUB_PROCESS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we didn't find CURR_PID in our exclusion list, then use it.
|
|
|
|
if (curr_pid != INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
// We found our process!
|
|
|
|
waitfor_pid = curr_pid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we haven't found our process yet, check for a timeout
|
|
|
|
// and then sleep for a bit until we poll again.
|
|
|
|
if (waitfor_pid == INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
if (timeout_abstime != NULL)
|
|
|
|
{
|
|
|
|
// Check to see if we have a waitfor-duration option that
|
|
|
|
// has timed out?
|
|
|
|
if (DNBTimer::TimeOfDayLaterThan(*timeout_abstime))
|
|
|
|
{
|
|
|
|
if (err_str && err_len > 0)
|
|
|
|
snprintf(err_str, err_len, "operation timed out");
|
|
|
|
DNBLogError ("error: waiting for process '%s' timed out.\n", waitfor_process_name);
|
|
|
|
return INVALID_NUB_PROCESS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the should cancel callback as well...
|
|
|
|
|
|
|
|
if (should_cancel_callback != NULL
|
|
|
|
&& should_cancel_callback (callback_data))
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf (LOG_PROCESS, "DNBProcessAttachWait cancelled by should_cancel callback.");
|
|
|
|
waitfor_pid = INVALID_NUB_PROCESS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
::usleep (waitfor_interval); // Sleep for WAITFOR_INTERVAL, then poll again
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (waitfor_pid != INVALID_NUB_PROCESS)
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf (LOG_PROCESS, "Attaching to %s with pid %i...\n", waitfor_process_name, waitfor_pid);
|
|
|
|
waitfor_pid = DNBProcessAttach (waitfor_pid, timeout_abstime, err_str, err_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool success = waitfor_pid != INVALID_NUB_PROCESS;
|
|
|
|
MachProcess::CleanupAfterAttach (attach_token, success, prepare_error);
|
|
|
|
|
|
|
|
return waitfor_pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessDetach (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->Detach();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessKill (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->Kill ();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessSignal (nub_process_t pid, int signal)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->Signal (signal);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessIsAlive (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return MachTask::IsValid (procSP->Task().TaskPort());
|
|
|
|
}
|
|
|
|
return eStateInvalid;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Process and Thread state information
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_state_t
|
|
|
|
DNBProcessGetState (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->GetState();
|
|
|
|
}
|
|
|
|
return eStateInvalid;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Process and Thread state information
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessGetExitStatus (nub_process_t pid, int* status)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->GetExitStatus(status);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessSetExitStatus (nub_process_t pid, int status)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
procSP->SetExitStatus(status);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char *
|
|
|
|
DNBThreadGetName (nub_process_t pid, nub_thread_t tid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->ThreadGetName(tid);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBThreadGetIdentifierInfo (nub_process_t pid, nub_thread_t tid, thread_identifier_info_data_t *ident_info)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetThreadList().GetIdentifierInfo(tid, ident_info);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_state_t
|
|
|
|
DNBThreadGetState (nub_process_t pid, nub_thread_t tid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->ThreadGetState(tid);
|
|
|
|
}
|
|
|
|
return eStateInvalid;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
DNBStateAsString(nub_state_t state)
|
|
|
|
{
|
|
|
|
switch (state)
|
|
|
|
{
|
2011-05-04 06:09:39 +08:00
|
|
|
case eStateInvalid: return "Invalid";
|
2010-06-09 00:52:24 +08:00
|
|
|
case eStateUnloaded: return "Unloaded";
|
|
|
|
case eStateAttaching: return "Attaching";
|
|
|
|
case eStateLaunching: return "Launching";
|
|
|
|
case eStateStopped: return "Stopped";
|
|
|
|
case eStateRunning: return "Running";
|
|
|
|
case eStateStepping: return "Stepping";
|
|
|
|
case eStateCrashed: return "Crashed";
|
|
|
|
case eStateDetached: return "Detached";
|
|
|
|
case eStateExited: return "Exited";
|
|
|
|
case eStateSuspended: return "Suspended";
|
|
|
|
}
|
|
|
|
return "nub_state_t ???";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
DNBProcessGetExecutablePath (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->Path();
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessGetArgumentCount (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->ArgumentCount();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
DNBProcessGetArgumentAtIndex (nub_process_t pid, nub_size_t idx)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->ArgumentAtIndex (idx);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Execution control
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessResume (nub_process_t pid, const DNBThreadResumeAction *actions, size_t num_actions)
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "%s(pid = %4.4x)", __FUNCTION__, pid);
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBThreadResumeActions thread_actions (actions, num_actions);
|
|
|
|
|
|
|
|
// Below we add a default thread plan just in case one wasn't
|
|
|
|
// provided so all threads always know what they were supposed to do
|
|
|
|
if (thread_actions.IsEmpty())
|
|
|
|
{
|
|
|
|
// No thread plans were given, so the default it to run all threads
|
|
|
|
thread_actions.SetDefaultThreadActionIfNeeded (eStateRunning, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Some thread plans were given which means anything that wasn't
|
|
|
|
// specified should remain stopped.
|
|
|
|
thread_actions.SetDefaultThreadActionIfNeeded (eStateStopped, 0);
|
|
|
|
}
|
|
|
|
return procSP->Resume (thread_actions);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessHalt (nub_process_t pid)
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf(LOG_PROCESS, "%s(pid = %4.4x)", __FUNCTION__, pid);
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->Signal (SIGSTOP);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
//nub_bool_t
|
|
|
|
//DNBThreadResume (nub_process_t pid, nub_thread_t tid, nub_bool_t step)
|
|
|
|
//{
|
|
|
|
// DNBLogThreadedIf(LOG_THREAD, "%s(pid = %4.4x, tid = %4.4x, step = %u)", __FUNCTION__, pid, tid, (uint32_t)step);
|
|
|
|
// MachProcessSP procSP;
|
|
|
|
// if (GetProcessSP (pid, procSP))
|
|
|
|
// {
|
|
|
|
// return procSP->Resume(tid, step, 0);
|
|
|
|
// }
|
|
|
|
// return false;
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//nub_bool_t
|
|
|
|
//DNBThreadResumeWithSignal (nub_process_t pid, nub_thread_t tid, nub_bool_t step, int signal)
|
|
|
|
//{
|
|
|
|
// DNBLogThreadedIf(LOG_THREAD, "%s(pid = %4.4x, tid = %4.4x, step = %u, signal = %i)", __FUNCTION__, pid, tid, (uint32_t)step, signal);
|
|
|
|
// MachProcessSP procSP;
|
|
|
|
// if (GetProcessSP (pid, procSP))
|
|
|
|
// {
|
|
|
|
// return procSP->Resume(tid, step, signal);
|
|
|
|
// }
|
|
|
|
// return false;
|
|
|
|
//}
|
|
|
|
|
|
|
|
nub_event_t
|
|
|
|
DNBProcessWaitForEvents (nub_process_t pid, nub_event_t event_mask, bool wait_for_set, struct timespec* timeout)
|
|
|
|
{
|
|
|
|
nub_event_t result = 0;
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
if (wait_for_set)
|
|
|
|
result = procSP->Events().WaitForSetEvents(event_mask, timeout);
|
|
|
|
else
|
|
|
|
result = procSP->Events().WaitForEventsToReset(event_mask, timeout);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DNBProcessResetEvents (nub_process_t pid, nub_event_t event_mask)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
procSP->Events().ResetEvents(event_mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DNBProcessInterruptEvents (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
procSP->Events().SetEvents(eEventProcessAsyncInterrupt);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Breakpoints
|
|
|
|
nub_break_t
|
|
|
|
DNBBreakpointSet (nub_process_t pid, nub_addr_t addr, nub_size_t size, nub_bool_t hardware)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->CreateBreakpoint(addr, size, hardware, THREAD_NULL);
|
|
|
|
}
|
|
|
|
return INVALID_NUB_BREAK_ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBBreakpointClear (nub_process_t pid, nub_break_t breakID)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(breakID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->DisableBreakpoint(breakID, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false; // Failed
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_ssize_t
|
|
|
|
DNBBreakpointGetHitCount (nub_process_t pid, nub_break_t breakID)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(breakID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID);
|
|
|
|
if (bp)
|
|
|
|
return bp->GetHitCount();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_ssize_t
|
|
|
|
DNBBreakpointGetIgnoreCount (nub_process_t pid, nub_break_t breakID)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(breakID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID);
|
|
|
|
if (bp)
|
|
|
|
return bp->GetIgnoreCount();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBBreakpointSetIgnoreCount (nub_process_t pid, nub_break_t breakID, nub_size_t ignore_count)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(breakID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID);
|
|
|
|
if (bp)
|
|
|
|
{
|
|
|
|
bp->SetIgnoreCount(ignore_count);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the callback function for a given breakpoint. The callback function will
|
|
|
|
// get called as soon as the breakpoint is hit. The function will be called
|
|
|
|
// with the process ID, thread ID, breakpoint ID and the baton, and can return
|
|
|
|
//
|
|
|
|
nub_bool_t
|
|
|
|
DNBBreakpointSetCallback (nub_process_t pid, nub_break_t breakID, DNBCallbackBreakpointHit callback, void *baton)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(breakID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID);
|
|
|
|
if (bp)
|
|
|
|
{
|
|
|
|
bp->SetCallback(callback, baton);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Dump the breakpoints stats for process PID for a breakpoint by ID.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
void
|
|
|
|
DNBBreakpointPrint (nub_process_t pid, nub_break_t breakID)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
procSP->DumpBreakpoint(breakID);
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Watchpoints
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_watch_t
|
|
|
|
DNBWatchpointSet (nub_process_t pid, nub_addr_t addr, nub_size_t size, uint32_t watch_flags, nub_bool_t hardware)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->CreateWatchpoint(addr, size, watch_flags, hardware, THREAD_NULL);
|
|
|
|
}
|
|
|
|
return INVALID_NUB_BREAK_ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBWatchpointClear (nub_process_t pid, nub_watch_t watchID)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(watchID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->DisableWatchpoint(watchID, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false; // Failed
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_ssize_t
|
|
|
|
DNBWatchpointGetHitCount (nub_process_t pid, nub_watch_t watchID)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(watchID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID);
|
|
|
|
if (bp)
|
|
|
|
return bp->GetHitCount();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_ssize_t
|
|
|
|
DNBWatchpointGetIgnoreCount (nub_process_t pid, nub_watch_t watchID)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(watchID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID);
|
|
|
|
if (bp)
|
|
|
|
return bp->GetIgnoreCount();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBWatchpointSetIgnoreCount (nub_process_t pid, nub_watch_t watchID, nub_size_t ignore_count)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(watchID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID);
|
|
|
|
if (bp)
|
|
|
|
{
|
|
|
|
bp->SetIgnoreCount(ignore_count);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the callback function for a given watchpoint. The callback function will
|
|
|
|
// get called as soon as the watchpoint is hit. The function will be called
|
|
|
|
// with the process ID, thread ID, watchpoint ID and the baton, and can return
|
|
|
|
//
|
|
|
|
nub_bool_t
|
|
|
|
DNBWatchpointSetCallback (nub_process_t pid, nub_watch_t watchID, DNBCallbackBreakpointHit callback, void *baton)
|
|
|
|
{
|
|
|
|
if (NUB_BREAK_ID_IS_VALID(watchID))
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID);
|
|
|
|
if (bp)
|
|
|
|
{
|
|
|
|
bp->SetCallback(callback, baton);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Dump the watchpoints stats for process PID for a watchpoint by ID.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
void
|
|
|
|
DNBWatchpointPrint (nub_process_t pid, nub_watch_t watchID)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
procSP->DumpWatchpoint(watchID);
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Read memory in the address space of process PID. This call will take
|
|
|
|
// care of setting and restoring permissions and breaking up the memory
|
|
|
|
// read into multiple chunks as required.
|
|
|
|
//
|
|
|
|
// RETURNS: number of bytes actually read
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessMemoryRead (nub_process_t pid, nub_addr_t addr, nub_size_t size, void *buf)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->ReadMemory(addr, size, buf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Write memory to the address space of process PID. This call will take
|
|
|
|
// care of setting and restoring permissions and breaking up the memory
|
|
|
|
// write into multiple chunks as required.
|
|
|
|
//
|
|
|
|
// RETURNS: number of bytes actually written
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessMemoryWrite (nub_process_t pid, nub_addr_t addr, nub_size_t size, const void *buf)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->WriteMemory(addr, size, buf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_addr_t
|
|
|
|
DNBProcessMemoryAllocate (nub_process_t pid, nub_size_t size, uint32_t permissions)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->Task().AllocateMemory (size, permissions);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessMemoryDeallocate (nub_process_t pid, nub_addr_t addr)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->Task().DeallocateMemory (addr);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Formatted output that uses memory and registers from process and
|
|
|
|
// thread in place of arguments.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_size_t
|
|
|
|
DNBPrintf (nub_process_t pid, nub_thread_t tid, nub_addr_t base_addr, FILE *file, const char *format)
|
|
|
|
{
|
|
|
|
if (file == NULL)
|
|
|
|
return 0;
|
|
|
|
enum printf_flags
|
|
|
|
{
|
|
|
|
alternate_form = (1 << 0),
|
|
|
|
zero_padding = (1 << 1),
|
|
|
|
negative_field_width = (1 << 2),
|
|
|
|
blank_space = (1 << 3),
|
|
|
|
show_sign = (1 << 4),
|
|
|
|
show_thousands_separator= (1 << 5),
|
|
|
|
};
|
|
|
|
|
|
|
|
enum printf_length_modifiers
|
|
|
|
{
|
|
|
|
length_mod_h = (1 << 0),
|
|
|
|
length_mod_hh = (1 << 1),
|
|
|
|
length_mod_l = (1 << 2),
|
|
|
|
length_mod_ll = (1 << 3),
|
|
|
|
length_mod_L = (1 << 4),
|
|
|
|
length_mod_j = (1 << 5),
|
|
|
|
length_mod_t = (1 << 6),
|
|
|
|
length_mod_z = (1 << 7),
|
|
|
|
length_mod_q = (1 << 8),
|
|
|
|
};
|
|
|
|
|
|
|
|
nub_addr_t addr = base_addr;
|
|
|
|
char *end_format = (char*)format + strlen(format);
|
|
|
|
char *end = NULL; // For strtoXXXX calls;
|
|
|
|
std::basic_string<uint8_t> buf;
|
|
|
|
nub_size_t total_bytes_read = 0;
|
|
|
|
DNBDataRef data;
|
|
|
|
const char *f;
|
|
|
|
for (f = format; *f != '\0' && f < end_format; f++)
|
|
|
|
{
|
|
|
|
char ch = *f;
|
|
|
|
switch (ch)
|
|
|
|
{
|
|
|
|
case '%':
|
|
|
|
{
|
|
|
|
f++; // Skip the '%' character
|
|
|
|
int min_field_width = 0;
|
|
|
|
int precision = 0;
|
|
|
|
uint32_t flags = 0;
|
|
|
|
uint32_t length_modifiers = 0;
|
|
|
|
uint32_t byte_size = 0;
|
|
|
|
uint32_t actual_byte_size = 0;
|
|
|
|
bool is_string = false;
|
|
|
|
bool is_register = false;
|
|
|
|
DNBRegisterValue register_value;
|
|
|
|
int64_t register_offset = 0;
|
|
|
|
nub_addr_t register_addr = INVALID_NUB_ADDRESS;
|
|
|
|
|
|
|
|
// Create the format string to use for this conversion specification
|
|
|
|
// so we can remove and mprintf specific flags and formatters.
|
|
|
|
std::string fprintf_format("%");
|
|
|
|
|
|
|
|
// Decode any flags
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case '#': fprintf_format += *f++; flags |= alternate_form; break;
|
|
|
|
case '0': fprintf_format += *f++; flags |= zero_padding; break;
|
|
|
|
case '-': fprintf_format += *f++; flags |= negative_field_width; break;
|
|
|
|
case ' ': fprintf_format += *f++; flags |= blank_space; break;
|
|
|
|
case '+': fprintf_format += *f++; flags |= show_sign; break;
|
|
|
|
case ',': fprintf_format += *f++; flags |= show_thousands_separator;break;
|
|
|
|
case '{':
|
|
|
|
case '[':
|
|
|
|
{
|
|
|
|
// We have a register name specification that can take two forms:
|
|
|
|
// ${regname} or ${regname+offset}
|
|
|
|
// The action is to read the register value and add the signed offset
|
|
|
|
// (if any) and use that as the value to format.
|
|
|
|
// $[regname] or $[regname+offset]
|
|
|
|
// The action is to read the register value and add the signed offset
|
|
|
|
// (if any) and use the result as an address to dereference. The size
|
|
|
|
// of what is dereferenced is specified by the actual byte size that
|
|
|
|
// follows the minimum field width and precision (see comments below).
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case '{':
|
|
|
|
case '[':
|
|
|
|
{
|
|
|
|
char open_scope_ch = *f;
|
|
|
|
f++;
|
|
|
|
const char *reg_name = f;
|
|
|
|
size_t reg_name_length = strcspn(f, "+-}]");
|
|
|
|
if (reg_name_length > 0)
|
|
|
|
{
|
|
|
|
std::string register_name(reg_name, reg_name_length);
|
|
|
|
f += reg_name_length;
|
|
|
|
register_offset = strtoll(f, &end, 0);
|
|
|
|
if (f < end)
|
|
|
|
f = end;
|
|
|
|
if ((open_scope_ch == '{' && *f != '}') || (open_scope_ch == '[' && *f != ']'))
|
|
|
|
{
|
|
|
|
fprintf(file, "error: Invalid register format string. Valid formats are %%{regname} or %%{regname+offset}, %%[regname] or %%[regname+offset]\n");
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
f++;
|
|
|
|
if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, register_name.c_str(), ®ister_value))
|
|
|
|
{
|
|
|
|
// Set the address to dereference using the register value plus the offset
|
|
|
|
switch (register_value.info.size)
|
|
|
|
{
|
|
|
|
default:
|
|
|
|
case 0:
|
|
|
|
fprintf (file, "error: unsupported register size of %u.\n", register_value.info.size);
|
|
|
|
return total_bytes_read;
|
|
|
|
|
|
|
|
case 1: register_addr = register_value.value.uint8 + register_offset; break;
|
|
|
|
case 2: register_addr = register_value.value.uint16 + register_offset; break;
|
|
|
|
case 4: register_addr = register_value.value.uint32 + register_offset; break;
|
|
|
|
case 8: register_addr = register_value.value.uint64 + register_offset; break;
|
|
|
|
case 16:
|
|
|
|
if (open_scope_ch == '[')
|
|
|
|
{
|
|
|
|
fprintf (file, "error: register size (%u) too large for address.\n", register_value.info.size);
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (open_scope_ch == '{')
|
|
|
|
{
|
|
|
|
byte_size = register_value.info.size;
|
|
|
|
is_register = true; // value is in a register
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
addr = register_addr; // Use register value and offset as the address
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(file, "error: unable to read register '%s' for process %#.4x and thread %#.4x\n", register_name.c_str(), pid, tid);
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(file, "error: %%$ must be followed by (regname + n) or [regname + n]\n");
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for a minimum field width
|
|
|
|
if (isdigit(*f))
|
|
|
|
{
|
|
|
|
min_field_width = strtoul(f, &end, 10);
|
|
|
|
if (end > f)
|
|
|
|
{
|
|
|
|
fprintf_format.append(f, end - f);
|
|
|
|
f = end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for a precision
|
|
|
|
if (*f == '.')
|
|
|
|
{
|
|
|
|
f++;
|
|
|
|
if (isdigit(*f))
|
|
|
|
{
|
|
|
|
fprintf_format += '.';
|
|
|
|
precision = strtoul(f, &end, 10);
|
|
|
|
if (end > f)
|
|
|
|
{
|
|
|
|
fprintf_format.append(f, end - f);
|
|
|
|
f = end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mprintf specific: read the optional actual byte size (abs)
|
|
|
|
// after the standard minimum field width (mfw) and precision (prec).
|
|
|
|
// Standard printf calls you can have "mfw.prec" or ".prec", but
|
|
|
|
// mprintf can have "mfw.prec.abs", ".prec.abs" or "..abs". This is nice
|
|
|
|
// for strings that may be in a fixed size buffer, but may not use all bytes
|
|
|
|
// in that buffer for printable characters.
|
|
|
|
if (*f == '.')
|
|
|
|
{
|
|
|
|
f++;
|
|
|
|
actual_byte_size = strtoul(f, &end, 10);
|
|
|
|
if (end > f)
|
|
|
|
{
|
|
|
|
byte_size = actual_byte_size;
|
|
|
|
f = end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the length modifiers
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case 'h': // h and hh length modifiers
|
|
|
|
fprintf_format += *f++;
|
|
|
|
length_modifiers |= length_mod_h;
|
|
|
|
if (*f == 'h')
|
|
|
|
{
|
|
|
|
fprintf_format += *f++;
|
|
|
|
length_modifiers |= length_mod_hh;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'l': // l and ll length modifiers
|
|
|
|
fprintf_format += *f++;
|
|
|
|
length_modifiers |= length_mod_l;
|
|
|
|
if (*f == 'h')
|
|
|
|
{
|
|
|
|
fprintf_format += *f++;
|
|
|
|
length_modifiers |= length_mod_ll;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'L': fprintf_format += *f++; length_modifiers |= length_mod_L; break;
|
|
|
|
case 'j': fprintf_format += *f++; length_modifiers |= length_mod_j; break;
|
|
|
|
case 't': fprintf_format += *f++; length_modifiers |= length_mod_t; break;
|
|
|
|
case 'z': fprintf_format += *f++; length_modifiers |= length_mod_z; break;
|
|
|
|
case 'q': fprintf_format += *f++; length_modifiers |= length_mod_q; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the conversion specifier
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case '_':
|
|
|
|
// mprintf specific format items
|
|
|
|
{
|
|
|
|
++f; // Skip the '_' character
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case 'a': // Print the current address
|
|
|
|
++f;
|
|
|
|
fprintf_format += "ll";
|
|
|
|
fprintf_format += *f; // actual format to show address with folows the 'a' ("%_ax")
|
|
|
|
fprintf (file, fprintf_format.c_str(), addr);
|
|
|
|
break;
|
|
|
|
case 'o': // offset from base address
|
|
|
|
++f;
|
|
|
|
fprintf_format += "ll";
|
|
|
|
fprintf_format += *f; // actual format to show address with folows the 'a' ("%_ox")
|
|
|
|
fprintf(file, fprintf_format.c_str(), addr - base_addr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf (file, "error: unsupported mprintf specific format character '%c'.\n", *f);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'D':
|
|
|
|
case 'O':
|
|
|
|
case 'U':
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (byte_size == 0)
|
|
|
|
byte_size = sizeof(long int);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'd':
|
|
|
|
case 'i':
|
|
|
|
case 'o':
|
|
|
|
case 'u':
|
|
|
|
case 'x':
|
|
|
|
case 'X':
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (byte_size == 0)
|
|
|
|
{
|
|
|
|
if (length_modifiers & length_mod_hh)
|
|
|
|
byte_size = sizeof(char);
|
|
|
|
else if (length_modifiers & length_mod_h)
|
|
|
|
byte_size = sizeof(short);
|
|
|
|
if (length_modifiers & length_mod_ll)
|
|
|
|
byte_size = sizeof(long long);
|
|
|
|
else if (length_modifiers & length_mod_l)
|
|
|
|
byte_size = sizeof(long);
|
|
|
|
else
|
|
|
|
byte_size = sizeof(int);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'a':
|
|
|
|
case 'A':
|
|
|
|
case 'f':
|
|
|
|
case 'F':
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
case 'g':
|
|
|
|
case 'G':
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (byte_size == 0)
|
|
|
|
{
|
|
|
|
if (length_modifiers & length_mod_L)
|
|
|
|
byte_size = sizeof(long double);
|
|
|
|
else
|
|
|
|
byte_size = sizeof(double);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'c':
|
|
|
|
if ((length_modifiers & length_mod_l) == 0)
|
|
|
|
{
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (byte_size == 0)
|
|
|
|
byte_size = sizeof(char);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Fall through to 'C' modifier below...
|
|
|
|
|
|
|
|
case 'C':
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (byte_size == 0)
|
|
|
|
byte_size = sizeof(wchar_t);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 's':
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (is_register || byte_size == 0)
|
|
|
|
is_string = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'p':
|
|
|
|
fprintf_format += *f;
|
|
|
|
if (byte_size == 0)
|
|
|
|
byte_size = sizeof(void*);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_string)
|
|
|
|
{
|
|
|
|
std::string mem_string;
|
|
|
|
const size_t string_buf_len = 4;
|
|
|
|
char string_buf[string_buf_len+1];
|
|
|
|
char *string_buf_end = string_buf + string_buf_len;
|
|
|
|
string_buf[string_buf_len] = '\0';
|
|
|
|
nub_size_t bytes_read;
|
|
|
|
nub_addr_t str_addr = is_register ? register_addr : addr;
|
|
|
|
while ((bytes_read = DNBProcessMemoryRead(pid, str_addr, string_buf_len, &string_buf[0])) > 0)
|
|
|
|
{
|
|
|
|
// Did we get a NULL termination character yet?
|
|
|
|
if (strchr(string_buf, '\0') == string_buf_end)
|
|
|
|
{
|
|
|
|
// no NULL terminator yet, append as a std::string
|
|
|
|
mem_string.append(string_buf, string_buf_len);
|
|
|
|
str_addr += string_buf_len;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// yep
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Append as a C-string so we don't get the extra NULL
|
|
|
|
// characters in the temp buffer (since it was resized)
|
|
|
|
mem_string += string_buf;
|
|
|
|
size_t mem_string_len = mem_string.size() + 1;
|
|
|
|
fprintf(file, fprintf_format.c_str(), mem_string.c_str());
|
|
|
|
if (mem_string_len > 0)
|
|
|
|
{
|
|
|
|
if (!is_register)
|
|
|
|
{
|
|
|
|
addr += mem_string_len;
|
|
|
|
total_bytes_read += mem_string_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (byte_size > 0)
|
|
|
|
{
|
|
|
|
buf.resize(byte_size);
|
|
|
|
nub_size_t bytes_read = 0;
|
|
|
|
if (is_register)
|
|
|
|
bytes_read = register_value.info.size;
|
|
|
|
else
|
|
|
|
bytes_read = DNBProcessMemoryRead(pid, addr, buf.size(), &buf[0]);
|
|
|
|
if (bytes_read > 0)
|
|
|
|
{
|
|
|
|
if (!is_register)
|
|
|
|
total_bytes_read += bytes_read;
|
|
|
|
|
|
|
|
if (bytes_read == byte_size)
|
|
|
|
{
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case 'd':
|
|
|
|
case 'i':
|
|
|
|
case 'o':
|
|
|
|
case 'u':
|
|
|
|
case 'X':
|
|
|
|
case 'x':
|
|
|
|
case 'a':
|
|
|
|
case 'A':
|
|
|
|
case 'f':
|
|
|
|
case 'F':
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
case 'g':
|
|
|
|
case 'G':
|
|
|
|
case 'p':
|
|
|
|
case 'c':
|
|
|
|
case 'C':
|
|
|
|
{
|
|
|
|
if (is_register)
|
|
|
|
data.SetData(®ister_value.value.v_uint8[0], register_value.info.size);
|
|
|
|
else
|
|
|
|
data.SetData(&buf[0], bytes_read);
|
|
|
|
DNBDataRef::offset_t data_offset = 0;
|
|
|
|
if (byte_size <= 4)
|
|
|
|
{
|
|
|
|
uint32_t u32 = data.GetMax32(&data_offset, byte_size);
|
|
|
|
// Show the actual byte width when displaying hex
|
|
|
|
fprintf(file, fprintf_format.c_str(), u32);
|
|
|
|
}
|
|
|
|
else if (byte_size <= 8)
|
|
|
|
{
|
|
|
|
uint64_t u64 = data.GetMax64(&data_offset, byte_size);
|
|
|
|
// Show the actual byte width when displaying hex
|
|
|
|
fprintf(file, fprintf_format.c_str(), u64);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(file, "error: integer size not supported, must be 8 bytes or less (%u bytes).\n", byte_size);
|
|
|
|
}
|
|
|
|
if (!is_register)
|
|
|
|
addr += byte_size;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 's':
|
|
|
|
fprintf(file, fprintf_format.c_str(), buf.c_str());
|
|
|
|
addr += byte_size;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(file, "error: unsupported conversion specifier '%c'.\n", *f);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '\\':
|
|
|
|
{
|
|
|
|
f++;
|
|
|
|
switch (*f)
|
|
|
|
{
|
|
|
|
case 'e': ch = '\e'; break;
|
|
|
|
case 'a': ch = '\a'; break;
|
|
|
|
case 'b': ch = '\b'; break;
|
|
|
|
case 'f': ch = '\f'; break;
|
|
|
|
case 'n': ch = '\n'; break;
|
|
|
|
case 'r': ch = '\r'; break;
|
|
|
|
case 't': ch = '\t'; break;
|
|
|
|
case 'v': ch = '\v'; break;
|
|
|
|
case '\'': ch = '\''; break;
|
|
|
|
case '\\': ch = '\\'; break;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
ch = strtoul(f, &end, 8);
|
|
|
|
f = end;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ch = *f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fputc(ch, file);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fputc(ch, file);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return total_bytes_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Get the number of threads for the specified process.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessGetNumThreads (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetNumThreads();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Get the thread ID of the current thread.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_thread_t
|
|
|
|
DNBProcessGetCurrentThread (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetCurrentThread();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Change the current thread.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_thread_t
|
|
|
|
DNBProcessSetCurrentThread (nub_process_t pid, nub_thread_t tid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->SetCurrentThread (tid);
|
|
|
|
return INVALID_NUB_THREAD;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Dump a string describing a thread's stop reason to the specified file
|
|
|
|
// handle
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBThreadGetStopReason (nub_process_t pid, nub_thread_t tid, struct DNBThreadStopInfo *stop_info)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetThreadStoppedReason (tid, stop_info);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Return string description for the specified thread.
|
|
|
|
//
|
|
|
|
// RETURNS: NULL if the thread isn't valid, else a NULL terminated C
|
|
|
|
// string from a static buffer that must be copied prior to subsequent
|
|
|
|
// calls.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
const char *
|
|
|
|
DNBThreadGetInfo (nub_process_t pid, nub_thread_t tid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetThreadInfo (tid);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Get the thread ID given a thread index.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_thread_t
|
|
|
|
DNBProcessGetThreadAtIndex (nub_process_t pid, size_t thread_idx)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetThreadAtIndex (thread_idx);
|
|
|
|
return INVALID_NUB_THREAD;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_addr_t
|
|
|
|
DNBProcessGetSharedLibraryInfoAddress (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
DNBError err;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->Task().GetDYLDAllImageInfosAddress (err);
|
|
|
|
return INVALID_NUB_ADDRESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessSharedLibrariesUpdated(nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
procSP->SharedLibrariesUpdated ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Get the current shared library information for a process. Only return
|
|
|
|
// the shared libraries that have changed since the last shared library
|
|
|
|
// state changed event if only_changed is non-zero.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessGetSharedLibraryInfo (nub_process_t pid, nub_bool_t only_changed, struct DNBExecutableImageInfo **image_infos)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->CopyImageInfos (image_infos, only_changed);
|
|
|
|
|
|
|
|
// If we have no process, then return NULL for the shared library info
|
|
|
|
// and zero for shared library count
|
|
|
|
*image_infos = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Get the register set information for a specific thread.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
const DNBRegisterSetInfo *
|
|
|
|
DNBGetRegisterSetInfo (nub_size_t *num_reg_sets)
|
|
|
|
{
|
2010-11-18 13:57:03 +08:00
|
|
|
return DNBArchProtocol::GetRegisterSetInfo (num_reg_sets);
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Read a register value by register set and register index.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBThreadGetRegisterValueByID (nub_process_t pid, nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *value)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
::bzero (value, sizeof(DNBRegisterValue));
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
if (tid != INVALID_NUB_THREAD)
|
|
|
|
return procSP->GetRegisterValue (tid, set, reg, value);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBThreadSetRegisterValueByID (nub_process_t pid, nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *value)
|
|
|
|
{
|
|
|
|
if (tid != INVALID_NUB_THREAD)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->SetRegisterValue (tid, set, reg, value);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_size_t
|
|
|
|
DNBThreadGetRegisterContext (nub_process_t pid, nub_thread_t tid, void *buf, size_t buf_len)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
if (tid != INVALID_NUB_THREAD)
|
|
|
|
return procSP->GetThreadList().GetRegisterContext (tid, buf, buf_len);
|
|
|
|
}
|
|
|
|
::bzero (buf, buf_len);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_size_t
|
|
|
|
DNBThreadSetRegisterContext (nub_process_t pid, nub_thread_t tid, const void *buf, size_t buf_len)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
if (tid != INVALID_NUB_THREAD)
|
|
|
|
return procSP->GetThreadList().SetRegisterContext (tid, buf, buf_len);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Read a register value by name.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBThreadGetRegisterValueByName (nub_process_t pid, nub_thread_t tid, uint32_t reg_set, const char *reg_name, DNBRegisterValue *value)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
::bzero (value, sizeof(DNBRegisterValue));
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
const struct DNBRegisterSetInfo *set_info;
|
|
|
|
nub_size_t num_reg_sets = 0;
|
|
|
|
set_info = DNBGetRegisterSetInfo (&num_reg_sets);
|
|
|
|
if (set_info)
|
|
|
|
{
|
|
|
|
uint32_t set = reg_set;
|
|
|
|
uint32_t reg;
|
|
|
|
if (set == REGISTER_SET_ALL)
|
|
|
|
{
|
|
|
|
for (set = 1; set < num_reg_sets; ++set)
|
|
|
|
{
|
|
|
|
for (reg = 0; reg < set_info[set].num_registers; ++reg)
|
|
|
|
{
|
|
|
|
if (strcasecmp(reg_name, set_info[set].registers[reg].name) == 0)
|
|
|
|
return procSP->GetRegisterValue (tid, set, reg, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (reg = 0; reg < set_info[set].num_registers; ++reg)
|
|
|
|
{
|
|
|
|
if (strcasecmp(reg_name, set_info[set].registers[reg].name) == 0)
|
|
|
|
return procSP->GetRegisterValue (tid, set, reg, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Read a register set and register number from the register name.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBGetRegisterInfoByName (const char *reg_name, DNBRegisterInfo* info)
|
|
|
|
{
|
|
|
|
const struct DNBRegisterSetInfo *set_info;
|
|
|
|
nub_size_t num_reg_sets = 0;
|
|
|
|
set_info = DNBGetRegisterSetInfo (&num_reg_sets);
|
|
|
|
if (set_info)
|
|
|
|
{
|
|
|
|
uint32_t set, reg;
|
|
|
|
for (set = 1; set < num_reg_sets; ++set)
|
|
|
|
{
|
|
|
|
for (reg = 0; reg < set_info[set].num_registers; ++reg)
|
|
|
|
{
|
|
|
|
if (strcasecmp(reg_name, set_info[set].registers[reg].name) == 0)
|
|
|
|
{
|
|
|
|
*info = set_info[set].registers[reg];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (set = 1; set < num_reg_sets; ++set)
|
|
|
|
{
|
|
|
|
uint32_t reg;
|
|
|
|
for (reg = 0; reg < set_info[set].num_registers; ++reg)
|
|
|
|
{
|
|
|
|
if (set_info[set].registers[reg].alt == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (strcasecmp(reg_name, set_info[set].registers[reg].alt) == 0)
|
|
|
|
{
|
|
|
|
*info = set_info[set].registers[reg];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
::bzero (info, sizeof(DNBRegisterInfo));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Set the name to address callback function that this nub can use
|
|
|
|
// for any name to address lookups that are needed.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessSetNameToAddressCallback (nub_process_t pid, DNBCallbackNameToAddress callback, void *baton)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
procSP->SetNameToAddressCallback (callback, baton);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Set the name to address callback function that this nub can use
|
|
|
|
// for any name to address lookups that are needed.
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nub_bool_t
|
|
|
|
DNBProcessSetSharedLibraryInfoCallback (nub_process_t pid, DNBCallbackCopyExecutableImageInfos callback, void *baton)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
procSP->SetSharedLibraryInfoCallback (callback, baton);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_addr_t
|
|
|
|
DNBProcessLookupAddress (nub_process_t pid, const char *name, const char *shlib)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
{
|
|
|
|
return procSP->LookupSymbol (name, shlib);
|
|
|
|
}
|
|
|
|
return INVALID_NUB_ADDRESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessGetAvailableSTDOUT (nub_process_t pid, char *buf, nub_size_t buf_size)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetAvailableSTDOUT (buf, buf_size);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessGetAvailableSTDERR (nub_process_t pid, char *buf, nub_size_t buf_size)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetAvailableSTDERR (buf, buf_size);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nub_size_t
|
|
|
|
DNBProcessGetStopCount (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->StopCount();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-02-25 06:24:29 +08:00
|
|
|
uint32_t
|
|
|
|
DNBProcessGetCPUType (nub_process_t pid)
|
|
|
|
{
|
|
|
|
MachProcessSP procSP;
|
|
|
|
if (GetProcessSP (pid, procSP))
|
|
|
|
return procSP->GetCPUType ();
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
nub_bool_t
|
|
|
|
DNBResolveExecutablePath (const char *path, char *resolved_path, size_t resolved_path_size)
|
|
|
|
{
|
|
|
|
if (path == NULL || path[0] == '\0')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
char max_path[PATH_MAX];
|
|
|
|
std::string result;
|
|
|
|
CFString::GlobPath(path, result);
|
|
|
|
|
|
|
|
if (result.empty())
|
|
|
|
result = path;
|
|
|
|
|
|
|
|
if (realpath(path, max_path))
|
|
|
|
{
|
|
|
|
// Found the path relatively...
|
|
|
|
::strncpy(resolved_path, max_path, resolved_path_size);
|
|
|
|
return strlen(resolved_path) + 1 < resolved_path_size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Not a relative path, check the PATH environment variable if the
|
|
|
|
const char *PATH = getenv("PATH");
|
|
|
|
if (PATH)
|
|
|
|
{
|
|
|
|
const char *curr_path_start = PATH;
|
|
|
|
const char *curr_path_end;
|
|
|
|
while (curr_path_start && *curr_path_start)
|
|
|
|
{
|
|
|
|
curr_path_end = strchr(curr_path_start, ':');
|
|
|
|
if (curr_path_end == NULL)
|
|
|
|
{
|
|
|
|
result.assign(curr_path_start);
|
|
|
|
curr_path_start = NULL;
|
|
|
|
}
|
|
|
|
else if (curr_path_end > curr_path_start)
|
|
|
|
{
|
|
|
|
size_t len = curr_path_end - curr_path_start;
|
|
|
|
result.assign(curr_path_start, len);
|
|
|
|
curr_path_start += len + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
|
|
|
|
result += '/';
|
|
|
|
result += path;
|
|
|
|
struct stat s;
|
|
|
|
if (stat(result.c_str(), &s) == 0)
|
|
|
|
{
|
|
|
|
::strncpy(resolved_path, result.c_str(), resolved_path_size);
|
|
|
|
return result.size() + 1 < resolved_path_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-11-18 13:57:03 +08:00
|
|
|
|
|
|
|
void
|
|
|
|
DNBInitialize()
|
|
|
|
{
|
|
|
|
DNBLogThreadedIf (LOG_PROCESS, "DNBInitialize ()");
|
|
|
|
#if defined (__i386__) || defined (__x86_64__)
|
|
|
|
DNBArchImplI386::Initialize();
|
|
|
|
DNBArchImplX86_64::Initialize();
|
|
|
|
#elif defined (__arm__)
|
|
|
|
DNBArchMachARM::Initialize();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DNBTerminate()
|
|
|
|
{
|
|
|
|
}
|
2010-12-02 06:45:40 +08:00
|
|
|
|
|
|
|
nub_bool_t
|
|
|
|
DNBSetArchitecture (const char *arch)
|
|
|
|
{
|
|
|
|
if (arch && arch[0])
|
|
|
|
{
|
|
|
|
if (strcasecmp (arch, "i386") == 0)
|
|
|
|
return DNBArchProtocol::SetArchitecture (CPU_TYPE_I386);
|
|
|
|
else if (strcasecmp (arch, "x86_64") == 0)
|
|
|
|
return DNBArchProtocol::SetArchitecture (CPU_TYPE_X86_64);
|
|
|
|
else if (strstr (arch, "arm") == arch)
|
|
|
|
return DNBArchProtocol::SetArchitecture (CPU_TYPE_ARM);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|