| //===-- DNB.cpp -------------------------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Created by Greg Clayton on 3/23/07. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "DNB.h" | |
| #include <cinttypes> | |
| #include <csignal> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <libproc.h> | |
| #include <map> | |
| #include <mutex> | |
| #include <sys/resource.h> | |
| #include <sys/stat.h> | |
| #include <sys/sysctl.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include <vector> | |
| #if defined(__APPLE__) | |
| #include <pthread.h> | |
| #include <sched.h> | |
| #endif | |
| #define TRY_KQUEUE 1 | |
| #ifdef TRY_KQUEUE | |
| #include <sys/event.h> | |
| #include <sys/time.h> | |
| #ifdef NOTE_EXIT_DETAIL | |
| #define USE_KQUEUE | |
| #endif | |
| #endif | |
| #include "CFBundle.h" | |
| #include "CFString.h" | |
| #include "DNBDataRef.h" | |
| #include "DNBLog.h" | |
| #include "DNBThreadResumeActions.h" | |
| #include "DNBTimer.h" | |
| #include "MacOSX/Genealogy.h" | |
| #include "MacOSX/MachProcess.h" | |
| #include "MacOSX/MachTask.h" | |
| #include "MacOSX/ThreadInfo.h" | |
| #include "RNBRemote.h" | |
| typedef std::shared_ptr<MachProcess> MachProcessSP; | |
| typedef std::map<nub_process_t, MachProcessSP> ProcessMap; | |
| typedef ProcessMap::iterator ProcessMapIter; | |
| typedef ProcessMap::const_iterator ProcessMapConstIter; | |
| 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 std::mutex g_process_map_mutex; | |
| std::lock_guard<std::mutex> guard(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; | |
| } | |
| #ifdef USE_KQUEUE | |
| void *kqueue_thread(void *arg) { | |
| int kq_id = (int)(intptr_t)arg; | |
| #if defined(__APPLE__) | |
| pthread_setname_np("kqueue thread"); | |
| #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) | |
| struct sched_param thread_param; | |
| int thread_sched_policy; | |
| if (pthread_getschedparam(pthread_self(), &thread_sched_policy, | |
| &thread_param) == 0) { | |
| thread_param.sched_priority = 47; | |
| pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param); | |
| } | |
| #endif | |
| #endif | |
| struct kevent death_event; | |
| while (true) { | |
| int n_events = kevent(kq_id, NULL, 0, &death_event, 1, NULL); | |
| if (n_events == -1) { | |
| if (errno == EINTR) | |
| continue; | |
| else { | |
| DNBLogError("kqueue failed with error: (%d): %s", errno, | |
| strerror(errno)); | |
| return NULL; | |
| } | |
| } else if (death_event.flags & EV_ERROR) { | |
| int error_no = static_cast<int>(death_event.data); | |
| const char *error_str = strerror(error_no); | |
| if (error_str == NULL) | |
| error_str = "Unknown error"; | |
| DNBLogError("Failed to initialize kqueue event: (%d): %s", error_no, | |
| error_str); | |
| return NULL; | |
| } else { | |
| int status; | |
| const pid_t pid = (pid_t)death_event.ident; | |
| const pid_t child_pid = waitpid(pid, &status, 0); | |
| bool exited = false; | |
| int signal = 0; | |
| int exit_status = 0; | |
| if (WIFSTOPPED(status)) { | |
| signal = WSTOPSIG(status); | |
| DNBLogThreadedIf(LOG_PROCESS, "waitpid (%i) -> STOPPED (signal = %i)", | |
| child_pid, signal); | |
| } else if (WIFEXITED(status)) { | |
| exit_status = WEXITSTATUS(status); | |
| exited = true; | |
| DNBLogThreadedIf(LOG_PROCESS, "waitpid (%i) -> EXITED (status = %i)", | |
| child_pid, exit_status); | |
| } else if (WIFSIGNALED(status)) { | |
| signal = WTERMSIG(status); | |
| if (child_pid == abs(pid)) { | |
| DNBLogThreadedIf(LOG_PROCESS, | |
| "waitpid (%i) -> SIGNALED and EXITED (signal = %i)", | |
| child_pid, signal); | |
| char exit_info[64]; | |
| ::snprintf(exit_info, sizeof(exit_info), | |
| "Terminated due to signal %i", signal); | |
| DNBProcessSetExitInfo(child_pid, exit_info); | |
| exited = true; | |
| exit_status = INT8_MAX; | |
| } else { | |
| DNBLogThreadedIf(LOG_PROCESS, | |
| "waitpid (%i) -> SIGNALED (signal = %i)", child_pid, | |
| signal); | |
| } | |
| } | |
| if (exited) { | |
| if (death_event.data & NOTE_EXIT_MEMORY) | |
| DNBProcessSetExitInfo(child_pid, "Terminated due to memory issue"); | |
| else if (death_event.data & NOTE_EXIT_DECRYPTFAIL) | |
| DNBProcessSetExitInfo(child_pid, "Terminated due to decrypt failure"); | |
| else if (death_event.data & NOTE_EXIT_CSERROR) | |
| DNBProcessSetExitInfo(child_pid, | |
| "Terminated due to code signing error"); | |
| DNBLogThreadedIf( | |
| LOG_PROCESS, | |
| "waitpid_process_thread (): setting exit status for pid = %i to %i", | |
| child_pid, exit_status); | |
| DNBProcessSetExitStatus(child_pid, status); | |
| return NULL; | |
| } | |
| } | |
| } | |
| } | |
| static bool spawn_kqueue_thread(pid_t pid) { | |
| pthread_t thread; | |
| int kq_id; | |
| kq_id = kqueue(); | |
| if (kq_id == -1) { | |
| DNBLogError("Could not get kqueue for pid = %i.", pid); | |
| return false; | |
| } | |
| struct kevent reg_event; | |
| EV_SET(®_event, pid, EVFILT_PROC, EV_ADD, | |
| NOTE_EXIT | NOTE_EXITSTATUS | NOTE_EXIT_DETAIL, 0, NULL); | |
| // Register the event: | |
| int result = kevent(kq_id, ®_event, 1, NULL, 0, NULL); | |
| if (result != 0) { | |
| DNBLogError( | |
| "Failed to register kqueue NOTE_EXIT event for pid %i, error: %d.", pid, | |
| result); | |
| return false; | |
| } | |
| int ret = | |
| ::pthread_create(&thread, NULL, kqueue_thread, (void *)(intptr_t)kq_id); | |
| // pthread_create returns 0 if successful | |
| if (ret == 0) { | |
| ::pthread_detach(thread); | |
| return true; | |
| } | |
| return false; | |
| } | |
| #endif // #if USE_KQUEUE | |
| static void *waitpid_thread(void *arg) { | |
| const pid_t pid = (pid_t)(intptr_t)arg; | |
| int status; | |
| #if defined(__APPLE__) | |
| pthread_setname_np("waitpid thread"); | |
| #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) | |
| struct sched_param thread_param; | |
| int thread_sched_policy; | |
| if (pthread_getschedparam(pthread_self(), &thread_sched_policy, | |
| &thread_param) == 0) { | |
| thread_param.sched_priority = 47; | |
| pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param); | |
| } | |
| #endif | |
| #endif | |
| while (true) { | |
| pid_t child_pid = waitpid(pid, &status, 0); | |
| DNBLogThreadedIf(LOG_PROCESS, "waitpid_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_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_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) { | |
| #ifdef USE_KQUEUE | |
| bool success = spawn_kqueue_thread(pid); | |
| if (success) | |
| return true; | |
| #endif | |
| pthread_t thread; | |
| int ret = | |
| ::pthread_create(&thread, NULL, waitpid_thread, (void *)(intptr_t)pid); | |
| // pthread_create returns 0 if successful | |
| if (ret == 0) { | |
| ::pthread_detach(thread); | |
| return true; | |
| } | |
| return false; | |
| } | |
| nub_process_t DNBProcessLaunch( | |
| RNBContext *ctx, const char *path, char const *argv[], const char *envp[], | |
| const char *working_directory, // NULL => don't change, non-NULL => set | |
| // working directory for inferior to this | |
| const char *stdin_path, const char *stdout_path, const char *stderr_path, | |
| bool no_stdio, int disable_aslr, const char *event_data, char *err_str, | |
| size_t err_len) { | |
| 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 = " | |
| "%llu) called...", | |
| __FUNCTION__, path, static_cast<void *>(argv), | |
| static_cast<void *>(envp), working_directory, stdin_path, | |
| stdout_path, stderr_path, no_stdio, ctx->LaunchFlavor(), | |
| disable_aslr, static_cast<void *>(err_str), | |
| static_cast<uint64_t>(err_len)); | |
| 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; | |
| pid_t pid = processSP->LaunchForDebug( | |
| path, argv, envp, working_directory, stdin_path, stdout_path, | |
| stderr_path, no_stdio, ctx->LaunchFlavor(), disable_aslr, event_data, | |
| ctx->GetIgnoredExceptions(), launch_err); | |
| if (err_str) { | |
| *err_str = '\0'; | |
| if (launch_err.Fail()) { | |
| const char *launch_err_str = launch_err.AsString(); | |
| if (launch_err_str) { | |
| strlcpy(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. | |
| // Kill our process otherwise it will be stopped at the entry | |
| // point and get reparented to someone else and never go away. | |
| DNBLog("Could not get task port for process, sending SIGKILL and " | |
| "exiting."); | |
| kill(pid, SIGKILL); | |
| 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 { | |
| const char *ent_name = | |
| #if TARGET_OS_OSX | |
| "com.apple.security.get-task-allow"; | |
| #else | |
| "get-task-allow"; | |
| #endif | |
| ::snprintf(err_str, err_len, | |
| "failed to get the task for process %i: this likely " | |
| "means the process cannot be debugged, either because " | |
| "it's a system process or because the process is " | |
| "missing the %s entitlement.", | |
| pid, ent_name); | |
| } | |
| } | |
| } else { | |
| bool res = AddProcessToMap(pid, processSP); | |
| UNUSED_IF_ASSERT_DISABLED(res); | |
| assert(res && "Couldn't add process to map!"); | |
| return pid; | |
| } | |
| } | |
| } | |
| return INVALID_NUB_PROCESS; | |
| } | |
| // If there is one process with a given name, return the pid for that process. | |
| nub_process_t DNBProcessGetPIDByName(const char *name) { | |
| std::vector<struct kinfo_proc> matching_proc_infos; | |
| size_t num_matching_proc_infos = | |
| GetAllInfosMatchingName(name, matching_proc_infos); | |
| if (num_matching_proc_infos == 1) { | |
| return matching_proc_infos[0].kp_proc.p_pid; | |
| } | |
| return INVALID_NUB_PROCESS; | |
| } | |
| nub_process_t DNBProcessAttachByName(const char *name, struct timespec *timeout, | |
| const RNBContext::IgnoredExceptions | |
| &ignored_exceptions, 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; | |
| } | |
| if (num_matching_proc_infos > 1) { | |
| DNBLogError("error: %llu processes match '%s':\n", | |
| (uint64_t)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; | |
| } | |
| return DNBProcessAttach(matching_proc_infos[0].kp_proc.p_pid, timeout, | |
| ignored_exceptions, err_str, err_len); | |
| } | |
| nub_process_t DNBProcessAttach(nub_process_t attach_pid, | |
| struct timespec *timeout, | |
| const RNBContext::IgnoredExceptions | |
| &ignored_exceptions, | |
| char *err_str, size_t err_len) { | |
| if (err_str && err_len > 0) | |
| err_str[0] = '\0'; | |
| if (getenv("LLDB_DEBUGSERVER_PATH") == NULL) { | |
| int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, | |
| static_cast<int>(attach_pid)}; | |
| struct kinfo_proc processInfo; | |
| size_t bufsize = sizeof(processInfo); | |
| if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, | |
| &bufsize, NULL, 0) == 0 && | |
| bufsize > 0) { | |
| if ((processInfo.kp_proc.p_flag & P_TRANSLATED) == P_TRANSLATED) { | |
| const char *translated_debugserver = | |
| "/Library/Apple/usr/libexec/oah/debugserver"; | |
| char fdstr[16]; | |
| char pidstr[16]; | |
| extern int communication_fd; | |
| if (communication_fd == -1) { | |
| DNBLogError("Trying to attach to a translated process with the " | |
| "native debugserver, exiting...\n"); | |
| return INVALID_NUB_PROCESS_ARCH; | |
| } | |
| struct stat st; | |
| if (::stat(translated_debugserver, &st) != 0) { | |
| DNBLogError("Translated inferior process but Rosetta debugserver not " | |
| "found at %s", | |
| translated_debugserver); | |
| return INVALID_NUB_PROCESS_ARCH; | |
| } | |
| snprintf(fdstr, sizeof(fdstr), "--fd=%d", communication_fd); | |
| snprintf(pidstr, sizeof(pidstr), "--attach=%d", attach_pid); | |
| execl(translated_debugserver, translated_debugserver, "--native-regs", | |
| "--setsid", fdstr, "--handoff-attach-from-native", pidstr, | |
| (char *)0); | |
| DNBLogThreadedIf(LOG_PROCESS, "Failed to launch debugserver for " | |
| "translated process: ", errno, strerror(errno)); | |
| __builtin_trap(); | |
| } | |
| } | |
| } | |
| if (DNBDebugserverIsTranslated()) { | |
| return INVALID_NUB_PROCESS_ARCH; | |
| } | |
| pid_t pid = INVALID_NUB_PROCESS; | |
| MachProcessSP processSP(new MachProcess); | |
| if (processSP.get()) { | |
| DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) attaching to pid %d...", | |
| attach_pid); | |
| pid = | |
| processSP->AttachForDebug(attach_pid, ignored_exceptions, err_str, | |
| err_len); | |
| if (pid != INVALID_NUB_PROCESS) { | |
| bool res = AddProcessToMap(pid, processSP); | |
| UNUSED_IF_ASSERT_DISABLED(res); | |
| assert(res && "Couldn't add process to map!"); | |
| spawn_waitpid_thread(pid); | |
| } | |
| } | |
| while (pid != INVALID_NUB_PROCESS) { | |
| // Wait for process to start up and hit entry point | |
| DNBLogThreadedIf(LOG_PROCESS, "%s DNBProcessWaitForEvent (%4.4x, " | |
| "eEventProcessRunningStateChanged | " | |
| "eEventProcessStoppedStateChanged, true, " | |
| "INFINITE)...", | |
| __FUNCTION__, pid); | |
| nub_event_t set_events = | |
| DNBProcessWaitForEvents(pid, eEventProcessRunningStateChanged | | |
| eEventProcessStoppedStateChanged, | |
| true, timeout); | |
| DNBLogThreadedIf(LOG_PROCESS, "%s DNBProcessWaitForEvent (%4.4x, " | |
| "eEventProcessRunningStateChanged | " | |
| "eEventProcessStoppedStateChanged, true, " | |
| "INFINITE) => 0x%8.8x", | |
| __FUNCTION__, pid, set_events); | |
| if (set_events == 0) { | |
| if (err_str && err_len > 0) | |
| snprintf(err_str, err_len, | |
| "attached to process, but could not pause execution; attach " | |
| "failed"); | |
| 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) { | |
| 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; | |
| } | |
| } | |
| DNBProcessResetEvents(pid, set_events); | |
| } | |
| } | |
| return INVALID_NUB_PROCESS; | |
| } | |
| size_t DNBGetAllInfos(std::vector<struct kinfo_proc> &proc_infos) { | |
| size_t size = 0; | |
| 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(); | |
| } | |
| JSONGenerator::ObjectSP DNBGetDyldProcessState(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetDyldProcessState(); | |
| } | |
| return {}; | |
| } | |
| 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++; | |
| const size_t process_name_len = strlen(process_name); | |
| std::vector<struct kinfo_proc> proc_infos; | |
| const size_t num_proc_infos = DNBGetAllInfos(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(process_name, proc_infos[i].kp_proc.p_comm, | |
| MAXCOMLEN) == 0) { | |
| if (process_name_len > MAXCOMLEN) { | |
| // We found a matching process name whose first MAXCOMLEN | |
| // characters match, but there is more to the name than | |
| // this. We need to get the full process name. Use proc_pidpath, | |
| // which will get | |
| // us the full path to the executed process. | |
| char proc_path_buf[PATH_MAX]; | |
| int return_val = proc_pidpath(proc_infos[i].kp_proc.p_pid, | |
| proc_path_buf, PATH_MAX); | |
| if (return_val > 0) { | |
| // Okay, now search backwards from that to see if there is a | |
| // slash in the name. Note, even though we got all the args we | |
| // don't care | |
| // because the list data is just a bunch of concatenated null | |
| // terminated strings | |
| // so strrchr will start from the end of argv0. | |
| const char *argv_basename = strrchr(proc_path_buf, '/'); | |
| if (argv_basename) { | |
| // Skip the '/' | |
| ++argv_basename; | |
| } else { | |
| // We didn't find a directory delimiter in the process argv[0], | |
| // just use what was in there | |
| argv_basename = proc_path_buf; | |
| } | |
| if (argv_basename) { | |
| if (::strncasecmp(process_name, argv_basename, PATH_MAX) == 0) { | |
| matching_proc_infos.push_back(proc_infos[i]); | |
| } | |
| } | |
| } | |
| } else { | |
| // 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 | |
| DNBProcessAttachWait(RNBContext *ctx, const char *waitfor_process_name, | |
| bool ignore_existing, struct timespec *timeout_abstime, | |
| useconds_t waitfor_interval, char *err_str, size_t err_len, | |
| DNBShouldCancelCallback should_cancel_callback, | |
| void *callback_data) { | |
| DNBError prepare_error; | |
| std::vector<struct kinfo_proc> exclude_proc_infos; | |
| size_t num_exclude_proc_infos; | |
| nub_launch_flavor_t launch_flavor = ctx->LaunchFlavor(); | |
| // 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) { | |
| if (ignore_existing) | |
| num_exclude_proc_infos = | |
| GetAllInfosMatchingName(waitfor_process_name, exclude_proc_infos); | |
| else | |
| num_exclude_proc_infos = 0; | |
| } | |
| 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, launch_flavor); | |
| 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; | |
| } | |
| // Now we're going to wait a while before polling again. But we also | |
| // need to check whether we've gotten an event from the debugger | |
| // telling us to interrupt the wait. So we'll use the wait for a possible | |
| // next event to also be our short pause... | |
| struct timespec short_timeout; | |
| DNBTimer::OffsetTimeOfDay(&short_timeout, 0, waitfor_interval); | |
| uint32_t event_mask = RNBContext::event_read_packet_available | |
| | RNBContext::event_read_thread_exiting; | |
| nub_event_t set_events = ctx->Events().WaitForSetEvents(event_mask, | |
| &short_timeout); | |
| if (set_events & RNBContext::event_read_packet_available) { | |
| // If we get any packet from the debugger while waiting on the async, | |
| // it has to be telling us to interrupt. So always exit here. | |
| // Over here in DNB land we can see that there was a packet, but all | |
| // the methods to actually handle it are protected. It's not worth | |
| // rearranging all that just to get which packet we were sent... | |
| DNBLogError("Interrupted by packet while waiting for '%s' to appear.\n", | |
| waitfor_process_name); | |
| break; | |
| } | |
| if (set_events & RNBContext::event_read_thread_exiting) { | |
| // The packet thread is shutting down, get out of here... | |
| DNBLogError("Interrupted by packet thread shutdown while waiting for " | |
| "%s to appear.\n", waitfor_process_name); | |
| break; | |
| } | |
| } | |
| } | |
| if (waitfor_pid != INVALID_NUB_PROCESS) { | |
| DNBLogThreadedIf(LOG_PROCESS, "Attaching to %s with pid %i...\n", | |
| waitfor_process_name, waitfor_pid); | |
| // In some cases, we attempt to attach during the transition from | |
| // /usr/lib/dyld to the dyld in the shared cache. If that happens, we may | |
| // end up in a state where there is no dyld in the process and from there | |
| // the debugging session is doomed. | |
| // In an attempt to make this scenario much less likely, we sleep | |
| // for an additional `waitfor_interval` number of microseconds before | |
| // attaching. | |
| ::usleep(waitfor_interval); | |
| waitfor_pid = DNBProcessAttach(waitfor_pid, timeout_abstime, | |
| ctx->GetIgnoredExceptions(), err_str, | |
| err_len); | |
| } | |
| bool success = waitfor_pid != INVALID_NUB_PROCESS; | |
| MachProcess::CleanupAfterAttach(attach_token, launch_flavor, success, | |
| prepare_error); | |
| return waitfor_pid; | |
| } | |
| nub_bool_t DNBProcessDetach(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| const bool remove = true; | |
| DNBLogThreaded( | |
| "Disabling breakpoints and watchpoints, and detaching from %d.", pid); | |
| procSP->DisableAllBreakpoints(remove); | |
| procSP->DisableAllWatchpoints(remove); | |
| 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 DNBProcessInterrupt(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->Interrupt(); | |
| return false; | |
| } | |
| nub_bool_t DNBProcessSendEvent(nub_process_t pid, const char *event) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| // FIXME: Do something with the error... | |
| DNBError send_error; | |
| return procSP->SendEvent(event, send_error); | |
| } | |
| 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 *DNBProcessGetExitInfo(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetExitInfo(); | |
| } | |
| return NULL; | |
| } | |
| nub_bool_t DNBProcessSetExitInfo(nub_process_t pid, const char *info) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| procSP->SetExitInfo(info); | |
| 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) { | |
| case eStateInvalid: | |
| return "Invalid"; | |
| 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 ???"; | |
| } | |
| Genealogy::ThreadActivitySP DNBGetGenealogyInfoForThread(nub_process_t pid, | |
| nub_thread_t tid, | |
| bool &timed_out) { | |
| Genealogy::ThreadActivitySP thread_activity_sp; | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| thread_activity_sp = procSP->GetGenealogyInfoForThread(tid, timed_out); | |
| return thread_activity_sp; | |
| } | |
| Genealogy::ProcessExecutableInfoSP DNBGetGenealogyImageInfo(nub_process_t pid, | |
| size_t idx) { | |
| Genealogy::ProcessExecutableInfoSP image_info_sp; | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| image_info_sp = procSP->GetGenealogyImageInfo(idx); | |
| } | |
| return image_info_sp; | |
| } | |
| ThreadInfo::QoS DNBGetRequestedQoSForThread(nub_process_t pid, nub_thread_t tid, | |
| nub_addr_t tsd, | |
| uint64_t dti_qos_class_index) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetRequestedQoS(tid, tsd, dti_qos_class_index); | |
| } | |
| return ThreadInfo::QoS(); | |
| } | |
| nub_addr_t DNBGetPThreadT(nub_process_t pid, nub_thread_t tid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetPThreadT(tid); | |
| } | |
| return INVALID_NUB_ADDRESS; | |
| } | |
| nub_addr_t DNBGetDispatchQueueT(nub_process_t pid, nub_thread_t tid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetDispatchQueueT(tid); | |
| } | |
| return INVALID_NUB_ADDRESS; | |
| } | |
| nub_addr_t | |
| DNBGetTSDAddressForThread(nub_process_t pid, nub_thread_t tid, | |
| uint64_t plo_pthread_tsd_base_address_offset, | |
| uint64_t plo_pthread_tsd_base_offset, | |
| uint64_t plo_pthread_tsd_entry_size) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetTSDAddressForThread( | |
| tid, plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset, | |
| plo_pthread_tsd_entry_size); | |
| } | |
| return INVALID_NUB_ADDRESS; | |
| } | |
| std::optional<std::pair<cpu_type_t, cpu_subtype_t>> | |
| DNBGetMainBinaryCPUTypes(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetMainBinaryCPUTypes(pid); | |
| return {}; | |
| } | |
| JSONGenerator::ObjectSP | |
| DNBGetAllLoadedLibrariesInfos(nub_process_t pid, | |
| DNBBinaryInformationLevel info_level) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetAllLoadedLibrariesInfos(pid, info_level); | |
| } | |
| return JSONGenerator::ObjectSP(); | |
| } | |
| JSONGenerator::ObjectSP | |
| DNBGetLibrariesInfoForAddresses(nub_process_t pid, | |
| DNBBinaryInformationLevel info_level, | |
| std::vector<uint64_t> &macho_addresses) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetLibrariesInfoForAddresses(pid, info_level, | |
| macho_addresses); | |
| } | |
| return JSONGenerator::ObjectSP(); | |
| } | |
| JSONGenerator::ObjectSP DNBGetSharedCacheInfo(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| return procSP->GetInferiorSharedCacheInfo(pid); | |
| } | |
| return JSONGenerator::ObjectSP(); | |
| } | |
| 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); | |
| } | |
| // Breakpoints | |
| nub_bool_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) != NULL; | |
| return false; | |
| } | |
| nub_bool_t DNBBreakpointClear(nub_process_t pid, nub_addr_t addr) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->DisableBreakpoint(addr, true); | |
| return false; // Failed | |
| } | |
| // Watchpoints | |
| nub_bool_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) != NULL; | |
| return false; | |
| } | |
| nub_bool_t DNBWatchpointClear(nub_process_t pid, nub_addr_t addr) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->DisableWatchpoint(addr, true); | |
| return false; // Failed | |
| } | |
| // Return the number of supported hardware watchpoints. | |
| uint32_t DNBWatchpointGetNumSupportedHWP(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetNumSupportedHardwareWatchpoints(); | |
| return 0; | |
| } | |
| // 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; | |
| } | |
| uint64_t DNBProcessMemoryReadInteger(nub_process_t pid, nub_addr_t addr, | |
| nub_size_t integer_size, | |
| uint64_t fail_value) { | |
| union Integers { | |
| uint8_t u8; | |
| uint16_t u16; | |
| uint32_t u32; | |
| uint64_t u64; | |
| }; | |
| if (integer_size <= sizeof(uint64_t)) { | |
| Integers ints; | |
| if (DNBProcessMemoryRead(pid, addr, integer_size, &ints) == integer_size) { | |
| switch (integer_size) { | |
| case 1: | |
| return ints.u8; | |
| case 2: | |
| return ints.u16; | |
| case 3: | |
| return ints.u32 & 0xffffffu; | |
| case 4: | |
| return ints.u32; | |
| case 5: | |
| return ints.u32 & 0x000000ffffffffffull; | |
| case 6: | |
| return ints.u32 & 0x0000ffffffffffffull; | |
| case 7: | |
| return ints.u32 & 0x00ffffffffffffffull; | |
| case 8: | |
| return ints.u64; | |
| } | |
| } | |
| } | |
| return fail_value; | |
| } | |
| nub_addr_t DNBProcessMemoryReadPointer(nub_process_t pid, nub_addr_t addr) { | |
| cpu_type_t cputype = DNBProcessGetCPUType(pid); | |
| if (cputype) { | |
| const nub_size_t pointer_size = (cputype & CPU_ARCH_ABI64) ? 8 : 4; | |
| return DNBProcessMemoryReadInteger(pid, addr, pointer_size, 0); | |
| } | |
| return 0; | |
| } | |
| std::string DNBProcessMemoryReadCString(nub_process_t pid, nub_addr_t addr) { | |
| std::string cstr; | |
| char buffer[256]; | |
| const nub_size_t max_buffer_cstr_length = sizeof(buffer) - 1; | |
| buffer[max_buffer_cstr_length] = '\0'; | |
| nub_size_t length = 0; | |
| nub_addr_t curr_addr = addr; | |
| do { | |
| nub_size_t bytes_read = | |
| DNBProcessMemoryRead(pid, curr_addr, max_buffer_cstr_length, buffer); | |
| if (bytes_read == 0) | |
| break; | |
| length = strlen(buffer); | |
| cstr.append(buffer, length); | |
| curr_addr += length; | |
| } while (length == max_buffer_cstr_length); | |
| return cstr; | |
| } | |
| std::string DNBProcessMemoryReadCStringFixed(nub_process_t pid, nub_addr_t addr, | |
| nub_size_t fixed_length) { | |
| std::string cstr; | |
| char buffer[fixed_length + 1]; | |
| buffer[fixed_length] = '\0'; | |
| nub_size_t bytes_read = DNBProcessMemoryRead(pid, addr, fixed_length, buffer); | |
| if (bytes_read > 0) | |
| cstr.assign(buffer); | |
| return cstr; | |
| } | |
| // 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; | |
| } | |
| // Find attributes of the memory region that contains ADDR for process PID, | |
| // if possible, and return a string describing those attributes. | |
| // | |
| // Returns 1 if we could find attributes for this region and OUTBUF can | |
| // be sent to the remote debugger. | |
| // | |
| // Returns 0 if we couldn't find the attributes for a region of memory at | |
| // that address and OUTBUF should not be sent. | |
| // | |
| // Returns -1 if this platform cannot look up information about memory regions | |
| // or if we do not yet have a valid launched process. | |
| // | |
| int DNBProcessMemoryRegionInfo(nub_process_t pid, nub_addr_t addr, | |
| DNBRegionInfo *region_info) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->Task().GetMemoryRegionInfo(addr, region_info); | |
| return -1; | |
| } | |
| nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr, | |
| nub_size_t size, | |
| std::vector<uint8_t> &tags) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->Task().GetMemoryTags(addr, size, tags); | |
| return false; | |
| } | |
| std::string DNBProcessGetProfileData(nub_process_t pid, | |
| DNBProfileDataScanType scanType) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->Task().GetProfileData(scanType); | |
| return std::string(""); | |
| } | |
| nub_bool_t DNBProcessSetEnableAsyncProfiling(nub_process_t pid, | |
| nub_bool_t enable, | |
| uint64_t interval_usec, | |
| DNBProfileDataScanType scan_type) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| procSP->SetEnableAsyncProfiling(enable, interval_usec, scan_type); | |
| return true; | |
| } | |
| return false; | |
| } | |
| // 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; | |
| } | |
| // Get the mach port number of the current thread. | |
| nub_thread_t DNBProcessGetCurrentThreadMachPort(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetCurrentThreadMachPort(); | |
| 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; | |
| } | |
| // Do whatever is needed to sync the thread's register state with it's kernel | |
| // values. | |
| nub_bool_t DNBProcessSyncThreadState(nub_process_t pid, nub_thread_t tid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->SyncThreadState(tid); | |
| return false; | |
| } | |
| 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; | |
| } | |
| std::optional<std::string> | |
| DNBGetDeploymentInfo(nub_process_t pid, bool is_executable, | |
| const struct load_command &lc, | |
| uint64_t load_command_address, uint32_t &major_version, | |
| uint32_t &minor_version, uint32_t &patch_version) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) { | |
| // FIXME: This doesn't return the correct result when xctest (a | |
| // macOS binary) is loaded with the macCatalyst dyld platform | |
| // override. The image info corrects for this, but qProcessInfo | |
| // will return what is in the binary. | |
| auto info = | |
| procSP->GetDeploymentInfo(lc, load_command_address, is_executable); | |
| major_version = info.major_version; | |
| minor_version = info.minor_version; | |
| patch_version = info.patch_version; | |
| // MachProcess::DeploymentInfo has a bool operator to tell whether we have | |
| // set the platform. If that's not true, don't report out the platform: | |
| if (!info) | |
| return {}; | |
| return procSP->GetPlatformString(info.platform); | |
| } | |
| return {}; | |
| } | |
| // 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; | |
| } | |
| uint32_t DNBGetRegisterCPUType() { | |
| return DNBArchProtocol::GetRegisterCPUType(); | |
| } | |
| // Get the register set information for a specific thread. | |
| const DNBRegisterSetInfo *DNBGetRegisterSetInfo(nub_size_t *num_reg_sets) { | |
| return DNBArchProtocol::GetRegisterSetInfo(num_reg_sets); | |
| } | |
| // 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; | |
| } | |
| uint32_t DNBThreadSaveRegisterState(nub_process_t pid, nub_thread_t tid) { | |
| if (tid != INVALID_NUB_THREAD) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetThreadList().SaveRegisterState(tid); | |
| } | |
| return 0; | |
| } | |
| nub_bool_t DNBThreadRestoreRegisterState(nub_process_t pid, nub_thread_t tid, | |
| uint32_t save_id) { | |
| if (tid != INVALID_NUB_THREAD) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetThreadList().RestoreRegisterState(tid, save_id); | |
| } | |
| return false; | |
| } | |
| // 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 DNBProcessGetAvailableProfileData(nub_process_t pid, char *buf, | |
| nub_size_t buf_size) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetAsyncProfileData(buf, buf_size); | |
| return 0; | |
| } | |
| nub_size_t DNBProcessGetStopCount(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->StopCount(); | |
| return 0; | |
| } | |
| uint32_t DNBProcessGetCPUType(nub_process_t pid) { | |
| MachProcessSP procSP; | |
| if (GetProcessSP(pid, procSP)) | |
| return procSP->GetCPUType(); | |
| return 0; | |
| } | |
| bool DNBProcessAddrSize(nub_process_t pid, int &addr_size) { | |
| addr_size = 0; | |
| // A single debugserver instance only attaches to one process, | |
| // so we can save it in a global static once it's known. | |
| static int g_addr_size = 0; | |
| if (g_addr_size == 0) { | |
| uint32_t cputype = DNBProcessGetCPUType(pid); | |
| // unable to get process cpu type | |
| if (cputype == 0) | |
| return false; | |
| if (cputype == CPU_TYPE_ARM64_32) | |
| g_addr_size = 4; | |
| else | |
| g_addr_size = 8; | |
| } | |
| addr_size = g_addr_size; | |
| return true; | |
| } | |
| 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; | |
| struct stat path_stat; | |
| if (::stat(path, &path_stat) == 0) { | |
| if ((path_stat.st_mode & S_IFMT) == S_IFDIR) { | |
| CFBundle bundle(path); | |
| CFReleaser<CFURLRef> url(bundle.CopyExecutableURL()); | |
| if (url.get()) { | |
| if (::CFURLGetFileSystemRepresentation( | |
| url.get(), true, (UInt8 *)resolved_path, resolved_path_size)) | |
| return true; | |
| } | |
| } | |
| } | |
| if (realpath(path, max_path)) { | |
| // Found the path relatively... | |
| ::strlcpy(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) { | |
| ::strlcpy(resolved_path, result.c_str(), resolved_path_size); | |
| return result.size() + 1 < resolved_path_size; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| bool DNBGetOSVersionNumbers(uint64_t *major, uint64_t *minor, uint64_t *patch) { | |
| return MachProcess::GetOSVersionNumbers(major, minor, patch); | |
| } | |
| std::string DNBGetMacCatalystVersionString() { | |
| return MachProcess::GetMacCatalystVersionString(); | |
| } | |
| void DNBInitialize() { | |
| DNBLogThreadedIf(LOG_PROCESS, "DNBInitialize ()"); | |
| #if defined(__i386__) || defined(__x86_64__) | |
| DNBArchImplX86_64::Initialize(); | |
| #elif defined(__arm__) || defined(__arm64__) || defined(__aarch64__) | |
| DNBArchMachARM64::Initialize(); | |
| #endif | |
| } | |
| void DNBTerminate() {} | |
| 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, | |
| CPU_SUBTYPE_X86_64_ALL); | |
| else if (strcasecmp(arch, "x86_64h") == 0) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_X86_64, | |
| CPU_SUBTYPE_X86_64_H); | |
| else if (strstr(arch, "arm64_32") == arch || | |
| strstr(arch, "aarch64_32") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM64_32); | |
| else if (strstr(arch, "arm64e") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM64, | |
| CPU_SUBTYPE_ARM64E); | |
| else if (strstr(arch, "arm64") == arch || strstr(arch, "aarch64") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM64, | |
| CPU_SUBTYPE_ARM64_ALL); | |
| #if defined(CPU_SUBTYPE_ARM_V8M_MAIN) | |
| else if (strstr(arch, "armv8m.main") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V8M_MAIN); | |
| #endif | |
| #if defined(CPU_SUBTYPE_ARM_V8M_BASE) | |
| else if (strstr(arch, "armv8m.base") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V8M_BASE); | |
| #endif | |
| #if defined(CPU_SUBTYPE_ARM_V8_1M_MAIN) | |
| else if (strstr(arch, "armv8.1m.main") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V8_1M_MAIN); | |
| #endif | |
| else if (strstr(arch, "armv8") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM64, | |
| CPU_SUBTYPE_ARM64_V8); | |
| else if (strstr(arch, "armv7em") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V7EM); | |
| else if (strstr(arch, "armv7m") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V7M); | |
| else if (strstr(arch, "armv7k") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V7K); | |
| else if (strstr(arch, "armv7s") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V7S); | |
| else if (strstr(arch, "armv7") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7); | |
| else if (strstr(arch, "armv6m") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V6M); | |
| else if (strstr(arch, "armv6") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6); | |
| else if (strstr(arch, "armv5") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V5TEJ); | |
| else if (strstr(arch, "armv4t") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_V4T); | |
| else if (strstr(arch, "arm") == arch) | |
| return DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM, | |
| CPU_SUBTYPE_ARM_ALL); | |
| } | |
| return false; | |
| } | |
| bool DNBDebugserverIsTranslated() { | |
| int ret = 0; | |
| size_t size = sizeof(ret); | |
| if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) | |
| return false; | |
| return ret == 1; | |
| } | |
| bool DNBGetAddressingBits(uint32_t &addressing_bits) { | |
| static uint32_t g_addressing_bits = 0; | |
| static std::once_flag g_once_flag; | |
| std::call_once(g_once_flag, [&](){ | |
| size_t len = sizeof(uint32_t); | |
| if (::sysctlbyname("machdep.virtual_address_size", &g_addressing_bits, &len, | |
| NULL, 0) != 0) { | |
| g_addressing_bits = 0; | |
| } | |
| }); | |
| addressing_bits = g_addressing_bits; | |
| return addressing_bits > 0; | |
| } | |
| nub_addr_t DNBFixAddress(nub_addr_t addr, nub_process_t pid) { | |
| uint32_t addressing_bits = 0; | |
| if (!DNBGetAddressingBits(addressing_bits)) | |
| return addr; | |
| // On arm64_32, no ptrauth bits to clear. | |
| #if !defined(__LP64__) | |
| return addr; | |
| #endif | |
| if (pid != INVALID_NUB_PROCESS) { | |
| cpu_type_t cputype = DNBProcessGetCPUType(pid); | |
| if (cputype == CPU_TYPE_ARM64_32) | |
| return addr; | |
| } | |
| uint64_t mask = ((1ULL << addressing_bits) - 1); | |
| // Normally PAC bit clearing needs to check b55 and either set the | |
| // non-addressing bits, or clear them, but debugserver only | |
| // debugs userland processes in low memory. | |
| return addr & mask; // high bits cleared to 0 | |
| } | |
| nub_process_t DNBGetParentProcessID(nub_process_t child_pid) { | |
| return MachProcess::GetParentProcessID(child_pid); | |
| } | |
| bool DNBProcessIsBeingDebugged(nub_process_t pid) { | |
| return MachProcess::ProcessIsBeingDebugged(pid); | |
| } | |
| bool DNBSharedCacheRegionAddr(nub_process_t pid, nub_addr_t &vmaddr, | |
| nub_addr_t &size) { | |
| vmaddr = size = 0; | |
| // Get the shared cache VM address start and size. | |
| JSONGenerator::ObjectSP sc_info = DNBGetSharedCacheInfo(pid); | |
| if (!sc_info || !sc_info->GetAsDictionary()) | |
| return false; | |
| JSONGenerator::Dictionary *sc_dict = sc_info->GetAsDictionary(); | |
| JSONGenerator::ObjectSP value = | |
| sc_dict->GetValueForKey("shared_cache_base_address"); | |
| if (!value || !value->GetAsInteger()) | |
| return false; | |
| // shared cache not yet set up; we're very early in process launch. | |
| if (value->GetAsInteger()->GetValue() == 0) | |
| return false; | |
| vmaddr = value->GetAsInteger()->GetValue(); | |
| value = sc_dict->GetValueForKey("shared_cache_size"); | |
| if (!value || !value->GetAsInteger()) | |
| return false; | |
| size = value->GetAsInteger()->GetValue(); | |
| if (size == 0) | |
| return false; | |
| return true; | |
| } | |
| bool DNBDyldNotificationFunctionAddr(nub_process_t pid, | |
| nub_addr_t &lldb_image_notifier) { | |
| static nub_addr_t g_notifier_breakpoint_addr = 0; | |
| lldb_image_notifier = 0; | |
| if (g_notifier_breakpoint_addr == 0) { | |
| nub_addr_t sc_vmaddr, sc_size; | |
| if (!DNBSharedCacheRegionAddr(pid, sc_vmaddr, sc_size)) | |
| return false; | |
| int addr_size; | |
| if (!DNBProcessAddrSize(pid, addr_size)) | |
| return false; | |
| // Early return if the dyld_all_image_infos is outside | |
| // the shared cache VM region. | |
| nub_addr_t dyld_all_image_infos = | |
| DNBProcessGetSharedLibraryInfoAddress(pid); | |
| if (dyld_all_image_infos < sc_vmaddr || | |
| dyld_all_image_infos > sc_vmaddr + sc_size) | |
| return false; | |
| nub_addr_t notifier_fptr_addr = dyld_all_image_infos + 4 + // version | |
| 4 + // infoArrayCount | |
| addr_size; // infoArray | |
| nub_addr_t notifier_fptr = DNBProcessMemoryReadInteger( | |
| pid, notifier_fptr_addr, addr_size, INVALID_NUB_ADDRESS); | |
| if (notifier_fptr == INVALID_NUB_ADDRESS) | |
| return false; | |
| g_notifier_breakpoint_addr = DNBFixAddress(notifier_fptr, pid); | |
| if (g_notifier_breakpoint_addr < sc_vmaddr || | |
| g_notifier_breakpoint_addr > sc_vmaddr + sc_size) | |
| g_notifier_breakpoint_addr = 0; | |
| } | |
| if (g_notifier_breakpoint_addr == 0) | |
| return false; | |
| lldb_image_notifier = g_notifier_breakpoint_addr; | |
| return true; | |
| } | |
| bool DNBGetBinariesLoadedInfo(nub_process_t pid, nub_thread_t tid, | |
| std::vector<uint64_t> &added_binaries, | |
| JSONGenerator::ObjectSP &detailed_binary_infos) { | |
| std::optional<nub_addr_t> arg1, arg2, arg3, pc; | |
| DNBRegisterValue regval; | |
| if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, | |
| GENERIC_REGNUM_PC, ®val)) | |
| pc = regval.value.uint64; | |
| if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, | |
| GENERIC_REGNUM_ARG1, ®val)) | |
| arg1 = regval.value.uint64; | |
| if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, | |
| GENERIC_REGNUM_ARG2, ®val)) | |
| arg2 = regval.value.uint64; | |
| if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, | |
| GENERIC_REGNUM_ARG3, ®val)) | |
| arg3 = regval.value.uint64; | |
| if (!arg1 || !arg2 || !arg3 || !pc) | |
| return false; | |
| nub_addr_t notifier_breakpoint_addr; | |
| if (!DNBDyldNotificationFunctionAddr(pid, notifier_breakpoint_addr)) | |
| return false; | |
| if (*pc != notifier_breakpoint_addr) | |
| return false; | |
| if (*arg1 != /*dyld_notify_adding=*/0) | |
| return false; | |
| uint64_t count = *arg2; | |
| if (count == 0) | |
| return false; | |
| nub_addr_t header_array = *arg3; | |
| int addr_size; | |
| if (!DNBProcessAddrSize(pid, addr_size)) | |
| return false; | |
| // header_array points to an array of image_infos_count elements, | |
| // each is | |
| // struct dyld_image_info { | |
| // const struct mach_header* imageLoadAddress; | |
| // const char* imageFilePath; | |
| // uintptr_t imageFileModDate; | |
| // }; | |
| // | |
| // and we only need the imageLoadAddress fields. | |
| for (uint64_t i = 0; i < count; i++) { | |
| nub_addr_t dyld_image_info = header_array + (addr_size * 3 * i); | |
| nub_addr_t load_addr = DNBProcessMemoryReadInteger( | |
| pid, dyld_image_info, addr_size, INVALID_NUB_ADDRESS); | |
| if (load_addr != INVALID_NUB_ADDRESS) | |
| added_binaries.push_back(load_addr); | |
| } | |
| if (added_binaries.size() == 1) | |
| detailed_binary_infos = DNBGetLibrariesInfoForAddresses( | |
| pid, DNBBinaryInformationLevel::eBinaryInformationLevelFull, | |
| added_binaries); | |
| return true; | |
| } |