diff options
Diffstat (limited to 'tools/debugserver/source/RNBRemote.cpp')
-rw-r--r-- | tools/debugserver/source/RNBRemote.cpp | 6083 |
1 files changed, 6083 insertions, 0 deletions
diff --git a/tools/debugserver/source/RNBRemote.cpp b/tools/debugserver/source/RNBRemote.cpp new file mode 100644 index 0000000..dadf479 --- /dev/null +++ b/tools/debugserver/source/RNBRemote.cpp @@ -0,0 +1,6083 @@ +//===-- RNBRemote.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 12/12/07. +// +//===----------------------------------------------------------------------===// + +#include "RNBRemote.h" + +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <mach/exception_types.h> +#include <mach-o/loader.h> +#include <sys/stat.h> +#include <sys/sysctl.h> + +#if defined (__APPLE__) +#include <pthread.h> +#include <sched.h> +#endif + +#include "DNB.h" +#include "DNBDataRef.h" +#include "DNBLog.h" +#include "DNBThreadResumeActions.h" +#include "JSONGenerator.h" +#include "RNBContext.h" +#include "RNBServices.h" +#include "RNBSocket.h" +#include "lldb/Utility/StringExtractor.h" +#include "MacOSX/Genealogy.h" +#include "JSONGenerator.h" + +#if defined (HAVE_LIBCOMPRESSION) +#include <compression.h> +#endif + +#if defined (HAVE_LIBZ) +#include <zlib.h> +#endif + +#include <iomanip> +#include <sstream> +#include <unordered_set> +#include <TargetConditionals.h> // for endianness predefines + +//---------------------------------------------------------------------- +// std::iostream formatting macros +//---------------------------------------------------------------------- +#define RAW_HEXBASE std::setfill('0') << std::hex << std::right +#define HEXBASE '0' << 'x' << RAW_HEXBASE +#define RAWHEX8(x) RAW_HEXBASE << std::setw(2) << ((uint32_t)((uint8_t)x)) +#define RAWHEX16 RAW_HEXBASE << std::setw(4) +#define RAWHEX32 RAW_HEXBASE << std::setw(8) +#define RAWHEX64 RAW_HEXBASE << std::setw(16) +#define HEX8(x) HEXBASE << std::setw(2) << ((uint32_t)(x)) +#define HEX16 HEXBASE << std::setw(4) +#define HEX32 HEXBASE << std::setw(8) +#define HEX64 HEXBASE << std::setw(16) +#define RAW_HEX(x) RAW_HEXBASE << std::setw(sizeof(x)*2) << (x) +#define HEX(x) HEXBASE << std::setw(sizeof(x)*2) << (x) +#define RAWHEX_SIZE(x, sz) RAW_HEXBASE << std::setw((sz)) << (x) +#define HEX_SIZE(x, sz) HEXBASE << std::setw((sz)) << (x) +#define STRING_WIDTH(w) std::setfill(' ') << std::setw(w) +#define LEFT_STRING_WIDTH(s, w) std::left << std::setfill(' ') << std::setw(w) << (s) << std::right +#define DECIMAL std::dec << std::setfill(' ') +#define DECIMAL_WIDTH(w) DECIMAL << std::setw(w) +#define FLOAT(n, d) std::setfill(' ') << std::setw((n)+(d)+1) << std::setprecision(d) << std::showpoint << std::fixed +#define INDENT_WITH_SPACES(iword_idx) std::setfill(' ') << std::setw((iword_idx)) << "" +#define INDENT_WITH_TABS(iword_idx) std::setfill('\t') << std::setw((iword_idx)) << "" +// Class to handle communications via gdb remote protocol. + + +//---------------------------------------------------------------------- +// Decode a single hex character and return the hex value as a number or +// -1 if "ch" is not a hex character. +//---------------------------------------------------------------------- +static inline int +xdigit_to_sint (char ch) +{ + if (ch >= 'a' && ch <= 'f') + return 10 + ch - 'a'; + if (ch >= 'A' && ch <= 'F') + return 10 + ch - 'A'; + if (ch >= '0' && ch <= '9') + return ch - '0'; + return -1; +} + +//---------------------------------------------------------------------- +// Decode a single hex ASCII byte. Return -1 on failure, a value 0-255 +// on success. +//---------------------------------------------------------------------- +static inline int +decoded_hex_ascii_char(const char *p) +{ + const int hi_nibble = xdigit_to_sint(p[0]); + if (hi_nibble == -1) + return -1; + const int lo_nibble = xdigit_to_sint(p[1]); + if (lo_nibble == -1) + return -1; + return (uint8_t)((hi_nibble << 4) + lo_nibble); +} + +//---------------------------------------------------------------------- +// Decode a hex ASCII string back into a string +//---------------------------------------------------------------------- +static std::string +decode_hex_ascii_string(const char *p, uint32_t max_length = UINT32_MAX) +{ + std::string arg; + if (p) + { + for (const char *c = p; ((c - p)/2) < max_length; c += 2) + { + int ch = decoded_hex_ascii_char(c); + if (ch == -1) + break; + else + arg.push_back(ch); + } + } + return arg; +} + +uint64_t +decode_uint64 (const char *p, int base, char **end = nullptr, uint64_t fail_value = 0) +{ + nub_addr_t addr = strtoull (p, end, 16); + if (addr == 0 && errno != 0) + return fail_value; + return addr; +} + +extern void ASLLogCallback(void *baton, uint32_t flags, const char *format, va_list args); + +#if defined (__APPLE__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101000) +// from System.framework/Versions/B/PrivateHeaders/sys/codesign.h +extern "C" { +#define CS_OPS_STATUS 0 /* return status */ +#define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */ +int csops(pid_t pid, unsigned int ops, void * useraddr, size_t usersize); + +// from rootless.h +bool rootless_allows_task_for_pid (pid_t pid); + +// from sys/csr.h +typedef uint32_t csr_config_t; +#define CSR_ALLOW_TASK_FOR_PID (1 << 2) +int csr_check(csr_config_t mask); +} +#endif + +RNBRemote::RNBRemote () : + m_ctx (), + m_comm (), + m_arch (), + m_continue_thread(-1), + m_thread(-1), + m_mutex(), + m_dispatch_queue_offsets (), + m_dispatch_queue_offsets_addr (INVALID_NUB_ADDRESS), + m_qSymbol_index (UINT32_MAX), + m_packets_recvd(0), + m_packets(), + m_rx_packets(), + m_rx_partial_data(), + m_rx_pthread(0), + m_max_payload_size(DEFAULT_GDB_REMOTE_PROTOCOL_BUFSIZE - 4), + m_extended_mode(false), + m_noack_mode(false), + m_thread_suffix_supported (false), + m_list_threads_in_stop_reply (false), + m_compression_minsize (384), + m_enable_compression_next_send_packet (false), + m_compression_mode (compression_types::none) +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%s", __PRETTY_FUNCTION__); + CreatePacketTable (); +} + + +RNBRemote::~RNBRemote() +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%s", __PRETTY_FUNCTION__); + StopReadRemoteDataThread(); +} + +void +RNBRemote::CreatePacketTable () +{ + // Step required to add new packets: + // 1 - Add new enumeration to RNBRemote::PacketEnum + // 2 - Create the RNBRemote::HandlePacket_ function if a new function is needed + // 3 - Register the Packet definition with any needed callbacks in this function + // - If no response is needed for a command, then use NULL for the normal callback + // - If the packet is not supported while the target is running, use NULL for the async callback + // 4 - If the packet is a standard packet (starts with a '$' character + // followed by the payload and then '#' and checksum, then you are done + // else go on to step 5 + // 5 - if the packet is a fixed length packet: + // - modify the switch statement for the first character in the payload + // in RNBRemote::CommDataReceived so it doesn't reject the new packet + // type as invalid + // - modify the switch statement for the first character in the payload + // in RNBRemote::GetPacketPayload and make sure the payload of the packet + // is returned correctly + + std::vector <Packet> &t = m_packets; + t.push_back (Packet (ack, NULL, NULL, "+", "ACK")); + t.push_back (Packet (nack, NULL, NULL, "-", "!ACK")); + t.push_back (Packet (read_memory, &RNBRemote::HandlePacket_m, NULL, "m", "Read memory")); + t.push_back (Packet (read_register, &RNBRemote::HandlePacket_p, NULL, "p", "Read one register")); + t.push_back (Packet (read_general_regs, &RNBRemote::HandlePacket_g, NULL, "g", "Read registers")); + t.push_back (Packet (write_memory, &RNBRemote::HandlePacket_M, NULL, "M", "Write memory")); + t.push_back (Packet (write_register, &RNBRemote::HandlePacket_P, NULL, "P", "Write one register")); + t.push_back (Packet (write_general_regs, &RNBRemote::HandlePacket_G, NULL, "G", "Write registers")); + t.push_back (Packet (insert_mem_bp, &RNBRemote::HandlePacket_z, NULL, "Z0", "Insert memory breakpoint")); + t.push_back (Packet (remove_mem_bp, &RNBRemote::HandlePacket_z, NULL, "z0", "Remove memory breakpoint")); + t.push_back (Packet (single_step, &RNBRemote::HandlePacket_s, NULL, "s", "Single step")); + t.push_back (Packet (cont, &RNBRemote::HandlePacket_c, NULL, "c", "continue")); + t.push_back (Packet (single_step_with_sig, &RNBRemote::HandlePacket_S, NULL, "S", "Single step with signal")); + t.push_back (Packet (set_thread, &RNBRemote::HandlePacket_H, NULL, "H", "Set thread")); + t.push_back (Packet (halt, &RNBRemote::HandlePacket_last_signal, &RNBRemote::HandlePacket_stop_process, "\x03", "^C")); +// t.push_back (Packet (use_extended_mode, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "!", "Use extended mode")); + t.push_back (Packet (why_halted, &RNBRemote::HandlePacket_last_signal, NULL, "?", "Why did target halt")); + t.push_back (Packet (set_argv, &RNBRemote::HandlePacket_A, NULL, "A", "Set argv")); +// t.push_back (Packet (set_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "B", "Set/clear breakpoint")); + t.push_back (Packet (continue_with_sig, &RNBRemote::HandlePacket_C, NULL, "C", "Continue with signal")); + t.push_back (Packet (detach, &RNBRemote::HandlePacket_D, NULL, "D", "Detach gdb from remote system")); +// t.push_back (Packet (step_inferior_one_cycle, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "i", "Step inferior by one clock cycle")); +// t.push_back (Packet (signal_and_step_inf_one_cycle, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "I", "Signal inferior, then step one clock cycle")); + t.push_back (Packet (kill, &RNBRemote::HandlePacket_k, NULL, "k", "Kill")); +// t.push_back (Packet (restart, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "R", "Restart inferior")); +// t.push_back (Packet (search_mem_backwards, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "t", "Search memory backwards")); + t.push_back (Packet (thread_alive_p, &RNBRemote::HandlePacket_T, NULL, "T", "Is thread alive")); + t.push_back (Packet (query_supported_features, &RNBRemote::HandlePacket_qSupported, NULL, "qSupported", "Query about supported features")); + t.push_back (Packet (vattach, &RNBRemote::HandlePacket_v, NULL, "vAttach", "Attach to a new process")); + t.push_back (Packet (vattachwait, &RNBRemote::HandlePacket_v, NULL, "vAttachWait", "Wait for a process to start up then attach to it")); + t.push_back (Packet (vattachorwait, &RNBRemote::HandlePacket_v, NULL, "vAttachOrWait", "Attach to the process or if it doesn't exist, wait for the process to start up then attach to it")); + t.push_back (Packet (vattachname, &RNBRemote::HandlePacket_v, NULL, "vAttachName", "Attach to an existing process by name")); + t.push_back (Packet (vcont_list_actions, &RNBRemote::HandlePacket_v, NULL, "vCont;", "Verbose resume with thread actions")); + t.push_back (Packet (vcont_list_actions, &RNBRemote::HandlePacket_v, NULL, "vCont?", "List valid continue-with-thread-actions actions")); + t.push_back (Packet (read_data_from_memory, &RNBRemote::HandlePacket_x, NULL, "x", "Read data from memory")); + t.push_back (Packet (write_data_to_memory, &RNBRemote::HandlePacket_X, NULL, "X", "Write data to memory")); +// t.push_back (Packet (insert_hardware_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "Z1", "Insert hardware breakpoint")); +// t.push_back (Packet (remove_hardware_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "z1", "Remove hardware breakpoint")); + t.push_back (Packet (insert_write_watch_bp, &RNBRemote::HandlePacket_z, NULL, "Z2", "Insert write watchpoint")); + t.push_back (Packet (remove_write_watch_bp, &RNBRemote::HandlePacket_z, NULL, "z2", "Remove write watchpoint")); + t.push_back (Packet (insert_read_watch_bp, &RNBRemote::HandlePacket_z, NULL, "Z3", "Insert read watchpoint")); + t.push_back (Packet (remove_read_watch_bp, &RNBRemote::HandlePacket_z, NULL, "z3", "Remove read watchpoint")); + t.push_back (Packet (insert_access_watch_bp, &RNBRemote::HandlePacket_z, NULL, "Z4", "Insert access watchpoint")); + t.push_back (Packet (remove_access_watch_bp, &RNBRemote::HandlePacket_z, NULL, "z4", "Remove access watchpoint")); + t.push_back (Packet (query_monitor, &RNBRemote::HandlePacket_qRcmd, NULL, "qRcmd", "Monitor command")); + t.push_back (Packet (query_current_thread_id, &RNBRemote::HandlePacket_qC, NULL, "qC", "Query current thread ID")); + t.push_back (Packet (query_echo, &RNBRemote::HandlePacket_qEcho, NULL, "qEcho:", "Echo the packet back to allow the debugger to sync up with this server")); + t.push_back (Packet (query_get_pid, &RNBRemote::HandlePacket_qGetPid, NULL, "qGetPid", "Query process id")); + t.push_back (Packet (query_thread_ids_first, &RNBRemote::HandlePacket_qThreadInfo, NULL, "qfThreadInfo", "Get list of active threads (first req)")); + t.push_back (Packet (query_thread_ids_subsequent, &RNBRemote::HandlePacket_qThreadInfo, NULL, "qsThreadInfo", "Get list of active threads (subsequent req)")); + // APPLE LOCAL: qThreadStopInfo + // syntax: qThreadStopInfoTTTT + // TTTT is hex thread ID + t.push_back (Packet (query_thread_stop_info, &RNBRemote::HandlePacket_qThreadStopInfo, NULL, "qThreadStopInfo", "Get detailed info on why the specified thread stopped")); + t.push_back (Packet (query_thread_extra_info, &RNBRemote::HandlePacket_qThreadExtraInfo,NULL, "qThreadExtraInfo", "Get printable status of a thread")); +// t.push_back (Packet (query_image_offsets, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "qOffsets", "Report offset of loaded program")); + t.push_back (Packet (query_launch_success, &RNBRemote::HandlePacket_qLaunchSuccess,NULL, "qLaunchSuccess", "Report the success or failure of the launch attempt")); + t.push_back (Packet (query_register_info, &RNBRemote::HandlePacket_qRegisterInfo, NULL, "qRegisterInfo", "Dynamically discover remote register context information.")); + t.push_back (Packet (query_shlib_notify_info_addr, &RNBRemote::HandlePacket_qShlibInfoAddr,NULL, "qShlibInfoAddr", "Returns the address that contains info needed for getting shared library notifications")); + t.push_back (Packet (query_step_packet_supported, &RNBRemote::HandlePacket_qStepPacketSupported,NULL, "qStepPacketSupported", "Replys with OK if the 's' packet is supported.")); + t.push_back (Packet (query_vattachorwait_supported, &RNBRemote::HandlePacket_qVAttachOrWaitSupported,NULL, "qVAttachOrWaitSupported", "Replys with OK if the 'vAttachOrWait' packet is supported.")); + t.push_back (Packet (query_sync_thread_state_supported, &RNBRemote::HandlePacket_qSyncThreadStateSupported,NULL, "qSyncThreadStateSupported", "Replys with OK if the 'QSyncThreadState:' packet is supported.")); + t.push_back (Packet (query_host_info, &RNBRemote::HandlePacket_qHostInfo , NULL, "qHostInfo", "Replies with multiple 'key:value;' tuples appended to each other.")); + t.push_back (Packet (query_gdb_server_version, &RNBRemote::HandlePacket_qGDBServerVersion , NULL, "qGDBServerVersion", "Replies with multiple 'key:value;' tuples appended to each other.")); + t.push_back (Packet (query_process_info, &RNBRemote::HandlePacket_qProcessInfo , NULL, "qProcessInfo", "Replies with multiple 'key:value;' tuples appended to each other.")); + t.push_back (Packet (query_symbol_lookup, &RNBRemote::HandlePacket_qSymbol , NULL, "qSymbol:", "Notify that host debugger is ready to do symbol lookups")); + t.push_back (Packet (json_query_thread_extended_info,&RNBRemote::HandlePacket_jThreadExtendedInfo , NULL, "jThreadExtendedInfo", "Replies with JSON data of thread extended information.")); + t.push_back (Packet (json_query_get_loaded_dynamic_libraries_infos, &RNBRemote::HandlePacket_jGetLoadedDynamicLibrariesInfos, NULL, "jGetLoadedDynamicLibrariesInfos", "Replies with JSON data of all the shared libraries loaded in this process.")); + t.push_back (Packet (json_query_threads_info, &RNBRemote::HandlePacket_jThreadsInfo , NULL, "jThreadsInfo", "Replies with JSON data with information about all threads.")); + t.push_back (Packet (start_noack_mode, &RNBRemote::HandlePacket_QStartNoAckMode , NULL, "QStartNoAckMode", "Request that " DEBUGSERVER_PROGRAM_NAME " stop acking remote protocol packets")); + t.push_back (Packet (prefix_reg_packets_with_tid, &RNBRemote::HandlePacket_QThreadSuffixSupported , NULL, "QThreadSuffixSupported", "Check if thread specific packets (register packets 'g', 'G', 'p', and 'P') support having the thread ID appended to the end of the command")); + t.push_back (Packet (set_logging_mode, &RNBRemote::HandlePacket_QSetLogging , NULL, "QSetLogging:", "Check if register packets ('g', 'G', 'p', and 'P' support having the thread ID prefix")); + t.push_back (Packet (set_max_packet_size, &RNBRemote::HandlePacket_QSetMaxPacketSize , NULL, "QSetMaxPacketSize:", "Tell " DEBUGSERVER_PROGRAM_NAME " the max sized packet gdb can handle")); + t.push_back (Packet (set_max_payload_size, &RNBRemote::HandlePacket_QSetMaxPayloadSize , NULL, "QSetMaxPayloadSize:", "Tell " DEBUGSERVER_PROGRAM_NAME " the max sized payload gdb can handle")); + t.push_back (Packet (set_environment_variable, &RNBRemote::HandlePacket_QEnvironment , NULL, "QEnvironment:", "Add an environment variable to the inferior's environment")); + t.push_back (Packet (set_environment_variable_hex, &RNBRemote::HandlePacket_QEnvironmentHexEncoded , NULL, "QEnvironmentHexEncoded:", "Add an environment variable to the inferior's environment")); + t.push_back (Packet (set_launch_arch, &RNBRemote::HandlePacket_QLaunchArch , NULL, "QLaunchArch:", "Set the architecture to use when launching a process for hosts that can run multiple architecture slices from universal files.")); + t.push_back (Packet (set_disable_aslr, &RNBRemote::HandlePacket_QSetDisableASLR , NULL, "QSetDisableASLR:", "Set whether to disable ASLR when launching the process with the set argv ('A') packet")); + t.push_back (Packet (set_stdin, &RNBRemote::HandlePacket_QSetSTDIO , NULL, "QSetSTDIN:", "Set the standard input for a process to be launched with the 'A' packet")); + t.push_back (Packet (set_stdout, &RNBRemote::HandlePacket_QSetSTDIO , NULL, "QSetSTDOUT:", "Set the standard output for a process to be launched with the 'A' packet")); + t.push_back (Packet (set_stderr, &RNBRemote::HandlePacket_QSetSTDIO , NULL, "QSetSTDERR:", "Set the standard error for a process to be launched with the 'A' packet")); + t.push_back (Packet (set_working_dir, &RNBRemote::HandlePacket_QSetWorkingDir , NULL, "QSetWorkingDir:", "Set the working directory for a process to be launched with the 'A' packet")); + t.push_back (Packet (set_list_threads_in_stop_reply,&RNBRemote::HandlePacket_QListThreadsInStopReply , NULL, "QListThreadsInStopReply", "Set if the 'threads' key should be added to the stop reply packets with a list of all thread IDs.")); + t.push_back (Packet (sync_thread_state, &RNBRemote::HandlePacket_QSyncThreadState , NULL, "QSyncThreadState:", "Do whatever is necessary to make sure 'thread' is in a safe state to call functions on.")); +// t.push_back (Packet (pass_signals_to_inferior, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "QPassSignals:", "Specify which signals are passed to the inferior")); + t.push_back (Packet (allocate_memory, &RNBRemote::HandlePacket_AllocateMemory, NULL, "_M", "Allocate memory in the inferior process.")); + t.push_back (Packet (deallocate_memory, &RNBRemote::HandlePacket_DeallocateMemory, NULL, "_m", "Deallocate memory in the inferior process.")); + t.push_back (Packet (save_register_state, &RNBRemote::HandlePacket_SaveRegisterState, NULL, "QSaveRegisterState", "Save the register state for the current thread and return a decimal save ID.")); + t.push_back (Packet (restore_register_state, &RNBRemote::HandlePacket_RestoreRegisterState, NULL, "QRestoreRegisterState:", "Restore the register state given a save ID previously returned from a call to QSaveRegisterState.")); + t.push_back (Packet (memory_region_info, &RNBRemote::HandlePacket_MemoryRegionInfo, NULL, "qMemoryRegionInfo", "Return size and attributes of a memory region that contains the given address")); + t.push_back (Packet (get_profile_data, &RNBRemote::HandlePacket_GetProfileData, NULL, "qGetProfileData", "Return profiling data of the current target.")); + t.push_back (Packet (set_enable_profiling, &RNBRemote::HandlePacket_SetEnableAsyncProfiling, NULL, "QSetEnableAsyncProfiling", "Enable or disable the profiling of current target.")); + t.push_back (Packet (enable_compression, &RNBRemote::HandlePacket_QEnableCompression, NULL, "QEnableCompression:", "Enable compression for the remainder of the connection")); + t.push_back (Packet (watchpoint_support_info, &RNBRemote::HandlePacket_WatchpointSupportInfo, NULL, "qWatchpointSupportInfo", "Return the number of supported hardware watchpoints")); + t.push_back (Packet (set_process_event, &RNBRemote::HandlePacket_QSetProcessEvent, NULL, "QSetProcessEvent:", "Set a process event, to be passed to the process, can be set before the process is started, or after.")); + t.push_back (Packet (set_detach_on_error, &RNBRemote::HandlePacket_QSetDetachOnError, NULL, "QSetDetachOnError:", "Set whether debugserver will detach (1) or kill (0) from the process it is controlling if it loses connection to lldb.")); + t.push_back (Packet (speed_test, &RNBRemote::HandlePacket_qSpeedTest, NULL, "qSpeedTest:", "Test the maximum speed at which packet can be sent/received.")); + t.push_back (Packet (query_transfer, &RNBRemote::HandlePacket_qXfer, NULL, "qXfer:", "Support the qXfer packet.")); +} + + +void +RNBRemote::FlushSTDIO () +{ + if (m_ctx.HasValidProcessID()) + { + nub_process_t pid = m_ctx.ProcessID(); + char buf[256]; + nub_size_t count; + do + { + count = DNBProcessGetAvailableSTDOUT(pid, buf, sizeof(buf)); + if (count > 0) + { + SendSTDOUTPacket (buf, count); + } + } while (count > 0); + + do + { + count = DNBProcessGetAvailableSTDERR(pid, buf, sizeof(buf)); + if (count > 0) + { + SendSTDERRPacket (buf, count); + } + } while (count > 0); + } +} + +void +RNBRemote::SendAsyncProfileData () +{ + if (m_ctx.HasValidProcessID()) + { + nub_process_t pid = m_ctx.ProcessID(); + char buf[1024]; + nub_size_t count; + do + { + count = DNBProcessGetAvailableProfileData(pid, buf, sizeof(buf)); + if (count > 0) + { + SendAsyncProfileDataPacket (buf, count); + } + } while (count > 0); + } +} + +rnb_err_t +RNBRemote::SendHexEncodedBytePacket (const char *header, const void *buf, size_t buf_len, const char *footer) +{ + std::ostringstream packet_sstrm; + // Append the header cstr if there was one + if (header && header[0]) + packet_sstrm << header; + nub_size_t i; + const uint8_t *ubuf8 = (const uint8_t *)buf; + for (i=0; i<buf_len; i++) + { + packet_sstrm << RAWHEX8(ubuf8[i]); + } + // Append the footer cstr if there was one + if (footer && footer[0]) + packet_sstrm << footer; + + return SendPacket(packet_sstrm.str()); +} + +rnb_err_t +RNBRemote::SendSTDOUTPacket (char *buf, nub_size_t buf_size) +{ + if (buf_size == 0) + return rnb_success; + return SendHexEncodedBytePacket("O", buf, buf_size, NULL); +} + +rnb_err_t +RNBRemote::SendSTDERRPacket (char *buf, nub_size_t buf_size) +{ + if (buf_size == 0) + return rnb_success; + return SendHexEncodedBytePacket("O", buf, buf_size, NULL); +} + +// This makes use of asynchronous bit 'A' in the gdb remote protocol. +rnb_err_t +RNBRemote::SendAsyncProfileDataPacket (char *buf, nub_size_t buf_size) +{ + if (buf_size == 0) + return rnb_success; + + std::string packet("A"); + packet.append(buf, buf_size); + return SendPacket(packet); +} + +// Given a std::string packet contents to send, possibly encode/compress it. +// If compression is enabled, the returned std::string will be in one of two +// forms: +// +// N<original packet contents uncompressed> +// C<size of original decompressed packet>:<packet compressed with the requested compression scheme> +// +// If compression is not requested, the original packet contents are returned + +std::string +RNBRemote::CompressString (const std::string &orig) +{ + std::string compressed; + compression_types compression_type = GetCompressionType(); + if (compression_type != compression_types::none) + { + bool compress_this_packet = false; + + if (orig.size() > m_compression_minsize) + { + compress_this_packet = true; + } + + if (compress_this_packet) + { + const size_t encoded_data_buf_size = orig.size() + 128; + std::vector<uint8_t> encoded_data (encoded_data_buf_size); + size_t compressed_size = 0; + +#if defined (HAVE_LIBCOMPRESSION) + if (compression_decode_buffer && compression_type == compression_types::lz4) + { + compressed_size = compression_encode_buffer (encoded_data.data(), + encoded_data_buf_size, + (uint8_t*) orig.c_str(), + orig.size(), + nullptr, + COMPRESSION_LZ4_RAW); + } + if (compression_decode_buffer && compression_type == compression_types::zlib_deflate) + { + compressed_size = compression_encode_buffer (encoded_data.data(), + encoded_data_buf_size, + (uint8_t*) orig.c_str(), + orig.size(), + nullptr, + COMPRESSION_ZLIB); + } + if (compression_decode_buffer && compression_type == compression_types::lzma) + { + compressed_size = compression_encode_buffer (encoded_data.data(), + encoded_data_buf_size, + (uint8_t*) orig.c_str(), + orig.size(), + nullptr, + COMPRESSION_LZMA); + } + if (compression_decode_buffer && compression_type == compression_types::lzfse) + { + compressed_size = compression_encode_buffer (encoded_data.data(), + encoded_data_buf_size, + (uint8_t*) orig.c_str(), + orig.size(), + nullptr, + COMPRESSION_LZFSE); + } +#endif + +#if defined (HAVE_LIBZ) + if (compressed_size == 0 && compression_type == compression_types::zlib_deflate) + { + z_stream stream; + memset (&stream, 0, sizeof (z_stream)); + stream.next_in = (Bytef *) orig.c_str(); + stream.avail_in = (uInt) orig.size(); + stream.next_out = (Bytef *) encoded_data.data(); + stream.avail_out = (uInt) encoded_data_buf_size; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + deflateInit2 (&stream, 5, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + int compress_status = deflate (&stream, Z_FINISH); + deflateEnd (&stream); + if (compress_status == Z_STREAM_END && stream.total_out > 0) + { + compressed_size = stream.total_out; + } + } +#endif + + if (compressed_size > 0) + { + compressed.clear (); + compressed.reserve (compressed_size); + compressed = "C"; + char numbuf[16]; + snprintf (numbuf, sizeof (numbuf), "%zu:", orig.size()); + numbuf[sizeof (numbuf) - 1] = '\0'; + compressed.append (numbuf); + + for (size_t i = 0; i < compressed_size; i++) + { + uint8_t byte = encoded_data[i]; + if (byte == '#' || byte == '$' || byte == '}' || byte == '*' || byte == '\0') + { + compressed.push_back (0x7d); + compressed.push_back (byte ^ 0x20); + } + else + { + compressed.push_back (byte); + } + } + } + else + { + compressed = "N" + orig; + } + } + else + { + compressed = "N" + orig; + } + } + else + { + compressed = orig; + } + + return compressed; +} + +rnb_err_t +RNBRemote::SendPacket (const std::string &s) +{ + DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s (%s) called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, s.c_str()); + + std::string s_compressed = CompressString (s); + + std::string sendpacket = "$" + s_compressed + "#"; + int cksum = 0; + char hexbuf[5]; + + if (m_noack_mode) + { + sendpacket += "00"; + } + else + { + for (size_t i = 0; i != s_compressed.size(); ++i) + cksum += s_compressed[i]; + snprintf (hexbuf, sizeof hexbuf, "%02x", cksum & 0xff); + sendpacket += hexbuf; + } + + rnb_err_t err = m_comm.Write (sendpacket.c_str(), sendpacket.size()); + if (err != rnb_success) + return err; + + if (m_noack_mode) + return rnb_success; + + std::string reply; + RNBRemote::Packet packet; + err = GetPacket (reply, packet, true); + + if (err != rnb_success) + { + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s (%s) got error trying to get reply...", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, sendpacket.c_str()); + return err; + } + + DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s (%s) got reply: '%s'", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, sendpacket.c_str(), reply.c_str()); + + if (packet.type == ack) + return rnb_success; + + // Should we try to resend the packet at this layer? + // if (packet.command == nack) + return rnb_err; +} + +/* Get a packet via gdb remote protocol. + Strip off the prefix/suffix, verify the checksum to make sure + a valid packet was received, send an ACK if they match. */ + +rnb_err_t +RNBRemote::GetPacketPayload (std::string &return_packet) +{ + //DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + + PThreadMutex::Locker locker(m_mutex); + if (m_rx_packets.empty()) + { + // Only reset the remote command available event if we have no more packets + m_ctx.Events().ResetEvents ( RNBContext::event_read_packet_available ); + //DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s error: no packets available...", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + return rnb_err; + } + + //DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s has %u queued packets", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, m_rx_packets.size()); + return_packet.swap(m_rx_packets.front()); + m_rx_packets.pop_front(); + locker.Reset(); // Release our lock on the mutex + + if (m_rx_packets.empty()) + { + // Reset the remote command available event if we have no more packets + m_ctx.Events().ResetEvents ( RNBContext::event_read_packet_available ); + } + + //DNBLogThreadedIf (LOG_RNB_MEDIUM, "%8u RNBRemote::%s: '%s'", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + + switch (return_packet[0]) + { + case '+': + case '-': + case '\x03': + break; + + case '$': + { + long packet_checksum = 0; + if (!m_noack_mode) + { + for (size_t i = return_packet.size() - 2; i < return_packet.size(); ++i) + { + char checksum_char = tolower (return_packet[i]); + if (!isxdigit (checksum_char)) + { + m_comm.Write ("-", 1); + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s error: packet with invalid checksum characters: %s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + return rnb_err; + } + } + packet_checksum = strtol (&return_packet[return_packet.size() - 2], NULL, 16); + } + + return_packet.erase(0,1); // Strip the leading '$' + return_packet.erase(return_packet.size() - 3);// Strip the #XX checksum + + if (!m_noack_mode) + { + // Compute the checksum + int computed_checksum = 0; + for (std::string::iterator it = return_packet.begin (); + it != return_packet.end (); + ++it) + { + computed_checksum += *it; + } + + if (packet_checksum == (computed_checksum & 0xff)) + { + //DNBLogThreadedIf (LOG_RNB_MEDIUM, "%8u RNBRemote::%s sending ACK for '%s'", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + m_comm.Write ("+", 1); + } + else + { + DNBLogThreadedIf (LOG_RNB_MEDIUM, "%8u RNBRemote::%s sending ACK for '%s' (error: packet checksum mismatch (0x%2.2lx != 0x%2.2x))", + (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), + __FUNCTION__, + return_packet.c_str(), + packet_checksum, + computed_checksum); + m_comm.Write ("-", 1); + return rnb_err; + } + } + } + break; + + default: + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s tossing unexpected packet???? %s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + if (!m_noack_mode) + m_comm.Write ("-", 1); + return rnb_err; + } + + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_UNIMPLEMENTED (const char* p) +{ + DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s(\"%s\")", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, p ? p : "NULL"); + return SendPacket (""); +} + +rnb_err_t +RNBRemote::HandlePacket_ILLFORMED (const char *file, int line, const char *p, const char *description) +{ + DNBLogThreadedIf (LOG_RNB_PACKETS, "%8u %s:%i ILLFORMED: '%s' (%s)", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), file, line, __FUNCTION__, p); + return SendPacket ("E03"); +} + +rnb_err_t +RNBRemote::GetPacket (std::string &packet_payload, RNBRemote::Packet& packet_info, bool wait) +{ + std::string payload; + rnb_err_t err = GetPacketPayload (payload); + if (err != rnb_success) + { + PThreadEvent& events = m_ctx.Events(); + nub_event_t set_events = events.GetEventBits(); + // TODO: add timeout version of GetPacket?? We would then need to pass + // that timeout value along to DNBProcessTimedWaitForEvent. + if (!wait || ((set_events & RNBContext::event_read_thread_running) == 0)) + return err; + + const nub_event_t events_to_wait_for = RNBContext::event_read_packet_available | RNBContext::event_read_thread_exiting; + + while ((set_events = events.WaitForSetEvents(events_to_wait_for)) != 0) + { + if (set_events & RNBContext::event_read_packet_available) + { + // Try the queue again now that we got an event + err = GetPacketPayload (payload); + if (err == rnb_success) + break; + } + + if (set_events & RNBContext::event_read_thread_exiting) + err = rnb_not_connected; + + if (err == rnb_not_connected) + return err; + + } while (err == rnb_err); + + if (set_events == 0) + err = rnb_not_connected; + } + + if (err == rnb_success) + { + Packet::iterator it; + for (it = m_packets.begin (); it != m_packets.end (); ++it) + { + if (payload.compare (0, it->abbrev.size(), it->abbrev) == 0) + break; + } + + // A packet we don't have an entry for. This can happen when we + // get a packet that we don't know about or support. We just reply + // accordingly and go on. + if (it == m_packets.end ()) + { + DNBLogThreadedIf (LOG_RNB_PACKETS, "unimplemented packet: '%s'", payload.c_str()); + HandlePacket_UNIMPLEMENTED(payload.c_str()); + return rnb_err; + } + else + { + packet_info = *it; + packet_payload = payload; + } + } + return err; +} + +rnb_err_t +RNBRemote::HandleAsyncPacket(PacketEnum *type) +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + static DNBTimer g_packetTimer(true); + rnb_err_t err = rnb_err; + std::string packet_data; + RNBRemote::Packet packet_info; + err = GetPacket (packet_data, packet_info, false); + + if (err == rnb_success) + { + if (!packet_data.empty() && isprint(packet_data[0])) + DNBLogThreadedIf (LOG_RNB_REMOTE | LOG_RNB_PACKETS, "HandleAsyncPacket (\"%s\");", packet_data.c_str()); + else + DNBLogThreadedIf (LOG_RNB_REMOTE | LOG_RNB_PACKETS, "HandleAsyncPacket (%s);", packet_info.printable_name.c_str()); + + HandlePacketCallback packet_callback = packet_info.async; + if (packet_callback != NULL) + { + if (type != NULL) + *type = packet_info.type; + return (this->*packet_callback)(packet_data.c_str()); + } + } + + return err; +} + +rnb_err_t +RNBRemote::HandleReceivedPacket(PacketEnum *type) +{ + static DNBTimer g_packetTimer(true); + + // DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + rnb_err_t err = rnb_err; + std::string packet_data; + RNBRemote::Packet packet_info; + err = GetPacket (packet_data, packet_info, false); + + if (err == rnb_success) + { + DNBLogThreadedIf (LOG_RNB_REMOTE, "HandleReceivedPacket (\"%s\");", packet_data.c_str()); + HandlePacketCallback packet_callback = packet_info.normal; + if (packet_callback != NULL) + { + if (type != NULL) + *type = packet_info.type; + return (this->*packet_callback)(packet_data.c_str()); + } + else + { + // Do not fall through to end of this function, if we have valid + // packet_info and it has a NULL callback, then we need to respect + // that it may not want any response or anything to be done. + return err; + } + } + return rnb_err; +} + +void +RNBRemote::CommDataReceived(const std::string& new_data) +{ + // DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + { + // Put the packet data into the buffer in a thread safe fashion + PThreadMutex::Locker locker(m_mutex); + + std::string data; + // See if we have any left over data from a previous call to this + // function? + if (!m_rx_partial_data.empty()) + { + // We do, so lets start with that data + data.swap(m_rx_partial_data); + } + // Append the new incoming data + data += new_data; + + // Parse up the packets into gdb remote packets + size_t idx = 0; + const size_t data_size = data.size(); + + while (idx < data_size) + { + // end_idx must be one past the last valid packet byte. Start + // it off with an invalid value that is the same as the current + // index. + size_t end_idx = idx; + + switch (data[idx]) + { + case '+': // Look for ack + case '-': // Look for cancel + case '\x03': // ^C to halt target + end_idx = idx + 1; // The command is one byte long... + break; + + case '$': + // Look for a standard gdb packet? + end_idx = data.find('#', idx + 1); + if (end_idx == std::string::npos || end_idx + 3 > data_size) + { + end_idx = std::string::npos; + } + else + { + // Add two for the checksum bytes and 1 to point to the + // byte just past the end of this packet + end_idx += 3; + } + break; + + default: + break; + } + + if (end_idx == std::string::npos) + { + // Not all data may be here for the packet yet, save it for + // next time through this function. + m_rx_partial_data += data.substr(idx); + //DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s saving data for later[%u, npos): '%s'",(uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, idx, m_rx_partial_data.c_str()); + idx = end_idx; + } + else + if (idx < end_idx) + { + m_packets_recvd++; + // Hack to get rid of initial '+' ACK??? + if (m_packets_recvd == 1 && (end_idx == idx + 1) && data[idx] == '+') + { + //DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s throwing first ACK away....[%u, npos): '+'",(uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, idx); + } + else + { + // We have a valid packet... + m_rx_packets.push_back(data.substr(idx, end_idx - idx)); + DNBLogThreadedIf (LOG_RNB_PACKETS, "getpkt: %s", m_rx_packets.back().c_str()); + } + idx = end_idx; + } + else + { + DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s tossing junk byte at %c",(uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, data[idx]); + idx = idx + 1; + } + } + } + + if (!m_rx_packets.empty()) + { + // Let the main thread know we have received a packet + + //DNBLogThreadedIf (LOG_RNB_EVENTS, "%8d RNBRemote::%s called events.SetEvent(RNBContext::event_read_packet_available)", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + PThreadEvent& events = m_ctx.Events(); + events.SetEvents (RNBContext::event_read_packet_available); + } +} + +rnb_err_t +RNBRemote::GetCommData () +{ + // DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + std::string comm_data; + rnb_err_t err = m_comm.Read (comm_data); + if (err == rnb_success) + { + if (!comm_data.empty()) + CommDataReceived (comm_data); + } + return err; +} + +void +RNBRemote::StartReadRemoteDataThread() +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + PThreadEvent& events = m_ctx.Events(); + if ((events.GetEventBits() & RNBContext::event_read_thread_running) == 0) + { + events.ResetEvents (RNBContext::event_read_thread_exiting); + int err = ::pthread_create (&m_rx_pthread, NULL, ThreadFunctionReadRemoteData, this); + if (err == 0) + { + // Our thread was successfully kicked off, wait for it to + // set the started event so we can safely continue + events.WaitForSetEvents (RNBContext::event_read_thread_running); + } + else + { + events.ResetEvents (RNBContext::event_read_thread_running); + events.SetEvents (RNBContext::event_read_thread_exiting); + } + } +} + +void +RNBRemote::StopReadRemoteDataThread() +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + PThreadEvent& events = m_ctx.Events(); + if ((events.GetEventBits() & RNBContext::event_read_thread_running) == RNBContext::event_read_thread_running) + { + m_comm.Disconnect(true); + struct timespec timeout_abstime; + DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0); + + // Wait for 2 seconds for the remote data thread to exit + if (events.WaitForSetEvents(RNBContext::event_read_thread_exiting, &timeout_abstime) == 0) + { + // Kill the remote data thread??? + } + } +} + + +void* +RNBRemote::ThreadFunctionReadRemoteData(void *arg) +{ + // Keep a shared pointer reference so this doesn't go away on us before the thread is killed. + DNBLogThreadedIf(LOG_RNB_REMOTE, "RNBRemote::%s (%p): thread starting...", __FUNCTION__, arg); + RNBRemoteSP remoteSP(g_remoteSP); + if (remoteSP.get() != NULL) + { + +#if defined (__APPLE__) + pthread_setname_np ("read gdb-remote packets 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 + + RNBRemote* remote = remoteSP.get(); + PThreadEvent& events = remote->Context().Events(); + events.SetEvents (RNBContext::event_read_thread_running); + // START: main receive remote command thread loop + bool done = false; + while (!done) + { + rnb_err_t err = remote->GetCommData(); + + switch (err) + { + case rnb_success: + break; + + default: + case rnb_err: + DNBLogThreadedIf (LOG_RNB_REMOTE, "RNBSocket::GetCommData returned error %u", err); + done = true; + break; + + case rnb_not_connected: + DNBLogThreadedIf (LOG_RNB_REMOTE, "RNBSocket::GetCommData returned not connected..."); + done = true; + break; + } + } + // START: main receive remote command thread loop + events.ResetEvents (RNBContext::event_read_thread_running); + events.SetEvents (RNBContext::event_read_thread_exiting); + } + DNBLogThreadedIf(LOG_RNB_REMOTE, "RNBRemote::%s (%p): thread exiting...", __FUNCTION__, arg); + return NULL; +} + + +// If we fail to get back a valid CPU type for the remote process, +// make a best guess for the CPU type based on the currently running +// debugserver binary -- the debugger may not handle the case of an +// un-specified process CPU type correctly. + +static cpu_type_t +best_guess_cpu_type () +{ +#if defined (__arm__) || defined (__arm64__) || defined (__aarch64__) + if (sizeof (char *) == 8) + { + return CPU_TYPE_ARM64; + } + else + { + return CPU_TYPE_ARM; + } +#elif defined (__i386__) || defined (__x86_64__) + if (sizeof (char*) == 8) + { + return CPU_TYPE_X86_64; + } + else + { + return CPU_TYPE_I386; + } +#endif + return 0; +} + + +/* Read the bytes in STR which are GDB Remote Protocol binary encoded bytes + (8-bit bytes). + This encoding uses 0x7d ('}') as an escape character for + 0x7d ('}'), 0x23 ('#'), 0x24 ('$'), 0x2a ('*'). + LEN is the number of bytes to be processed. If a character is escaped, + it is 2 characters for LEN. A LEN of -1 means decode-until-nul-byte + (end of string). */ + +std::vector<uint8_t> +decode_binary_data (const char *str, size_t len) +{ + std::vector<uint8_t> bytes; + if (len == 0) + { + return bytes; + } + if (len == (size_t)-1) + len = strlen (str); + + while (len--) + { + unsigned char c = *str; + if (c == 0x7d && len > 0) + { + len--; + str++; + c = *str ^ 0x20; + } + bytes.push_back (c); + } + return bytes; +} + +// Quote any meta characters in a std::string as per the binary +// packet convention in the gdb-remote protocol. + +std::string +binary_encode_string (const std::string &s) +{ + std::string output; + const size_t s_size = s.size(); + const char *s_chars = s.c_str(); + + for (size_t i = 0; i < s_size; i++) + { + unsigned char ch = *(s_chars + i); + if (ch == '#' || ch == '$' || ch == '}' || ch == '*') + { + output.push_back ('}'); // 0x7d + output.push_back (ch ^ 0x20); + } + else + { + output.push_back (ch); + } + } + return output; +} + +// If the value side of a key-value pair in JSON is a string, +// and that string has a " character in it, the " character must +// be escaped. + +std::string +json_string_quote_metachars (const std::string &s) +{ + if (s.find('"') == std::string::npos) + return s; + + std::string output; + const size_t s_size = s.size(); + const char *s_chars = s.c_str(); + for (size_t i = 0; i < s_size; i++) + { + unsigned char ch = *(s_chars + i); + if (ch == '"') + { + output.push_back ('\\'); + } + output.push_back (ch); + } + return output; +} + +typedef struct register_map_entry +{ + uint32_t debugserver_regnum; // debugserver register number + uint32_t offset; // Offset in bytes into the register context data with no padding between register values + DNBRegisterInfo nub_info; // debugnub register info + std::vector<uint32_t> value_regnums; + std::vector<uint32_t> invalidate_regnums; +} register_map_entry_t; + + + +// If the notion of registers differs from what is handed out by the +// architecture, then flavors can be defined here. + +static std::vector<register_map_entry_t> g_dynamic_register_map; +static register_map_entry_t *g_reg_entries = NULL; +static size_t g_num_reg_entries = 0; + +void +RNBRemote::Initialize() +{ + DNBInitialize(); +} + + +bool +RNBRemote::InitializeRegisters (bool force) +{ + pid_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return false; + + DNBLogThreadedIf (LOG_RNB_PROC, "RNBRemote::%s() getting native registers from DNB interface", __FUNCTION__); + // Discover the registers by querying the DNB interface and letting it + // state the registers that it would like to export. This allows the + // registers to be discovered using multiple qRegisterInfo calls to get + // all register information after the architecture for the process is + // determined. + if (force) + { + g_dynamic_register_map.clear(); + g_reg_entries = NULL; + g_num_reg_entries = 0; + } + + if (g_dynamic_register_map.empty()) + { + nub_size_t num_reg_sets = 0; + const DNBRegisterSetInfo *reg_sets = DNBGetRegisterSetInfo (&num_reg_sets); + + assert (num_reg_sets > 0 && reg_sets != NULL); + + uint32_t regnum = 0; + uint32_t reg_data_offset = 0; + typedef std::map<std::string, uint32_t> NameToRegNum; + NameToRegNum name_to_regnum; + for (nub_size_t set = 0; set < num_reg_sets; ++set) + { + if (reg_sets[set].registers == NULL) + continue; + + for (uint32_t reg=0; reg < reg_sets[set].num_registers; ++reg) + { + register_map_entry_t reg_entry = { + regnum++, // register number starts at zero and goes up with no gaps + reg_data_offset, // Offset into register context data, no gaps between registers + reg_sets[set].registers[reg] // DNBRegisterInfo + }; + + name_to_regnum[reg_entry.nub_info.name] = reg_entry.debugserver_regnum; + + if (reg_entry.nub_info.value_regs == NULL) + { + reg_data_offset += reg_entry.nub_info.size; + } + + g_dynamic_register_map.push_back (reg_entry); + } + } + + // Now we must find any registers whose values are in other registers and fix up + // the offsets since we removed all gaps... + for (auto ®_entry: g_dynamic_register_map) + { + if (reg_entry.nub_info.value_regs) + { + uint32_t new_offset = UINT32_MAX; + for (size_t i=0; reg_entry.nub_info.value_regs[i] != NULL; ++i) + { + const char *name = reg_entry.nub_info.value_regs[i]; + auto pos = name_to_regnum.find(name); + if (pos != name_to_regnum.end()) + { + regnum = pos->second; + reg_entry.value_regnums.push_back(regnum); + if (regnum < g_dynamic_register_map.size()) + { + // The offset for value_regs registers is the offset within the register with the lowest offset + const uint32_t reg_offset = g_dynamic_register_map[regnum].offset + reg_entry.nub_info.offset; + if (new_offset > reg_offset) + new_offset = reg_offset; + } + } + } + + if (new_offset != UINT32_MAX) + { + reg_entry.offset = new_offset; + } + else + { + DNBLogThreaded("no offset was calculated entry for register %s", reg_entry.nub_info.name); + reg_entry.offset = UINT32_MAX; + } + } + + if (reg_entry.nub_info.update_regs) + { + for (size_t i=0; reg_entry.nub_info.update_regs[i] != NULL; ++i) + { + const char *name = reg_entry.nub_info.update_regs[i]; + auto pos = name_to_regnum.find(name); + if (pos != name_to_regnum.end()) + { + regnum = pos->second; + reg_entry.invalidate_regnums.push_back(regnum); + } + } + } + } + + +// for (auto ®_entry: g_dynamic_register_map) +// { +// DNBLogThreaded("%4i: size = %3u, pseudo = %i, name = %s", +// reg_entry.offset, +// reg_entry.nub_info.size, +// reg_entry.nub_info.value_regs != NULL, +// reg_entry.nub_info.name); +// } + + g_reg_entries = g_dynamic_register_map.data(); + g_num_reg_entries = g_dynamic_register_map.size(); + } + return true; +} + +/* The inferior has stopped executing; send a packet + to gdb to let it know. */ + +void +RNBRemote::NotifyThatProcessStopped (void) +{ + RNBRemote::HandlePacket_last_signal (NULL); + return; +} + + +/* 'A arglen,argnum,arg,...' + Update the inferior context CTX with the program name and arg + list. + The documentation for this packet is underwhelming but my best reading + of this is that it is a series of (len, position #, arg)'s, one for + each argument with "arg" hex encoded (two 0-9a-f chars?). + Why we need BOTH a "len" and a hex encoded "arg" is beyond me - either + is sufficient to get around the "," position separator escape issue. + + e.g. our best guess for a valid 'A' packet for "gdb -q a.out" is + + 6,0,676462,4,1,2d71,10,2,612e6f7574 + + Note that "argnum" and "arglen" are numbers in base 10. Again, that's + not documented either way but I'm assuming it's so. */ + +rnb_err_t +RNBRemote::HandlePacket_A (const char *p) +{ + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Null packet for 'A' pkt"); + } + p++; + if (*p == '\0' || !isdigit (*p)) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "arglen not specified on 'A' pkt"); + } + + /* I promise I don't modify it anywhere in this function. strtoul()'s + 2nd arg has to be non-const which makes it problematic to step + through the string easily. */ + char *buf = const_cast<char *>(p); + + RNBContext& ctx = Context(); + + while (*buf != '\0') + { + unsigned long arglen, argnum; + std::string arg; + char *c; + + errno = 0; + arglen = strtoul (buf, &c, 10); + if (errno != 0 && arglen == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "arglen not a number on 'A' pkt"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "arglen not followed by comma on 'A' pkt"); + } + buf = c + 1; + + errno = 0; + argnum = strtoul (buf, &c, 10); + if (errno != 0 && argnum == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "argnum not a number on 'A' pkt"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "arglen not followed by comma on 'A' pkt"); + } + buf = c + 1; + + c = buf; + buf = buf + arglen; + while (c < buf && *c != '\0' && c + 1 < buf && *(c + 1) != '\0') + { + char smallbuf[3]; + smallbuf[0] = *c; + smallbuf[1] = *(c + 1); + smallbuf[2] = '\0'; + + errno = 0; + int ch = static_cast<int>(strtoul (smallbuf, NULL, 16)); + if (errno != 0 && ch == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in arg on 'A' pkt"); + } + + arg.push_back(ch); + c += 2; + } + + ctx.PushArgument (arg.c_str()); + if (*buf == ',') + buf++; + } + SendPacket ("OK"); + + return rnb_success; +} + +/* 'H c t' + Set the thread for subsequent actions; 'c' for step/continue ops, + 'g' for other ops. -1 means all threads, 0 means any thread. */ + +rnb_err_t +RNBRemote::HandlePacket_H (const char *p) +{ + p++; // skip 'H' + if (*p != 'c' && *p != 'g') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Missing 'c' or 'g' type in H packet"); + } + + if (!m_ctx.HasValidProcessID()) + { + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + } + + errno = 0; + nub_thread_t tid = strtoul (p + 1, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid thread number in H packet"); + } + if (*p == 'c') + SetContinueThread (tid); + if (*p == 'g') + SetCurrentThread (tid); + + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_qLaunchSuccess (const char *p) +{ + if (m_ctx.HasValidProcessID() || m_ctx.LaunchStatus().Error() == 0) + return SendPacket("OK"); + std::ostringstream ret_str; + std::string status_str; + ret_str << "E" << m_ctx.LaunchStatusAsString(status_str); + + return SendPacket (ret_str.str()); +} + +rnb_err_t +RNBRemote::HandlePacket_qShlibInfoAddr (const char *p) +{ + if (m_ctx.HasValidProcessID()) + { + nub_addr_t shlib_info_addr = DNBProcessGetSharedLibraryInfoAddress(m_ctx.ProcessID()); + if (shlib_info_addr != INVALID_NUB_ADDRESS) + { + std::ostringstream ostrm; + ostrm << RAW_HEXBASE << shlib_info_addr; + return SendPacket (ostrm.str ()); + } + } + return SendPacket ("E44"); +} + +rnb_err_t +RNBRemote::HandlePacket_qStepPacketSupported (const char *p) +{ + // Normally the "s" packet is mandatory, yet in gdb when using ARM, they + // get around the need for this packet by implementing software single + // stepping from gdb. Current versions of debugserver do support the "s" + // packet, yet some older versions do not. We need a way to tell if this + // packet is supported so we can disable software single stepping in gdb + // for remote targets (so the "s" packet will get used). + return SendPacket("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_qSyncThreadStateSupported (const char *p) +{ + // We support attachOrWait meaning attach if the process exists, otherwise wait to attach. + return SendPacket("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_qVAttachOrWaitSupported (const char *p) +{ + // We support attachOrWait meaning attach if the process exists, otherwise wait to attach. + return SendPacket("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_qThreadStopInfo (const char *p) +{ + p += strlen ("qThreadStopInfo"); + nub_thread_t tid = strtoul(p, 0, 16); + return SendStopReplyPacketForThread (tid); +} + +rnb_err_t +RNBRemote::HandlePacket_qThreadInfo (const char *p) +{ + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + // Only "qfThreadInfo" and "qsThreadInfo" get into this function so + // we only need to check the second byte to tell which is which + if (p[1] == 'f') + { + nub_size_t numthreads = DNBProcessGetNumThreads (pid); + std::ostringstream ostrm; + ostrm << "m"; + bool first = true; + for (nub_size_t i = 0; i < numthreads; ++i) + { + if (first) + first = false; + else + ostrm << ","; + nub_thread_t th = DNBProcessGetThreadAtIndex (pid, i); + ostrm << std::hex << th; + } + return SendPacket (ostrm.str ()); + } + else + { + return SendPacket ("l"); + } +} + +rnb_err_t +RNBRemote::HandlePacket_qThreadExtraInfo (const char *p) +{ + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + /* This is supposed to return a string like 'Runnable' or + 'Blocked on Mutex'. + The returned string is formatted like the "A" packet - a + sequence of letters encoded in as 2-hex-chars-per-letter. */ + p += strlen ("qThreadExtraInfo"); + if (*p++ != ',') + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Illformed qThreadExtraInfo packet"); + errno = 0; + nub_thread_t tid = strtoul (p, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid thread number in qThreadExtraInfo packet"); + } + + const char * threadInfo = DNBThreadGetInfo(pid, tid); + if (threadInfo != NULL && threadInfo[0]) + { + return SendHexEncodedBytePacket(NULL, threadInfo, strlen(threadInfo), NULL); + } + else + { + // "OK" == 4f6b + // Return "OK" as a ASCII hex byte stream if things go wrong + return SendPacket ("4f6b"); + } + + return SendPacket (""); +} + + +const char *k_space_delimiters = " \t"; +static void +skip_spaces (std::string &line) +{ + if (!line.empty()) + { + size_t space_pos = line.find_first_not_of (k_space_delimiters); + if (space_pos > 0) + line.erase(0, space_pos); + } +} + +static std::string +get_identifier (std::string &line) +{ + std::string word; + skip_spaces (line); + const size_t line_size = line.size(); + size_t end_pos; + for (end_pos = 0; end_pos < line_size; ++end_pos) + { + if (end_pos == 0) + { + if (isalpha(line[end_pos]) || line[end_pos] == '_') + continue; + } + else if (isalnum(line[end_pos]) || line[end_pos] == '_') + continue; + break; + } + word.assign (line, 0, end_pos); + line.erase(0, end_pos); + return word; +} + +static std::string +get_operator (std::string &line) +{ + std::string op; + skip_spaces (line); + if (!line.empty()) + { + if (line[0] == '=') + { + op = '='; + line.erase(0,1); + } + } + return op; +} + +static std::string +get_value (std::string &line) +{ + std::string value; + skip_spaces (line); + if (!line.empty()) + { + value.swap(line); + } + return value; +} + +extern void FileLogCallback(void *baton, uint32_t flags, const char *format, va_list args); +extern void ASLLogCallback(void *baton, uint32_t flags, const char *format, va_list args); + +rnb_err_t +RNBRemote::HandlePacket_qRcmd (const char *p) +{ + const char *c = p + strlen("qRcmd,"); + std::string line; + while (c[0] && c[1]) + { + char smallbuf[3] = { c[0], c[1], '\0' }; + errno = 0; + int ch = static_cast<int>(strtoul (smallbuf, NULL, 16)); + if (errno != 0 && ch == 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in payload of qRcmd packet"); + line.push_back(ch); + c += 2; + } + if (*c == '\0') + { + std::string command = get_identifier(line); + if (command.compare("set") == 0) + { + std::string variable = get_identifier (line); + std::string op = get_operator (line); + std::string value = get_value (line); + if (variable.compare("logfile") == 0) + { + FILE *log_file = fopen(value.c_str(), "w"); + if (log_file) + { + DNBLogSetLogCallback(FileLogCallback, log_file); + return SendPacket ("OK"); + } + return SendPacket ("E71"); + } + else if (variable.compare("logmask") == 0) + { + char *end; + errno = 0; + uint32_t logmask = static_cast<uint32_t>(strtoul (value.c_str(), &end, 0)); + if (errno == 0 && end && *end == '\0') + { + DNBLogSetLogMask (logmask); + if (!DNBLogGetLogCallback()) + DNBLogSetLogCallback(ASLLogCallback, NULL); + return SendPacket ("OK"); + } + errno = 0; + logmask = static_cast<uint32_t>(strtoul (value.c_str(), &end, 16)); + if (errno == 0 && end && *end == '\0') + { + DNBLogSetLogMask (logmask); + return SendPacket ("OK"); + } + return SendPacket ("E72"); + } + return SendPacket ("E70"); + } + return SendPacket ("E69"); + } + return SendPacket ("E73"); +} + +rnb_err_t +RNBRemote::HandlePacket_qC (const char *p) +{ + nub_thread_t tid; + std::ostringstream rep; + // If we haven't run the process yet, we tell the debugger the + // pid is 0. That way it can know to tell use to run later on. + if (!m_ctx.HasValidProcessID()) + tid = 0; + else + { + // Grab the current thread. + tid = DNBProcessGetCurrentThread (m_ctx.ProcessID()); + // Make sure we set the current thread so g and p packets return + // the data the gdb will expect. + SetCurrentThread (tid); + } + rep << "QC" << std::hex << tid; + return SendPacket (rep.str()); +} + +rnb_err_t +RNBRemote::HandlePacket_qEcho (const char *p) +{ + // Just send the exact same packet back that we received to + // synchronize the response packets after a previous packet + // timed out. This allows the debugger to get back on track + // with responses after a packet timeout. + return SendPacket (p); +} + +rnb_err_t +RNBRemote::HandlePacket_qGetPid (const char *p) +{ + nub_process_t pid; + std::ostringstream rep; + // If we haven't run the process yet, we tell the debugger the + // pid is 0. That way it can know to tell use to run later on. + if (m_ctx.HasValidProcessID()) + pid = m_ctx.ProcessID(); + else + pid = 0; + rep << std::hex << pid; + return SendPacket (rep.str()); +} + +rnb_err_t +RNBRemote::HandlePacket_qRegisterInfo (const char *p) +{ + if (g_num_reg_entries == 0) + InitializeRegisters (); + + p += strlen ("qRegisterInfo"); + + nub_size_t num_reg_sets = 0; + const DNBRegisterSetInfo *reg_set_info = DNBGetRegisterSetInfo (&num_reg_sets); + uint32_t reg_num = static_cast<uint32_t>(strtoul(p, 0, 16)); + + if (reg_num < g_num_reg_entries) + { + const register_map_entry_t *reg_entry = &g_reg_entries[reg_num]; + std::ostringstream ostrm; + if (reg_entry->nub_info.name) + ostrm << "name:" << reg_entry->nub_info.name << ';'; + if (reg_entry->nub_info.alt) + ostrm << "alt-name:" << reg_entry->nub_info.alt << ';'; + + ostrm << "bitsize:" << std::dec << reg_entry->nub_info.size * 8 << ';'; + ostrm << "offset:" << std::dec << reg_entry->offset << ';'; + + switch (reg_entry->nub_info.type) + { + case Uint: ostrm << "encoding:uint;"; break; + case Sint: ostrm << "encoding:sint;"; break; + case IEEE754: ostrm << "encoding:ieee754;"; break; + case Vector: ostrm << "encoding:vector;"; break; + } + + switch (reg_entry->nub_info.format) + { + case Binary: ostrm << "format:binary;"; break; + case Decimal: ostrm << "format:decimal;"; break; + case Hex: ostrm << "format:hex;"; break; + case Float: ostrm << "format:float;"; break; + case VectorOfSInt8: ostrm << "format:vector-sint8;"; break; + case VectorOfUInt8: ostrm << "format:vector-uint8;"; break; + case VectorOfSInt16: ostrm << "format:vector-sint16;"; break; + case VectorOfUInt16: ostrm << "format:vector-uint16;"; break; + case VectorOfSInt32: ostrm << "format:vector-sint32;"; break; + case VectorOfUInt32: ostrm << "format:vector-uint32;"; break; + case VectorOfFloat32: ostrm << "format:vector-float32;"; break; + case VectorOfUInt128: ostrm << "format:vector-uint128;"; break; + }; + + if (reg_set_info && reg_entry->nub_info.set < num_reg_sets) + ostrm << "set:" << reg_set_info[reg_entry->nub_info.set].name << ';'; + + if (reg_entry->nub_info.reg_ehframe != INVALID_NUB_REGNUM) + ostrm << "ehframe:" << std::dec << reg_entry->nub_info.reg_ehframe << ';'; + + if (reg_entry->nub_info.reg_dwarf != INVALID_NUB_REGNUM) + ostrm << "dwarf:" << std::dec << reg_entry->nub_info.reg_dwarf << ';'; + + switch (reg_entry->nub_info.reg_generic) + { + case GENERIC_REGNUM_FP: ostrm << "generic:fp;"; break; + case GENERIC_REGNUM_PC: ostrm << "generic:pc;"; break; + case GENERIC_REGNUM_SP: ostrm << "generic:sp;"; break; + case GENERIC_REGNUM_RA: ostrm << "generic:ra;"; break; + case GENERIC_REGNUM_FLAGS: ostrm << "generic:flags;"; break; + case GENERIC_REGNUM_ARG1: ostrm << "generic:arg1;"; break; + case GENERIC_REGNUM_ARG2: ostrm << "generic:arg2;"; break; + case GENERIC_REGNUM_ARG3: ostrm << "generic:arg3;"; break; + case GENERIC_REGNUM_ARG4: ostrm << "generic:arg4;"; break; + case GENERIC_REGNUM_ARG5: ostrm << "generic:arg5;"; break; + case GENERIC_REGNUM_ARG6: ostrm << "generic:arg6;"; break; + case GENERIC_REGNUM_ARG7: ostrm << "generic:arg7;"; break; + case GENERIC_REGNUM_ARG8: ostrm << "generic:arg8;"; break; + default: break; + } + + if (!reg_entry->value_regnums.empty()) + { + ostrm << "container-regs:"; + for (size_t i=0, n=reg_entry->value_regnums.size(); i < n; ++i) + { + if (i > 0) + ostrm << ','; + ostrm << RAW_HEXBASE << reg_entry->value_regnums[i]; + } + ostrm << ';'; + } + + if (!reg_entry->invalidate_regnums.empty()) + { + ostrm << "invalidate-regs:"; + for (size_t i=0, n=reg_entry->invalidate_regnums.size(); i < n; ++i) + { + if (i > 0) + ostrm << ','; + ostrm << RAW_HEXBASE << reg_entry->invalidate_regnums[i]; + } + ostrm << ';'; + } + + return SendPacket (ostrm.str ()); + } + return SendPacket ("E45"); +} + + +/* This expects a packet formatted like + + QSetLogging:bitmask=LOG_ALL|LOG_RNB_REMOTE; + + with the "QSetLogging:" already removed from the start. Maybe in the + future this packet will include other keyvalue pairs like + + QSetLogging:bitmask=LOG_ALL;mode=asl; + */ + +rnb_err_t +set_logging (const char *p) +{ + int bitmask = 0; + while (p && *p != '\0') + { + if (strncmp (p, "bitmask=", sizeof ("bitmask=") - 1) == 0) + { + p += sizeof ("bitmask=") - 1; + while (p && *p != '\0' && *p != ';') + { + if (*p == '|') + p++; + +// to regenerate the LOG_ entries (not including the LOG_RNB entries) +// $ for logname in `grep '^#define LOG_' DNBDefs.h | egrep -v 'LOG_HI|LOG_LO' | awk '{print $2}'` +// do +// echo " else if (strncmp (p, \"$logname\", sizeof (\"$logname\") - 1) == 0)" +// echo " {" +// echo " p += sizeof (\"$logname\") - 1;" +// echo " bitmask |= $logname;" +// echo " }" +// done + if (strncmp (p, "LOG_VERBOSE", sizeof ("LOG_VERBOSE") - 1) == 0) + { + p += sizeof ("LOG_VERBOSE") - 1; + bitmask |= LOG_VERBOSE; + } + else if (strncmp (p, "LOG_PROCESS", sizeof ("LOG_PROCESS") - 1) == 0) + { + p += sizeof ("LOG_PROCESS") - 1; + bitmask |= LOG_PROCESS; + } + else if (strncmp (p, "LOG_THREAD", sizeof ("LOG_THREAD") - 1) == 0) + { + p += sizeof ("LOG_THREAD") - 1; + bitmask |= LOG_THREAD; + } + else if (strncmp (p, "LOG_EXCEPTIONS", sizeof ("LOG_EXCEPTIONS") - 1) == 0) + { + p += sizeof ("LOG_EXCEPTIONS") - 1; + bitmask |= LOG_EXCEPTIONS; + } + else if (strncmp (p, "LOG_SHLIB", sizeof ("LOG_SHLIB") - 1) == 0) + { + p += sizeof ("LOG_SHLIB") - 1; + bitmask |= LOG_SHLIB; + } + else if (strncmp (p, "LOG_MEMORY", sizeof ("LOG_MEMORY") - 1) == 0) + { + p += sizeof ("LOG_MEMORY") - 1; + bitmask |= LOG_MEMORY; + } + else if (strncmp (p, "LOG_MEMORY_DATA_SHORT", sizeof ("LOG_MEMORY_DATA_SHORT") - 1) == 0) + { + p += sizeof ("LOG_MEMORY_DATA_SHORT") - 1; + bitmask |= LOG_MEMORY_DATA_SHORT; + } + else if (strncmp (p, "LOG_MEMORY_DATA_LONG", sizeof ("LOG_MEMORY_DATA_LONG") - 1) == 0) + { + p += sizeof ("LOG_MEMORY_DATA_LONG") - 1; + bitmask |= LOG_MEMORY_DATA_LONG; + } + else if (strncmp (p, "LOG_MEMORY_PROTECTIONS", sizeof ("LOG_MEMORY_PROTECTIONS") - 1) == 0) + { + p += sizeof ("LOG_MEMORY_PROTECTIONS") - 1; + bitmask |= LOG_MEMORY_PROTECTIONS; + } + else if (strncmp (p, "LOG_BREAKPOINTS", sizeof ("LOG_BREAKPOINTS") - 1) == 0) + { + p += sizeof ("LOG_BREAKPOINTS") - 1; + bitmask |= LOG_BREAKPOINTS; + } + else if (strncmp (p, "LOG_EVENTS", sizeof ("LOG_EVENTS") - 1) == 0) + { + p += sizeof ("LOG_EVENTS") - 1; + bitmask |= LOG_EVENTS; + } + else if (strncmp (p, "LOG_WATCHPOINTS", sizeof ("LOG_WATCHPOINTS") - 1) == 0) + { + p += sizeof ("LOG_WATCHPOINTS") - 1; + bitmask |= LOG_WATCHPOINTS; + } + else if (strncmp (p, "LOG_STEP", sizeof ("LOG_STEP") - 1) == 0) + { + p += sizeof ("LOG_STEP") - 1; + bitmask |= LOG_STEP; + } + else if (strncmp (p, "LOG_TASK", sizeof ("LOG_TASK") - 1) == 0) + { + p += sizeof ("LOG_TASK") - 1; + bitmask |= LOG_TASK; + } + else if (strncmp (p, "LOG_ALL", sizeof ("LOG_ALL") - 1) == 0) + { + p += sizeof ("LOG_ALL") - 1; + bitmask |= LOG_ALL; + } + else if (strncmp (p, "LOG_DEFAULT", sizeof ("LOG_DEFAULT") - 1) == 0) + { + p += sizeof ("LOG_DEFAULT") - 1; + bitmask |= LOG_DEFAULT; + } +// end of auto-generated entries + + else if (strncmp (p, "LOG_NONE", sizeof ("LOG_NONE") - 1) == 0) + { + p += sizeof ("LOG_NONE") - 1; + bitmask = 0; + } + else if (strncmp (p, "LOG_RNB_MINIMAL", sizeof ("LOG_RNB_MINIMAL") - 1) == 0) + { + p += sizeof ("LOG_RNB_MINIMAL") - 1; + bitmask |= LOG_RNB_MINIMAL; + } + else if (strncmp (p, "LOG_RNB_MEDIUM", sizeof ("LOG_RNB_MEDIUM") - 1) == 0) + { + p += sizeof ("LOG_RNB_MEDIUM") - 1; + bitmask |= LOG_RNB_MEDIUM; + } + else if (strncmp (p, "LOG_RNB_MAX", sizeof ("LOG_RNB_MAX") - 1) == 0) + { + p += sizeof ("LOG_RNB_MAX") - 1; + bitmask |= LOG_RNB_MAX; + } + else if (strncmp (p, "LOG_RNB_COMM", sizeof ("LOG_RNB_COMM") - 1) == 0) + { + p += sizeof ("LOG_RNB_COMM") - 1; + bitmask |= LOG_RNB_COMM; + } + else if (strncmp (p, "LOG_RNB_REMOTE", sizeof ("LOG_RNB_REMOTE") - 1) == 0) + { + p += sizeof ("LOG_RNB_REMOTE") - 1; + bitmask |= LOG_RNB_REMOTE; + } + else if (strncmp (p, "LOG_RNB_EVENTS", sizeof ("LOG_RNB_EVENTS") - 1) == 0) + { + p += sizeof ("LOG_RNB_EVENTS") - 1; + bitmask |= LOG_RNB_EVENTS; + } + else if (strncmp (p, "LOG_RNB_PROC", sizeof ("LOG_RNB_PROC") - 1) == 0) + { + p += sizeof ("LOG_RNB_PROC") - 1; + bitmask |= LOG_RNB_PROC; + } + else if (strncmp (p, "LOG_RNB_PACKETS", sizeof ("LOG_RNB_PACKETS") - 1) == 0) + { + p += sizeof ("LOG_RNB_PACKETS") - 1; + bitmask |= LOG_RNB_PACKETS; + } + else if (strncmp (p, "LOG_RNB_ALL", sizeof ("LOG_RNB_ALL") - 1) == 0) + { + p += sizeof ("LOG_RNB_ALL") - 1; + bitmask |= LOG_RNB_ALL; + } + else if (strncmp (p, "LOG_RNB_DEFAULT", sizeof ("LOG_RNB_DEFAULT") - 1) == 0) + { + p += sizeof ("LOG_RNB_DEFAULT") - 1; + bitmask |= LOG_RNB_DEFAULT; + } + else if (strncmp (p, "LOG_RNB_NONE", sizeof ("LOG_RNB_NONE") - 1) == 0) + { + p += sizeof ("LOG_RNB_NONE") - 1; + bitmask = 0; + } + else + { + /* Unrecognized logging bit; ignore it. */ + const char *c = strchr (p, '|'); + if (c) + { + p = c; + } + else + { + c = strchr (p, ';'); + if (c) + { + p = c; + } + else + { + // Improperly terminated word; just go to end of str + p = strchr (p, '\0'); + } + } + } + } + // Did we get a properly formatted logging bitmask? + if (p && *p == ';') + { + // Enable DNB logging + DNBLogSetLogCallback(ASLLogCallback, NULL); + DNBLogSetLogMask (bitmask); + p++; + } + } + // We're not going to support logging to a file for now. All logging + // goes through ASL. +#if 0 + else if (strncmp (p, "mode=", sizeof ("mode=") - 1) == 0) + { + p += sizeof ("mode=") - 1; + if (strncmp (p, "asl;", sizeof ("asl;") - 1) == 0) + { + DNBLogToASL (); + p += sizeof ("asl;") - 1; + } + else if (strncmp (p, "file;", sizeof ("file;") - 1) == 0) + { + DNBLogToFile (); + p += sizeof ("file;") - 1; + } + else + { + // Ignore unknown argument + const char *c = strchr (p, ';'); + if (c) + p = c + 1; + else + p = strchr (p, '\0'); + } + } + else if (strncmp (p, "filename=", sizeof ("filename=") - 1) == 0) + { + p += sizeof ("filename=") - 1; + const char *c = strchr (p, ';'); + if (c == NULL) + { + c = strchr (p, '\0'); + continue; + } + char *fn = (char *) alloca (c - p + 1); + strncpy (fn, p, c - p); + fn[c - p] = '\0'; + + // A file name of "asl" is special and is another way to indicate + // that logging should be done via ASL, not by file. + if (strcmp (fn, "asl") == 0) + { + DNBLogToASL (); + } + else + { + FILE *f = fopen (fn, "w"); + if (f) + { + DNBLogSetLogFile (f); + DNBEnableLogging (f, DNBLogGetLogMask ()); + DNBLogToFile (); + } + } + p = c + 1; + } +#endif /* #if 0 to enforce ASL logging only. */ + else + { + // Ignore unknown argument + const char *c = strchr (p, ';'); + if (c) + p = c + 1; + else + p = strchr (p, '\0'); + } + } + + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_QThreadSuffixSupported (const char *p) +{ + m_thread_suffix_supported = true; + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_QStartNoAckMode (const char *p) +{ + // Send the OK packet first so the correct checksum is appended... + rnb_err_t result = SendPacket ("OK"); + m_noack_mode = true; + return result; +} + + +rnb_err_t +RNBRemote::HandlePacket_QSetLogging (const char *p) +{ + p += sizeof ("QSetLogging:") - 1; + rnb_err_t result = set_logging (p); + if (result == rnb_success) + return SendPacket ("OK"); + else + return SendPacket ("E35"); +} + +rnb_err_t +RNBRemote::HandlePacket_QSetDisableASLR (const char *p) +{ + extern int g_disable_aslr; + p += sizeof ("QSetDisableASLR:") - 1; + switch (*p) + { + case '0': g_disable_aslr = 0; break; + case '1': g_disable_aslr = 1; break; + default: + return SendPacket ("E56"); + } + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_QSetSTDIO (const char *p) +{ + // Only set stdin/out/err if we don't already have a process + if (!m_ctx.HasValidProcessID()) + { + bool success = false; + // Check the seventh character since the packet will be one of: + // QSetSTDIN + // QSetSTDOUT + // QSetSTDERR + StringExtractor packet(p); + packet.SetFilePos (7); + char ch = packet.GetChar(); + while (packet.GetChar() != ':') + /* Do nothing. */; + + switch (ch) + { + case 'I': // STDIN + packet.GetHexByteString (m_ctx.GetSTDIN()); + success = !m_ctx.GetSTDIN().empty(); + break; + + case 'O': // STDOUT + packet.GetHexByteString (m_ctx.GetSTDOUT()); + success = !m_ctx.GetSTDOUT().empty(); + break; + + case 'E': // STDERR + packet.GetHexByteString (m_ctx.GetSTDERR()); + success = !m_ctx.GetSTDERR().empty(); + break; + + default: + break; + } + if (success) + return SendPacket ("OK"); + return SendPacket ("E57"); + } + return SendPacket ("E58"); +} + +rnb_err_t +RNBRemote::HandlePacket_QSetWorkingDir (const char *p) +{ + // Only set the working directory if we don't already have a process + if (!m_ctx.HasValidProcessID()) + { + StringExtractor packet(p += sizeof ("QSetWorkingDir:") - 1); + if (packet.GetHexByteString (m_ctx.GetWorkingDir())) + { + struct stat working_dir_stat; + if (::stat(m_ctx.GetWorkingDirPath(), &working_dir_stat) == -1) + { + m_ctx.GetWorkingDir().clear(); + return SendPacket ("E61"); // Working directory doesn't exist... + } + else if ((working_dir_stat.st_mode & S_IFMT) == S_IFDIR) + { + return SendPacket ("OK"); + } + else + { + m_ctx.GetWorkingDir().clear(); + return SendPacket ("E62"); // Working directory isn't a directory... + } + } + return SendPacket ("E59"); // Invalid path + } + return SendPacket ("E60"); // Already had a process, too late to set working dir +} + +rnb_err_t +RNBRemote::HandlePacket_QSyncThreadState (const char *p) +{ + if (!m_ctx.HasValidProcessID()) + { + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + return SendPacket ("OK"); + } + + errno = 0; + p += strlen("QSyncThreadState:"); + nub_thread_t tid = strtoul (p, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid thread number in QSyncThreadState packet"); + } + if (DNBProcessSyncThreadState(m_ctx.ProcessID(), tid)) + return SendPacket("OK"); + else + return SendPacket ("E61"); +} + +rnb_err_t +RNBRemote::HandlePacket_QSetDetachOnError (const char *p) +{ + p += sizeof ("QSetDetachOnError:") - 1; + bool should_detach = true; + switch (*p) + { + case '0': should_detach = false; break; + case '1': should_detach = true; break; + default: + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid value for QSetDetachOnError - should be 0 or 1"); + break; + } + + m_ctx.SetDetachOnError(should_detach); + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_QListThreadsInStopReply (const char *p) +{ + // If this packet is received, it allows us to send an extra key/value + // pair in the stop reply packets where we will list all of the thread IDs + // separated by commas: + // + // "threads:10a,10b,10c;" + // + // This will get included in the stop reply packet as something like: + // + // "T11thread:10a;00:00000000;01:00010203:threads:10a,10b,10c;" + // + // This can save two packets on each stop: qfThreadInfo/qsThreadInfo and + // speed things up a bit. + // + // Send the OK packet first so the correct checksum is appended... + rnb_err_t result = SendPacket ("OK"); + m_list_threads_in_stop_reply = true; + + return result; +} + + +rnb_err_t +RNBRemote::HandlePacket_QSetMaxPayloadSize (const char *p) +{ + /* The number of characters in a packet payload that gdb is + prepared to accept. The packet-start char, packet-end char, + 2 checksum chars and terminating null character are not included + in this size. */ + p += sizeof ("QSetMaxPayloadSize:") - 1; + errno = 0; + uint32_t size = static_cast<uint32_t>(strtoul (p, NULL, 16)); + if (errno != 0 && size == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in QSetMaxPayloadSize packet"); + } + m_max_payload_size = size; + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_QSetMaxPacketSize (const char *p) +{ + /* This tells us the largest packet that gdb can handle. + i.e. the size of gdb's packet-reading buffer. + QSetMaxPayloadSize is preferred because it is less ambiguous. */ + p += sizeof ("QSetMaxPacketSize:") - 1; + errno = 0; + uint32_t size = static_cast<uint32_t>(strtoul (p, NULL, 16)); + if (errno != 0 && size == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in QSetMaxPacketSize packet"); + } + m_max_payload_size = size - 5; + return SendPacket ("OK"); +} + + + + +rnb_err_t +RNBRemote::HandlePacket_QEnvironment (const char *p) +{ + /* This sets the environment for the target program. The packet is of the form: + + QEnvironment:VARIABLE=VALUE + + */ + + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s Handling QEnvironment: \"%s\"", + (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, p); + + p += sizeof ("QEnvironment:") - 1; + RNBContext& ctx = Context(); + + ctx.PushEnvironment (p); + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_QEnvironmentHexEncoded (const char *p) +{ + /* This sets the environment for the target program. The packet is of the form: + + QEnvironmentHexEncoded:VARIABLE=VALUE + + The VARIABLE=VALUE part is sent hex-encoded so characters like '#' with special + meaning in the remote protocol won't break it. + */ + + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s Handling QEnvironmentHexEncoded: \"%s\"", + (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, p); + + p += sizeof ("QEnvironmentHexEncoded:") - 1; + + std::string arg; + const char *c; + c = p; + while (*c != '\0') + { + if (*(c + 1) == '\0') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in arg on 'QEnvironmentHexEncoded' pkt"); + } + char smallbuf[3]; + smallbuf[0] = *c; + smallbuf[1] = *(c + 1); + smallbuf[2] = '\0'; + errno = 0; + int ch = static_cast<int>(strtoul (smallbuf, NULL, 16)); + if (errno != 0 && ch == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in arg on 'QEnvironmentHexEncoded' pkt"); + } + arg.push_back(ch); + c += 2; + } + + RNBContext& ctx = Context(); + if (arg.length() > 0) + ctx.PushEnvironment (arg.c_str()); + + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_QLaunchArch (const char *p) +{ + p += sizeof ("QLaunchArch:") - 1; + if (DNBSetArchitecture(p)) + return SendPacket ("OK"); + return SendPacket ("E63"); +} + +rnb_err_t +RNBRemote::HandlePacket_QSetProcessEvent (const char *p) +{ + p += sizeof ("QSetProcessEvent:") - 1; + // If the process is running, then send the event to the process, otherwise + // store it in the context. + if (Context().HasValidProcessID()) + { + if (DNBProcessSendEvent (Context().ProcessID(), p)) + return SendPacket("OK"); + else + return SendPacket ("E80"); + } + else + { + Context().PushProcessEvent(p); + } + return SendPacket ("OK"); +} + +void +append_hex_value (std::ostream& ostrm, const void *buf, size_t buf_size, bool swap) +{ + int i; + const uint8_t *p = (const uint8_t *)buf; + if (swap) + { + for (i = static_cast<int>(buf_size)-1; i >= 0; i--) + ostrm << RAWHEX8(p[i]); + } + else + { + for (size_t i = 0; i < buf_size; i++) + ostrm << RAWHEX8(p[i]); + } +} + +void +append_hexified_string (std::ostream& ostrm, const std::string &string) +{ + size_t string_size = string.size(); + const char *string_buf = string.c_str(); + for (size_t i = 0; i < string_size; i++) + { + ostrm << RAWHEX8(*(string_buf + i)); + } +} + + + +void +register_value_in_hex_fixed_width (std::ostream& ostrm, + nub_process_t pid, + nub_thread_t tid, + const register_map_entry_t* reg, + const DNBRegisterValue *reg_value_ptr) +{ + if (reg != NULL) + { + DNBRegisterValue reg_value; + if (reg_value_ptr == NULL) + { + if (DNBThreadGetRegisterValueByID (pid, tid, reg->nub_info.set, reg->nub_info.reg, ®_value)) + reg_value_ptr = ®_value; + } + + if (reg_value_ptr) + { + append_hex_value (ostrm, reg_value_ptr->value.v_uint8, reg->nub_info.size, false); + } + else + { + // If we fail to read a register value, check if it has a default + // fail value. If it does, return this instead in case some of + // the registers are not available on the current system. + if (reg->nub_info.size > 0) + { + std::basic_string<uint8_t> zeros(reg->nub_info.size, '\0'); + append_hex_value (ostrm, zeros.data(), zeros.size(), false); + } + } + } +} + + +void +debugserver_regnum_with_fixed_width_hex_register_value (std::ostream& ostrm, + nub_process_t pid, + nub_thread_t tid, + const register_map_entry_t* reg, + const DNBRegisterValue *reg_value_ptr) +{ + // Output the register number as 'NN:VVVVVVVV;' where NN is a 2 bytes HEX + // gdb register number, and VVVVVVVV is the correct number of hex bytes + // as ASCII for the register value. + if (reg != NULL) + { + ostrm << RAWHEX8(reg->debugserver_regnum) << ':'; + register_value_in_hex_fixed_width (ostrm, pid, tid, reg, reg_value_ptr); + ostrm << ';'; + } +} + + +void +RNBRemote::DispatchQueueOffsets::GetThreadQueueInfo (nub_process_t pid, + nub_addr_t dispatch_qaddr, + std::string &queue_name, + uint64_t &queue_width, + uint64_t &queue_serialnum) const +{ + queue_name.clear(); + queue_width = 0; + queue_serialnum = 0; + + if (IsValid() && dispatch_qaddr != INVALID_NUB_ADDRESS && dispatch_qaddr != 0) + { + nub_addr_t dispatch_queue_addr = DNBProcessMemoryReadPointer (pid, dispatch_qaddr); + if (dispatch_queue_addr) + { + queue_width = DNBProcessMemoryReadInteger (pid, dispatch_queue_addr + dqo_width, dqo_width_size, 0); + queue_serialnum = DNBProcessMemoryReadInteger (pid, dispatch_queue_addr + dqo_serialnum, dqo_serialnum_size, 0); + + if (dqo_version >= 4) + { + // libdispatch versions 4+, pointer to dispatch name is in the + // queue structure. + nub_addr_t pointer_to_label_address = dispatch_queue_addr + dqo_label; + nub_addr_t label_addr = DNBProcessMemoryReadPointer (pid, pointer_to_label_address); + if (label_addr) + queue_name = std::move(DNBProcessMemoryReadCString (pid, label_addr)); + } + else + { + // libdispatch versions 1-3, dispatch name is a fixed width char array + // in the queue structure. + queue_name = std::move(DNBProcessMemoryReadCStringFixed(pid, dispatch_queue_addr + dqo_label, dqo_label_size)); + } + } + } +} + +struct StackMemory +{ + uint8_t bytes[2*sizeof(nub_addr_t)]; + nub_size_t length; +}; +typedef std::map<nub_addr_t, StackMemory> StackMemoryMap; + + +static void +ReadStackMemory (nub_process_t pid, nub_thread_t tid, StackMemoryMap &stack_mmap, uint32_t backtrace_limit = 256) +{ + DNBRegisterValue reg_value; + if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_FP, ®_value)) + { + uint32_t frame_count = 0; + uint64_t fp = 0; + if (reg_value.info.size == 4) + fp = reg_value.value.uint32; + else + fp = reg_value.value.uint64; + while (fp != 0) + { + // Make sure we never recurse more than 256 times so we don't recurse too far or + // store up too much memory in the expedited cache + if (++frame_count > backtrace_limit) + break; + + const nub_size_t read_size = reg_value.info.size*2; + StackMemory stack_memory; + stack_memory.length = read_size; + if (DNBProcessMemoryRead(pid, fp, read_size, stack_memory.bytes) != read_size) + break; + // Make sure we don't try to put the same stack memory in more than once + if (stack_mmap.find(fp) != stack_mmap.end()) + break; + // Put the entry into the cache + stack_mmap[fp] = stack_memory; + // Dereference the frame pointer to get to the previous frame pointer + if (reg_value.info.size == 4) + fp = ((uint32_t *)stack_memory.bytes)[0]; + else + fp = ((uint64_t *)stack_memory.bytes)[0]; + } + } +} + +rnb_err_t +RNBRemote::SendStopReplyPacketForThread (nub_thread_t tid) +{ + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket("E50"); + + struct DNBThreadStopInfo tid_stop_info; + + /* Fill the remaining space in this packet with as many registers + as we can stuff in there. */ + + if (DNBThreadGetStopReason (pid, tid, &tid_stop_info)) + { + const bool did_exec = tid_stop_info.reason == eStopTypeExec; + if (did_exec) + { + RNBRemote::InitializeRegisters(true); + + // Reset any symbols that need resetting when we exec + m_dispatch_queue_offsets_addr = INVALID_NUB_ADDRESS; + m_dispatch_queue_offsets.Clear(); + } + + std::ostringstream ostrm; + // Output the T packet with the thread + ostrm << 'T'; + int signum = tid_stop_info.details.signal.signo; + DNBLogThreadedIf (LOG_RNB_PROC, "%8d %s got signal signo = %u, exc_type = %u", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, signum, tid_stop_info.details.exception.type); + + // Translate any mach exceptions to gdb versions, unless they are + // common exceptions like a breakpoint or a soft signal. + switch (tid_stop_info.details.exception.type) + { + default: signum = 0; break; + case EXC_BREAKPOINT: signum = SIGTRAP; break; + case EXC_BAD_ACCESS: signum = TARGET_EXC_BAD_ACCESS; break; + case EXC_BAD_INSTRUCTION: signum = TARGET_EXC_BAD_INSTRUCTION; break; + case EXC_ARITHMETIC: signum = TARGET_EXC_ARITHMETIC; break; + case EXC_EMULATION: signum = TARGET_EXC_EMULATION; break; + case EXC_SOFTWARE: + if (tid_stop_info.details.exception.data_count == 2 && + tid_stop_info.details.exception.data[0] == EXC_SOFT_SIGNAL) + signum = static_cast<int>(tid_stop_info.details.exception.data[1]); + else + signum = TARGET_EXC_SOFTWARE; + break; + } + + ostrm << RAWHEX8(signum & 0xff); + + ostrm << std::hex << "thread:" << tid << ';'; + + const char *thread_name = DNBThreadGetName (pid, tid); + if (thread_name && thread_name[0]) + { + size_t thread_name_len = strlen(thread_name); + + + if (::strcspn (thread_name, "$#+-;:") == thread_name_len) + ostrm << std::hex << "name:" << thread_name << ';'; + else + { + // the thread name contains special chars, send as hex bytes + ostrm << std::hex << "hexname:"; + uint8_t *u_thread_name = (uint8_t *)thread_name; + for (size_t i = 0; i < thread_name_len; i++) + ostrm << RAWHEX8(u_thread_name[i]); + ostrm << ';'; + } + } + + thread_identifier_info_data_t thread_ident_info; + if (DNBThreadGetIdentifierInfo (pid, tid, &thread_ident_info)) + { + if (thread_ident_info.dispatch_qaddr != 0) + { + ostrm << "qaddr:" << std::hex << thread_ident_info.dispatch_qaddr << ';'; + const DispatchQueueOffsets *dispatch_queue_offsets = GetDispatchQueueOffsets(); + if (dispatch_queue_offsets) + { + std::string queue_name; + uint64_t queue_width = 0; + uint64_t queue_serialnum = 0; + dispatch_queue_offsets->GetThreadQueueInfo(pid, thread_ident_info.dispatch_qaddr, queue_name, queue_width, queue_serialnum); + if (!queue_name.empty()) + { + ostrm << "qname:"; + append_hex_value(ostrm, queue_name.data(), queue_name.size(), false); + ostrm << ';'; + } + if (queue_width == 1) + ostrm << "qkind:serial;"; + else if (queue_width > 1) + ostrm << "qkind:concurrent;"; + + if (queue_serialnum > 0) + ostrm << "qserial:" << DECIMAL << queue_serialnum << ';'; + } + } + } + + // If a 'QListThreadsInStopReply' was sent to enable this feature, we + // will send all thread IDs back in the "threads" key whose value is + // a list of hex thread IDs separated by commas: + // "threads:10a,10b,10c;" + // This will save the debugger from having to send a pair of qfThreadInfo + // and qsThreadInfo packets, but it also might take a lot of room in the + // stop reply packet, so it must be enabled only on systems where there + // are no limits on packet lengths. + if (m_list_threads_in_stop_reply) + { + const nub_size_t numthreads = DNBProcessGetNumThreads (pid); + if (numthreads > 0) + { + std::vector<uint64_t> pc_values; + ostrm << std::hex << "threads:"; + for (nub_size_t i = 0; i < numthreads; ++i) + { + nub_thread_t th = DNBProcessGetThreadAtIndex (pid, i); + if (i > 0) + ostrm << ','; + ostrm << std::hex << th; + DNBRegisterValue pc_regval; + if (DNBThreadGetRegisterValueByID (pid, th, REGISTER_SET_GENERIC, GENERIC_REGNUM_PC, &pc_regval)) + { + uint64_t pc = INVALID_NUB_ADDRESS; + if (pc_regval.value.uint64 != INVALID_NUB_ADDRESS) + { + if (pc_regval.info.size == 4) + { + pc = pc_regval.value.uint32; + } + else if (pc_regval.info.size == 8) + { + pc = pc_regval.value.uint64; + } + if (pc != INVALID_NUB_ADDRESS) + { + pc_values.push_back (pc); + } + } + } + } + ostrm << ';'; + + // If we failed to get any of the thread pc values, the size of our vector will not + // be the same as the # of threads. Don't provide any expedited thread pc values in + // that case. This should not happen. + if (pc_values.size() == numthreads) + { + ostrm << std::hex << "thread-pcs:"; + for (nub_size_t i = 0; i < numthreads; ++i) + { + if (i > 0) + ostrm << ','; + ostrm << std::hex << pc_values[i]; + } + ostrm << ';'; + } + } + + // Include JSON info that describes the stop reason for any threads + // that actually have stop reasons. We use the new "jstopinfo" key + // whose values is hex ascii JSON that contains the thread IDs + // thread stop info only for threads that have stop reasons. Only send + // this if we have more than one thread otherwise this packet has all + // the info it needs. + if (numthreads > 1) + { + const bool threads_with_valid_stop_info_only = true; + JSONGenerator::ObjectSP threads_info_sp = GetJSONThreadsInfo(threads_with_valid_stop_info_only); + if (threads_info_sp) + { + ostrm << std::hex << "jstopinfo:"; + std::ostringstream json_strm; + threads_info_sp->Dump (json_strm); + append_hexified_string (ostrm, json_strm.str()); + ostrm << ';'; + } + } + } + + + if (g_num_reg_entries == 0) + InitializeRegisters (); + + if (g_reg_entries != NULL) + { + DNBRegisterValue reg_value; + for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) + { + // Expedite all registers in the first register set that aren't + // contained in other registers + if (g_reg_entries[reg].nub_info.set == 1 && + g_reg_entries[reg].nub_info.value_regs == NULL) + { + if (!DNBThreadGetRegisterValueByID (pid, tid, g_reg_entries[reg].nub_info.set, g_reg_entries[reg].nub_info.reg, ®_value)) + continue; + + debugserver_regnum_with_fixed_width_hex_register_value (ostrm, pid, tid, &g_reg_entries[reg], ®_value); + } + } + } + + if (did_exec) + { + ostrm << "reason:exec;"; + } + else if (tid_stop_info.details.exception.type) + { + ostrm << "metype:" << std::hex << tid_stop_info.details.exception.type << ';'; + ostrm << "mecount:" << std::hex << tid_stop_info.details.exception.data_count << ';'; + for (nub_size_t i = 0; i < tid_stop_info.details.exception.data_count; ++i) + ostrm << "medata:" << std::hex << tid_stop_info.details.exception.data[i] << ';'; + } + + // Add expedited stack memory so stack backtracing doesn't need to read anything from the + // frame pointer chain. + StackMemoryMap stack_mmap; + ReadStackMemory (pid, tid, stack_mmap, 2); + if (!stack_mmap.empty()) + { + for (const auto &stack_memory : stack_mmap) + { + ostrm << "memory:" << HEXBASE << stack_memory.first << '='; + append_hex_value (ostrm, stack_memory.second.bytes, stack_memory.second.length, false); + ostrm << ';'; + } + } + + return SendPacket (ostrm.str ()); + } + return SendPacket("E51"); +} + +/* '?' + The stop reply packet - tell gdb what the status of the inferior is. + Often called the questionmark_packet. */ + +rnb_err_t +RNBRemote::HandlePacket_last_signal (const char *unused) +{ + if (!m_ctx.HasValidProcessID()) + { + // Inferior is not yet specified/running + return SendPacket ("E02"); + } + + nub_process_t pid = m_ctx.ProcessID(); + nub_state_t pid_state = DNBProcessGetState (pid); + + switch (pid_state) + { + case eStateAttaching: + case eStateLaunching: + case eStateRunning: + case eStateStepping: + case eStateDetached: + return rnb_success; // Ignore + + case eStateSuspended: + case eStateStopped: + case eStateCrashed: + { + nub_thread_t tid = DNBProcessGetCurrentThread (pid); + // Make sure we set the current thread so g and p packets return + // the data the gdb will expect. + SetCurrentThread (tid); + + SendStopReplyPacketForThread (tid); + } + break; + + case eStateInvalid: + case eStateUnloaded: + case eStateExited: + { + char pid_exited_packet[16] = ""; + int pid_status = 0; + // Process exited with exit status + if (!DNBProcessGetExitStatus(pid, &pid_status)) + pid_status = 0; + + if (pid_status) + { + if (WIFEXITED (pid_status)) + snprintf (pid_exited_packet, sizeof(pid_exited_packet), "W%02x", WEXITSTATUS (pid_status)); + else if (WIFSIGNALED (pid_status)) + snprintf (pid_exited_packet, sizeof(pid_exited_packet), "X%02x", WEXITSTATUS (pid_status)); + else if (WIFSTOPPED (pid_status)) + snprintf (pid_exited_packet, sizeof(pid_exited_packet), "S%02x", WSTOPSIG (pid_status)); + } + + // If we have an empty exit packet, lets fill one in to be safe. + if (!pid_exited_packet[0]) + { + strncpy (pid_exited_packet, "W00", sizeof(pid_exited_packet)-1); + pid_exited_packet[sizeof(pid_exited_packet)-1] = '\0'; + } + + const char *exit_info = DNBProcessGetExitInfo (pid); + if (exit_info != NULL && *exit_info != '\0') + { + std::ostringstream exit_packet; + exit_packet << pid_exited_packet; + exit_packet << ';'; + exit_packet << RAW_HEXBASE << "description"; + exit_packet << ':'; + for (size_t i = 0; exit_info[i] != '\0'; i++) + exit_packet << RAWHEX8(exit_info[i]); + exit_packet << ';'; + return SendPacket (exit_packet.str()); + } + else + return SendPacket (pid_exited_packet); + } + break; + } + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_M (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Too short M packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid address in M packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Comma sep missing in M packet"); + } + + /* Advance 'p' to the length part of the packet. */ + p += (c - p) + 1; + + errno = 0; + unsigned long length = strtoul (p, &c, 16); + if (errno != 0 && length == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in M packet"); + } + if (length == 0) + { + return SendPacket ("OK"); + } + + if (*c != ':') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Missing colon in M packet"); + } + /* Advance 'p' to the data part of the packet. */ + p += (c - p) + 1; + + size_t datalen = strlen (p); + if (datalen & 0x1) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Uneven # of hex chars for data in M packet"); + } + if (datalen == 0) + { + return SendPacket ("OK"); + } + + uint8_t *buf = (uint8_t *) alloca (datalen / 2); + uint8_t *i = buf; + + while (*p != '\0' && *(p + 1) != '\0') + { + char hexbuf[3]; + hexbuf[0] = *p; + hexbuf[1] = *(p + 1); + hexbuf[2] = '\0'; + errno = 0; + uint8_t byte = strtoul (hexbuf, NULL, 16); + if (errno != 0 && byte == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid hex byte in M packet"); + } + *i++ = byte; + p += 2; + } + + nub_size_t wrote = DNBProcessMemoryWrite (m_ctx.ProcessID(), addr, length, buf); + if (wrote != length) + return SendPacket ("E09"); + else + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_m (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Too short m packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid address in m packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Comma sep missing in m packet"); + } + + /* Advance 'p' to the length part of the packet. */ + p += (c - p) + 1; + + errno = 0; + auto length = strtoul (p, NULL, 16); + if (errno != 0 && length == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in m packet"); + } + if (length == 0) + { + return SendPacket (""); + } + + std::string buf(length, '\0'); + if (buf.empty()) + { + return SendPacket ("E78"); + } + nub_size_t bytes_read = DNBProcessMemoryRead (m_ctx.ProcessID(), addr, buf.size(), &buf[0]); + if (bytes_read == 0) + { + return SendPacket ("E08"); + } + + // "The reply may contain fewer bytes than requested if the server was able + // to read only part of the region of memory." + length = bytes_read; + + std::ostringstream ostrm; + for (unsigned long i = 0; i < length; i++) + ostrm << RAWHEX8(buf[i]); + return SendPacket (ostrm.str ()); +} + +// Read memory, sent it up as binary data. +// Usage: xADDR,LEN +// ADDR and LEN are both base 16. + +// Responds with 'OK' for zero-length request +// or +// +// DATA +// +// where DATA is the binary data payload. + +rnb_err_t +RNBRemote::HandlePacket_x (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Too short X packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid address in X packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Comma sep missing in X packet"); + } + + /* Advance 'p' to the number of bytes to be read. */ + p += (c - p) + 1; + + errno = 0; + auto length = strtoul (p, NULL, 16); + if (errno != 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in x packet"); + } + + // zero length read means this is a test of whether that packet is implemented or not. + if (length == 0) + { + return SendPacket ("OK"); + } + + std::vector<uint8_t> buf (length); + + if (buf.capacity() != length) + { + return SendPacket ("E79"); + } + nub_size_t bytes_read = DNBProcessMemoryRead (m_ctx.ProcessID(), addr, buf.size(), &buf[0]); + if (bytes_read == 0) + { + return SendPacket ("E80"); + } + + std::vector<uint8_t> buf_quoted; + buf_quoted.reserve (bytes_read + 30); + for (nub_size_t i = 0; i < bytes_read; i++) + { + if (buf[i] == '#' || buf[i] == '$' || buf[i] == '}' || buf[i] == '*') + { + buf_quoted.push_back(0x7d); + buf_quoted.push_back(buf[i] ^ 0x20); + } + else + { + buf_quoted.push_back(buf[i]); + } + } + length = buf_quoted.size(); + + std::ostringstream ostrm; + for (unsigned long i = 0; i < length; i++) + ostrm << buf_quoted[i]; + + return SendPacket (ostrm.str ()); +} + +rnb_err_t +RNBRemote::HandlePacket_X (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Too short X packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid address in X packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Comma sep missing in X packet"); + } + + /* Advance 'p' to the length part of the packet. NB this is the length of the packet + including any escaped chars. The data payload may be a little bit smaller after + decoding. */ + p += (c - p) + 1; + + errno = 0; + auto length = strtoul (p, NULL, 16); + if (errno != 0 && length == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in X packet"); + } + + // I think gdb sends a zero length write request to test whether this + // packet is accepted. + if (length == 0) + { + return SendPacket ("OK"); + } + + std::vector<uint8_t> data = decode_binary_data (c, -1); + std::vector<uint8_t>::const_iterator it; + uint8_t *buf = (uint8_t *) alloca (data.size ()); + uint8_t *i = buf; + for (it = data.begin (); it != data.end (); ++it) + { + *i++ = *it; + } + + nub_size_t wrote = DNBProcessMemoryWrite (m_ctx.ProcessID(), addr, data.size(), buf); + if (wrote != data.size ()) + return SendPacket ("E08"); + return SendPacket ("OK"); +} + +/* 'g' -- read registers + Get the contents of the registers for the current thread, + send them to gdb. + Should the setting of the Hg packet determine which thread's registers + are returned? */ + +rnb_err_t +RNBRemote::HandlePacket_g (const char *p) +{ + std::ostringstream ostrm; + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E11"); + } + + if (g_num_reg_entries == 0) + InitializeRegisters (); + + nub_process_t pid = m_ctx.ProcessID (); + nub_thread_t tid = ExtractThreadIDFromThreadSuffix (p + 1); + if (tid == INVALID_NUB_THREAD) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in p packet"); + + // Get the register context size first by calling with NULL buffer + nub_size_t reg_ctx_size = DNBThreadGetRegisterContext(pid, tid, NULL, 0); + if (reg_ctx_size) + { + // Now allocate enough space for the entire register context + std::vector<uint8_t> reg_ctx; + reg_ctx.resize(reg_ctx_size); + // Now read the register context + reg_ctx_size = DNBThreadGetRegisterContext(pid, tid, ®_ctx[0], reg_ctx.size()); + if (reg_ctx_size) + { + append_hex_value (ostrm, reg_ctx.data(), reg_ctx.size(), false); + return SendPacket (ostrm.str ()); + } + } + return SendPacket ("E74"); +} + +/* 'G XXX...' -- write registers + How is the thread for these specified, beyond "the current thread"? + Does gdb actually use the Hg packet to set this? */ + +rnb_err_t +RNBRemote::HandlePacket_G (const char *p) +{ + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E11"); + } + + if (g_num_reg_entries == 0) + InitializeRegisters (); + + StringExtractor packet(p); + packet.SetFilePos(1); // Skip the 'G' + + nub_process_t pid = m_ctx.ProcessID(); + nub_thread_t tid = ExtractThreadIDFromThreadSuffix (p); + if (tid == INVALID_NUB_THREAD) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in p packet"); + + // Get the register context size first by calling with NULL buffer + nub_size_t reg_ctx_size = DNBThreadGetRegisterContext(pid, tid, NULL, 0); + if (reg_ctx_size) + { + // Now allocate enough space for the entire register context + std::vector<uint8_t> reg_ctx; + reg_ctx.resize(reg_ctx_size); + + const nub_size_t bytes_extracted = packet.GetHexBytes (®_ctx[0], reg_ctx.size(), 0xcc); + if (bytes_extracted == reg_ctx.size()) + { + // Now write the register context + reg_ctx_size = DNBThreadSetRegisterContext(pid, tid, reg_ctx.data(), reg_ctx.size()); + if (reg_ctx_size == reg_ctx.size()) + return SendPacket ("OK"); + else + return SendPacket ("E55"); + } + else + { + DNBLogError("RNBRemote::HandlePacket_G(%s): extracted %llu of %llu bytes, size mismatch\n", p, (uint64_t)bytes_extracted, (uint64_t)reg_ctx_size); + return SendPacket ("E64"); + } + } + return SendPacket ("E65"); +} + +static bool +RNBRemoteShouldCancelCallback (void *not_used) +{ + RNBRemoteSP remoteSP(g_remoteSP); + if (remoteSP.get() != NULL) + { + RNBRemote* remote = remoteSP.get(); + if (remote->Comm().IsConnected()) + return false; + else + return true; + } + return true; +} + + +// FORMAT: _MXXXXXX,PPP +// XXXXXX: big endian hex chars +// PPP: permissions can be any combo of r w x chars +// +// RESPONSE: XXXXXX +// XXXXXX: hex address of the newly allocated memory +// EXX: error code +// +// EXAMPLES: +// _M123000,rw +// _M123000,rwx +// _M123000,xw + +rnb_err_t +RNBRemote::HandlePacket_AllocateMemory (const char *p) +{ + StringExtractor packet (p); + packet.SetFilePos(2); // Skip the "_M" + + nub_addr_t size = packet.GetHexMaxU64 (StringExtractor::BigEndian, 0); + if (size != 0) + { + if (packet.GetChar() == ',') + { + uint32_t permissions = 0; + char ch; + bool success = true; + while (success && (ch = packet.GetChar()) != '\0') + { + switch (ch) + { + case 'r': permissions |= eMemoryPermissionsReadable; break; + case 'w': permissions |= eMemoryPermissionsWritable; break; + case 'x': permissions |= eMemoryPermissionsExecutable; break; + default: success = false; break; + } + } + + if (success) + { + nub_addr_t addr = DNBProcessMemoryAllocate (m_ctx.ProcessID(), size, permissions); + if (addr != INVALID_NUB_ADDRESS) + { + std::ostringstream ostrm; + ostrm << RAW_HEXBASE << addr; + return SendPacket (ostrm.str ()); + } + } + } + } + return SendPacket ("E53"); +} + +// FORMAT: _mXXXXXX +// XXXXXX: address that was previously allocated +// +// RESPONSE: XXXXXX +// OK: address was deallocated +// EXX: error code +// +// EXAMPLES: +// _m123000 + +rnb_err_t +RNBRemote::HandlePacket_DeallocateMemory (const char *p) +{ + StringExtractor packet (p); + packet.SetFilePos(2); // Skip the "_m" + nub_addr_t addr = packet.GetHexMaxU64 (StringExtractor::BigEndian, INVALID_NUB_ADDRESS); + + if (addr != INVALID_NUB_ADDRESS) + { + if (DNBProcessMemoryDeallocate (m_ctx.ProcessID(), addr)) + return SendPacket ("OK"); + } + return SendPacket ("E54"); +} + + +// FORMAT: QSaveRegisterState;thread:TTTT; (when thread suffix is supported) +// FORMAT: QSaveRegisterState (when thread suffix is NOT supported) +// TTTT: thread ID in hex +// +// RESPONSE: +// SAVEID: Where SAVEID is a decimal number that represents the save ID +// that can be passed back into a "QRestoreRegisterState" packet +// EXX: error code +// +// EXAMPLES: +// QSaveRegisterState;thread:1E34; (when thread suffix is supported) +// QSaveRegisterState (when thread suffix is NOT supported) + +rnb_err_t +RNBRemote::HandlePacket_SaveRegisterState (const char *p) +{ + nub_process_t pid = m_ctx.ProcessID (); + nub_thread_t tid = ExtractThreadIDFromThreadSuffix (p); + if (tid == INVALID_NUB_THREAD) + { + if (m_thread_suffix_supported) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in QSaveRegisterState packet"); + else + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread was is set with the Hg packet"); + } + + // Get the register context size first by calling with NULL buffer + const uint32_t save_id = DNBThreadSaveRegisterState(pid, tid); + if (save_id != 0) + { + char response[64]; + snprintf (response, sizeof(response), "%u", save_id); + return SendPacket (response); + } + else + { + return SendPacket ("E75"); + } +} +// FORMAT: QRestoreRegisterState:SAVEID;thread:TTTT; (when thread suffix is supported) +// FORMAT: QRestoreRegisterState:SAVEID (when thread suffix is NOT supported) +// TTTT: thread ID in hex +// SAVEID: a decimal number that represents the save ID that was +// returned from a call to "QSaveRegisterState" +// +// RESPONSE: +// OK: successfully restored registers for the specified thread +// EXX: error code +// +// EXAMPLES: +// QRestoreRegisterState:1;thread:1E34; (when thread suffix is supported) +// QRestoreRegisterState:1 (when thread suffix is NOT supported) + +rnb_err_t +RNBRemote::HandlePacket_RestoreRegisterState (const char *p) +{ + nub_process_t pid = m_ctx.ProcessID (); + nub_thread_t tid = ExtractThreadIDFromThreadSuffix (p); + if (tid == INVALID_NUB_THREAD) + { + if (m_thread_suffix_supported) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in QSaveRegisterState packet"); + else + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread was is set with the Hg packet"); + } + + StringExtractor packet (p); + packet.SetFilePos(strlen("QRestoreRegisterState:")); // Skip the "QRestoreRegisterState:" + const uint32_t save_id = packet.GetU32(0); + + if (save_id != 0) + { + // Get the register context size first by calling with NULL buffer + if (DNBThreadRestoreRegisterState(pid, tid, save_id)) + return SendPacket ("OK"); + else + return SendPacket ("E77"); + } + return SendPacket ("E76"); +} + +static bool +GetProcessNameFrom_vAttach (const char *&p, std::string &attach_name) +{ + bool return_val = true; + while (*p != '\0') + { + char smallbuf[3]; + smallbuf[0] = *p; + smallbuf[1] = *(p + 1); + smallbuf[2] = '\0'; + + errno = 0; + int ch = static_cast<int>(strtoul (smallbuf, NULL, 16)); + if (errno != 0 && ch == 0) + { + return_val = false; + break; + } + + attach_name.push_back(ch); + p += 2; + } + return return_val; +} + +rnb_err_t +RNBRemote::HandlePacket_qSupported (const char *p) +{ + uint32_t max_packet_size = 128 * 1024; // 128KBytes is a reasonable max packet size--debugger can always use less + char buf[256]; + snprintf (buf, sizeof(buf), "qXfer:features:read+;PacketSize=%x;qEcho+", max_packet_size); + + // By default, don't enable compression. It's only worth doing when we are working + // with a low speed communication channel. + bool enable_compression = false; + (void)enable_compression; + + // Enable compression when debugserver is running on a watchOS device where communication may be over Bluetooth. +#if defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + enable_compression = true; +#endif + +#if defined (HAVE_LIBCOMPRESSION) + // libcompression is weak linked so test if compression_decode_buffer() is available + if (enable_compression && compression_decode_buffer != NULL) + { + strcat (buf, ";SupportedCompressions=lzfse,zlib-deflate,lz4,lzma;DefaultCompressionMinSize="); + char numbuf[16]; + snprintf (numbuf, sizeof (numbuf), "%zu", m_compression_minsize); + numbuf[sizeof (numbuf) - 1] = '\0'; + strcat (buf, numbuf); + } +#elif defined (HAVE_LIBZ) + if (enable_compression) + { + strcat (buf, ";SupportedCompressions=zlib-deflate;DefaultCompressionMinSize="); + char numbuf[16]; + snprintf (numbuf, sizeof (numbuf), "%zu", m_compression_minsize); + numbuf[sizeof (numbuf) - 1] = '\0'; + strcat (buf, numbuf); + } +#endif + + return SendPacket (buf); +} + +/* + vAttach;pid + + Attach to a new process with the specified process ID. pid is a hexadecimal integer + identifying the process. If the stub is currently controlling a process, it is + killed. The attached process is stopped.This packet is only available in extended + mode (see extended mode). + + Reply: + "ENN" for an error + "Any Stop Reply Packet" for success + */ + +rnb_err_t +RNBRemote::HandlePacket_v (const char *p) +{ + if (strcmp (p, "vCont;c") == 0) + { + // Simple continue + return RNBRemote::HandlePacket_c("c"); + } + else if (strcmp (p, "vCont;s") == 0) + { + // Simple step + return RNBRemote::HandlePacket_s("s"); + } + else if (strstr (p, "vCont") == p) + { + typedef struct + { + nub_thread_t tid; + char action; + int signal; + } vcont_action_t; + + DNBThreadResumeActions thread_actions; + char *c = (char *)(p += strlen("vCont")); + char *c_end = c + strlen(c); + if (*c == '?') + return SendPacket ("vCont;c;C;s;S"); + + while (c < c_end && *c == ';') + { + ++c; // Skip the semi-colon + DNBThreadResumeAction thread_action; + thread_action.tid = INVALID_NUB_THREAD; + thread_action.state = eStateInvalid; + thread_action.signal = 0; + thread_action.addr = INVALID_NUB_ADDRESS; + + char action = *c++; + + switch (action) + { + case 'C': + errno = 0; + thread_action.signal = static_cast<int>(strtoul (c, &c, 16)); + if (errno != 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse signal in vCont packet"); + // Fall through to next case... + + case 'c': + // Continue + thread_action.state = eStateRunning; + break; + + case 'S': + errno = 0; + thread_action.signal = static_cast<int>(strtoul (c, &c, 16)); + if (errno != 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse signal in vCont packet"); + // Fall through to next case... + + case 's': + // Step + thread_action.state = eStateStepping; + break; + + default: + HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Unsupported action in vCont packet"); + break; + } + if (*c == ':') + { + errno = 0; + thread_action.tid = strtoul (++c, &c, 16); + if (errno != 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse thread number in vCont packet"); + } + + thread_actions.Append (thread_action); + } + + // If a default action for all other threads wasn't mentioned + // then we should stop the threads + thread_actions.SetDefaultThreadActionIfNeeded (eStateStopped, 0); + DNBProcessResume(m_ctx.ProcessID(), thread_actions.GetFirst (), thread_actions.GetSize()); + return rnb_success; + } + else if (strstr (p, "vAttach") == p) + { + nub_process_t attach_pid = INVALID_NUB_PROCESS; // attach_pid will be set to 0 if the attach fails + nub_process_t pid_attaching_to = INVALID_NUB_PROCESS; // pid_attaching_to is the original pid specified + char err_str[1024]={'\0'}; + std::string attach_name; + + if (strstr (p, "vAttachWait;") == p) + { + p += strlen("vAttachWait;"); + if (!GetProcessNameFrom_vAttach(p, attach_name)) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in arg on 'vAttachWait' pkt"); + } + const bool ignore_existing = true; + attach_pid = DNBProcessAttachWait(attach_name.c_str (), m_ctx.LaunchFlavor(), ignore_existing, NULL, 1000, err_str, sizeof(err_str), RNBRemoteShouldCancelCallback); + + } + else if (strstr (p, "vAttachOrWait;") == p) + { + p += strlen("vAttachOrWait;"); + if (!GetProcessNameFrom_vAttach(p, attach_name)) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in arg on 'vAttachOrWait' pkt"); + } + const bool ignore_existing = false; + attach_pid = DNBProcessAttachWait(attach_name.c_str (), m_ctx.LaunchFlavor(), ignore_existing, NULL, 1000, err_str, sizeof(err_str), RNBRemoteShouldCancelCallback); + } + else if (strstr (p, "vAttachName;") == p) + { + p += strlen("vAttachName;"); + if (!GetProcessNameFrom_vAttach(p, attach_name)) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "non-hex char in arg on 'vAttachName' pkt"); + } + + attach_pid = DNBProcessAttachByName (attach_name.c_str(), NULL, err_str, sizeof(err_str)); + + } + else if (strstr (p, "vAttach;") == p) + { + p += strlen("vAttach;"); + char *end = NULL; + pid_attaching_to = static_cast<int>(strtoul (p, &end, 16)); // PID will be in hex, so use base 16 to decode + if (p != end && *end == '\0') + { + // Wait at most 30 second for attach + struct timespec attach_timeout_abstime; + DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, 30, 0); + attach_pid = DNBProcessAttach(pid_attaching_to, &attach_timeout_abstime, err_str, sizeof(err_str)); + } + } + else + { + return HandlePacket_UNIMPLEMENTED(p); + } + + + if (attach_pid != INVALID_NUB_PROCESS) + { + if (m_ctx.ProcessID() != attach_pid) + m_ctx.SetProcessID(attach_pid); + // Send a stop reply packet to indicate we successfully attached! + NotifyThatProcessStopped (); + return rnb_success; + } + else + { + m_ctx.LaunchStatus().SetError(-1, DNBError::Generic); + if (err_str[0]) + m_ctx.LaunchStatus().SetErrorString(err_str); + else + m_ctx.LaunchStatus().SetErrorString("attach failed"); + +#if defined (__APPLE__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101000) + if (pid_attaching_to == INVALID_NUB_PROCESS && !attach_name.empty()) + { + pid_attaching_to = DNBProcessGetPIDByName (attach_name.c_str()); + } + if (pid_attaching_to != INVALID_NUB_PROCESS && strcmp (err_str, "No such process") != 0) + { + // csr_check(CSR_ALLOW_TASK_FOR_PID) will be nonzero if System Integrity Protection is in effect. + if (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) + { + bool attach_failed_due_to_sip = false; + + if (rootless_allows_task_for_pid (pid_attaching_to) == 0) + { + attach_failed_due_to_sip = true; + } + + if (attach_failed_due_to_sip == false) + { + int csops_flags = 0; + int retval = ::csops (pid_attaching_to, CS_OPS_STATUS, &csops_flags, sizeof (csops_flags)); + if (retval != -1 && (csops_flags & CS_RESTRICT)) + { + attach_failed_due_to_sip = true; + } + } + if (attach_failed_due_to_sip) + { + SendPacket ("E87"); // E87 is the magic value which says that we are not allowed to attach + DNBLogError ("Attach failed because process does not allow attaching: \"%s\".", err_str); + return rnb_err; + } + } + } + +#endif + + SendPacket ("E01"); // E01 is our magic error value for attach failed. + DNBLogError ("Attach failed: \"%s\".", err_str); + return rnb_err; + } + } + + // All other failures come through here + return HandlePacket_UNIMPLEMENTED(p); +} + +/* 'T XX' -- status of thread + Check if the specified thread is alive. + The thread number is in hex? */ + +rnb_err_t +RNBRemote::HandlePacket_T (const char *p) +{ + p++; + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in T packet"); + } + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E15"); + } + errno = 0; + nub_thread_t tid = strtoul (p, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse thread number in T packet"); + } + + nub_state_t state = DNBThreadGetState (m_ctx.ProcessID(), tid); + if (state == eStateInvalid || state == eStateExited || state == eStateCrashed) + { + return SendPacket ("E16"); + } + + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_z (const char *p) +{ + if (p == NULL || *p == '\0') + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in z packet"); + + if (!m_ctx.HasValidProcessID()) + return SendPacket ("E15"); + + char packet_cmd = *p++; + char break_type = *p++; + + if (*p++ != ',') + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Comma separator missing in z packet"); + + char *c = NULL; + nub_process_t pid = m_ctx.ProcessID(); + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid address in z packet"); + p = c; + if (*p++ != ',') + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Comma separator missing in z packet"); + + errno = 0; + auto byte_size = strtoul (p, &c, 16); + if (errno != 0 && byte_size == 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid length in z packet"); + + if (packet_cmd == 'Z') + { + // set + switch (break_type) + { + case '0': // set software breakpoint + case '1': // set hardware breakpoint + { + // gdb can send multiple Z packets for the same address and + // these calls must be ref counted. + bool hardware = (break_type == '1'); + + if (DNBBreakpointSet (pid, addr, byte_size, hardware)) + { + // We successfully created a breakpoint, now lets full out + // a ref count structure with the breakID and add it to our + // map. + return SendPacket ("OK"); + } + else + { + // We failed to set the software breakpoint + return SendPacket ("E09"); + } + } + break; + + case '2': // set write watchpoint + case '3': // set read watchpoint + case '4': // set access watchpoint + { + bool hardware = true; + uint32_t watch_flags = 0; + if (break_type == '2') + watch_flags = WATCH_TYPE_WRITE; + else if (break_type == '3') + watch_flags = WATCH_TYPE_READ; + else + watch_flags = WATCH_TYPE_READ | WATCH_TYPE_WRITE; + + if (DNBWatchpointSet (pid, addr, byte_size, watch_flags, hardware)) + { + return SendPacket ("OK"); + } + else + { + // We failed to set the watchpoint + return SendPacket ("E09"); + } + } + break; + + default: + break; + } + } + else if (packet_cmd == 'z') + { + // remove + switch (break_type) + { + case '0': // remove software breakpoint + case '1': // remove hardware breakpoint + if (DNBBreakpointClear (pid, addr)) + { + return SendPacket ("OK"); + } + else + { + return SendPacket ("E08"); + } + break; + + case '2': // remove write watchpoint + case '3': // remove read watchpoint + case '4': // remove access watchpoint + if (DNBWatchpointClear (pid, addr)) + { + return SendPacket ("OK"); + } + else + { + return SendPacket ("E08"); + } + break; + + default: + break; + } + } + return HandlePacket_UNIMPLEMENTED(p); +} + +// Extract the thread number from the thread suffix that might be appended to +// thread specific packets. This will only be enabled if m_thread_suffix_supported +// is true. +nub_thread_t +RNBRemote::ExtractThreadIDFromThreadSuffix (const char *p) +{ + if (m_thread_suffix_supported) + { + nub_thread_t tid = INVALID_NUB_THREAD; + if (p) + { + const char *tid_cstr = strstr (p, "thread:"); + if (tid_cstr) + { + tid_cstr += strlen ("thread:"); + tid = strtoul(tid_cstr, NULL, 16); + } + } + return tid; + } + return GetCurrentThread(); + +} + +/* 'p XX' + print the contents of register X */ + +rnb_err_t +RNBRemote::HandlePacket_p (const char *p) +{ + if (g_num_reg_entries == 0) + InitializeRegisters (); + + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in p packet"); + } + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E15"); + } + nub_process_t pid = m_ctx.ProcessID(); + errno = 0; + char *tid_cstr = NULL; + uint32_t reg = static_cast<uint32_t>(strtoul (p + 1, &tid_cstr, 16)); + if (errno != 0 && reg == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse register number in p packet"); + } + + nub_thread_t tid = ExtractThreadIDFromThreadSuffix (tid_cstr); + if (tid == INVALID_NUB_THREAD) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in p packet"); + + const register_map_entry_t *reg_entry; + + if (reg < g_num_reg_entries) + reg_entry = &g_reg_entries[reg]; + else + reg_entry = NULL; + + std::ostringstream ostrm; + if (reg_entry == NULL) + { + DNBLogError("RNBRemote::HandlePacket_p(%s): unknown register number %u requested\n", p, reg); + ostrm << "00000000"; + } + else if (reg_entry->nub_info.reg == (uint32_t)-1) + { + if (reg_entry->nub_info.size > 0) + { + std::basic_string<uint8_t> zeros(reg_entry->nub_info.size, '\0'); + append_hex_value(ostrm, zeros.data(), zeros.size(), false); + } + } + else + { + register_value_in_hex_fixed_width (ostrm, pid, tid, reg_entry, NULL); + } + return SendPacket (ostrm.str()); +} + +/* 'Pnn=rrrrr' + Set register number n to value r. + n and r are hex strings. */ + +rnb_err_t +RNBRemote::HandlePacket_P (const char *p) +{ + if (g_num_reg_entries == 0) + InitializeRegisters (); + + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Empty P packet"); + } + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E28"); + } + + nub_process_t pid = m_ctx.ProcessID(); + + StringExtractor packet (p); + + const char cmd_char = packet.GetChar(); + // Register ID is always in big endian + const uint32_t reg = packet.GetHexMaxU32 (false, UINT32_MAX); + const char equal_char = packet.GetChar(); + + if (cmd_char != 'P') + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Improperly formed P packet"); + + if (reg == UINT32_MAX) + return SendPacket ("E29"); + + if (equal_char != '=') + return SendPacket ("E30"); + + const register_map_entry_t *reg_entry; + + if (reg >= g_num_reg_entries) + return SendPacket("E47"); + + reg_entry = &g_reg_entries[reg]; + + if (reg_entry->nub_info.set == (uint32_t)-1 && reg_entry->nub_info.reg == (uint32_t)-1) + { + DNBLogError("RNBRemote::HandlePacket_P(%s): unknown register number %u requested\n", p, reg); + return SendPacket("E48"); + } + + DNBRegisterValue reg_value; + reg_value.info = reg_entry->nub_info; + packet.GetHexBytes (reg_value.value.v_sint8, reg_entry->nub_info.size, 0xcc); + + nub_thread_t tid = ExtractThreadIDFromThreadSuffix (p); + if (tid == INVALID_NUB_THREAD) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "No thread specified in p packet"); + + if (!DNBThreadSetRegisterValueByID (pid, tid, reg_entry->nub_info.set, reg_entry->nub_info.reg, ®_value)) + { + return SendPacket ("E32"); + } + return SendPacket ("OK"); +} + +/* 'c [addr]' + Continue, optionally from a specified address. */ + +rnb_err_t +RNBRemote::HandlePacket_c (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E23"); + + DNBThreadResumeAction action = { INVALID_NUB_THREAD, eStateRunning, 0, INVALID_NUB_ADDRESS }; + + if (*(p + 1) != '\0') + { + action.tid = GetContinueThread(); + errno = 0; + action.addr = strtoull (p + 1, NULL, 16); + if (errno != 0 && action.addr == 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse address in c packet"); + } + + DNBThreadResumeActions thread_actions; + thread_actions.Append(action); + thread_actions.SetDefaultThreadActionIfNeeded(eStateRunning, 0); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E25"); + // Don't send an "OK" packet; response is the stopped/exited message. + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_MemoryRegionInfo (const char *p) +{ + /* This packet will find memory attributes (e.g. readable, writable, executable, stack, jitted code) + for the memory region containing a given address and return that information. + + Users of this packet must be prepared for three results: + + Region information is returned + Region information is unavailable for this address because the address is in unmapped memory + Region lookup cannot be performed on this platform or process is not yet launched + This packet isn't implemented + + Examples of use: + qMemoryRegionInfo:3a55140 + start:3a50000,size:100000,permissions:rwx + + qMemoryRegionInfo:0 + error:address in unmapped region + + qMemoryRegionInfo:3a551140 (on a different platform) + error:region lookup cannot be performed + + qMemoryRegionInfo + OK // this packet is implemented by the remote nub + */ + + p += sizeof ("qMemoryRegionInfo") - 1; + if (*p == '\0') + return SendPacket ("OK"); + if (*p++ != ':') + return SendPacket ("E67"); + if (*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X')) + p += 2; + + errno = 0; + uint64_t address = strtoul (p, NULL, 16); + if (errno != 0 && address == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Invalid address in qMemoryRegionInfo packet"); + } + + DNBRegionInfo region_info = { 0, 0, 0 }; + DNBProcessMemoryRegionInfo (m_ctx.ProcessID(), address, ®ion_info); + std::ostringstream ostrm; + + // start:3a50000,size:100000,permissions:rwx + ostrm << "start:" << std::hex << region_info.addr << ';'; + + if (region_info.size > 0) + ostrm << "size:" << std::hex << region_info.size << ';'; + + if (region_info.permissions) + { + ostrm << "permissions:"; + + if (region_info.permissions & eMemoryPermissionsReadable) + ostrm << 'r'; + if (region_info.permissions & eMemoryPermissionsWritable) + ostrm << 'w'; + if (region_info.permissions & eMemoryPermissionsExecutable) + ostrm << 'x'; + ostrm << ';'; + } + return SendPacket (ostrm.str()); +} + +// qGetProfileData;scan_type:0xYYYYYYY +rnb_err_t +RNBRemote::HandlePacket_GetProfileData (const char *p) +{ + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + StringExtractor packet(p += sizeof ("qGetProfileData")); + DNBProfileDataScanType scan_type = eProfileAll; + std::string name; + std::string value; + while (packet.GetNameColonValue(name, value)) + { + if (name.compare ("scan_type") == 0) + { + std::istringstream iss(value); + uint32_t int_value = 0; + if (iss >> std::hex >> int_value) + { + scan_type = (DNBProfileDataScanType)int_value; + } + } + } + + std::string data = DNBProcessGetProfileData(pid, scan_type); + if (!data.empty()) + { + return SendPacket (data.c_str()); + } + else + { + return SendPacket ("OK"); + } +} + +// QSetEnableAsyncProfiling;enable:[0|1]:interval_usec:XXXXXX;scan_type:0xYYYYYYY +rnb_err_t +RNBRemote::HandlePacket_SetEnableAsyncProfiling (const char *p) +{ + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + StringExtractor packet(p += sizeof ("QSetEnableAsyncProfiling")); + bool enable = false; + uint64_t interval_usec = 0; + DNBProfileDataScanType scan_type = eProfileAll; + std::string name; + std::string value; + while (packet.GetNameColonValue(name, value)) + { + if (name.compare ("enable") == 0) + { + enable = strtoul(value.c_str(), NULL, 10) > 0; + } + else if (name.compare ("interval_usec") == 0) + { + interval_usec = strtoul(value.c_str(), NULL, 10); + } + else if (name.compare ("scan_type") == 0) + { + std::istringstream iss(value); + uint32_t int_value = 0; + if (iss >> std::hex >> int_value) + { + scan_type = (DNBProfileDataScanType)int_value; + } + } + } + + if (interval_usec == 0) + { + enable = 0; + } + + DNBProcessSetEnableAsyncProfiling(pid, enable, interval_usec, scan_type); + return SendPacket ("OK"); +} + +// QEnableCompression:type:<COMPRESSION-TYPE>;minsize:<MINIMUM PACKET SIZE TO COMPRESS>; +// +// type: must be a type previously reported by the qXfer:features: SupportedCompressions list +// +// minsize: is optional; by default the qXfer:features: DefaultCompressionMinSize value is used +// debugserver may have a better idea of what a good minimum packet size to compress is than lldb. + +rnb_err_t +RNBRemote::HandlePacket_QEnableCompression (const char *p) +{ + p += sizeof ("QEnableCompression:") - 1; + + size_t new_compression_minsize = m_compression_minsize; + const char *new_compression_minsize_str = strstr (p, "minsize:"); + if (new_compression_minsize_str) + { + new_compression_minsize_str += strlen ("minsize:"); + errno = 0; + new_compression_minsize = strtoul (new_compression_minsize_str, NULL, 10); + if (errno != 0 || new_compression_minsize == ULONG_MAX) + { + new_compression_minsize = m_compression_minsize; + } + } + +#if defined (HAVE_LIBCOMPRESSION) + if (compression_decode_buffer != NULL) + { + if (strstr (p, "type:zlib-deflate;") != nullptr) + { + EnableCompressionNextSendPacket (compression_types::zlib_deflate); + m_compression_minsize = new_compression_minsize; + return SendPacket ("OK"); + } + else if (strstr (p, "type:lz4;") != nullptr) + { + EnableCompressionNextSendPacket (compression_types::lz4); + m_compression_minsize = new_compression_minsize; + return SendPacket ("OK"); + } + else if (strstr (p, "type:lzma;") != nullptr) + { + EnableCompressionNextSendPacket (compression_types::lzma); + m_compression_minsize = new_compression_minsize; + return SendPacket ("OK"); + } + else if (strstr (p, "type:lzfse;") != nullptr) + { + EnableCompressionNextSendPacket (compression_types::lzfse); + m_compression_minsize = new_compression_minsize; + return SendPacket ("OK"); + } + } +#endif + +#if defined (HAVE_LIBZ) + if (strstr (p, "type:zlib-deflate;") != nullptr) + { + EnableCompressionNextSendPacket (compression_types::zlib_deflate); + m_compression_minsize = new_compression_minsize; + return SendPacket ("OK"); + } +#endif + + return SendPacket ("E88"); +} + +rnb_err_t +RNBRemote::HandlePacket_qSpeedTest (const char *p) +{ + p += strlen ("qSpeedTest:response_size:"); + char *end = NULL; + errno = 0; + uint64_t response_size = ::strtoul (p, &end, 16); + if (errno != 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Didn't find response_size value at right offset"); + else if (*end == ';') + { + static char g_data[4*1024*1024+16] = "data:"; + memset(g_data + 5, 'a', response_size); + g_data[response_size + 5] = '\0'; + return SendPacket (g_data); + } + else + { + return SendPacket ("E79"); + } +} + +rnb_err_t +RNBRemote::HandlePacket_WatchpointSupportInfo (const char *p) +{ + /* This packet simply returns the number of supported hardware watchpoints. + + Examples of use: + qWatchpointSupportInfo: + num:4 + + qWatchpointSupportInfo + OK // this packet is implemented by the remote nub + */ + + p += sizeof ("qWatchpointSupportInfo") - 1; + if (*p == '\0') + return SendPacket ("OK"); + if (*p++ != ':') + return SendPacket ("E67"); + + errno = 0; + uint32_t num = DNBWatchpointGetNumSupportedHWP (m_ctx.ProcessID()); + std::ostringstream ostrm; + + // size:4 + ostrm << "num:" << std::dec << num << ';'; + return SendPacket (ostrm.str()); +} + +/* 'C sig [;addr]' + Resume with signal sig, optionally at address addr. */ + +rnb_err_t +RNBRemote::HandlePacket_C (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E36"); + + DNBThreadResumeAction action = { INVALID_NUB_THREAD, eStateRunning, 0, INVALID_NUB_ADDRESS }; + int process_signo = -1; + if (*(p + 1) != '\0') + { + action.tid = GetContinueThread(); + char *end = NULL; + errno = 0; + process_signo = static_cast<int>(strtoul (p + 1, &end, 16)); + if (errno != 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse signal in C packet"); + else if (*end == ';') + { + errno = 0; + action.addr = strtoull (end + 1, NULL, 16); + if (errno != 0 && action.addr == 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse address in C packet"); + } + } + + DNBThreadResumeActions thread_actions; + thread_actions.Append (action); + thread_actions.SetDefaultThreadActionIfNeeded (eStateRunning, action.signal); + if (!DNBProcessSignal(pid, process_signo)) + return SendPacket ("E52"); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E38"); + /* Don't send an "OK" packet; response is the stopped/exited message. */ + return rnb_success; +} + +//---------------------------------------------------------------------- +// 'D' packet +// Detach from gdb. +//---------------------------------------------------------------------- +rnb_err_t +RNBRemote::HandlePacket_D (const char *p) +{ + if (m_ctx.HasValidProcessID()) + { + if (DNBProcessDetach(m_ctx.ProcessID())) + SendPacket ("OK"); + else + SendPacket ("E"); + } + else + { + SendPacket ("E"); + } + return rnb_success; +} + +/* 'k' + Kill the inferior process. */ + +rnb_err_t +RNBRemote::HandlePacket_k (const char *p) +{ + DNBLog ("Got a 'k' packet, killing the inferior process."); + // No response to should be sent to the kill packet + if (m_ctx.HasValidProcessID()) + DNBProcessKill (m_ctx.ProcessID()); + SendPacket ("X09"); + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_stop_process (const char *p) +{ +//#define TEST_EXIT_ON_INTERRUPT // This should only be uncommented to test exiting on interrupt +#if defined(TEST_EXIT_ON_INTERRUPT) + rnb_err_t err = HandlePacket_k (p); + m_comm.Disconnect(true); + return err; +#else + if (!DNBProcessInterrupt(m_ctx.ProcessID())) + { + // If we failed to interrupt the process, then send a stop + // reply packet as the process was probably already stopped + HandlePacket_last_signal (NULL); + } + return rnb_success; +#endif +} + +/* 's' + Step the inferior process. */ + +rnb_err_t +RNBRemote::HandlePacket_s (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E32"); + + // Hardware supported stepping not supported on arm + nub_thread_t tid = GetContinueThread (); + if (tid == 0 || tid == (nub_thread_t)-1) + tid = GetCurrentThread(); + + if (tid == INVALID_NUB_THREAD) + return SendPacket ("E33"); + + DNBThreadResumeActions thread_actions; + thread_actions.AppendAction(tid, eStateStepping); + + // Make all other threads stop when we are stepping + thread_actions.SetDefaultThreadActionIfNeeded (eStateStopped, 0); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E49"); + // Don't send an "OK" packet; response is the stopped/exited message. + return rnb_success; +} + +/* 'S sig [;addr]' + Step with signal sig, optionally at address addr. */ + +rnb_err_t +RNBRemote::HandlePacket_S (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E36"); + + DNBThreadResumeAction action = { INVALID_NUB_THREAD, eStateStepping, 0, INVALID_NUB_ADDRESS }; + + if (*(p + 1) != '\0') + { + char *end = NULL; + errno = 0; + action.signal = static_cast<int>(strtoul (p + 1, &end, 16)); + if (errno != 0) + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse signal in S packet"); + else if (*end == ';') + { + errno = 0; + action.addr = strtoull (end + 1, NULL, 16); + if (errno != 0 && action.addr == 0) + { + return HandlePacket_ILLFORMED (__FILE__, __LINE__, p, "Could not parse address in S packet"); + } + } + } + + action.tid = GetContinueThread (); + if (action.tid == 0 || action.tid == (nub_thread_t)-1) + return SendPacket ("E40"); + + nub_state_t tstate = DNBThreadGetState (pid, action.tid); + if (tstate == eStateInvalid || tstate == eStateExited) + return SendPacket ("E37"); + + + DNBThreadResumeActions thread_actions; + thread_actions.Append (action); + + // Make all other threads stop when we are stepping + thread_actions.SetDefaultThreadActionIfNeeded(eStateStopped, 0); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E39"); + + // Don't send an "OK" packet; response is the stopped/exited message. + return rnb_success; +} + +static const char * +GetArchName (const uint32_t cputype, const uint32_t cpusubtype) +{ + switch (cputype) + { + case CPU_TYPE_ARM: + switch (cpusubtype) + { + case 5: return "armv4"; + case 6: return "armv6"; + case 7: return "armv5t"; + case 8: return "xscale"; + case 9: return "armv7"; + case 10: return "armv7f"; + case 11: return "armv7s"; + case 12: return "armv7k"; + case 14: return "armv6m"; + case 15: return "armv7m"; + case 16: return "armv7em"; + default: return "arm"; + } + break; + case CPU_TYPE_ARM64: return "arm64"; + case CPU_TYPE_I386: return "i386"; + case CPU_TYPE_X86_64: + switch (cpusubtype) + { + default: return "x86_64"; + case 8: return "x86_64h"; + } + break; + } + return NULL; +} + +static bool +GetHostCPUType (uint32_t &cputype, uint32_t &cpusubtype, uint32_t &is_64_bit_capable, bool &promoted_to_64) +{ + static uint32_t g_host_cputype = 0; + static uint32_t g_host_cpusubtype = 0; + static uint32_t g_is_64_bit_capable = 0; + static bool g_promoted_to_64 = false; + + if (g_host_cputype == 0) + { + g_promoted_to_64 = false; + size_t len = sizeof(uint32_t); + if (::sysctlbyname("hw.cputype", &g_host_cputype, &len, NULL, 0) == 0) + { + len = sizeof (uint32_t); + if (::sysctlbyname("hw.cpu64bit_capable", &g_is_64_bit_capable, &len, NULL, 0) == 0) + { + if (g_is_64_bit_capable && ((g_host_cputype & CPU_ARCH_ABI64) == 0)) + { + g_promoted_to_64 = true; + g_host_cputype |= CPU_ARCH_ABI64; + } + } + } + + len = sizeof(uint32_t); + if (::sysctlbyname("hw.cpusubtype", &g_host_cpusubtype, &len, NULL, 0) == 0) + { + if (g_promoted_to_64 && + g_host_cputype == CPU_TYPE_X86_64 && g_host_cpusubtype == CPU_SUBTYPE_486) + g_host_cpusubtype = CPU_SUBTYPE_X86_64_ALL; + } + } + + cputype = g_host_cputype; + cpusubtype = g_host_cpusubtype; + is_64_bit_capable = g_is_64_bit_capable; + promoted_to_64 = g_promoted_to_64; + return g_host_cputype != 0; +} + +rnb_err_t +RNBRemote::HandlePacket_qHostInfo (const char *p) +{ + std::ostringstream strm; + + uint32_t cputype = 0; + uint32_t cpusubtype = 0; + uint32_t is_64_bit_capable = 0; + bool promoted_to_64 = false; + if (GetHostCPUType (cputype, cpusubtype, is_64_bit_capable, promoted_to_64)) + { + strm << "cputype:" << std::dec << cputype << ';'; + strm << "cpusubtype:" << std::dec << cpusubtype << ';'; + } + + // The OS in the triple should be "ios" or "macosx" which doesn't match our + // "Darwin" which gets returned from "kern.ostype", so we need to hardcode + // this for now. + if (cputype == CPU_TYPE_ARM || cputype == CPU_TYPE_ARM64) + { +#if defined (TARGET_OS_TV) && TARGET_OS_TV == 1 + strm << "ostype:tvos;"; +#elif defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + strm << "ostype:watchos;"; +#else + strm << "ostype:ios;"; +#endif + + // On armv7 we use "synchronous" watchpoints which means the exception is delivered before the instruction executes. + strm << "watchpoint_exceptions_received:before;"; + } + else + { + strm << "ostype:macosx;"; + strm << "watchpoint_exceptions_received:after;"; + } +// char ostype[64]; +// len = sizeof(ostype); +// if (::sysctlbyname("kern.ostype", &ostype, &len, NULL, 0) == 0) +// { +// len = strlen(ostype); +// std::transform (ostype, ostype + len, ostype, tolower); +// strm << "ostype:" << std::dec << ostype << ';'; +// } + + strm << "vendor:apple;"; + + uint64_t major, minor, patch; + if (DNBGetOSVersionNumbers (&major, &minor, &patch)) + { + strm << "osmajor:" << major << ";"; + strm << "osminor:" << minor << ";"; + strm << "ospatch:" << patch << ";"; + + strm << "version:" << major << "." << minor; + if (patch != 0) + { + strm << "." << patch; + } + strm << ";"; + } + +#if defined (__LITTLE_ENDIAN__) + strm << "endian:little;"; +#elif defined (__BIG_ENDIAN__) + strm << "endian:big;"; +#elif defined (__PDP_ENDIAN__) + strm << "endian:pdp;"; +#endif + + if (promoted_to_64) + strm << "ptrsize:8;"; + else + strm << "ptrsize:" << std::dec << sizeof(void *) << ';'; + +#if defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + strm << "default_packet_timeout:10;"; +#endif + + return SendPacket (strm.str()); +} + +void +XMLElementStart (std::ostringstream &s, uint32_t indent, const char *name, bool has_attributes) +{ + if (indent) + s << INDENT_WITH_SPACES(indent); + s << '<' << name; + if (!has_attributes) + s << '>' << std::endl; +} + +void +XMLElementStartEndAttributes (std::ostringstream &s, bool empty) +{ + if (empty) + s << '/'; + s << '>' << std::endl; +} + +void +XMLElementEnd (std::ostringstream &s, uint32_t indent, const char *name) +{ + if (indent) + s << INDENT_WITH_SPACES(indent); + s << '<' << '/' << name << '>' << std::endl; +} + +void +XMLElementWithStringValue (std::ostringstream &s, uint32_t indent, const char *name, const char *value, bool close = true) +{ + if (value) + { + if (indent) + s << INDENT_WITH_SPACES(indent); + s << '<' << name << '>' << value; + if (close) + XMLElementEnd(s, 0, name); + } +} + +void +XMLElementWithUnsignedValue (std::ostringstream &s, uint32_t indent, const char *name, uint64_t value, bool close = true) +{ + if (indent) + s << INDENT_WITH_SPACES(indent); + + s << '<' << name << '>' << DECIMAL << value; + if (close) + XMLElementEnd(s, 0, name); +} + +void +XMLAttributeString (std::ostringstream &s, const char *name, const char *value, const char *default_value = NULL) +{ + if (value) + { + if (default_value && strcmp(value, default_value) == 0) + return; // No need to emit the attribute because it matches the default value + s <<' ' << name << "=\"" << value << "\""; + } +} + +void +XMLAttributeUnsignedDecimal (std::ostringstream &s, const char *name, uint64_t value) +{ + s <<' ' << name << "=\"" << DECIMAL << value << "\""; +} + +void +GenerateTargetXMLRegister (std::ostringstream &s, + const uint32_t reg_num, + nub_size_t num_reg_sets, + const DNBRegisterSetInfo *reg_set_info, + const register_map_entry_t ®) +{ + const char *default_lldb_encoding = "uint"; + const char *lldb_encoding = default_lldb_encoding; + const char *gdb_group = "general"; + const char *default_gdb_type = "int"; + const char *gdb_type = default_gdb_type; + const char *default_lldb_format = "hex"; + const char *lldb_format = default_lldb_format; + const char *lldb_set = NULL; + + switch (reg.nub_info.type) + { + case Uint: lldb_encoding = "uint"; break; + case Sint: lldb_encoding = "sint"; break; + case IEEE754: lldb_encoding = "ieee754"; if (reg.nub_info.set > 0) gdb_group = "float"; break; + case Vector: lldb_encoding = "vector"; if (reg.nub_info.set > 0) gdb_group = "vector"; break; + } + + switch (reg.nub_info.format) + { + case Binary: lldb_format = "binary"; break; + case Decimal: lldb_format = "decimal"; break; + case Hex: lldb_format = "hex"; break; + case Float: gdb_type = "float"; lldb_format = "float"; break; + case VectorOfSInt8: gdb_type = "float"; lldb_format = "vector-sint8"; break; + case VectorOfUInt8: gdb_type = "float"; lldb_format = "vector-uint8"; break; + case VectorOfSInt16: gdb_type = "float"; lldb_format = "vector-sint16"; break; + case VectorOfUInt16: gdb_type = "float"; lldb_format = "vector-uint16"; break; + case VectorOfSInt32: gdb_type = "float"; lldb_format = "vector-sint32"; break; + case VectorOfUInt32: gdb_type = "float"; lldb_format = "vector-uint32"; break; + case VectorOfFloat32: gdb_type = "float"; lldb_format = "vector-float32"; break; + case VectorOfUInt128: gdb_type = "float"; lldb_format = "vector-uint128"; break; + }; + if (reg_set_info && reg.nub_info.set < num_reg_sets) + lldb_set = reg_set_info[reg.nub_info.set].name; + + uint32_t indent = 2; + + XMLElementStart(s, indent, "reg", true); + XMLAttributeString(s, "name", reg.nub_info.name); + XMLAttributeUnsignedDecimal(s, "regnum", reg_num); + XMLAttributeUnsignedDecimal(s, "offset", reg.offset); + XMLAttributeUnsignedDecimal(s, "bitsize", reg.nub_info.size * 8); + XMLAttributeString(s, "group", gdb_group); + XMLAttributeString(s, "type", gdb_type, default_gdb_type); + XMLAttributeString (s, "altname", reg.nub_info.alt); + XMLAttributeString(s, "encoding", lldb_encoding, default_lldb_encoding); + XMLAttributeString(s, "format", lldb_format, default_lldb_format); + XMLAttributeUnsignedDecimal(s, "group_id", reg.nub_info.set); + if (reg.nub_info.reg_ehframe != INVALID_NUB_REGNUM) + XMLAttributeUnsignedDecimal(s, "ehframe_regnum", reg.nub_info.reg_ehframe); + if (reg.nub_info.reg_dwarf != INVALID_NUB_REGNUM) + XMLAttributeUnsignedDecimal(s, "dwarf_regnum", reg.nub_info.reg_dwarf); + + const char *lldb_generic = NULL; + switch (reg.nub_info.reg_generic) + { + case GENERIC_REGNUM_FP: lldb_generic = "fp"; break; + case GENERIC_REGNUM_PC: lldb_generic = "pc"; break; + case GENERIC_REGNUM_SP: lldb_generic = "sp"; break; + case GENERIC_REGNUM_RA: lldb_generic = "ra"; break; + case GENERIC_REGNUM_FLAGS: lldb_generic = "flags"; break; + case GENERIC_REGNUM_ARG1: lldb_generic = "arg1"; break; + case GENERIC_REGNUM_ARG2: lldb_generic = "arg2"; break; + case GENERIC_REGNUM_ARG3: lldb_generic = "arg3"; break; + case GENERIC_REGNUM_ARG4: lldb_generic = "arg4"; break; + case GENERIC_REGNUM_ARG5: lldb_generic = "arg5"; break; + case GENERIC_REGNUM_ARG6: lldb_generic = "arg6"; break; + case GENERIC_REGNUM_ARG7: lldb_generic = "arg7"; break; + case GENERIC_REGNUM_ARG8: lldb_generic = "arg8"; break; + default: break; + } + XMLAttributeString(s, "generic", lldb_generic); + + + bool empty = reg.value_regnums.empty() && reg.invalidate_regnums.empty(); + if (!empty) + { + if (!reg.value_regnums.empty()) + { + std::ostringstream regnums; + bool first = true; + regnums << DECIMAL; + for (auto regnum : reg.value_regnums) + { + if (!first) + regnums << ','; + regnums << regnum; + first = false; + } + XMLAttributeString(s, "value_regnums", regnums.str().c_str()); + } + + if (!reg.invalidate_regnums.empty()) + { + std::ostringstream regnums; + bool first = true; + regnums << DECIMAL; + for (auto regnum : reg.invalidate_regnums) + { + if (!first) + regnums << ','; + regnums << regnum; + first = false; + } + XMLAttributeString(s, "invalidate_regnums", regnums.str().c_str()); + } + } + XMLElementStartEndAttributes(s, true); +} + +void +GenerateTargetXMLRegisters (std::ostringstream &s) +{ + nub_size_t num_reg_sets = 0; + const DNBRegisterSetInfo *reg_sets = DNBGetRegisterSetInfo (&num_reg_sets); + + + uint32_t cputype = DNBGetRegisterCPUType(); + if (cputype) + { + XMLElementStart(s, 0, "feature", true); + std::ostringstream name_strm; + name_strm << "com.apple.debugserver." << GetArchName (cputype, 0); + XMLAttributeString(s, "name", name_strm.str().c_str()); + XMLElementStartEndAttributes(s, false); + for (uint32_t reg_num = 0; reg_num < g_num_reg_entries; ++reg_num) +// for (const auto ®: g_dynamic_register_map) + { + GenerateTargetXMLRegister(s, reg_num, num_reg_sets, reg_sets, g_reg_entries[reg_num]); + } + XMLElementEnd(s, 0, "feature"); + + if (num_reg_sets > 0) + { + XMLElementStart(s, 0, "groups", false); + for (uint32_t set=1; set<num_reg_sets; ++set) + { + XMLElementStart(s, 2, "group", true); + XMLAttributeUnsignedDecimal(s, "id", set); + XMLAttributeString(s, "name", reg_sets[set].name); + XMLElementStartEndAttributes(s, true); + } + XMLElementEnd(s, 0, "groups"); + } + } +} + +static const char *g_target_xml_header = R"(<?xml version="1.0"?> +<target version="1.0">)"; + +static const char *g_target_xml_footer = "</target>"; + +static std::string g_target_xml; + +void +UpdateTargetXML () +{ + std::ostringstream s; + s << g_target_xml_header << std::endl; + + // Set the architecture + //s << "<architecture>" << arch "</architecture>" << std::endl; + + // Set the OSABI + //s << "<osabi>abi-name</osabi>" + + GenerateTargetXMLRegisters(s); + + s << g_target_xml_footer << std::endl; + + // Save the XML output in case it gets retrieved in chunks + g_target_xml = s.str(); +} + +rnb_err_t +RNBRemote::HandlePacket_qXfer (const char *command) +{ + const char *p = command; + p += strlen ("qXfer:"); + const char *sep = strchr(p, ':'); + if (sep) + { + std::string object(p, sep - p); // "auxv", "backtrace", "features", etc + p = sep + 1; + sep = strchr(p, ':'); + if (sep) + { + std::string rw(p, sep - p); // "read" or "write" + p = sep + 1; + sep = strchr(p, ':'); + if (sep) + { + std::string annex(p, sep - p); // "read" or "write" + + p = sep + 1; + sep = strchr(p, ','); + if (sep) + { + std::string offset_str(p, sep - p); // read the length as a string + p = sep + 1; + std::string length_str(p); // read the offset as a string + char *end = nullptr; + const uint64_t offset = strtoul(offset_str.c_str(), &end, 16); // convert offset_str to a offset + if (*end == '\0') + { + const uint64_t length = strtoul(length_str.c_str(), &end, 16); // convert length_str to a length + if (*end == '\0') + { + if (object == "features" && + rw == "read" && + annex == "target.xml") + { + std::ostringstream xml_out; + + if (offset == 0) + { + InitializeRegisters (true); + + UpdateTargetXML(); + if (g_target_xml.empty()) + return SendPacket("E83"); + + if (length > g_target_xml.size()) + { + xml_out << 'l'; // No more data + xml_out << binary_encode_string(g_target_xml); + } + else + { + xml_out << 'm'; // More data needs to be read with a subsequent call + xml_out << binary_encode_string(std::string(g_target_xml, offset, length)); + } + } + else + { + // Retrieving target XML in chunks + if (offset < g_target_xml.size()) + { + std::string chunk(g_target_xml, offset, length); + if (chunk.size() < length) + xml_out << 'l'; // No more data + else + xml_out << 'm'; // More data needs to be read with a subsequent call + xml_out << binary_encode_string(chunk.data()); + } + } + return SendPacket(xml_out.str()); + } + // Well formed, put not supported + return HandlePacket_UNIMPLEMENTED (command); + } + } + } + } + else + { + SendPacket ("E85"); + } + } + else + { + SendPacket ("E86"); + } + } + return SendPacket ("E82"); +} + + +rnb_err_t +RNBRemote::HandlePacket_qGDBServerVersion (const char *p) +{ + std::ostringstream strm; + +#if defined(DEBUGSERVER_PROGRAM_NAME) + strm << "name:" DEBUGSERVER_PROGRAM_NAME ";"; +#else + strm << "name:debugserver;"; +#endif + strm << "version:" << DEBUGSERVER_VERSION_NUM << ";"; + + return SendPacket (strm.str()); +} + +// A helper function that retrieves a single integer value from +// a one-level-deep JSON dictionary of key-value pairs. e.g. +// jThreadExtendedInfo:{"plo_pthread_tsd_base_address_offset":0,"plo_pthread_tsd_base_offset":224,"plo_pthread_tsd_entry_size":8,"thread":144305}] +// +uint64_t +get_integer_value_for_key_name_from_json (const char *key, const char *json_string) +{ + uint64_t retval = INVALID_NUB_ADDRESS; + std::string key_with_quotes = "\""; + key_with_quotes += key; + key_with_quotes += "\""; + const char *c = strstr (json_string, key_with_quotes.c_str()); + if (c) + { + c += key_with_quotes.size(); + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + + if (*c == ':') + { + c++; + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + + errno = 0; + retval = strtoul (c, NULL, 10); + if (errno != 0) + { + retval = INVALID_NUB_ADDRESS; + } + } + } + return retval; + +} + +JSONGenerator::ObjectSP +RNBRemote::GetJSONThreadsInfo(bool threads_with_valid_stop_info_only) +{ + JSONGenerator::ArraySP threads_array_sp; + if (m_ctx.HasValidProcessID()) + { + threads_array_sp.reset(new JSONGenerator::Array()); + + nub_process_t pid = m_ctx.ProcessID(); + + nub_size_t numthreads = DNBProcessGetNumThreads (pid); + for (nub_size_t i = 0; i < numthreads; ++i) + { + nub_thread_t tid = DNBProcessGetThreadAtIndex (pid, i); + + struct DNBThreadStopInfo tid_stop_info; + + const bool stop_info_valid = DNBThreadGetStopReason (pid, tid, &tid_stop_info); + + // If we are doing stop info only, then we only show threads that have a + // valid stop reason + if (threads_with_valid_stop_info_only) + { + if (!stop_info_valid || tid_stop_info.reason == eStopTypeInvalid) + continue; + } + + JSONGenerator::DictionarySP thread_dict_sp(new JSONGenerator::Dictionary()); + thread_dict_sp->AddIntegerItem("tid", tid); + + std::string reason_value("none"); + + if (stop_info_valid) + { + switch (tid_stop_info.reason) + { + case eStopTypeInvalid: + break; + + case eStopTypeSignal: + if (tid_stop_info.details.signal.signo != 0) + { + thread_dict_sp->AddIntegerItem("signal", tid_stop_info.details.signal.signo); + reason_value = "signal"; + } + break; + + case eStopTypeException: + if (tid_stop_info.details.exception.type != 0) + { + reason_value = "exception"; + thread_dict_sp->AddIntegerItem("metype", tid_stop_info.details.exception.type); + JSONGenerator::ArraySP medata_array_sp(new JSONGenerator::Array()); + for (nub_size_t i=0; i<tid_stop_info.details.exception.data_count; ++i) + { + medata_array_sp->AddItem(JSONGenerator::IntegerSP(new JSONGenerator::Integer(tid_stop_info.details.exception.data[i]))); + } + thread_dict_sp->AddItem("medata", medata_array_sp); + } + break; + + case eStopTypeExec: + reason_value = "exec"; + break; + } + } + + thread_dict_sp->AddStringItem("reason", reason_value); + + if (threads_with_valid_stop_info_only == false) + { + const char *thread_name = DNBThreadGetName (pid, tid); + if (thread_name && thread_name[0]) + thread_dict_sp->AddStringItem("name", thread_name); + + thread_identifier_info_data_t thread_ident_info; + if (DNBThreadGetIdentifierInfo (pid, tid, &thread_ident_info)) + { + if (thread_ident_info.dispatch_qaddr != 0) + { + thread_dict_sp->AddIntegerItem("qaddr", thread_ident_info.dispatch_qaddr); + + const DispatchQueueOffsets *dispatch_queue_offsets = GetDispatchQueueOffsets(); + if (dispatch_queue_offsets) + { + std::string queue_name; + uint64_t queue_width = 0; + uint64_t queue_serialnum = 0; + dispatch_queue_offsets->GetThreadQueueInfo(pid, thread_ident_info.dispatch_qaddr, queue_name, queue_width, queue_serialnum); + if (!queue_name.empty()) + thread_dict_sp->AddStringItem("qname", queue_name); + if (queue_width == 1) + thread_dict_sp->AddStringItem("qkind", "serial"); + else if (queue_width > 1) + thread_dict_sp->AddStringItem("qkind", "concurrent"); + if (queue_serialnum > 0) + thread_dict_sp->AddIntegerItem("qserial", queue_serialnum); + } + } + } + + DNBRegisterValue reg_value; + + if (g_reg_entries != NULL) + { + JSONGenerator::DictionarySP registers_dict_sp(new JSONGenerator::Dictionary()); + + for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) + { + // Expedite all registers in the first register set that aren't + // contained in other registers + if (g_reg_entries[reg].nub_info.set == 1 && + g_reg_entries[reg].nub_info.value_regs == NULL) + { + if (!DNBThreadGetRegisterValueByID (pid, tid, g_reg_entries[reg].nub_info.set, g_reg_entries[reg].nub_info.reg, ®_value)) + continue; + + std::ostringstream reg_num; + reg_num << std::dec << g_reg_entries[reg].debugserver_regnum; + // Encode native byte ordered bytes as hex ascii + registers_dict_sp->AddBytesAsHexASCIIString(reg_num.str(), reg_value.value.v_uint8, g_reg_entries[reg].nub_info.size); + } + } + thread_dict_sp->AddItem("registers", registers_dict_sp); + } + + // Add expedited stack memory so stack backtracing doesn't need to read anything from the + // frame pointer chain. + StackMemoryMap stack_mmap; + ReadStackMemory (pid, tid, stack_mmap); + if (!stack_mmap.empty()) + { + JSONGenerator::ArraySP memory_array_sp(new JSONGenerator::Array()); + + for (const auto &stack_memory : stack_mmap) + { + JSONGenerator::DictionarySP stack_memory_sp(new JSONGenerator::Dictionary()); + stack_memory_sp->AddIntegerItem("address", stack_memory.first); + stack_memory_sp->AddBytesAsHexASCIIString("bytes", stack_memory.second.bytes, stack_memory.second.length); + memory_array_sp->AddItem(stack_memory_sp); + } + thread_dict_sp->AddItem("memory", memory_array_sp); + } + } + + threads_array_sp->AddItem(thread_dict_sp); + } + } + return threads_array_sp; +} + +rnb_err_t +RNBRemote::HandlePacket_jThreadsInfo (const char *p) +{ + JSONGenerator::ObjectSP threads_info_sp; + std::ostringstream json; + std::ostringstream reply_strm; + // If we haven't run the process yet, return an error. + if (m_ctx.HasValidProcessID()) + { + const bool threads_with_valid_stop_info_only = false; + JSONGenerator::ObjectSP threads_info_sp = GetJSONThreadsInfo(threads_with_valid_stop_info_only); + + if (threads_info_sp) + { + std::ostringstream strm; + threads_info_sp->Dump (strm); + std::string binary_packet = binary_encode_string (strm.str()); + if (!binary_packet.empty()) + return SendPacket (binary_packet.c_str()); + } + } + return SendPacket ("E85"); + +} + +rnb_err_t +RNBRemote::HandlePacket_jThreadExtendedInfo (const char *p) +{ + nub_process_t pid; + std::ostringstream json; + // If we haven't run the process yet, return an error. + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E81"); + } + + pid = m_ctx.ProcessID(); + + const char thread_extended_info_str[] = { "jThreadExtendedInfo:{" }; + if (strncmp (p, thread_extended_info_str, sizeof (thread_extended_info_str) - 1) == 0) + { + p += strlen (thread_extended_info_str); + + uint64_t tid = get_integer_value_for_key_name_from_json ("thread", p); + uint64_t plo_pthread_tsd_base_address_offset = get_integer_value_for_key_name_from_json ("plo_pthread_tsd_base_address_offset", p); + uint64_t plo_pthread_tsd_base_offset = get_integer_value_for_key_name_from_json ("plo_pthread_tsd_base_offset", p); + uint64_t plo_pthread_tsd_entry_size = get_integer_value_for_key_name_from_json ("plo_pthread_tsd_entry_size", p); + uint64_t dti_qos_class_index = get_integer_value_for_key_name_from_json ("dti_qos_class_index", p); + // Commented out the two variables below as they are not being used +// uint64_t dti_queue_index = get_integer_value_for_key_name_from_json ("dti_queue_index", p); +// uint64_t dti_voucher_index = get_integer_value_for_key_name_from_json ("dti_voucher_index", p); + + if (tid != INVALID_NUB_ADDRESS) + { + nub_addr_t pthread_t_value = DNBGetPThreadT (pid, tid); + + uint64_t tsd_address = INVALID_NUB_ADDRESS; + if (plo_pthread_tsd_entry_size != INVALID_NUB_ADDRESS + && plo_pthread_tsd_base_offset != INVALID_NUB_ADDRESS + && plo_pthread_tsd_entry_size != INVALID_NUB_ADDRESS) + { + tsd_address = DNBGetTSDAddressForThread (pid, tid, plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset, plo_pthread_tsd_entry_size); + } + + bool timed_out = false; + Genealogy::ThreadActivitySP thread_activity_sp; + + // If the pthread_t value is invalid, or if we were able to fetch the thread's TSD base + // and got an invalid value back, then we have a thread in early startup or shutdown and + // it's possible that gathering the genealogy information for this thread go badly. + // Ideally fetching this info for a thread in these odd states shouldn't matter - but + // we've seen some problems with these new SPI and threads in edge-casey states. + + double genealogy_fetch_time = 0; + if (pthread_t_value != INVALID_NUB_ADDRESS && tsd_address != INVALID_NUB_ADDRESS) + { + DNBTimer timer(false); + thread_activity_sp = DNBGetGenealogyInfoForThread (pid, tid, timed_out); + genealogy_fetch_time = timer.ElapsedMicroSeconds(false) / 1000000.0; + } + + std::unordered_set<uint32_t> process_info_indexes; // an array of the process info #'s seen + + json << "{"; + + bool need_to_print_comma = false; + + if (thread_activity_sp && timed_out == false) + { + const Genealogy::Activity *activity = &thread_activity_sp->current_activity; + bool need_vouchers_comma_sep = false; + json << "\"activity_query_timed_out\":false,"; + if (genealogy_fetch_time != 0) + { + // If we append the floating point value with << we'll get it in scientific + // notation. + char floating_point_ascii_buffer[64]; + floating_point_ascii_buffer[0] = '\0'; + snprintf (floating_point_ascii_buffer, sizeof (floating_point_ascii_buffer), "%f", genealogy_fetch_time); + if (strlen (floating_point_ascii_buffer) > 0) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + json << "\"activity_query_duration\":" << floating_point_ascii_buffer; + } + } + if (activity->activity_id != 0) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + need_vouchers_comma_sep = true; + json << "\"activity\":{"; + json << "\"start\":" << activity->activity_start << ","; + json << "\"id\":" << activity->activity_id << ","; + json << "\"parent_id\":" << activity->parent_id << ","; + json << "\"name\":\"" << json_string_quote_metachars (activity->activity_name) << "\","; + json << "\"reason\":\"" << json_string_quote_metachars (activity->reason) << "\""; + json << "}"; + } + if (thread_activity_sp->messages.size() > 0) + { + need_to_print_comma = true; + if (need_vouchers_comma_sep) + json << ","; + need_vouchers_comma_sep = true; + json << "\"trace_messages\":["; + bool printed_one_message = false; + for (auto iter = thread_activity_sp->messages.begin() ; iter != thread_activity_sp->messages.end(); ++iter) + { + if (printed_one_message) + json << ","; + else + printed_one_message = true; + json << "{"; + json << "\"timestamp\":" << iter->timestamp << ","; + json << "\"activity_id\":" << iter->activity_id << ","; + json << "\"trace_id\":" << iter->trace_id << ","; + json << "\"thread\":" << iter->thread << ","; + json << "\"type\":" << (int) iter->type << ","; + json << "\"process_info_index\":" << iter->process_info_index << ","; + process_info_indexes.insert (iter->process_info_index); + json << "\"message\":\"" << json_string_quote_metachars (iter->message) << "\""; + json << "}"; + } + json << "]"; + } + if (thread_activity_sp->breadcrumbs.size() == 1) + { + need_to_print_comma = true; + if (need_vouchers_comma_sep) + json << ","; + need_vouchers_comma_sep = true; + json << "\"breadcrumb\":{"; + for (auto iter = thread_activity_sp->breadcrumbs.begin() ; iter != thread_activity_sp->breadcrumbs.end(); ++iter) + { + json << "\"breadcrumb_id\":" << iter->breadcrumb_id << ","; + json << "\"activity_id\":" << iter->activity_id << ","; + json << "\"timestamp\":" << iter->timestamp << ","; + json << "\"name\":\"" << json_string_quote_metachars (iter->name) << "\""; + } + json << "}"; + } + if (process_info_indexes.size() > 0) + { + need_to_print_comma = true; + if (need_vouchers_comma_sep) + json << ","; + need_vouchers_comma_sep = true; + json << "\"process_infos\":["; + bool printed_one_process_info = false; + for (auto iter = process_info_indexes.begin(); iter != process_info_indexes.end(); ++iter) + { + if (printed_one_process_info) + json << ","; + else + printed_one_process_info = true; + Genealogy::ProcessExecutableInfoSP image_info_sp; + uint32_t idx = *iter; + image_info_sp = DNBGetGenealogyImageInfo (pid, idx); + json << "{"; + char uuid_buf[37]; + uuid_unparse_upper (image_info_sp->image_uuid, uuid_buf); + json << "\"process_info_index\":" << idx << ","; + json << "\"image_path\":\"" << json_string_quote_metachars (image_info_sp->image_path) << "\","; + json << "\"image_uuid\":\"" << uuid_buf <<"\""; + json << "}"; + } + json << "]"; + } + } + else + { + if (timed_out) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + json << "\"activity_query_timed_out\":true"; + if (genealogy_fetch_time != 0) + { + // If we append the floating point value with << we'll get it in scientific + // notation. + char floating_point_ascii_buffer[64]; + floating_point_ascii_buffer[0] = '\0'; + snprintf (floating_point_ascii_buffer, sizeof (floating_point_ascii_buffer), "%f", genealogy_fetch_time); + if (strlen (floating_point_ascii_buffer) > 0) + { + json << ","; + json << "\"activity_query_duration\":" << floating_point_ascii_buffer; + } + } + } + } + + if (tsd_address != INVALID_NUB_ADDRESS) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + json << "\"tsd_address\":" << tsd_address; + + if (dti_qos_class_index != 0 && dti_qos_class_index != UINT64_MAX) + { + ThreadInfo::QoS requested_qos = DNBGetRequestedQoSForThread (pid, tid, tsd_address, dti_qos_class_index); + if (requested_qos.IsValid()) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + json << "\"requested_qos\":{"; + json << "\"enum_value\":" << requested_qos.enum_value << ","; + json << "\"constant_name\":\"" << json_string_quote_metachars (requested_qos.constant_name) << "\","; + json << "\"printable_name\":\"" << json_string_quote_metachars (requested_qos.printable_name) << "\""; + json << "}"; + } + } + } + + if (pthread_t_value != INVALID_NUB_ADDRESS) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + json << "\"pthread_t\":" << pthread_t_value; + } + + nub_addr_t dispatch_queue_t_value = DNBGetDispatchQueueT (pid, tid); + if (dispatch_queue_t_value != INVALID_NUB_ADDRESS) + { + if (need_to_print_comma) + json << ","; + need_to_print_comma = true; + json << "\"dispatch_queue_t\":" << dispatch_queue_t_value; + } + + json << "}"; + std::string json_quoted = binary_encode_string (json.str()); + return SendPacket (json_quoted); + } + } + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_jGetLoadedDynamicLibrariesInfos (const char *p) +{ + nub_process_t pid; + // If we haven't run the process yet, return an error. + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E83"); + } + + pid = m_ctx.ProcessID(); + + const char get_loaded_dynamic_libraries_infos_str[] = { "jGetLoadedDynamicLibrariesInfos:{" }; + if (strncmp (p, get_loaded_dynamic_libraries_infos_str, sizeof (get_loaded_dynamic_libraries_infos_str) - 1) == 0) + { + p += strlen (get_loaded_dynamic_libraries_infos_str); + + nub_addr_t image_list_address = get_integer_value_for_key_name_from_json ("image_list_address", p); + nub_addr_t image_count = get_integer_value_for_key_name_from_json ("image_count", p); + + if (image_list_address != INVALID_NUB_ADDRESS && image_count != INVALID_NUB_ADDRESS) + { + JSONGenerator::ObjectSP json_sp; + + json_sp = DNBGetLoadedDynamicLibrariesInfos (pid, image_list_address, image_count); + + if (json_sp.get()) + { + std::ostringstream json_str; + json_sp->Dump (json_str); + if (json_str.str().size() > 0) + { + std::string json_str_quoted = binary_encode_string (json_str.str()); + return SendPacket (json_str_quoted.c_str()); + } + else + { + SendPacket ("E84"); + } + } + } + } + return SendPacket ("OK"); +} + +static bool +MachHeaderIsMainExecutable (nub_process_t pid, uint32_t addr_size, nub_addr_t mach_header_addr, mach_header &mh) +{ + DNBLogThreadedIf (LOG_RNB_PROC, "GetMachHeaderForMainExecutable(pid = %u, addr_size = %u, mach_header_addr = 0x%16.16llx)", pid, addr_size, mach_header_addr); + const nub_size_t bytes_read = DNBProcessMemoryRead(pid, mach_header_addr, sizeof(mh), &mh); + if (bytes_read == sizeof(mh)) + { + DNBLogThreadedIf (LOG_RNB_PROC, "GetMachHeaderForMainExecutable(pid = %u, addr_size = %u, mach_header_addr = 0x%16.16llx): mh = {\n magic = 0x%8.8x\n cpu = 0x%8.8x\n sub = 0x%8.8x\n filetype = %u\n ncmds = %u\n sizeofcmds = 0x%8.8x\n flags = 0x%8.8x }", pid, addr_size, mach_header_addr, mh.magic, mh.cputype, mh.cpusubtype, mh.filetype, mh.ncmds, mh.sizeofcmds, mh.flags); + if ((addr_size == 4 && mh.magic == MH_MAGIC) || + (addr_size == 8 && mh.magic == MH_MAGIC_64)) + { + if (mh.filetype == MH_EXECUTE) + { + DNBLogThreadedIf (LOG_RNB_PROC, "GetMachHeaderForMainExecutable(pid = %u, addr_size = %u, mach_header_addr = 0x%16.16llx) -> this is the executable!!!", pid, addr_size, mach_header_addr); + return true; + } + } + } + return false; +} + +static nub_addr_t +GetMachHeaderForMainExecutable (const nub_process_t pid, const uint32_t addr_size, mach_header &mh) +{ + struct AllImageInfos + { + uint32_t version; + uint32_t dylib_info_count; + uint64_t dylib_info_addr; + }; + + uint64_t mach_header_addr = 0; + + const nub_addr_t shlib_addr = DNBProcessGetSharedLibraryInfoAddress (pid); + uint8_t bytes[256]; + nub_size_t bytes_read = 0; + DNBDataRef data (bytes, sizeof(bytes), false); + DNBDataRef::offset_t offset = 0; + data.SetPointerSize(addr_size); + + //---------------------------------------------------------------------- + // When we are sitting at __dyld_start, the kernel has placed the + // address of the mach header of the main executable on the stack. If we + // read the SP and dereference a pointer, we might find the mach header + // for the executable. We also just make sure there is only 1 thread + // since if we are at __dyld_start we shouldn't have multiple threads. + //---------------------------------------------------------------------- + if (DNBProcessGetNumThreads(pid) == 1) + { + nub_thread_t tid = DNBProcessGetThreadAtIndex(pid, 0); + if (tid != INVALID_NUB_THREAD) + { + DNBRegisterValue sp_value; + if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_SP, &sp_value)) + { + uint64_t sp = addr_size == 8 ? sp_value.value.uint64 : sp_value.value.uint32; + bytes_read = DNBProcessMemoryRead(pid, sp, addr_size, bytes); + if (bytes_read == addr_size) + { + offset = 0; + mach_header_addr = data.GetPointer(&offset); + if (MachHeaderIsMainExecutable(pid, addr_size, mach_header_addr, mh)) + return mach_header_addr; + } + } + } + } + + //---------------------------------------------------------------------- + // Check the dyld_all_image_info structure for a list of mach header + // since it is a very easy thing to check + //---------------------------------------------------------------------- + if (shlib_addr != INVALID_NUB_ADDRESS) + { + bytes_read = DNBProcessMemoryRead(pid, shlib_addr, sizeof(AllImageInfos), bytes); + if (bytes_read > 0) + { + AllImageInfos aii; + offset = 0; + aii.version = data.Get32(&offset); + aii.dylib_info_count = data.Get32(&offset); + if (aii.dylib_info_count > 0) + { + aii.dylib_info_addr = data.GetPointer(&offset); + if (aii.dylib_info_addr != 0) + { + const size_t image_info_byte_size = 3 * addr_size; + for (uint32_t i=0; i<aii.dylib_info_count; ++i) + { + bytes_read = DNBProcessMemoryRead(pid, aii.dylib_info_addr + i * image_info_byte_size, image_info_byte_size, bytes); + if (bytes_read != image_info_byte_size) + break; + offset = 0; + mach_header_addr = data.GetPointer(&offset); + if (MachHeaderIsMainExecutable(pid, addr_size, mach_header_addr, mh)) + return mach_header_addr; + } + } + } + } + } + + //---------------------------------------------------------------------- + // We failed to find the executable's mach header from the all image + // infos and by dereferencing the stack pointer. Now we fall back to + // enumerating the memory regions and looking for regions that are + // executable. + //---------------------------------------------------------------------- + DNBRegionInfo region_info; + mach_header_addr = 0; + while (DNBProcessMemoryRegionInfo(pid, mach_header_addr, ®ion_info)) + { + if (region_info.size == 0) + break; + + if (region_info.permissions & eMemoryPermissionsExecutable) + { + DNBLogThreadedIf (LOG_RNB_PROC, "[0x%16.16llx - 0x%16.16llx) permissions = %c%c%c: checking region for executable mach header", region_info.addr, region_info.addr + region_info.size, (region_info.permissions & eMemoryPermissionsReadable) ? 'r' : '-', (region_info.permissions & eMemoryPermissionsWritable) ? 'w' : '-', (region_info.permissions & eMemoryPermissionsExecutable) ? 'x' : '-'); + if (MachHeaderIsMainExecutable(pid, addr_size, mach_header_addr, mh)) + return mach_header_addr; + } + else + { + DNBLogThreadedIf (LOG_RNB_PROC, "[0x%16.16llx - 0x%16.16llx): permissions = %c%c%c: skipping region", region_info.addr, region_info.addr + region_info.size, (region_info.permissions & eMemoryPermissionsReadable) ? 'r' : '-', (region_info.permissions & eMemoryPermissionsWritable) ? 'w' : '-', (region_info.permissions & eMemoryPermissionsExecutable) ? 'x' : '-'); + } + // Set the address to the next mapped region + mach_header_addr = region_info.addr + region_info.size; + } + bzero (&mh, sizeof(mh)); + return INVALID_NUB_ADDRESS; +} + +rnb_err_t +RNBRemote::HandlePacket_qSymbol (const char *command) +{ + const char *p = command; + p += strlen ("qSymbol:"); + const char *sep = strchr(p, ':'); + + std::string symbol_name; + std::string symbol_value_str; + // Extract the symbol value if there is one + if (sep > p) + symbol_value_str.assign(p, sep - p); + p = sep + 1; + + if (*p) + { + // We have a symbol name + symbol_name = std::move(decode_hex_ascii_string(p)); + if (!symbol_value_str.empty()) + { + nub_addr_t symbol_value = decode_uint64(symbol_value_str.c_str(), 16); + if (symbol_name == "dispatch_queue_offsets") + m_dispatch_queue_offsets_addr = symbol_value; + } + ++m_qSymbol_index; + } + else + { + // No symbol name, set our symbol index to zero so we can + // read any symbols that we need + m_qSymbol_index = 0; + } + + symbol_name.clear(); + + if (m_qSymbol_index == 0) + { + if (m_dispatch_queue_offsets_addr == INVALID_NUB_ADDRESS) + symbol_name = "dispatch_queue_offsets"; + else + ++m_qSymbol_index; + } + +// // Lookup next symbol when we have one... +// if (m_qSymbol_index == 1) +// { +// } + + + if (symbol_name.empty()) + { + // Done with symbol lookups + return SendPacket ("OK"); + } + else + { + std::ostringstream reply; + reply << "qSymbol:"; + for (size_t i = 0; i < symbol_name.size(); ++i) + reply << RAWHEX8(symbol_name[i]); + return SendPacket (reply.str().c_str()); + } +} + +// Note that all numeric values returned by qProcessInfo are hex encoded, +// including the pid and the cpu type. + +rnb_err_t +RNBRemote::HandlePacket_qProcessInfo (const char *p) +{ + nub_process_t pid; + std::ostringstream rep; + + // If we haven't run the process yet, return an error. + if (!m_ctx.HasValidProcessID()) + return SendPacket ("E68"); + + pid = m_ctx.ProcessID(); + + rep << "pid:" << std::hex << pid << ';'; + + int procpid_mib[4]; + procpid_mib[0] = CTL_KERN; + procpid_mib[1] = KERN_PROC; + procpid_mib[2] = KERN_PROC_PID; + procpid_mib[3] = pid; + struct kinfo_proc proc_kinfo; + size_t proc_kinfo_size = sizeof(struct kinfo_proc); + + if (::sysctl (procpid_mib, 4, &proc_kinfo, &proc_kinfo_size, NULL, 0) == 0) + { + if (proc_kinfo_size > 0) + { + rep << "parent-pid:" << std::hex << proc_kinfo.kp_eproc.e_ppid << ';'; + rep << "real-uid:" << std::hex << proc_kinfo.kp_eproc.e_pcred.p_ruid << ';'; + rep << "real-gid:" << std::hex << proc_kinfo.kp_eproc.e_pcred.p_rgid << ';'; + rep << "effective-uid:" << std::hex << proc_kinfo.kp_eproc.e_ucred.cr_uid << ';'; + if (proc_kinfo.kp_eproc.e_ucred.cr_ngroups > 0) + rep << "effective-gid:" << std::hex << proc_kinfo.kp_eproc.e_ucred.cr_groups[0] << ';'; + } + } + + cpu_type_t cputype = DNBProcessGetCPUType (pid); + if (cputype == 0) + { + DNBLog ("Unable to get the process cpu_type, making a best guess."); + cputype = best_guess_cpu_type(); + } + + uint32_t addr_size = 0; + if (cputype != 0) + { + rep << "cputype:" << std::hex << cputype << ";"; + if (cputype & CPU_ARCH_ABI64) + addr_size = 8; + else + addr_size = 4; + } + + bool host_cpu_is_64bit = false; + uint32_t is64bit_capable; + size_t is64bit_capable_len = sizeof (is64bit_capable); + if (sysctlbyname("hw.cpu64bit_capable", &is64bit_capable, &is64bit_capable_len, NULL, 0) == 0) + host_cpu_is_64bit = is64bit_capable != 0; + + uint32_t cpusubtype; + size_t cpusubtype_len = sizeof(cpusubtype); + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &cpusubtype_len, NULL, 0) == 0) + { + // If a process is CPU_TYPE_X86, then ignore the cpusubtype that we detected + // from the host and use CPU_SUBTYPE_I386_ALL because we don't want the + // CPU_SUBTYPE_X86_ARCH1 or CPU_SUBTYPE_X86_64_H to be used as the cpu subtype + // for i386... + if (host_cpu_is_64bit) + { + if (cputype == CPU_TYPE_X86) + { + cpusubtype = 3; // CPU_SUBTYPE_I386_ALL + } + else if (cputype == CPU_TYPE_ARM) + { + // We can query a process' cputype but we cannot query a process' cpusubtype. + // If the process has cputype CPU_TYPE_ARM, then it is an armv7 (32-bit process) and we + // need to override the host cpusubtype (which is in the CPU_SUBTYPE_ARM64 subtype namespace) + // with a reasonable CPU_SUBTYPE_ARMV7 subtype. + cpusubtype = 11; // CPU_SUBTYPE_ARM_V7S + } + } + rep << "cpusubtype:" << std::hex << cpusubtype << ';'; + } + + bool os_handled = false; + if (addr_size > 0) + { + rep << "ptrsize:" << std::dec << addr_size << ';'; + +#if (defined (__x86_64__) || defined (__i386__)) + // Try and get the OS type by looking at the load commands in the main + // executable and looking for a LC_VERSION_MIN load command. This is the + // most reliable way to determine the "ostype" value when on desktop. + + mach_header mh; + nub_addr_t exe_mach_header_addr = GetMachHeaderForMainExecutable (pid, addr_size, mh); + if (exe_mach_header_addr != INVALID_NUB_ADDRESS) + { + uint64_t load_command_addr = exe_mach_header_addr + ((addr_size == 8) ? sizeof(mach_header_64) : sizeof(mach_header)); + load_command lc; + for (uint32_t i=0; i<mh.ncmds && !os_handled; ++i) + { + const nub_size_t bytes_read = DNBProcessMemoryRead (pid, load_command_addr, sizeof(lc), &lc); + uint32_t raw_cmd = lc.cmd & ~LC_REQ_DYLD; + if (bytes_read != sizeof(lc)) + break; + switch (raw_cmd) + { + case LC_VERSION_MIN_IPHONEOS: + os_handled = true; + rep << "ostype:ios;"; + DNBLogThreadedIf (LOG_RNB_PROC, "LC_VERSION_MIN_IPHONEOS -> 'ostype:ios;'"); + break; + + case LC_VERSION_MIN_MACOSX: + os_handled = true; + rep << "ostype:macosx;"; + DNBLogThreadedIf (LOG_RNB_PROC, "LC_VERSION_MIN_MACOSX -> 'ostype:macosx;'"); + break; + +#if defined (TARGET_OS_TV) && TARGET_OS_TV == 1 + case LC_VERSION_MIN_TVOS: + os_handled = true; + rep << "ostype:tvos;"; + DNBLogThreadedIf (LOG_RNB_PROC, "LC_VERSION_MIN_TVOS -> 'ostype:tvos;'"); + break; +#endif + +#if defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + case LC_VERSION_MIN_WATCHOS: + os_handled = true; + rep << "ostype:watchos;"; + DNBLogThreadedIf (LOG_RNB_PROC, "LC_VERSION_MIN_WATCHOS -> 'ostype:watchos;'"); + break; +#endif + + default: + break; + } + load_command_addr = load_command_addr + lc.cmdsize; + } + } +#endif + } + + // If we weren't able to find the OS in a LC_VERSION_MIN load command, try + // to set it correctly by using the cpu type and other tricks + if (!os_handled) + { + // The OS in the triple should be "ios" or "macosx" which doesn't match our + // "Darwin" which gets returned from "kern.ostype", so we need to hardcode + // this for now. + if (cputype == CPU_TYPE_ARM || cputype == CPU_TYPE_ARM64) + { +#if defined (TARGET_OS_TV) && TARGET_OS_TV == 1 + rep << "ostype:tvos;"; +#elif defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + rep << "ostype:watchos;"; +#else + rep << "ostype:ios;"; +#endif + } + else + { + bool is_ios_simulator = false; + if (cputype == CPU_TYPE_X86 || cputype == CPU_TYPE_X86_64) + { + // Check for iOS simulator binaries by getting the process argument + // and environment and checking for SIMULATOR_UDID in the environment + int proc_args_mib[3] = { CTL_KERN, KERN_PROCARGS2, (int)pid }; + + uint8_t arg_data[8192]; + size_t arg_data_size = sizeof(arg_data); + if (::sysctl (proc_args_mib, 3, arg_data, &arg_data_size , NULL, 0) == 0) + { + DNBDataRef data (arg_data, arg_data_size, false); + DNBDataRef::offset_t offset = 0; + uint32_t argc = data.Get32 (&offset); + const char *cstr; + + cstr = data.GetCStr (&offset); + if (cstr) + { + // Skip NULLs + while (1) + { + const char *p = data.PeekCStr(offset); + if ((p == NULL) || (*p != '\0')) + break; + ++offset; + } + // Now skip all arguments + for (uint32_t i = 0; i < argc; ++i) + { + data.GetCStr(&offset); + } + + // Now iterate across all environment variables + while ((cstr = data.GetCStr(&offset))) + { + if (strncmp(cstr, "SIMULATOR_UDID=", strlen("SIMULATOR_UDID=")) == 0) + { + is_ios_simulator = true; + break; + } + if (cstr[0] == '\0') + break; + + } + } + } + } + if (is_ios_simulator) + { +#if defined (TARGET_OS_TV) && TARGET_OS_TV == 1 + rep << "ostype:tvos;"; +#elif defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + rep << "ostype:watchos;"; +#else + rep << "ostype:ios;"; +#endif + } + else + { + rep << "ostype:macosx;"; + } + } + } + + rep << "vendor:apple;"; + +#if defined (__LITTLE_ENDIAN__) + rep << "endian:little;"; +#elif defined (__BIG_ENDIAN__) + rep << "endian:big;"; +#elif defined (__PDP_ENDIAN__) + rep << "endian:pdp;"; +#endif + + if (addr_size == 0) + { +#if (defined (__x86_64__) || defined (__i386__)) && defined (x86_THREAD_STATE) + nub_thread_t thread = DNBProcessGetCurrentThreadMachPort (pid); + kern_return_t kr; + x86_thread_state_t gp_regs; + mach_msg_type_number_t gp_count = x86_THREAD_STATE_COUNT; + kr = thread_get_state (static_cast<thread_act_t>(thread), + x86_THREAD_STATE, + (thread_state_t) &gp_regs, + &gp_count); + if (kr == KERN_SUCCESS) + { + if (gp_regs.tsh.flavor == x86_THREAD_STATE64) + rep << "ptrsize:8;"; + else + rep << "ptrsize:4;"; + } +#elif defined (__arm__) + rep << "ptrsize:4;"; +#elif (defined (__arm64__) || defined (__aarch64__)) && defined (ARM_UNIFIED_THREAD_STATE) + nub_thread_t thread = DNBProcessGetCurrentThreadMachPort (pid); + kern_return_t kr; + arm_unified_thread_state_t gp_regs; + mach_msg_type_number_t gp_count = ARM_UNIFIED_THREAD_STATE_COUNT; + kr = thread_get_state (thread, ARM_UNIFIED_THREAD_STATE, + (thread_state_t) &gp_regs, &gp_count); + if (kr == KERN_SUCCESS) + { + if (gp_regs.ash.flavor == ARM_THREAD_STATE64) + rep << "ptrsize:8;"; + else + rep << "ptrsize:4;"; + } +#endif + } + + return SendPacket (rep.str()); +} + +const RNBRemote::DispatchQueueOffsets * +RNBRemote::GetDispatchQueueOffsets() +{ + if (!m_dispatch_queue_offsets.IsValid() && m_dispatch_queue_offsets_addr != INVALID_NUB_ADDRESS && m_ctx.HasValidProcessID()) + { + nub_process_t pid = m_ctx.ProcessID(); + nub_size_t bytes_read = DNBProcessMemoryRead(pid, m_dispatch_queue_offsets_addr, sizeof(m_dispatch_queue_offsets), &m_dispatch_queue_offsets); + if (bytes_read != sizeof(m_dispatch_queue_offsets)) + m_dispatch_queue_offsets.Clear(); + } + + if (m_dispatch_queue_offsets.IsValid()) + return &m_dispatch_queue_offsets; + else + return nullptr; +} + +void +RNBRemote::EnableCompressionNextSendPacket (compression_types type) +{ + m_compression_mode = type; + m_enable_compression_next_send_packet = true; +} + +compression_types +RNBRemote::GetCompressionType () +{ + // The first packet we send back to the debugger after a QEnableCompression request + // should be uncompressed -- so we can indicate whether the compression was enabled + // or not via OK / Enn returns. After that, all packets sent will be using the + // compression protocol. + + if (m_enable_compression_next_send_packet) + { + // One time, we send back "None" as our compression type + m_enable_compression_next_send_packet = false; + return compression_types::none; + } + return m_compression_mode; +} |