|  | //===-- 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 <mach-o/loader.h> | 
|  | #include <mach/exception_types.h> | 
|  | #include <signal.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/sysctl.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #if defined(__APPLE__) | 
|  | #include <pthread.h> | 
|  | #include <sched.h> | 
|  | #endif | 
|  |  | 
|  | #include "DNB.h" | 
|  | #include "DNBDataRef.h" | 
|  | #include "DNBLog.h" | 
|  | #include "DNBThreadResumeActions.h" | 
|  | #include "DarwinLogCollector.h" | 
|  | #include "DarwinLogEvent.h" | 
|  | #include "JSON.h" | 
|  | #include "JSONGenerator.h" | 
|  | #include "JSONGenerator.h" | 
|  | #include "MacOSX/Genealogy.h" | 
|  | #include "OsLogger.h" | 
|  | #include "RNBContext.h" | 
|  | #include "RNBServices.h" | 
|  | #include "RNBSocket.h" | 
|  | #include "StdStringExtractor.h" | 
|  |  | 
|  | #if defined(HAVE_LIBCOMPRESSION) | 
|  | #include <compression.h> | 
|  | #endif | 
|  |  | 
|  | #if defined(HAVE_LIBZ) | 
|  | #include <zlib.h> | 
|  | #endif | 
|  |  | 
|  | #include <llvm/Support/Compiler.h> | 
|  | #include <TargetConditionals.h> // for endianness predefines | 
|  | #include <iomanip> | 
|  | #include <sstream> | 
|  | #include <unordered_set> | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | // constants | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | static const std::string OS_LOG_EVENTS_KEY_NAME("events"); | 
|  | static const std::string JSON_ASYNC_TYPE_KEY_NAME("type"); | 
|  | static const DarwinLogEventVector::size_type DARWIN_LOG_MAX_EVENTS_PER_PACKET = | 
|  | 10; | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | // 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. | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | // Prototypes | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | static std::string binary_encode_string(const std::string &s); | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | // 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(json_query_get_shared_cache_info, | 
|  | &RNBRemote::HandlePacket_jGetSharedCacheInfo, NULL, | 
|  | "jGetSharedCacheInfo", "Replies with JSON data about the " | 
|  | "location and uuid of the shared " | 
|  | "cache in the inferior process.")); | 
|  | 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.")); | 
|  | t.push_back( | 
|  | Packet(query_supported_async_json_packets, | 
|  | &RNBRemote::HandlePacket_qStructuredDataPlugins, NULL, | 
|  | "qStructuredDataPlugins", | 
|  | "Query for the structured data plugins supported by the remote.")); | 
|  | t.push_back( | 
|  | Packet(configure_darwin_log, &RNBRemote::HandlePacket_QConfigureDarwinLog, | 
|  | NULL, "QConfigureDarwinLog:", | 
|  | "Configure the DarwinLog structured data plugin support.")); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RNBRemote::SendAsyncDarwinLogData() { | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): enter", __FUNCTION__); | 
|  |  | 
|  | if (!m_ctx.HasValidProcessID()) { | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): ignoring due to" | 
|  | "invalid process id", | 
|  | __FUNCTION__); | 
|  | return; | 
|  | } | 
|  |  | 
|  | nub_process_t pid = m_ctx.ProcessID(); | 
|  | DarwinLogEventVector::size_type entry_count = 0; | 
|  |  | 
|  | // NOTE: the current looping structure here does nothing | 
|  | // to guarantee that we can send off async packets faster | 
|  | // than we generate them.  It will keep sending as long | 
|  | // as there's data to send. | 
|  | do { | 
|  | DarwinLogEventVector events = DNBProcessGetAvailableDarwinLogEvents(pid); | 
|  | entry_count = events.size(); | 
|  |  | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): outer loop enter", | 
|  | __FUNCTION__); | 
|  |  | 
|  | for (DarwinLogEventVector::size_type base_entry = 0; | 
|  | base_entry < entry_count; | 
|  | base_entry += DARWIN_LOG_MAX_EVENTS_PER_PACKET) { | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): inner loop enter", | 
|  | __FUNCTION__); | 
|  |  | 
|  | // We limit the total number of entries we pack | 
|  | // into a single JSON async packet just so it | 
|  | // doesn't get too large. | 
|  | JSONGenerator::Dictionary async_dictionary; | 
|  |  | 
|  | // Specify the type of the JSON async data we're sending. | 
|  | async_dictionary.AddStringItem(JSON_ASYNC_TYPE_KEY_NAME, "DarwinLog"); | 
|  |  | 
|  | // Create an array entry in the dictionary to hold all | 
|  | // the events going in this packet. | 
|  | JSONGenerator::ArraySP events_array(new JSONGenerator::Array()); | 
|  | async_dictionary.AddItem(OS_LOG_EVENTS_KEY_NAME, events_array); | 
|  |  | 
|  | // We bundle up to DARWIN_LOG_MAX_EVENTS_PER_PACKET events in | 
|  | // a single packet. | 
|  | const auto inner_loop_bound = | 
|  | std::min(base_entry + DARWIN_LOG_MAX_EVENTS_PER_PACKET, entry_count); | 
|  | for (DarwinLogEventVector::size_type i = base_entry; i < inner_loop_bound; | 
|  | ++i) { | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): adding " | 
|  | "entry index %lu to the JSON packet", | 
|  | __FUNCTION__, i); | 
|  | events_array->AddItem(events[i]); | 
|  | } | 
|  |  | 
|  | // Send off the packet. | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): sending JSON " | 
|  | "packet, %lu entries remain", | 
|  | __FUNCTION__, entry_count - inner_loop_bound); | 
|  | SendAsyncJSONPacket(async_dictionary); | 
|  | } | 
|  |  | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): outer loop exit", | 
|  | __FUNCTION__); | 
|  |  | 
|  | } while (entry_count > 0); | 
|  |  | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): exit", | 
|  | __PRETTY_FUNCTION__); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | rnb_err_t | 
|  | RNBRemote::SendAsyncJSONPacket(const JSONGenerator::Dictionary &dictionary) { | 
|  | std::ostringstream stream; | 
|  | // We're choosing something that is easy to spot if we somehow get one | 
|  | // of these coming out at the wrong time (i.e. when the remote side | 
|  | // is not waiting for a process control completion response). | 
|  | stream << "JSON-async:"; | 
|  | dictionary.Dump(stream); | 
|  | const std::string payload = binary_encode_string(stream.str()); | 
|  | return SendPacket(payload); | 
|  | } | 
|  |  | 
|  | // 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_type == compression_types::lz4) { | 
|  | compressed_size = compression_encode_buffer( | 
|  | encoded_data.data(), encoded_data_buf_size, | 
|  | (const uint8_t *)orig.c_str(), orig.size(), nullptr, | 
|  | COMPRESSION_LZ4_RAW); | 
|  | } | 
|  | if (compression_type == compression_types::zlib_deflate) { | 
|  | compressed_size = compression_encode_buffer( | 
|  | encoded_data.data(), encoded_data_buf_size, | 
|  | (const uint8_t *)orig.c_str(), orig.size(), nullptr, | 
|  | COMPRESSION_ZLIB); | 
|  | } | 
|  | if (compression_type == compression_types::lzma) { | 
|  | compressed_size = compression_encode_buffer( | 
|  | encoded_data.data(), encoded_data_buf_size, | 
|  | (const uint8_t *)orig.c_str(), orig.size(), nullptr, | 
|  | COMPRESSION_LZMA); | 
|  | } | 
|  | if (compression_type == compression_types::lzfse) { | 
|  | compressed_size = compression_encode_buffer( | 
|  | encoded_data.data(), encoded_data_buf_size, | 
|  | (const 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; | 
|  |  | 
|  | 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--; | 
|  | 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. | 
|  |  | 
|  | static 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().Status() == 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_DARWIN_LOG", sizeof("LOG_DARWIN_LOG") - 1) == | 
|  | 0) { | 
|  | p += sizeof("LOG_DARWIN_LOG") - 1; | 
|  | bitmask |= LOG_DARWIN_LOG; | 
|  | } 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. | 
|  | // Use the existing log callback if one was already configured. | 
|  | if (!DNBLogGetLogCallback()) { | 
|  | // Use the os_log()-based logger if available; otherwise, | 
|  | // fallback to ASL. | 
|  | auto log_callback = OsLogger::GetLogFunction(); | 
|  | if (log_callback) | 
|  | DNBLogSetLogCallback(log_callback, nullptr); | 
|  | else | 
|  | DNBLogSetLogCallback(ASLLogCallback, nullptr); | 
|  | } | 
|  |  | 
|  | // Update logging to use the configured log channel bitmask. | 
|  | DNBLogSetLogMask(bitmask); | 
|  | p++; | 
|  | } | 
|  | } | 
|  | // We're not going to support logging to a file for now.  All logging | 
|  | // goes through ASL or the previously arranged log callback. | 
|  | #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); | 
|  | strlcpy (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 | 
|  | StdStringExtractor 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()) { | 
|  | StdStringExtractor 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_qStructuredDataPlugins(const char *p) { | 
|  | // We'll return a JSON array of supported packet types. | 
|  | // The type is significant.  For each of the supported | 
|  | // packet types that have been enabled, there will be a | 
|  | // 'J' async packet sent to the client with payload data. | 
|  | // This payload data will be a JSON dictionary, and the | 
|  | // top level dictionary will contain a string field with | 
|  | // its value set to the relevant packet type from this list. | 
|  | JSONGenerator::Array supported_json_packets; | 
|  |  | 
|  | // Check for DarwinLog (libtrace os_log/activity support). | 
|  | if (DarwinLogCollector::IsSupported()) | 
|  | supported_json_packets.AddItem( | 
|  | JSONGenerator::StringSP(new JSONGenerator::String("DarwinLog"))); | 
|  |  | 
|  | // Send back the array. | 
|  | std::ostringstream stream; | 
|  | supported_json_packets.Dump(stream); | 
|  | return SendPacket(stream.str()); | 
|  | } | 
|  |  | 
|  | rnb_err_t RNBRemote::HandlePacket_QConfigureDarwinLog(const char *p) { | 
|  | if (!DarwinLogCollector::IsSupported()) { | 
|  | // We should never have been given this request. | 
|  | return SendPacket("E89"); | 
|  | } | 
|  |  | 
|  | // Ensure we have a process.  We expect a separate configure request for | 
|  | // each process launched/attached. | 
|  | const nub_process_t pid = m_ctx.ProcessID(); | 
|  | if (pid == INVALID_NUB_PROCESS) | 
|  | return SendPacket("E94"); | 
|  |  | 
|  | // Get the configuration dictionary. | 
|  | p += strlen("QConfigureDarwinLog:"); | 
|  |  | 
|  | // The configuration dictionary is binary encoded. | 
|  | std::vector<uint8_t> unescaped_config_data = decode_binary_data(p, -1); | 
|  | std::string unescaped_config_string((const char *)&unescaped_config_data[0], | 
|  | unescaped_config_data.size()); | 
|  | DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLog: received config data: \"%s\"", | 
|  | unescaped_config_string.c_str()); | 
|  | auto configuration_sp = | 
|  | JSONParser(unescaped_config_string.c_str()).ParseJSONValue(); | 
|  | if (!configuration_sp) { | 
|  | // Malformed request - we require configuration data | 
|  | // indicating whether we're enabling or disabling. | 
|  | return SendPacket("E90"); | 
|  | } | 
|  |  | 
|  | if (!JSONObject::classof(configuration_sp.get())) { | 
|  | // Configuration data is not of the right type. | 
|  | return SendPacket("E91"); | 
|  | } | 
|  | JSONObject &config_dict = *static_cast<JSONObject *>(configuration_sp.get()); | 
|  |  | 
|  | // Check if we're enabling or disabling. | 
|  | auto enabled_sp = config_dict.GetObject("enabled"); | 
|  | if (!enabled_sp) { | 
|  | // Missing required "enabled" field. | 
|  | return SendPacket("E92"); | 
|  | } | 
|  | if (!JSONTrue::classof(enabled_sp.get()) && | 
|  | !JSONFalse::classof(enabled_sp.get())) { | 
|  | // Should be a boolean type, but wasn't. | 
|  | return SendPacket("E93"); | 
|  | } | 
|  | const bool enabling = JSONTrue::classof(enabled_sp.get()); | 
|  |  | 
|  | // TODO - handle other configuration parameters here. | 
|  |  | 
|  | // Shut down any active activity stream for the process. | 
|  | DarwinLogCollector::CancelStreamForProcess(pid); | 
|  |  | 
|  | if (enabling) { | 
|  | // Look up the procecess. | 
|  | if (!DarwinLogCollector::StartCollectingForProcess(pid, config_dict)) | 
|  | return SendPacket("E95"); | 
|  | } | 
|  |  | 
|  | 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, nub_addr_t &dispatch_queue_t, | 
|  | 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) { | 
|  | dispatch_queue_t = DNBProcessMemoryReadPointer(pid, dispatch_qaddr); | 
|  | if (dispatch_queue_t) { | 
|  | queue_width = DNBProcessMemoryReadInteger( | 
|  | pid, dispatch_queue_t + dqo_width, dqo_width_size, 0); | 
|  | queue_serialnum = DNBProcessMemoryReadInteger( | 
|  | pid, dispatch_queue_t + 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_t + dqo_label; | 
|  | nub_addr_t label_addr = | 
|  | DNBProcessMemoryReadPointer(pid, pointer_to_label_address); | 
|  | if (label_addr) | 
|  | queue_name = DNBProcessMemoryReadCString(pid, label_addr); | 
|  | } else { | 
|  | // libdispatch versions 1-3, dispatch name is a fixed width char array | 
|  | // in the queue structure. | 
|  | queue_name = DNBProcessMemoryReadCStringFixed( | 
|  | pid, dispatch_queue_t + 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:"; | 
|  | const uint8_t *u_thread_name = (const uint8_t *)thread_name; | 
|  | for (size_t i = 0; i < thread_name_len; i++) | 
|  | ostrm << RAWHEX8(u_thread_name[i]); | 
|  | ostrm << ';'; | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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]) { | 
|  | strlcpy(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(); | 
|  |  | 
|  | StdStringExtractor 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) { | 
|  | StdStringExtractor packet(p); | 
|  | packet.SetFilePos(2); // Skip the "_M" | 
|  |  | 
|  | nub_addr_t size = packet.GetHexMaxU64(StdStringExtractor::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) { | 
|  | StdStringExtractor packet(p); | 
|  | packet.SetFilePos(2); // Skip the "_m" | 
|  | nub_addr_t addr = | 
|  | packet.GetHexMaxU64(StdStringExtractor::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"); | 
|  | } | 
|  |  | 
|  | StdStringExtractor 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); | 
|  |  | 
|  | bool enable_compression = false; | 
|  | (void)enable_compression; | 
|  |  | 
|  | #if (defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1) || (defined (TARGET_OS_IOS) && TARGET_OS_IOS == 1) || (defined (TARGET_OS_TV) && TARGET_OS_TV == 1) | 
|  | enable_compression = true; | 
|  | #endif | 
|  |  | 
|  | #if defined(HAVE_LIBCOMPRESSION) | 
|  | // libcompression is weak linked so test if compression_decode_buffer() is | 
|  | // available | 
|  | if (enable_compression) { | 
|  | 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) { | 
|  | DNBThreadResumeActions thread_actions; | 
|  | char *c = const_cast<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... | 
|  | LLVM_FALLTHROUGH; | 
|  | 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... | 
|  | LLVM_FALLTHROUGH; | 
|  | 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(); | 
|  |  | 
|  | StdStringExtractor 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"); | 
|  |  | 
|  | StdStringExtractor 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"); | 
|  |  | 
|  | StdStringExtractor 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 (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 | 
|  | DNBLogThreaded("RNBRemote::HandlePacket_stop_process() sending extra stop " | 
|  | "reply because DNBProcessInterrupt returned false"); | 
|  | 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;"; | 
|  | #elif defined(TARGET_OS_BRIDGE) && TARGET_OS_BRIDGE == 1 | 
|  | strm << "ostype:bridgeos;"; | 
|  | #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 << "os_version:" << major << "." << minor; | 
|  | if (patch != UINT64_MAX) | 
|  | 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 | 
|  | // | 
|  | // On raw targets (no OS, vendor info), I've seen replies like | 
|  | // <architecture>i386:x86-64</architecture> (for x86_64 systems - from vmware) | 
|  | // <architecture>arm</architecture> (for an unspecified arm device - from a Segger JLink) | 
|  | // For good interop, I'm not sure what's expected here.  e.g. will anyone understand | 
|  | // <architecture>x86_64</architecture> ? Or is i386:x86_64 the expected phrasing? | 
|  | // | 
|  | // 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; | 
|  | } | 
|  |  | 
|  | // A helper function that retrieves a boolean value from | 
|  | // a one-level-deep JSON dictionary of key-value pairs.  e.g. | 
|  | // jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true}] | 
|  |  | 
|  | // Returns true if it was able to find the key name, and sets the 'value' | 
|  | // argument to the value found. | 
|  |  | 
|  | bool get_boolean_value_for_key_name_from_json(const char *key, | 
|  | const char *json_string, | 
|  | bool &value) { | 
|  | 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++; | 
|  |  | 
|  | if (strncmp(c, "true", 4) == 0) { | 
|  | value = true; | 
|  | return true; | 
|  | } else if (strncmp(c, "false", 5) == 0) { | 
|  | value = false; | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // A helper function that reads an array of uint64_t's from | 
|  | // a one-level-deep JSON dictionary of key-value pairs.  e.g. | 
|  | // jGetLoadedDynamicLibrariesInfos:{"solib_addrs":[31345823,7768020384,7310483024]}] | 
|  |  | 
|  | // Returns true if it was able to find the key name, false if it did not. | 
|  | // "ints" will have all integers found in the array appended to it. | 
|  |  | 
|  | bool get_array_of_ints_value_for_key_name_from_json( | 
|  | const char *key, const char *json_string, std::vector<uint64_t> &ints) { | 
|  | 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++; | 
|  |  | 
|  | if (*c == '[') { | 
|  | c++; | 
|  | while (*c != '\0' && | 
|  | (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) | 
|  | c++; | 
|  | while (1) { | 
|  | if (!isdigit(*c)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | errno = 0; | 
|  | char *endptr; | 
|  | uint64_t value = strtoul(c, &endptr, 10); | 
|  | if (errno == 0) { | 
|  | ints.push_back(value); | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | if (endptr == c || endptr == nullptr || *endptr == '\0') { | 
|  | break; | 
|  | } | 
|  | c = endptr; | 
|  |  | 
|  | 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++; | 
|  | if (*c == ']') { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | nub_addr_t dispatch_queue_t = INVALID_NUB_ADDRESS; | 
|  | dispatch_queue_offsets->GetThreadQueueInfo( | 
|  | pid, thread_ident_info.dispatch_qaddr, dispatch_queue_t, | 
|  | queue_name, queue_width, queue_serialnum); | 
|  | if (dispatch_queue_t == 0 && queue_name.empty() && | 
|  | queue_serialnum == 0) { | 
|  | thread_dict_sp->AddBooleanItem("associated_with_dispatch_queue", | 
|  | false); | 
|  | } else { | 
|  | thread_dict_sp->AddBooleanItem("associated_with_dispatch_queue", | 
|  | true); | 
|  | } | 
|  | if (dispatch_queue_t != INVALID_NUB_ADDRESS && | 
|  | dispatch_queue_t != 0) | 
|  | thread_dict_sp->AddIntegerItem("dispatch_queue_t", | 
|  | dispatch_queue_t); | 
|  | 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("qserialnum", 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); | 
|  |  | 
|  | 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; | 
|  | 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 << ","; | 
|  | Genealogy::ProcessExecutableInfoSP image_info_sp; | 
|  | uint32_t idx = *iter; | 
|  | image_info_sp = DNBGetGenealogyImageInfo(pid, idx); | 
|  | if (image_info_sp) { | 
|  | if (!printed_one_process_info) { | 
|  | json << "\"process_infos\":["; | 
|  | printed_one_process_info = true; | 
|  | } | 
|  |  | 
|  | 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 << "}"; | 
|  | } | 
|  | } | 
|  | if (printed_one_process_info) | 
|  | 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"); | 
|  | } | 
|  |  | 
|  | //  This packet may be called in one of three ways: | 
|  | // | 
|  | //  jGetLoadedDynamicLibrariesInfos:{"image_count":40,"image_list_address":4295244704} | 
|  | //      Look for an array of the old dyld_all_image_infos style of binary infos | 
|  | //      at the image_list_address. | 
|  | //      This an array of {void* load_addr, void* mod_date, void* pathname} | 
|  | // | 
|  | //  jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true} | 
|  | //      Use the new style (macOS 10.12, tvOS 10, iOS 10, watchOS 3) dyld SPI to | 
|  | //      get a list of all the | 
|  | //      libraries loaded | 
|  | // | 
|  | //  jGetLoadedDynamicLibrariesInfos:{"solib_addresses":[8382824135,3258302053,830202858503]} | 
|  | //      Use the new style (macOS 10.12, tvOS 10, iOS 10, watchOS 3) dyld SPI to | 
|  | //      get the information | 
|  | //      about the libraries loaded at these addresses. | 
|  | // | 
|  | 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); | 
|  |  | 
|  | JSONGenerator::ObjectSP json_sp; | 
|  |  | 
|  | std::vector<uint64_t> macho_addresses; | 
|  | bool fetch_all_solibs = false; | 
|  | if (get_boolean_value_for_key_name_from_json("fetch_all_solibs", p, | 
|  | fetch_all_solibs) && | 
|  | fetch_all_solibs) { | 
|  | json_sp = DNBGetAllLoadedLibrariesInfos(pid); | 
|  | } else if (get_array_of_ints_value_for_key_name_from_json( | 
|  | "solib_addresses", p, macho_addresses)) { | 
|  | json_sp = DNBGetLibrariesInfoForAddresses(pid, macho_addresses); | 
|  | } else { | 
|  | 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) { | 
|  | 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"); | 
|  | } | 
|  |  | 
|  | // This packet does not currently take any arguments.  So the behavior is | 
|  | //    jGetSharedCacheInfo:{} | 
|  | //         send information about the inferior's shared cache | 
|  | //    jGetSharedCacheInfo: | 
|  | //         send "OK" to indicate that this packet is supported | 
|  | rnb_err_t RNBRemote::HandlePacket_jGetSharedCacheInfo(const char *p) { | 
|  | nub_process_t pid; | 
|  | // If we haven't run the process yet, return an error. | 
|  | if (!m_ctx.HasValidProcessID()) { | 
|  | return SendPacket("E85"); | 
|  | } | 
|  |  | 
|  | pid = m_ctx.ProcessID(); | 
|  |  | 
|  | const char get_shared_cache_info_str[] = {"jGetSharedCacheInfo:{"}; | 
|  | if (strncmp(p, get_shared_cache_info_str, | 
|  | sizeof(get_shared_cache_info_str) - 1) == 0) { | 
|  | JSONGenerator::ObjectSP json_sp = DNBGetSharedCacheInfo(pid); | 
|  |  | 
|  | 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("E86"); | 
|  | } | 
|  | } | 
|  | } | 
|  | 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 = 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 = 12; // CPU_SUBTYPE_ARM_V7K | 
|  | } | 
|  | } | 
|  | 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); | 
|  | (void)bytes_read; | 
|  |  | 
|  | uint32_t major_version, minor_version, patch_version; | 
|  | auto *platform = DNBGetDeploymentInfo(pid, lc, load_command_addr, | 
|  | major_version, minor_version, | 
|  | patch_version); | 
|  | if (platform) { | 
|  | os_handled = true; | 
|  | rep << "ostype:" << platform << ";"; | 
|  | break; | 
|  | } | 
|  | load_command_addr = load_command_addr + lc.cmdsize; | 
|  | } | 
|  | } | 
|  | #endif // when compiling this on x86 targets | 
|  | } | 
|  |  | 
|  | // 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;"; | 
|  | #elif defined(TARGET_OS_BRIDGE) && TARGET_OS_BRIDGE == 1 | 
|  | rep << "ostype:bridgeos;"; | 
|  | #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;"; | 
|  | #elif defined(TARGET_OS_BRIDGE) && TARGET_OS_BRIDGE == 1 | 
|  | rep << "ostype:bridgeos;"; | 
|  | #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; | 
|  | } |