diff options
Diffstat (limited to 'source/Core')
25 files changed, 6951 insertions, 1031 deletions
diff --git a/source/Core/Address.cpp b/source/Core/Address.cpp index de2165c..5ac2bcc 100644 --- a/source/Core/Address.cpp +++ b/source/Core/Address.cpp @@ -16,6 +16,7 @@ #include "lldb/Symbol/VariableList.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Symbol/SymbolVendor.h" @@ -327,15 +328,27 @@ Address::GetLoadAddress (Target *target) const addr_t Address::GetCallableLoadAddress (Target *target, bool is_indirect) const { - if (is_indirect && target) { + addr_t code_addr = LLDB_INVALID_ADDRESS; + + if (is_indirect && target) + { ProcessSP processSP = target->GetProcessSP(); Error error; if (processSP.get()) - return processSP->ResolveIndirectFunction(this, error); + { + code_addr = processSP->ResolveIndirectFunction(this, error); + if (!error.Success()) + code_addr = LLDB_INVALID_ADDRESS; + } } - - addr_t code_addr = GetLoadAddress (target); - + else + { + code_addr = GetLoadAddress (target); + } + + if (code_addr == LLDB_INVALID_ADDRESS) + return code_addr; + if (target) return target->GetCallableLoadAddress (code_addr, GetAddressClass()); return code_addr; diff --git a/source/Core/ArchSpec.cpp b/source/Core/ArchSpec.cpp index f2eb375..f4fa224 100644 --- a/source/Core/ArchSpec.cpp +++ b/source/Core/ArchSpec.cpp @@ -104,6 +104,7 @@ static const CoreDefinition g_core_definitions[ArchSpec::kNumCores] = { eByteOrderLittle, 4, 1, 15, llvm::Triple::x86 , ArchSpec::eCore_x86_32_i486sx , "i486sx" }, { eByteOrderLittle, 8, 1, 15, llvm::Triple::x86_64 , ArchSpec::eCore_x86_64_x86_64 , "x86_64" }, + { eByteOrderLittle, 8, 1, 15, llvm::Triple::x86_64 , ArchSpec::eCore_x86_64_x86_64h , "x86_64h" }, { eByteOrderLittle, 4, 4, 4 , llvm::Triple::UnknownArch , ArchSpec::eCore_uknownMach32 , "unknown-mach-32" }, { eByteOrderLittle, 8, 4, 4 , llvm::Triple::UnknownArch , ArchSpec::eCore_uknownMach64 , "unknown-mach-64" } }; @@ -205,10 +206,11 @@ static const ArchDefinitionEntry g_macho_arch_entries[] = { ArchSpec::eCore_x86_32_i386 , llvm::MachO::CPU_TYPE_I386 , 3 , UINT32_MAX , SUBTYPE_MASK }, { ArchSpec::eCore_x86_32_i486 , llvm::MachO::CPU_TYPE_I386 , 4 , UINT32_MAX , SUBTYPE_MASK }, { ArchSpec::eCore_x86_32_i486sx , llvm::MachO::CPU_TYPE_I386 , 0x84 , UINT32_MAX , SUBTYPE_MASK }, - { ArchSpec::eCore_x86_32_i386 , llvm::MachO::CPU_TYPE_I386 , CPU_ANY, UINT32_MAX , UINT32_MAX }, + { ArchSpec::eCore_x86_32_i386 , llvm::MachO::CPU_TYPE_I386 , CPU_ANY, UINT32_MAX , UINT32_MAX }, { ArchSpec::eCore_x86_64_x86_64 , llvm::MachO::CPU_TYPE_X86_64 , 3 , UINT32_MAX , SUBTYPE_MASK }, { ArchSpec::eCore_x86_64_x86_64 , llvm::MachO::CPU_TYPE_X86_64 , 4 , UINT32_MAX , SUBTYPE_MASK }, - { ArchSpec::eCore_x86_64_x86_64 , llvm::MachO::CPU_TYPE_X86_64 , CPU_ANY, UINT32_MAX , UINT32_MAX }, + { ArchSpec::eCore_x86_64_x86_64h , llvm::MachO::CPU_TYPE_X86_64 , 8 , UINT32_MAX , SUBTYPE_MASK }, + { ArchSpec::eCore_x86_64_x86_64 , llvm::MachO::CPU_TYPE_X86_64 , CPU_ANY, UINT32_MAX , UINT32_MAX }, // Catch any unknown mach architectures so we can always use the object and symbol mach-o files { ArchSpec::eCore_uknownMach32 , 0 , 0 , 0xFF000000u, 0x00000000u }, { ArchSpec::eCore_uknownMach64 , llvm::MachO::CPU_ARCH_ABI64 , 0 , 0xFF000000u, 0x00000000u } @@ -349,14 +351,16 @@ FindArchDefinitionEntry (const ArchDefinition *def, ArchSpec::Core core) ArchSpec::ArchSpec() : m_triple (), m_core (kCore_invalid), - m_byte_order (eByteOrderInvalid) + m_byte_order (eByteOrderInvalid), + m_distribution_id () { } ArchSpec::ArchSpec (const char *triple_cstr, Platform *platform) : m_triple (), m_core (kCore_invalid), - m_byte_order (eByteOrderInvalid) + m_byte_order (eByteOrderInvalid), + m_distribution_id () { if (triple_cstr) SetTriple(triple_cstr, platform); @@ -366,7 +370,8 @@ ArchSpec::ArchSpec (const char *triple_cstr, Platform *platform) : ArchSpec::ArchSpec (const char *triple_cstr) : m_triple (), m_core (kCore_invalid), - m_byte_order (eByteOrderInvalid) + m_byte_order (eByteOrderInvalid), + m_distribution_id () { if (triple_cstr) SetTriple(triple_cstr); @@ -375,7 +380,8 @@ ArchSpec::ArchSpec (const char *triple_cstr) : ArchSpec::ArchSpec(const llvm::Triple &triple) : m_triple (), m_core (kCore_invalid), - m_byte_order (eByteOrderInvalid) + m_byte_order (eByteOrderInvalid), + m_distribution_id () { SetTriple(triple); } @@ -383,7 +389,8 @@ ArchSpec::ArchSpec(const llvm::Triple &triple) : ArchSpec::ArchSpec (ArchitectureType arch_type, uint32_t cpu, uint32_t subtype) : m_triple (), m_core (kCore_invalid), - m_byte_order (eByteOrderInvalid) + m_byte_order (eByteOrderInvalid), + m_distribution_id () { SetArchitecture (arch_type, cpu, subtype); } @@ -403,6 +410,7 @@ ArchSpec::operator= (const ArchSpec& rhs) m_triple = rhs.m_triple; m_core = rhs.m_core; m_byte_order = rhs.m_byte_order; + m_distribution_id = rhs.m_distribution_id; } return *this; } @@ -413,6 +421,7 @@ ArchSpec::Clear() m_triple = llvm::Triple(); m_core = kCore_invalid; m_byte_order = eByteOrderInvalid; + m_distribution_id.Clear (); } //===----------------------------------------------------------------------===// @@ -468,6 +477,18 @@ ArchSpec::GetMachine () const return llvm::Triple::UnknownArch; } +const ConstString& +ArchSpec::GetDistributionId () const +{ + return m_distribution_id; +} + +void +ArchSpec::SetDistributionId (const char* distribution_id) +{ + m_distribution_id.SetCString (distribution_id); +} + uint32_t ArchSpec::GetAddressByteSize() const { @@ -763,6 +784,8 @@ ArchSpec::IsCompatibleMatch (const ArchSpec& rhs) const bool ArchSpec::IsEqualTo (const ArchSpec& rhs, bool exact_match) const { + // explicitly ignoring m_distribution_id in this method. + if (GetByteOrder() != rhs.GetByteOrder()) return false; @@ -873,7 +896,7 @@ cores_match (const ArchSpec::Core core1, const ArchSpec::Core core2, bool try_in if (core2 == ArchSpec::kCore_arm_any) return true; break; - + case ArchSpec::kCore_x86_32_any: if ((core2 >= ArchSpec::kCore_x86_32_first && core2 <= ArchSpec::kCore_x86_32_last) || (core2 == ArchSpec::kCore_x86_32_any)) return true; diff --git a/source/Core/Broadcaster.cpp b/source/Core/Broadcaster.cpp index 5af7497..88f3996 100644 --- a/source/Core/Broadcaster.cpp +++ b/source/Core/Broadcaster.cpp @@ -313,18 +313,22 @@ Broadcaster::RestoreBroadcaster () { Mutex::Locker event_types_locker(m_listeners_mutex); - Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_EVENTS)); - if (log) + if (!m_hijacking_listeners.empty()) { - Listener *listener = m_hijacking_listeners.back(); - log->Printf ("%p Broadcaster(\"%s\")::RestoreBroadcaster (about to pop listener(\"%s\")=%p)", - this, - m_broadcaster_name.AsCString(""), - listener->m_name.c_str(), - listener); + Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_EVENTS)); + if (log) + { + Listener *listener = m_hijacking_listeners.back(); + log->Printf ("%p Broadcaster(\"%s\")::RestoreBroadcaster (about to pop listener(\"%s\")=%p)", + this, + m_broadcaster_name.AsCString(""), + listener->m_name.c_str(), + listener); + } + m_hijacking_listeners.pop_back(); } - m_hijacking_listeners.pop_back(); - m_hijacking_masks.pop_back(); + if (!m_hijacking_masks.empty()) + m_hijacking_masks.pop_back(); } ConstString & diff --git a/source/Core/Communication.cpp b/source/Core/Communication.cpp index 6ea7a11..f05ce32 100644 --- a/source/Core/Communication.cpp +++ b/source/Core/Communication.cpp @@ -269,6 +269,16 @@ Communication::StopReadThread (Error *error_ptr) return status; } +bool +Communication::JoinReadThread (Error *error_ptr) +{ + if (!IS_VALID_LLDB_HOST_THREAD(m_read_thread)) + return true; + + bool success = Host::ThreadJoin (m_read_thread, NULL, error_ptr); + m_read_thread = LLDB_INVALID_HOST_THREAD; + return success; +} size_t Communication::GetCachedBytes (void *dst, size_t dst_len) diff --git a/source/Core/ConnectionFileDescriptor.cpp b/source/Core/ConnectionFileDescriptor.cpp index 5764a212..ed876e5 100644 --- a/source/Core/ConnectionFileDescriptor.cpp +++ b/source/Core/ConnectionFileDescriptor.cpp @@ -97,11 +97,11 @@ ConnectionFileDescriptor::ConnectionFileDescriptor () : m_fd_send_type (eFDTypeFile), m_fd_recv_type (eFDTypeFile), m_udp_send_sockaddr (new SocketAddress()), - m_should_close_fd (false), m_socket_timeout_usec(0), m_pipe_read(-1), m_pipe_write(-1), m_mutex (Mutex::eMutexTypeRecursive), + m_should_close_fd (false), m_shutting_down (false) { Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_CONNECTION | LIBLLDB_LOG_OBJECT)); @@ -116,11 +116,11 @@ ConnectionFileDescriptor::ConnectionFileDescriptor (int fd, bool owns_fd) : m_fd_send_type (eFDTypeFile), m_fd_recv_type (eFDTypeFile), m_udp_send_sockaddr (new SocketAddress()), - m_should_close_fd (owns_fd), m_socket_timeout_usec(0), m_pipe_read(-1), m_pipe_write(-1), m_mutex (Mutex::eMutexTypeRecursive), + m_should_close_fd (owns_fd), m_shutting_down (false) { Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_CONNECTION | LIBLLDB_LOG_OBJECT)); @@ -218,12 +218,15 @@ ConnectionFileDescriptor::Connect (const char *s, Error *error_ptr) if (s && s[0]) { - char *end = NULL; if (strstr(s, "listen://")) { // listen://HOST:PORT - unsigned long listen_port = ::strtoul(s + strlen("listen://"), &end, 0); - return SocketListen (listen_port, error_ptr); + return SocketListen (s + strlen("listen://"), error_ptr); + } + else if (strstr(s, "accept://")) + { + // unix://SOCKNAME + return NamedSocketAccept (s + strlen("accept://"), error_ptr); } else if (strstr(s, "unix-accept://")) { @@ -363,6 +366,9 @@ ConnectionFileDescriptor::Disconnect (Error *error_ptr) if (log) log->Printf ("%p ConnectionFileDescriptor::Disconnect ()", this); + // Reset the port predicate when disconnecting and don't broadcast + m_port_predicate.SetValue(0, eBroadcastNever); + ConnectionStatus status = eConnectionStatusSuccess; if (m_fd_send < 0 && m_fd_recv < 0) @@ -1281,16 +1287,31 @@ ConnectionFileDescriptor::NamedSocketConnect (const char *socket_name, Error *er } ConnectionStatus -ConnectionFileDescriptor::SocketListen (uint16_t listen_port_num, Error *error_ptr) +ConnectionFileDescriptor::SocketListen (const char *host_and_port, Error *error_ptr) { Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_CONNECTION)); if (log) - log->Printf ("%p ConnectionFileDescriptor::SocketListen (port = %i)", this, listen_port_num); + log->Printf ("%p ConnectionFileDescriptor::SocketListen (%s)", this, host_and_port); Disconnect (NULL); m_fd_send_type = m_fd_recv_type = eFDTypeSocket; - int listen_port = ::socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (listen_port == -1) + std::string host_str; + std::string port_str; + int32_t port = INT32_MIN; + if (!DecodeHostAndPort (host_and_port, host_str, port_str, port, error_ptr)) + { + // Might be just a port number + port = Args::StringToSInt32(host_and_port, -1); + if (port == -1) + return eConnectionStatusError; + else + host_str.clear(); + } + const sa_family_t family = AF_INET; + const int socktype = SOCK_STREAM; + const int protocol = IPPROTO_TCP; + int listen_fd = ::socket (family, socktype, protocol); + if (listen_fd == -1) { if (error_ptr) error_ptr->SetErrorToErrno(); @@ -1298,41 +1319,119 @@ ConnectionFileDescriptor::SocketListen (uint16_t listen_port_num, Error *error_p } // enable local address reuse - SetSocketOption (listen_port, SOL_SOCKET, SO_REUSEADDR, 1); + SetSocketOption (listen_fd, SOL_SOCKET, SO_REUSEADDR, 1); - SocketAddress localhost; - if (localhost.SetToLocalhost (AF_INET, listen_port_num)) + SocketAddress listen_addr; + if (host_str.empty()) + listen_addr.SetToLocalhost(family, port); + else if (host_str.compare("*") == 0) + listen_addr.SetToAnyAddress(family, port); + else { - int err = ::bind (listen_port, localhost, localhost.GetLength()); + if (!listen_addr.getaddrinfo(host_str.c_str(), port_str.c_str(), family, socktype, protocol)) + { + if (error_ptr) + error_ptr->SetErrorStringWithFormat("unable to resolve hostname '%s'", host_str.c_str()); + Close (listen_fd, eFDTypeSocket, NULL); + return eConnectionStatusError; + } + } + + SocketAddress anyaddr; + if (anyaddr.SetToAnyAddress (family, port)) + { + int err = ::bind (listen_fd, anyaddr, anyaddr.GetLength()); if (err == -1) { if (error_ptr) error_ptr->SetErrorToErrno(); - Close (listen_port, eFDTypeSocket, NULL); + Close (listen_fd, eFDTypeSocket, NULL); return eConnectionStatusError; } - err = ::listen (listen_port, 1); + err = ::listen (listen_fd, 1); if (err == -1) { if (error_ptr) error_ptr->SetErrorToErrno(); - Close (listen_port, eFDTypeSocket, NULL); + Close (listen_fd, eFDTypeSocket, NULL); return eConnectionStatusError; } - m_fd_send = m_fd_recv = ::accept (listen_port, NULL, 0); + // We were asked to listen on port zero which means we + // must now read the actual port that was given to us + // as port zero is a special code for "find an open port + // for me". + if (port == 0) + port = GetSocketPort(listen_fd); + + // Set the port predicate since when doing a listen://<host>:<port> + // it often needs to accept the incoming connection which is a blocking + // system call. Allowing access to the bound port using a predicate allows + // us to wait for the port predicate to be set to a non-zero value from + // another thread in an efficient manor. + m_port_predicate.SetValue(port, eBroadcastAlways); + + + bool accept_connection = false; + + // Loop until we are happy with our connection + while (!accept_connection) + { + struct sockaddr_in accept_addr; + ::memset (&accept_addr, 0, sizeof accept_addr); +#if !(defined (__linux__) || defined(_MSC_VER)) + accept_addr.sin_len = sizeof accept_addr; +#endif + socklen_t accept_addr_len = sizeof accept_addr; + + int fd = ::accept (listen_fd, (struct sockaddr *)&accept_addr, &accept_addr_len); + + if (fd == -1) + { + if (error_ptr) + error_ptr->SetErrorToErrno(); + break; + } + + if (listen_addr.sockaddr_in().sin_addr.s_addr == INADDR_ANY) + { + accept_connection = true; + m_fd_send = m_fd_recv = fd; + } + else + { + if ( +#if !(defined(__linux__) || (defined(_MSC_VER))) + accept_addr_len == listen_addr.sockaddr_in().sin_len && +#endif + accept_addr.sin_addr.s_addr == listen_addr.sockaddr_in().sin_addr.s_addr) + { + accept_connection = true; + m_fd_send = m_fd_recv = fd; + } + else + { + ::close (fd); + m_fd_send = m_fd_recv = -1; + const uint8_t *accept_ip = (const uint8_t *)&accept_addr.sin_addr.s_addr; + const uint8_t *listen_ip = (const uint8_t *)&listen_addr.sockaddr_in().sin_addr.s_addr; + ::fprintf (stderr, "error: rejecting incoming connection from %u.%u.%u.%u (expecting %u.%u.%u.%u)\n", + accept_ip[0], accept_ip[1], accept_ip[2], accept_ip[3], + listen_ip[0], listen_ip[1], listen_ip[2], listen_ip[3]); + } + } + } + if (m_fd_send == -1) { - if (error_ptr) - error_ptr->SetErrorToErrno(); - Close (listen_port, eFDTypeSocket, NULL); + Close (listen_fd, eFDTypeSocket, NULL); return eConnectionStatusError; } } // We are done with the listen port - Close (listen_port, eFDTypeSocket, NULL); + Close (listen_fd, eFDTypeSocket, NULL); m_should_close_fd = true; @@ -1446,7 +1545,7 @@ ConnectionFileDescriptor::ConnectUDP (const char *host_and_port, Error *error_pt { // Socket was created, now lets bind to the requested port SocketAddress addr; - addr.SetToLocalhost (AF_INET, 0); + addr.SetToAnyAddress (AF_INET, 0); if (::bind (m_fd_recv, addr, addr.GetLength()) == -1) { @@ -1581,21 +1680,23 @@ ConnectionFileDescriptor::SetSocketReceiveTimeout (uint32_t timeout_usec) return false; } -in_port_t +uint16_t ConnectionFileDescriptor::GetSocketPort (int fd) { // We bound to port zero, so we need to figure out which port we actually bound to - SocketAddress sock_addr; - socklen_t sock_addr_len = sock_addr.GetMaxLength (); - if (::getsockname (fd, sock_addr, &sock_addr_len) == 0) - return sock_addr.GetPort (); - + if (fd >= 0) + { + SocketAddress sock_addr; + socklen_t sock_addr_len = sock_addr.GetMaxLength (); + if (::getsockname (fd, sock_addr, &sock_addr_len) == 0) + return sock_addr.GetPort (); + } return 0; } // If the read file descriptor is a socket, then return // the port number that is being used by the socket. -in_port_t +uint16_t ConnectionFileDescriptor::GetReadPort () const { return ConnectionFileDescriptor::GetSocketPort (m_fd_recv); @@ -1603,10 +1704,23 @@ ConnectionFileDescriptor::GetReadPort () const // If the write file descriptor is a socket, then return // the port number that is being used by the socket. -in_port_t +uint16_t ConnectionFileDescriptor::GetWritePort () const { return ConnectionFileDescriptor::GetSocketPort (m_fd_send); } - +uint16_t +ConnectionFileDescriptor::GetBoundPort (uint32_t timeout_sec) +{ + uint16_t bound_port = 0; + if (timeout_sec == UINT32_MAX) + m_port_predicate.WaitForValueNotEqualTo (0, bound_port); + else + { + TimeValue timeout = TimeValue::Now(); + timeout.OffsetWithSeconds(timeout_sec); + m_port_predicate.WaitForValueNotEqualTo (0, bound_port, &timeout); + } + return bound_port; +} diff --git a/source/Core/DataExtractor.cpp b/source/Core/DataExtractor.cpp index d1f3c09..b42c6ff 100644 --- a/source/Core/DataExtractor.cpp +++ b/source/Core/DataExtractor.cpp @@ -37,6 +37,7 @@ #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" using namespace lldb; @@ -45,69 +46,97 @@ using namespace lldb_private; static inline uint16_t ReadInt16(const unsigned char* ptr, offset_t offset) { - return *(uint16_t *)(ptr + offset); + uint16_t value; + memcpy (&value, ptr + offset, 2); + return value; } + static inline uint32_t ReadInt32 (const unsigned char* ptr, offset_t offset) { - return *(uint32_t *)(ptr + offset); + uint32_t value; + memcpy (&value, ptr + offset, 4); + return value; } static inline uint64_t ReadInt64(const unsigned char* ptr, offset_t offset) { - return *(uint64_t *)(ptr + offset); + uint64_t value; + memcpy (&value, ptr + offset, 8); + return value; } static inline uint16_t ReadInt16(const void* ptr) { - return *(uint16_t *)(ptr); + uint16_t value; + memcpy (&value, ptr, 2); + return value; } + static inline uint32_t ReadInt32 (const void* ptr) { - return *(uint32_t *)(ptr); + uint32_t value; + memcpy (&value, ptr, 4); + return value; } static inline uint64_t ReadInt64(const void* ptr) { - return *(uint64_t *)(ptr); + uint64_t value; + memcpy (&value, ptr, 8); + return value; } static inline uint16_t ReadSwapInt16(const unsigned char* ptr, offset_t offset) { - return llvm::ByteSwap_16(*(uint16_t *)(ptr + offset)); + uint16_t value; + memcpy (&value, ptr + offset, 2); + return llvm::ByteSwap_16(value); } static inline uint32_t ReadSwapInt32 (const unsigned char* ptr, offset_t offset) { - return llvm::ByteSwap_32(*(uint32_t *)(ptr + offset)); + uint32_t value; + memcpy (&value, ptr + offset, 4); + return llvm::ByteSwap_32(value); } + static inline uint64_t ReadSwapInt64(const unsigned char* ptr, offset_t offset) { - return llvm::ByteSwap_64(*(uint64_t *)(ptr + offset)); + uint64_t value; + memcpy (&value, ptr + offset, 8); + return llvm::ByteSwap_64(value); } static inline uint16_t ReadSwapInt16(const void* ptr) { - return llvm::ByteSwap_16(*(uint16_t *)(ptr)); + uint16_t value; + memcpy (&value, ptr, 2); + return llvm::ByteSwap_16(value); } static inline uint32_t ReadSwapInt32 (const void* ptr) { - return llvm::ByteSwap_32(*(uint32_t *)(ptr)); + uint32_t value; + memcpy (&value, ptr, 4); + return llvm::ByteSwap_32(value); } + static inline uint64_t ReadSwapInt64(const void* ptr) { - return llvm::ByteSwap_64(*(uint64_t *)(ptr)); + uint64_t value; + memcpy (&value, ptr, 8); + return llvm::ByteSwap_64(value); } #define NON_PRINTABLE_CHAR '.' @@ -502,13 +531,17 @@ uint32_t DataExtractor::GetU32 (offset_t *offset_ptr) const { uint32_t val = 0; - const uint32_t *data = (const uint32_t *)GetData (offset_ptr, sizeof(val)); + const uint8_t *data = (const uint8_t *)GetData (offset_ptr, sizeof(val)); if (data) { if (m_byte_order != lldb::endian::InlHostByteOrder()) + { val = ReadSwapInt32 (data); + } else - val = *data; + { + memcpy (&val, data, 4); + } } return val; } @@ -561,13 +594,17 @@ uint64_t DataExtractor::GetU64 (offset_t *offset_ptr) const { uint64_t val = 0; - const uint64_t *data = (const uint64_t *)GetData (offset_ptr, sizeof(val)); + const uint8_t *data = (const uint8_t *)GetData (offset_ptr, sizeof(val)); if (data) { if (m_byte_order != lldb::endian::InlHostByteOrder()) + { val = ReadSwapInt64 (data); + } else - val = *data; + { + memcpy (&val, data, 8); + } } return val; } @@ -1808,6 +1845,7 @@ DataExtractor::Dump (Stream *s, case ArchSpec::eCore_x86_32_i486: case ArchSpec::eCore_x86_32_i486sx: case ArchSpec::eCore_x86_64_x86_64: + case ArchSpec::eCore_x86_64_x86_64h: // clang will assert when contructing the apfloat if we use a 16 byte integer value if (GetAPInt (*this, &offset, 10, apint)) { diff --git a/source/Core/Debugger.cpp b/source/Core/Debugger.cpp index b57c605..5b346ed 100644 --- a/source/Core/Debugger.cpp +++ b/source/Core/Debugger.cpp @@ -18,13 +18,13 @@ #include "lldb/lldb-private.h" #include "lldb/Core/ConnectionFileDescriptor.h" -#include "lldb/Core/InputReader.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/RegisterValue.h" #include "lldb/Core/State.h" #include "lldb/Core/StreamAsynchronousIO.h" #include "lldb/Core/StreamCallback.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/Timer.h" #include "lldb/Core/ValueObject.h" @@ -180,6 +180,7 @@ Debugger::SetPropertyValue (const ExecutionContext *exe_ctx, std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes (new_prompt, GetUseColor()); if (str.length()) new_prompt = str.c_str(); + GetCommandInterpreter().UpdatePrompt(new_prompt); EventSP prompt_change_event_sp (new Event(CommandInterpreter::eBroadcastBitResetPrompt, new EventDataBytes (new_prompt))); GetCommandInterpreter().BroadcastEvent (prompt_change_event_sp); } @@ -196,12 +197,16 @@ Debugger::SetPropertyValue (const ExecutionContext *exe_ctx, StreamString feedback_stream; if (!target_sp->LoadScriptingResources(errors,&feedback_stream)) { - for (auto error : errors) + StreamFileSP stream_sp (GetErrorFile()); + if (stream_sp) { - GetErrorStream().Printf("%s\n",error.AsCString()); + for (auto error : errors) + { + stream_sp->Printf("%s\n",error.AsCString()); + } + if (feedback_stream.GetSize()) + stream_sp->Printf("%s",feedback_stream.GetData()); } - if (feedback_stream.GetSize()) - GetErrorStream().Printf("%s",feedback_stream.GetData()); } } } @@ -246,8 +251,7 @@ Debugger::SetPrompt(const char *p) std::string str = lldb_utility::ansi::FormatAnsiTerminalCodes (new_prompt, GetUseColor()); if (str.length()) new_prompt = str.c_str(); - EventSP prompt_change_event_sp (new Event(CommandInterpreter::eBroadcastBitResetPrompt, new EventDataBytes (new_prompt)));; - GetCommandInterpreter().BroadcastEvent (prompt_change_event_sp); + GetCommandInterpreter().UpdatePrompt(new_prompt); } const char * @@ -611,10 +615,9 @@ Debugger::FindTargetWithProcess (Process *process) Debugger::Debugger (lldb::LogOutputCallback log_callback, void *baton) : UserID (g_unique_id++), Properties(OptionValuePropertiesSP(new OptionValueProperties())), - m_input_comm("debugger.input"), - m_input_file (), - m_output_file (), - m_error_file (), + m_input_file_sp (new StreamFile (stdin, false)), + m_output_file_sp (new StreamFile (stdout, false)), + m_error_file_sp (new StreamFile (stderr, false)), m_terminal_state (), m_target_list (*this), m_platform_list (), @@ -623,8 +626,11 @@ Debugger::Debugger (lldb::LogOutputCallback log_callback, void *baton) : m_source_file_cache(), m_command_interpreter_ap (new CommandInterpreter (*this, eScriptLanguageDefault, false)), m_input_reader_stack (), - m_input_reader_data (), - m_instance_name() + m_instance_name (), + m_loaded_plugins (), + m_event_handler_thread (LLDB_INVALID_HOST_THREAD), + m_io_handler_thread (LLDB_INVALID_HOST_THREAD), + m_event_handler_thread_alive(false) { char instance_cstr[256]; snprintf(instance_cstr, sizeof(instance_cstr), "debugger_%d", (int)GetID()); @@ -667,7 +673,9 @@ Debugger::~Debugger () void Debugger::Clear() { - CleanUpInputReaders(); + ClearIOHandlers(); + StopIOHandlerThread(); + StopEventHandlerThread(); m_listener.Clear(); int num_targets = m_target_list.GetNumTargets(); for (int i = 0; i < num_targets; i++) @@ -686,23 +694,21 @@ Debugger::Clear() // Close the input file _before_ we close the input read communications class // as it does NOT own the input file, our m_input_file does. m_terminal_state.Clear(); - GetInputFile().Close (); - // Now that we have closed m_input_file, we can now tell our input communication - // class to close down. Its read thread should quickly exit after we close - // the input file handle above. - m_input_comm.Clear (); + if (m_input_file_sp) + m_input_file_sp->GetFile().Close (); } bool Debugger::GetCloseInputOnEOF () const { - return m_input_comm.GetCloseOnEOF(); +// return m_input_comm.GetCloseOnEOF(); + return false; } void Debugger::SetCloseInputOnEOF (bool b) { - m_input_comm.SetCloseOnEOF(b); +// m_input_comm.SetCloseOnEOF(b); } bool @@ -721,37 +727,28 @@ Debugger::SetAsyncExecution (bool async_execution) void Debugger::SetInputFileHandle (FILE *fh, bool tranfer_ownership) { - File &in_file = GetInputFile(); - in_file.SetStream (fh, tranfer_ownership); + if (m_input_file_sp) + m_input_file_sp->GetFile().SetStream (fh, tranfer_ownership); + else + m_input_file_sp.reset (new StreamFile (fh, tranfer_ownership)); + + File &in_file = m_input_file_sp->GetFile(); if (in_file.IsValid() == false) in_file.SetStream (stdin, true); - // Disconnect from any old connection if we had one - m_input_comm.Disconnect (); - // Pass false as the second argument to ConnectionFileDescriptor below because - // our "in_file" above will already take ownership if requested and we don't - // want to objects trying to own and close a file descriptor. - m_input_comm.SetConnection (new ConnectionFileDescriptor (in_file.GetDescriptor(), false)); - m_input_comm.SetReadThreadBytesReceivedCallback (Debugger::DispatchInputCallback, this); - // Save away the terminal state if that is relevant, so that we can restore it in RestoreInputState. SaveInputTerminalState (); - - Error error; - if (m_input_comm.StartReadThread (&error) == false) - { - File &err_file = GetErrorFile(); - - err_file.Printf ("error: failed to main input read thread: %s", error.AsCString() ? error.AsCString() : "unkown error"); - exit(1); - } } void Debugger::SetOutputFileHandle (FILE *fh, bool tranfer_ownership) { - File &out_file = GetOutputFile(); - out_file.SetStream (fh, tranfer_ownership); + if (m_output_file_sp) + m_output_file_sp->GetFile().SetStream (fh, tranfer_ownership); + else + m_output_file_sp.reset (new StreamFile (fh, tranfer_ownership)); + + File &out_file = m_output_file_sp->GetFile(); if (out_file.IsValid() == false) out_file.SetStream (stdout, false); @@ -766,8 +763,12 @@ Debugger::SetOutputFileHandle (FILE *fh, bool tranfer_ownership) void Debugger::SetErrorFileHandle (FILE *fh, bool tranfer_ownership) { - File &err_file = GetErrorFile(); - err_file.SetStream (fh, tranfer_ownership); + if (m_error_file_sp) + m_error_file_sp->GetFile().SetStream (fh, tranfer_ownership); + else + m_error_file_sp.reset (new StreamFile (fh, tranfer_ownership)); + + File &err_file = m_error_file_sp->GetFile(); if (err_file.IsValid() == false) err_file.SetStream (stderr, false); } @@ -775,9 +776,12 @@ Debugger::SetErrorFileHandle (FILE *fh, bool tranfer_ownership) void Debugger::SaveInputTerminalState () { - File &in_file = GetInputFile(); - if (in_file.GetDescriptor() != File::kInvalidDescriptor) - m_terminal_state.Save(in_file.GetDescriptor(), true); + if (m_input_file_sp) + { + File &in_file = m_input_file_sp->GetFile(); + if (in_file.GetDescriptor() != File::kInvalidDescriptor) + m_terminal_state.Save(in_file.GetDescriptor(), true); + } } void @@ -812,245 +816,211 @@ Debugger::GetSelectedExecutionContext () return exe_ctx; } -InputReaderSP -Debugger::GetCurrentInputReader () -{ - InputReaderSP reader_sp; - - if (!m_input_reader_stack.IsEmpty()) - { - // Clear any finished readers from the stack - while (CheckIfTopInputReaderIsDone()) ; - - if (!m_input_reader_stack.IsEmpty()) - reader_sp = m_input_reader_stack.Top(); - } - - return reader_sp; -} - -void -Debugger::DispatchInputCallback (void *baton, const void *bytes, size_t bytes_len) -{ - if (bytes_len > 0) - ((Debugger *)baton)->DispatchInput ((char *)bytes, bytes_len); - else - ((Debugger *)baton)->DispatchInputEndOfFile (); -} - - -void -Debugger::DispatchInput (const char *bytes, size_t bytes_len) -{ - if (bytes == NULL || bytes_len == 0) - return; - - WriteToDefaultReader (bytes, bytes_len); -} - void Debugger::DispatchInputInterrupt () { - m_input_reader_data.clear(); - - InputReaderSP reader_sp (GetCurrentInputReader ()); + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + IOHandlerSP reader_sp (m_input_reader_stack.Top()); if (reader_sp) - { - reader_sp->Notify (eInputReaderInterrupt); - - // If notifying the reader of the interrupt finished the reader, we should pop it off the stack. - while (CheckIfTopInputReaderIsDone ()) ; - } + reader_sp->Interrupt(); } void Debugger::DispatchInputEndOfFile () { - m_input_reader_data.clear(); - - InputReaderSP reader_sp (GetCurrentInputReader ()); + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + IOHandlerSP reader_sp (m_input_reader_stack.Top()); if (reader_sp) - { - reader_sp->Notify (eInputReaderEndOfFile); - - // If notifying the reader of the end-of-file finished the reader, we should pop it off the stack. - while (CheckIfTopInputReaderIsDone ()) ; - } + reader_sp->GotEOF(); } void -Debugger::CleanUpInputReaders () +Debugger::ClearIOHandlers () { - m_input_reader_data.clear(); - // The bottom input reader should be the main debugger input reader. We do not want to close that one here. + Mutex::Locker locker (m_input_reader_stack.GetMutex()); while (m_input_reader_stack.GetSize() > 1) { - InputReaderSP reader_sp (GetCurrentInputReader ()); + IOHandlerSP reader_sp (m_input_reader_stack.Top()); if (reader_sp) { - reader_sp->Notify (eInputReaderEndOfFile); - reader_sp->SetIsDone (true); + m_input_reader_stack.Pop(); + reader_sp->SetIsDone(true); + reader_sp->Interrupt(); } } } void -Debugger::NotifyTopInputReader (InputReaderAction notification) +Debugger::ExecuteIOHanders() { - InputReaderSP reader_sp (GetCurrentInputReader()); - if (reader_sp) - { - reader_sp->Notify (notification); + + while (1) + { + IOHandlerSP reader_sp(m_input_reader_stack.Top()); + if (!reader_sp) + break; - // Flush out any input readers that are done. - while (CheckIfTopInputReaderIsDone ()) - /* Do nothing. */; + reader_sp->Activate(); + reader_sp->Run(); + reader_sp->Deactivate(); + + // Remove all input readers that are done from the top of the stack + while (1) + { + IOHandlerSP top_reader_sp = m_input_reader_stack.Top(); + if (top_reader_sp && top_reader_sp->GetIsDone()) + m_input_reader_stack.Pop(); + else + break; + } } + ClearIOHandlers(); } bool -Debugger::InputReaderIsTopReader (const InputReaderSP& reader_sp) +Debugger::IsTopIOHandler (const lldb::IOHandlerSP& reader_sp) { - InputReaderSP top_reader_sp (GetCurrentInputReader()); + return m_input_reader_stack.IsTop (reader_sp); +} + - return (reader_sp.get() == top_reader_sp.get()); +ConstString +Debugger::GetTopIOHandlerControlSequence(char ch) +{ + return m_input_reader_stack.GetTopIOHandlerControlSequence (ch); } - void -Debugger::WriteToDefaultReader (const char *bytes, size_t bytes_len) +Debugger::RunIOHandler (const IOHandlerSP& reader_sp) { - if (bytes && bytes_len) - m_input_reader_data.append (bytes, bytes_len); - - if (m_input_reader_data.empty()) - return; + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + PushIOHandler (reader_sp); + reader_sp->Activate(); + reader_sp->Run(); + PopIOHandler (reader_sp); +} - while (!m_input_reader_stack.IsEmpty() && !m_input_reader_data.empty()) +void +Debugger::AdoptTopIOHandlerFilesIfInvalid (StreamFileSP &in, StreamFileSP &out, StreamFileSP &err) +{ + // Before an IOHandler runs, it must have in/out/err streams. + // This function is called when one ore more of the streams + // are NULL. We use the top input reader's in/out/err streams, + // or fall back to the debugger file handles, or we fall back + // onto stdin/stdout/stderr as a last resort. + + Mutex::Locker locker (m_input_reader_stack.GetMutex()); + IOHandlerSP top_reader_sp (m_input_reader_stack.Top()); + // If no STDIN has been set, then set it appropriately + if (!in) { - // Get the input reader from the top of the stack - InputReaderSP reader_sp (GetCurrentInputReader ()); - if (!reader_sp) - break; - - size_t bytes_handled = reader_sp->HandleRawBytes (m_input_reader_data.c_str(), - m_input_reader_data.size()); - if (bytes_handled) - { - m_input_reader_data.erase (0, bytes_handled); - } + if (top_reader_sp) + in = top_reader_sp->GetInputStreamFile(); else - { - // No bytes were handled, we might not have reached our - // granularity, just return and wait for more data - break; - } + in = GetInputFile(); + + // If there is nothing, use stdin + if (!in) + in = StreamFileSP(new StreamFile(stdin, false)); + } + // If no STDOUT has been set, then set it appropriately + if (!out) + { + if (top_reader_sp) + out = top_reader_sp->GetOutputStreamFile(); + else + out = GetOutputFile(); + + // If there is nothing, use stdout + if (!out) + out = StreamFileSP(new StreamFile(stdout, false)); + } + // If no STDERR has been set, then set it appropriately + if (!err) + { + if (top_reader_sp) + err = top_reader_sp->GetErrorStreamFile(); + else + err = GetErrorFile(); + + // If there is nothing, use stderr + if (!err) + err = StreamFileSP(new StreamFile(stdout, false)); + } - - // Flush out any input readers that are done. - while (CheckIfTopInputReaderIsDone ()) - /* Do nothing. */; - } void -Debugger::PushInputReader (const InputReaderSP& reader_sp) +Debugger::PushIOHandler (const IOHandlerSP& reader_sp) { if (!reader_sp) return; - // Deactivate the old top reader - InputReaderSP top_reader_sp (GetCurrentInputReader ()); + // Got the current top input reader... + IOHandlerSP top_reader_sp (m_input_reader_stack.Top()); - if (top_reader_sp) - top_reader_sp->Notify (eInputReaderDeactivate); - + // Push our new input reader m_input_reader_stack.Push (reader_sp); - reader_sp->Notify (eInputReaderActivate); - ActivateInputReader (reader_sp); + + // Interrupt the top input reader to it will exit its Run() function + // and let this new input reader take over + if (top_reader_sp) + top_reader_sp->Deactivate(); } bool -Debugger::PopInputReader (const InputReaderSP& pop_reader_sp) +Debugger::PopIOHandler (const IOHandlerSP& pop_reader_sp) { bool result = false; + + Mutex::Locker locker (m_input_reader_stack.GetMutex()); // The reader on the stop of the stack is done, so let the next // read on the stack referesh its prompt and if there is one... if (!m_input_reader_stack.IsEmpty()) { - // Cannot call GetCurrentInputReader here, as that would cause an infinite loop. - InputReaderSP reader_sp(m_input_reader_stack.Top()); + IOHandlerSP reader_sp(m_input_reader_stack.Top()); if (!pop_reader_sp || pop_reader_sp.get() == reader_sp.get()) { + reader_sp->Deactivate(); m_input_reader_stack.Pop (); - reader_sp->Notify (eInputReaderDeactivate); - reader_sp->Notify (eInputReaderDone); - result = true; + + reader_sp = m_input_reader_stack.Top(); + if (reader_sp) + reader_sp->Activate(); - if (!m_input_reader_stack.IsEmpty()) - { - reader_sp = m_input_reader_stack.Top(); - if (reader_sp) - { - ActivateInputReader (reader_sp); - reader_sp->Notify (eInputReaderReactivate); - } - } + result = true; } } return result; } bool -Debugger::CheckIfTopInputReaderIsDone () +Debugger::HideTopIOHandler() { - bool result = false; - if (!m_input_reader_stack.IsEmpty()) + Mutex::Locker locker; + + if (locker.TryLock(m_input_reader_stack.GetMutex())) { - // Cannot call GetCurrentInputReader here, as that would cause an infinite loop. - InputReaderSP reader_sp(m_input_reader_stack.Top()); - - if (reader_sp && reader_sp->IsDone()) - { - result = true; - PopInputReader (reader_sp); - } + IOHandlerSP reader_sp(m_input_reader_stack.Top()); + if (reader_sp) + reader_sp->Hide(); + return true; } - return result; + return false; } void -Debugger::ActivateInputReader (const InputReaderSP &reader_sp) +Debugger::RefreshTopIOHandler() { - int input_fd = m_input_file.GetFile().GetDescriptor(); - - if (input_fd >= 0) - { - Terminal tty(input_fd); - - tty.SetEcho(reader_sp->GetEcho()); - - switch (reader_sp->GetGranularity()) - { - case eInputReaderGranularityByte: - case eInputReaderGranularityWord: - tty.SetCanonical (false); - break; - - case eInputReaderGranularityLine: - case eInputReaderGranularityAll: - tty.SetCanonical (true); - break; - - default: - break; - } - } + IOHandlerSP reader_sp(m_input_reader_stack.Top()); + if (reader_sp) + reader_sp->Refresh(); } + StreamSP Debugger::GetAsyncOutputStream () { @@ -2624,7 +2594,7 @@ Debugger::EnableLog (const char *channel, const char **categories, const char *l } else if (log_file == NULL || *log_file == '\0') { - log_stream_sp.reset(new StreamFile(GetOutputFile().GetDescriptor(), false)); + log_stream_sp = GetOutputFile(); } else { @@ -2680,3 +2650,514 @@ Debugger::GetSourceManager () } + +// This function handles events that were broadcast by the process. +void +Debugger::HandleBreakpointEvent (const EventSP &event_sp) +{ + using namespace lldb; + const uint32_t event_type = Breakpoint::BreakpointEventData::GetBreakpointEventTypeFromEvent (event_sp); + +// if (event_type & eBreakpointEventTypeAdded +// || event_type & eBreakpointEventTypeRemoved +// || event_type & eBreakpointEventTypeEnabled +// || event_type & eBreakpointEventTypeDisabled +// || event_type & eBreakpointEventTypeCommandChanged +// || event_type & eBreakpointEventTypeConditionChanged +// || event_type & eBreakpointEventTypeIgnoreChanged +// || event_type & eBreakpointEventTypeLocationsResolved) +// { +// // Don't do anything about these events, since the breakpoint commands already echo these actions. +// } +// + if (event_type & eBreakpointEventTypeLocationsAdded) + { + uint32_t num_new_locations = Breakpoint::BreakpointEventData::GetNumBreakpointLocationsFromEvent(event_sp); + if (num_new_locations > 0) + { + BreakpointSP breakpoint = Breakpoint::BreakpointEventData::GetBreakpointFromEvent(event_sp); + StreamFileSP output_sp (GetOutputFile()); + if (output_sp) + { + output_sp->Printf("%d location%s added to breakpoint %d\n", + num_new_locations, + num_new_locations == 1 ? "" : "s", + breakpoint->GetID()); + RefreshTopIOHandler(); + } + } + } +// else if (event_type & eBreakpointEventTypeLocationsRemoved) +// { +// // These locations just get disabled, not sure it is worth spamming folks about this on the command line. +// } +// else if (event_type & eBreakpointEventTypeLocationsResolved) +// { +// // This might be an interesting thing to note, but I'm going to leave it quiet for now, it just looked noisy. +// } +} + +size_t +Debugger::GetProcessSTDOUT (Process *process, Stream *stream) +{ + size_t total_bytes = 0; + if (stream == NULL) + stream = GetOutputFile().get(); + + if (stream) + { + // The process has stuff waiting for stdout; get it and write it out to the appropriate place. + if (process == NULL) + { + TargetSP target_sp = GetTargetList().GetSelectedTarget(); + if (target_sp) + process = target_sp->GetProcessSP().get(); + } + if (process) + { + Error error; + size_t len; + char stdio_buffer[1024]; + while ((len = process->GetSTDOUT (stdio_buffer, sizeof (stdio_buffer), error)) > 0) + { + stream->Write(stdio_buffer, len); + total_bytes += len; + } + } + stream->Flush(); + } + return total_bytes; +} + +size_t +Debugger::GetProcessSTDERR (Process *process, Stream *stream) +{ + size_t total_bytes = 0; + if (stream == NULL) + stream = GetOutputFile().get(); + + if (stream) + { + // The process has stuff waiting for stderr; get it and write it out to the appropriate place. + if (process == NULL) + { + TargetSP target_sp = GetTargetList().GetSelectedTarget(); + if (target_sp) + process = target_sp->GetProcessSP().get(); + } + if (process) + { + Error error; + size_t len; + char stdio_buffer[1024]; + while ((len = process->GetSTDERR (stdio_buffer, sizeof (stdio_buffer), error)) > 0) + { + stream->Write(stdio_buffer, len); + total_bytes += len; + } + } + stream->Flush(); + } + return total_bytes; +} + +// This function handles events that were broadcast by the process. +void +Debugger::HandleProcessEvent (const EventSP &event_sp) +{ + using namespace lldb; + const uint32_t event_type = event_sp->GetType(); + ProcessSP process_sp = Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); + + const bool gui_enabled = IsForwardingEvents(); + bool top_io_handler_hid = false; + if (gui_enabled == false) + top_io_handler_hid = HideTopIOHandler(); + + assert (process_sp); + + if (event_type & Process::eBroadcastBitSTDOUT) + { + // The process has stdout available, get it and write it out to the + // appropriate place. + if (top_io_handler_hid) + GetProcessSTDOUT (process_sp.get(), NULL); + } + else if (event_type & Process::eBroadcastBitSTDERR) + { + // The process has stderr available, get it and write it out to the + // appropriate place. + if (top_io_handler_hid) + GetProcessSTDERR (process_sp.get(), NULL); + } + else if (event_type & Process::eBroadcastBitStateChanged) + { + // Drain all stout and stderr so we don't see any output come after + // we print our prompts + if (top_io_handler_hid) + { + StreamFileSP stream_sp (GetOutputFile()); + GetProcessSTDOUT (process_sp.get(), stream_sp.get()); + GetProcessSTDERR (process_sp.get(), NULL); + // Something changed in the process; get the event and report the process's current status and location to + // the user. + StateType event_state = Process::ProcessEventData::GetStateFromEvent (event_sp.get()); + if (event_state == eStateInvalid) + return; + + switch (event_state) + { + case eStateInvalid: + case eStateUnloaded: + case eStateConnected: + case eStateAttaching: + case eStateLaunching: + case eStateStepping: + case eStateDetached: + { + stream_sp->Printf("Process %" PRIu64 " %s\n", + process_sp->GetID(), + StateAsCString (event_state)); + } + break; + + case eStateRunning: + // Don't be chatty when we run... + break; + + case eStateExited: + process_sp->GetStatus(*stream_sp); + break; + + case eStateStopped: + case eStateCrashed: + case eStateSuspended: + // Make sure the program hasn't been auto-restarted: + if (Process::ProcessEventData::GetRestartedFromEvent (event_sp.get())) + { + size_t num_reasons = Process::ProcessEventData::GetNumRestartedReasons(event_sp.get()); + if (num_reasons > 0) + { + // FIXME: Do we want to report this, or would that just be annoyingly chatty? + if (num_reasons == 1) + { + const char *reason = Process::ProcessEventData::GetRestartedReasonAtIndex (event_sp.get(), 0); + stream_sp->Printf("Process %" PRIu64 " stopped and restarted: %s\n", + process_sp->GetID(), + reason ? reason : "<UNKNOWN REASON>"); + } + else + { + stream_sp->Printf("Process %" PRIu64 " stopped and restarted, reasons:\n", + process_sp->GetID()); + + + for (size_t i = 0; i < num_reasons; i++) + { + const char *reason = Process::ProcessEventData::GetRestartedReasonAtIndex (event_sp.get(), i); + stream_sp->Printf("\t%s\n", reason ? reason : "<UNKNOWN REASON>"); + } + } + } + } + else + { + // Lock the thread list so it doesn't change on us + ThreadList &thread_list = process_sp->GetThreadList(); + Mutex::Locker locker (thread_list.GetMutex()); + + ThreadSP curr_thread (thread_list.GetSelectedThread()); + ThreadSP thread; + StopReason curr_thread_stop_reason = eStopReasonInvalid; + if (curr_thread) + curr_thread_stop_reason = curr_thread->GetStopReason(); + if (!curr_thread || + !curr_thread->IsValid() || + curr_thread_stop_reason == eStopReasonInvalid || + curr_thread_stop_reason == eStopReasonNone) + { + // Prefer a thread that has just completed its plan over another thread as current thread. + ThreadSP plan_thread; + ThreadSP other_thread; + const size_t num_threads = thread_list.GetSize(); + size_t i; + for (i = 0; i < num_threads; ++i) + { + thread = thread_list.GetThreadAtIndex(i); + StopReason thread_stop_reason = thread->GetStopReason(); + switch (thread_stop_reason) + { + case eStopReasonInvalid: + case eStopReasonNone: + break; + + case eStopReasonTrace: + case eStopReasonBreakpoint: + case eStopReasonWatchpoint: + case eStopReasonSignal: + case eStopReasonException: + case eStopReasonExec: + case eStopReasonThreadExiting: + if (!other_thread) + other_thread = thread; + break; + case eStopReasonPlanComplete: + if (!plan_thread) + plan_thread = thread; + break; + } + } + if (plan_thread) + thread_list.SetSelectedThreadByID (plan_thread->GetID()); + else if (other_thread) + thread_list.SetSelectedThreadByID (other_thread->GetID()); + else + { + if (curr_thread && curr_thread->IsValid()) + thread = curr_thread; + else + thread = thread_list.GetThreadAtIndex(0); + + if (thread) + thread_list.SetSelectedThreadByID (thread->GetID()); + } + } + + if (GetTargetList().GetSelectedTarget().get() == &process_sp->GetTarget()) + { + const bool only_threads_with_stop_reason = true; + const uint32_t start_frame = 0; + const uint32_t num_frames = 1; + const uint32_t num_frames_with_source = 1; + process_sp->GetStatus(*stream_sp); + process_sp->GetThreadStatus (*stream_sp, + only_threads_with_stop_reason, + start_frame, + num_frames, + num_frames_with_source); + } + else + { + uint32_t target_idx = GetTargetList().GetIndexOfTarget(process_sp->GetTarget().shared_from_this()); + if (target_idx != UINT32_MAX) + stream_sp->Printf ("Target %d: (", target_idx); + else + stream_sp->Printf ("Target <unknown index>: ("); + process_sp->GetTarget().Dump (stream_sp.get(), eDescriptionLevelBrief); + stream_sp->Printf (") stopped.\n"); + } + } + break; + } + } + } + + if (top_io_handler_hid) + RefreshTopIOHandler(); +} + +void +Debugger::HandleThreadEvent (const EventSP &event_sp) +{ + // At present the only thread event we handle is the Frame Changed event, + // and all we do for that is just reprint the thread status for that thread. + using namespace lldb; + const uint32_t event_type = event_sp->GetType(); + if (event_type == Thread::eBroadcastBitStackChanged || + event_type == Thread::eBroadcastBitThreadSelected ) + { + ThreadSP thread_sp (Thread::ThreadEventData::GetThreadFromEvent (event_sp.get())); + if (thread_sp) + { + HideTopIOHandler(); + StreamFileSP stream_sp (GetOutputFile()); + thread_sp->GetStatus(*stream_sp, 0, 1, 1); + RefreshTopIOHandler(); + } + } +} + +bool +Debugger::IsForwardingEvents () +{ + return (bool)m_forward_listener_sp; +} + +void +Debugger::EnableForwardEvents (const ListenerSP &listener_sp) +{ + m_forward_listener_sp = listener_sp; +} + +void +Debugger::CancelForwardEvents (const ListenerSP &listener_sp) +{ + m_forward_listener_sp.reset(); +} + + +void +Debugger::DefaultEventHandler() +{ + Listener& listener(GetListener()); + ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); + BroadcastEventSpec target_event_spec (broadcaster_class_target, + Target::eBroadcastBitBreakpointChanged); + + BroadcastEventSpec process_event_spec (broadcaster_class_process, + Process::eBroadcastBitStateChanged | + Process::eBroadcastBitSTDOUT | + Process::eBroadcastBitSTDERR); + + BroadcastEventSpec thread_event_spec (broadcaster_class_thread, + Thread::eBroadcastBitStackChanged | + Thread::eBroadcastBitThreadSelected ); + + listener.StartListeningForEventSpec (*this, target_event_spec); + listener.StartListeningForEventSpec (*this, process_event_spec); + listener.StartListeningForEventSpec (*this, thread_event_spec); + listener.StartListeningForEvents (m_command_interpreter_ap.get(), + CommandInterpreter::eBroadcastBitQuitCommandReceived | + CommandInterpreter::eBroadcastBitAsynchronousOutputData | + CommandInterpreter::eBroadcastBitAsynchronousErrorData ); + + bool done = false; + while (!done) + { +// Mutex::Locker locker; +// if (locker.TryLock(m_input_reader_stack.GetMutex())) +// { +// if (m_input_reader_stack.IsEmpty()) +// break; +// } +// + EventSP event_sp; + if (listener.WaitForEvent(NULL, event_sp)) + { + if (event_sp) + { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) + { + uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class (broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) + { + HandleProcessEvent (event_sp); + } + else if (broadcaster_class == broadcaster_class_target) + { + if (Breakpoint::BreakpointEventData::GetEventDataFromEvent(event_sp.get())) + { + HandleBreakpointEvent (event_sp); + } + } + else if (broadcaster_class == broadcaster_class_thread) + { + HandleThreadEvent (event_sp); + } + else if (broadcaster == m_command_interpreter_ap.get()) + { + if (event_type & CommandInterpreter::eBroadcastBitQuitCommandReceived) + { + done = true; + } + else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousErrorData) + { + const char *data = reinterpret_cast<const char *>(EventDataBytes::GetBytesFromEvent (event_sp.get())); + if (data && data[0]) + { + StreamFileSP error_sp (GetErrorFile()); + if (error_sp) + { + HideTopIOHandler(); + error_sp->PutCString(data); + error_sp->Flush(); + RefreshTopIOHandler(); + } + } + } + else if (event_type & CommandInterpreter::eBroadcastBitAsynchronousOutputData) + { + const char *data = reinterpret_cast<const char *>(EventDataBytes::GetBytesFromEvent (event_sp.get())); + if (data && data[0]) + { + StreamFileSP output_sp (GetOutputFile()); + if (output_sp) + { + HideTopIOHandler(); + output_sp->PutCString(data); + output_sp->Flush(); + RefreshTopIOHandler(); + } + } + } + } + } + + if (m_forward_listener_sp) + m_forward_listener_sp->AddEvent(event_sp); + } + } + } +} + +lldb::thread_result_t +Debugger::EventHandlerThread (lldb::thread_arg_t arg) +{ + ((Debugger *)arg)->DefaultEventHandler(); + return NULL; +} + +bool +Debugger::StartEventHandlerThread() +{ + if (!IS_VALID_LLDB_HOST_THREAD(m_event_handler_thread)) + m_event_handler_thread = Host::ThreadCreate("lldb.debugger.event-handler", EventHandlerThread, this, NULL); + return IS_VALID_LLDB_HOST_THREAD(m_event_handler_thread); +} + +void +Debugger::StopEventHandlerThread() +{ + if (IS_VALID_LLDB_HOST_THREAD(m_event_handler_thread)) + { + GetCommandInterpreter().BroadcastEvent(CommandInterpreter::eBroadcastBitQuitCommandReceived); + Host::ThreadJoin(m_event_handler_thread, NULL, NULL); + m_event_handler_thread = LLDB_INVALID_HOST_THREAD; + } +} + + +lldb::thread_result_t +Debugger::IOHandlerThread (lldb::thread_arg_t arg) +{ + Debugger *debugger = (Debugger *)arg; + debugger->ExecuteIOHanders(); + debugger->StopEventHandlerThread(); + return NULL; +} + +bool +Debugger::StartIOHandlerThread() +{ + if (!IS_VALID_LLDB_HOST_THREAD(m_io_handler_thread)) + m_io_handler_thread = Host::ThreadCreate("lldb.debugger.io-handler", IOHandlerThread, this, NULL); + return IS_VALID_LLDB_HOST_THREAD(m_io_handler_thread); +} + +void +Debugger::StopIOHandlerThread() +{ + if (IS_VALID_LLDB_HOST_THREAD(m_io_handler_thread)) + { + if (m_input_file_sp) + m_input_file_sp->GetFile().Close(); + Host::ThreadJoin(m_io_handler_thread, NULL, NULL); + m_io_handler_thread = LLDB_INVALID_HOST_THREAD; + } +} + + diff --git a/source/Core/Disassembler.cpp b/source/Core/Disassembler.cpp index 7f830ac..1d2b8cf 100644 --- a/source/Core/Disassembler.cpp +++ b/source/Core/Disassembler.cpp @@ -35,6 +35,7 @@ #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/Target.h" @@ -1044,10 +1045,8 @@ InstructionList::GetIndexOfNextBranchInstruction(uint32_t start) const } uint32_t -InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Target &target) +InstructionList::GetIndexOfInstructionAtAddress (const Address &address) { - Address address; - address.SetLoadAddress(load_addr, &target); size_t num_instructions = m_instructions.size(); uint32_t index = UINT32_MAX; for (size_t i = 0; i < num_instructions; i++) @@ -1061,6 +1060,15 @@ InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Tar return index; } + +uint32_t +InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Target &target) +{ + Address address; + address.SetLoadAddress(load_addr, &target); + return GetIndexOfInstructionAtAddress(address); +} + size_t Disassembler::ParseInstructions (const ExecutionContext *exe_ctx, const AddressRange &range, @@ -1235,25 +1243,25 @@ PseudoInstruction::SetOpcode (size_t opcode_size, void *opcode_data) case 8: { uint8_t value8 = *((uint8_t *) opcode_data); - m_opcode.SetOpcode8 (value8); + m_opcode.SetOpcode8 (value8, eByteOrderInvalid); break; } case 16: { uint16_t value16 = *((uint16_t *) opcode_data); - m_opcode.SetOpcode16 (value16); + m_opcode.SetOpcode16 (value16, eByteOrderInvalid); break; } case 32: { uint32_t value32 = *((uint32_t *) opcode_data); - m_opcode.SetOpcode32 (value32); + m_opcode.SetOpcode32 (value32, eByteOrderInvalid); break; } case 64: { uint64_t value64 = *((uint64_t *) opcode_data); - m_opcode.SetOpcode64 (value64); + m_opcode.SetOpcode64 (value64, eByteOrderInvalid); break; } default: diff --git a/source/Core/DynamicLoader.cpp b/source/Core/DynamicLoader.cpp index 82f8404..1f545b7 100644 --- a/source/Core/DynamicLoader.cpp +++ b/source/Core/DynamicLoader.cpp @@ -10,7 +10,11 @@ #include "lldb/lldb-private.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/Section.h" using namespace lldb; using namespace lldb_private; @@ -74,3 +78,137 @@ DynamicLoader::SetStopWhenImagesChange (bool stop) m_process->SetStopOnSharedLibraryEvents (stop); } +ModuleSP +DynamicLoader::GetTargetExecutable() +{ + Target &target = m_process->GetTarget(); + ModuleSP executable = target.GetExecutableModule(); + + if (executable.get()) + { + if (executable->GetFileSpec().Exists()) + { + ModuleSpec module_spec (executable->GetFileSpec(), executable->GetArchitecture()); + ModuleSP module_sp (new Module (module_spec)); + + // Check if the executable has changed and set it to the target executable if they differ. + if (module_sp.get() && module_sp->GetUUID().IsValid() && executable->GetUUID().IsValid()) + { + if (module_sp->GetUUID() != executable->GetUUID()) + executable.reset(); + } + else if (executable->FileHasChanged()) + { + executable.reset(); + } + + if (!executable.get()) + { + executable = target.GetSharedModule(module_spec); + if (executable.get() != target.GetExecutableModulePointer()) + { + // Don't load dependent images since we are in dyld where we will know + // and find out about all images that are loaded + const bool get_dependent_images = false; + target.SetExecutableModule(executable, get_dependent_images); + } + } + } + } + return executable; +} + +void +DynamicLoader::UpdateLoadedSections(ModuleSP module, addr_t link_map_addr, addr_t base_addr) +{ + UpdateLoadedSectionsCommon(module, base_addr); +} + +void +DynamicLoader::UpdateLoadedSectionsCommon(ModuleSP module, addr_t base_addr) +{ + bool changed; + const bool base_addr_is_offset = true; + module->SetLoadAddress(m_process->GetTarget(), base_addr, base_addr_is_offset, changed); +} + +void +DynamicLoader::UnloadSections(const ModuleSP module) +{ + UnloadSectionsCommon(module); +} + +void +DynamicLoader::UnloadSectionsCommon(const ModuleSP module) +{ + Target &target = m_process->GetTarget(); + const SectionList *sections = GetSectionListFromModule(module); + + assert(sections && "SectionList missing from unloaded module."); + + const size_t num_sections = sections->GetSize(); + for (size_t i = 0; i < num_sections; ++i) + { + SectionSP section_sp (sections->GetSectionAtIndex(i)); + target.SetSectionUnloaded(section_sp); + } +} + + +const SectionList * +DynamicLoader::GetSectionListFromModule(const ModuleSP module) const +{ + SectionList *sections = nullptr; + if (module.get()) + { + ObjectFile *obj_file = module->GetObjectFile(); + if (obj_file) + { + sections = obj_file->GetSectionList(); + } + } + return sections; +} + +ModuleSP +DynamicLoader::LoadModuleAtAddress(const FileSpec &file, addr_t link_map_addr, addr_t base_addr) +{ + Target &target = m_process->GetTarget(); + ModuleList &modules = target.GetImages(); + ModuleSP module_sp; + + ModuleSpec module_spec (file, target.GetArchitecture()); + if ((module_sp = modules.FindFirstModule (module_spec))) + { + UpdateLoadedSections(module_sp, link_map_addr, base_addr); + } + else if ((module_sp = target.GetSharedModule(module_spec))) + { + UpdateLoadedSections(module_sp, link_map_addr, base_addr); + } + + return module_sp; +} + +int64_t +DynamicLoader::ReadUnsignedIntWithSizeInBytes(addr_t addr, int size_in_bytes) +{ + Error error; + + uint64_t value = m_process->ReadUnsignedIntegerFromMemory(addr, size_in_bytes, 0, error); + if (error.Fail()) + return -1; + else + return (int64_t)value; +} + +addr_t +DynamicLoader::ReadPointer(addr_t addr) +{ + Error error; + addr_t value = m_process->ReadPointerFromMemory(addr, error); + if (error.Fail()) + return LLDB_INVALID_ADDRESS; + else + return value; +} diff --git a/source/Core/IOHandler.cpp b/source/Core/IOHandler.cpp new file mode 100644 index 0000000..bdec19c --- /dev/null +++ b/source/Core/IOHandler.cpp @@ -0,0 +1,5294 @@ +//===-- IOHandler.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "lldb/lldb-python.h" + +#include <string> + +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/IOHandler.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/State.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/ValueObjectRegister.h" +#include "lldb/Host/Editline.h" +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/ThreadPlan.h" + +#ifndef LLDB_DISABLE_CURSES +#include <ncurses.h> +#include <panel.h> +#endif + +using namespace lldb; +using namespace lldb_private; + +IOHandler::IOHandler (Debugger &debugger) : + IOHandler (debugger, + StreamFileSP(), // Adopt STDIN from top input reader + StreamFileSP(), // Adopt STDOUT from top input reader + StreamFileSP(), // Adopt STDERR from top input reader + 0) // Flags +{ +} + + +IOHandler::IOHandler (Debugger &debugger, + const lldb::StreamFileSP &input_sp, + const lldb::StreamFileSP &output_sp, + const lldb::StreamFileSP &error_sp, + uint32_t flags) : + m_debugger (debugger), + m_input_sp (input_sp), + m_output_sp (output_sp), + m_error_sp (error_sp), + m_flags (flags), + m_user_data (NULL), + m_done (false), + m_active (false) +{ + // If any files are not specified, then adopt them from the top input reader. + if (!m_input_sp || !m_output_sp || !m_error_sp) + debugger.AdoptTopIOHandlerFilesIfInvalid (m_input_sp, + m_output_sp, + m_error_sp); +} + +IOHandler::~IOHandler() +{ +} + + +int +IOHandler::GetInputFD() +{ + if (m_input_sp) + return m_input_sp->GetFile().GetDescriptor(); + return -1; +} + +int +IOHandler::GetOutputFD() +{ + if (m_output_sp) + return m_output_sp->GetFile().GetDescriptor(); + return -1; +} + +int +IOHandler::GetErrorFD() +{ + if (m_error_sp) + return m_error_sp->GetFile().GetDescriptor(); + return -1; +} + +FILE * +IOHandler::GetInputFILE() +{ + if (m_input_sp) + return m_input_sp->GetFile().GetStream(); + return NULL; +} + +FILE * +IOHandler::GetOutputFILE() +{ + if (m_output_sp) + return m_output_sp->GetFile().GetStream(); + return NULL; +} + +FILE * +IOHandler::GetErrorFILE() +{ + if (m_error_sp) + return m_error_sp->GetFile().GetStream(); + return NULL; +} + +StreamFileSP & +IOHandler::GetInputStreamFile() +{ + return m_input_sp; +} + +StreamFileSP & +IOHandler::GetOutputStreamFile() +{ + return m_output_sp; +} + + +StreamFileSP & +IOHandler::GetErrorStreamFile() +{ + return m_error_sp; +} + +bool +IOHandler::GetIsInteractive () +{ + return GetInputStreamFile()->GetFile().GetIsInteractive (); +} + +bool +IOHandler::GetIsRealTerminal () +{ + return GetInputStreamFile()->GetFile().GetIsRealTerminal(); +} + +IOHandlerConfirm::IOHandlerConfirm (Debugger &debugger, + const char *prompt, + bool default_response) : + IOHandlerEditline(debugger, + NULL, // NULL editline_name means no history loaded/saved + NULL, + false, // Multi-line + *this), + m_default_response (default_response), + m_user_response (default_response) +{ + StreamString prompt_stream; + prompt_stream.PutCString(prompt); + if (m_default_response) + prompt_stream.Printf(": [Y/n] "); + else + prompt_stream.Printf(": [y/N] "); + + SetPrompt (prompt_stream.GetString().c_str()); + +} + + +IOHandlerConfirm::~IOHandlerConfirm () +{ +} + +int +IOHandlerConfirm::IOHandlerComplete (IOHandler &io_handler, + const char *current_line, + const char *cursor, + const char *last_char, + int skip_first_n_matches, + int max_matches, + StringList &matches) +{ + if (current_line == cursor) + { + if (m_default_response) + { + matches.AppendString("y"); + } + else + { + matches.AppendString("n"); + } + } + return matches.GetSize(); +} + +void +IOHandlerConfirm::IOHandlerInputComplete (IOHandler &io_handler, std::string &line) +{ + if (line.empty()) + { + // User just hit enter, set the response to the default + m_user_response = m_default_response; + io_handler.SetIsDone(true); + return; + } + + if (line.size() == 1) + { + switch (line[0]) + { + case 'y': + case 'Y': + m_user_response = true; + io_handler.SetIsDone(true); + return; + case 'n': + case 'N': + m_user_response = false; + io_handler.SetIsDone(true); + return; + default: + break; + } + } + + if (line == "yes" || line == "YES" || line == "Yes") + { + m_user_response = true; + io_handler.SetIsDone(true); + } + else if (line == "no" || line == "NO" || line == "No") + { + m_user_response = false; + io_handler.SetIsDone(true); + } +} + +int +IOHandlerDelegate::IOHandlerComplete (IOHandler &io_handler, + const char *current_line, + const char *cursor, + const char *last_char, + int skip_first_n_matches, + int max_matches, + StringList &matches) +{ + switch (m_completion) + { + case Completion::None: + break; + + case Completion::LLDBCommand: + return io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion (current_line, + cursor, + last_char, + skip_first_n_matches, + max_matches, + matches); + + case Completion::Expression: + { + bool word_complete = false; + const char *word_start = cursor; + if (cursor > current_line) + --word_start; + while (word_start > current_line && !isspace(*word_start)) + --word_start; + CommandCompletions::InvokeCommonCompletionCallbacks (io_handler.GetDebugger().GetCommandInterpreter(), + CommandCompletions::eVariablePathCompletion, + word_start, + skip_first_n_matches, + max_matches, + NULL, + word_complete, + matches); + + size_t num_matches = matches.GetSize(); + if (num_matches > 0) + { + std::string common_prefix; + matches.LongestCommonPrefix (common_prefix); + const size_t partial_name_len = strlen(word_start); + + // If we matched a unique single command, add a space... + // Only do this if the completer told us this was a complete word, however... + if (num_matches == 1 && word_complete) + { + common_prefix.push_back(' '); + } + common_prefix.erase (0, partial_name_len); + matches.InsertStringAtIndex(0, std::move(common_prefix)); + } + return num_matches; + } + break; + } + + + return 0; +} + + +IOHandlerEditline::IOHandlerEditline (Debugger &debugger, + const char *editline_name, // Used for saving history files + const char *prompt, + bool multi_line, + IOHandlerDelegate &delegate) : + IOHandlerEditline(debugger, + StreamFileSP(), // Inherit input from top input reader + StreamFileSP(), // Inherit output from top input reader + StreamFileSP(), // Inherit error from top input reader + 0, // Flags + editline_name, // Used for saving history files + prompt, + multi_line, + delegate) +{ +} + +IOHandlerEditline::IOHandlerEditline (Debugger &debugger, + const lldb::StreamFileSP &input_sp, + const lldb::StreamFileSP &output_sp, + const lldb::StreamFileSP &error_sp, + uint32_t flags, + const char *editline_name, // Used for saving history files + const char *prompt, + bool multi_line, + IOHandlerDelegate &delegate) : + IOHandler (debugger, input_sp, output_sp, error_sp, flags), + m_editline_ap (), + m_delegate (delegate), + m_prompt (), + m_multi_line (multi_line) +{ + SetPrompt(prompt); + + bool use_editline = false; + +#ifndef _MSC_VER + use_editline = m_input_sp->GetFile().GetIsRealTerminal(); +#else + use_editline = true; +#endif + + if (use_editline) + { + m_editline_ap.reset(new Editline (editline_name, + prompt ? prompt : "", + GetInputFILE (), + GetOutputFILE (), + GetErrorFILE ())); + m_editline_ap->SetLineCompleteCallback (LineCompletedCallback, this); + m_editline_ap->SetAutoCompleteCallback (AutoCompleteCallback, this); + } + +} + +IOHandlerEditline::~IOHandlerEditline () +{ + m_editline_ap.reset(); +} + + +bool +IOHandlerEditline::GetLine (std::string &line) +{ + if (m_editline_ap) + { + return m_editline_ap->GetLine(line).Success(); + } + else + { + line.clear(); + + FILE *in = GetInputFILE(); + if (in) + { + if (GetIsInteractive()) + { + const char *prompt = GetPrompt(); + if (prompt && prompt[0]) + { + FILE *out = GetOutputFILE(); + if (out) + { + ::fprintf(out, "%s", prompt); + ::fflush(out); + } + } + } + char buffer[256]; + bool done = false; + bool got_line = false; + while (!done) + { + if (fgets(buffer, sizeof(buffer), in) == NULL) + done = true; + else + { + got_line = true; + size_t buffer_len = strlen(buffer); + assert (buffer[buffer_len] == '\0'); + char last_char = buffer[buffer_len-1]; + if (last_char == '\r' || last_char == '\n') + { + done = true; + // Strip trailing newlines + while (last_char == '\r' || last_char == '\n') + { + --buffer_len; + if (buffer_len == 0) + break; + last_char = buffer[buffer_len-1]; + } + } + line.append(buffer, buffer_len); + } + } + // We might have gotten a newline on a line by itself + // make sure to return true in this case. + return got_line; + } + else + { + // No more input file, we are done... + SetIsDone(true); + } + return false; + } +} + + +LineStatus +IOHandlerEditline::LineCompletedCallback (Editline *editline, + StringList &lines, + uint32_t line_idx, + Error &error, + void *baton) +{ + IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton; + return editline_reader->m_delegate.IOHandlerLinesUpdated(*editline_reader, lines, line_idx, error); +} + +int +IOHandlerEditline::AutoCompleteCallback (const char *current_line, + const char *cursor, + const char *last_char, + int skip_first_n_matches, + int max_matches, + StringList &matches, + void *baton) +{ + IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton; + if (editline_reader) + return editline_reader->m_delegate.IOHandlerComplete (*editline_reader, + current_line, + cursor, + last_char, + skip_first_n_matches, + max_matches, + matches); + return 0; +} + +const char * +IOHandlerEditline::GetPrompt () +{ + if (m_editline_ap) + return m_editline_ap->GetPrompt (); + else if (m_prompt.empty()) + return NULL; + return m_prompt.c_str(); +} + +bool +IOHandlerEditline::SetPrompt (const char *p) +{ + if (p && p[0]) + m_prompt = p; + else + m_prompt.clear(); + if (m_editline_ap) + m_editline_ap->SetPrompt (m_prompt.empty() ? NULL : m_prompt.c_str()); + return true; +} + +bool +IOHandlerEditline::GetLines (StringList &lines) +{ + bool success = false; + if (m_editline_ap) + { + std::string end_token; + success = m_editline_ap->GetLines(end_token, lines).Success(); + } + else + { + LineStatus lines_status = LineStatus::Success; + + while (lines_status == LineStatus::Success) + { + std::string line; + if (GetLine(line)) + { + lines.AppendString(line); + Error error; + lines_status = m_delegate.IOHandlerLinesUpdated(*this, lines, lines.GetSize() - 1, error); + } + else + { + lines_status = LineStatus::Done; + } + } + success = lines.GetSize() > 0; + } + return success; +} + +// Each IOHandler gets to run until it is done. It should read data +// from the "in" and place output into "out" and "err and return +// when done. +void +IOHandlerEditline::Run () +{ + std::string line; + while (IsActive()) + { + if (m_multi_line) + { + StringList lines; + if (GetLines (lines)) + { + line = lines.CopyList(); + m_delegate.IOHandlerInputComplete(*this, line); + } + else + { + m_done = true; + } + } + else + { + if (GetLine(line)) + { + m_delegate.IOHandlerInputComplete(*this, line); + } + else + { + m_done = true; + } + } + } +} + +void +IOHandlerEditline::Hide () +{ + if (m_editline_ap && m_editline_ap->GettingLine()) + m_editline_ap->Hide(); +} + + +void +IOHandlerEditline::Refresh () +{ + if (m_editline_ap && m_editline_ap->GettingLine()) + m_editline_ap->Refresh(); + else + { + const char *prompt = GetPrompt(); + if (prompt && prompt[0]) + { + FILE *out = GetOutputFILE(); + if (out) + { + ::fprintf(out, "%s", prompt); + ::fflush(out); + } + } + } +} + +void +IOHandlerEditline::Interrupt () +{ + if (m_editline_ap) + m_editline_ap->Interrupt(); +} + +void +IOHandlerEditline::GotEOF() +{ + if (m_editline_ap) + m_editline_ap->Interrupt(); +} + +// we may want curses to be disabled for some builds +// for instance, windows +#ifndef LLDB_DISABLE_CURSES + +#include "lldb/Core/ValueObject.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/StackFrame.h" + +#define KEY_RETURN 10 +#define KEY_ESCAPE 27 + +namespace curses +{ + class Menu; + class MenuDelegate; + class Window; + class WindowDelegate; + typedef std::shared_ptr<Menu> MenuSP; + typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; + typedef std::shared_ptr<Window> WindowSP; + typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; + typedef std::vector<MenuSP> Menus; + typedef std::vector<WindowSP> Windows; + typedef std::vector<WindowDelegateSP> WindowDelegates; + +#if 0 +type summary add -s "x=${var.x}, y=${var.y}" curses::Point +type summary add -s "w=${var.width}, h=${var.height}" curses::Size +type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect +#endif + struct Point + { + int x; + int y; + + Point (int _x = 0, int _y = 0) : + x(_x), + y(_y) + { + } + + void + Clear () + { + x = 0; + y = 0; + } + + Point & + operator += (const Point &rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + + void + Dump () + { + printf ("(x=%i, y=%i)\n", x, y); + } + + }; + + bool operator == (const Point &lhs, const Point &rhs) + { + return lhs.x == rhs.x && lhs.y == rhs.y; + } + bool operator != (const Point &lhs, const Point &rhs) + { + return lhs.x != rhs.x || lhs.y != rhs.y; + } + + struct Size + { + int width; + int height; + Size (int w = 0, int h = 0) : + width (w), + height (h) + { + } + + void + Clear () + { + width = 0; + height = 0; + } + + void + Dump () + { + printf ("(w=%i, h=%i)\n", width, height); + } + + }; + + bool operator == (const Size &lhs, const Size &rhs) + { + return lhs.width == rhs.width && lhs.height == rhs.height; + } + bool operator != (const Size &lhs, const Size &rhs) + { + return lhs.width != rhs.width || lhs.height != rhs.height; + } + + struct Rect + { + Point origin; + Size size; + + Rect () : + origin(), + size() + { + } + + Rect (const Point &p, const Size &s) : + origin (p), + size (s) + { + } + + void + Clear () + { + origin.Clear(); + size.Clear(); + } + + void + Dump () + { + printf ("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, size.height); + } + + void + Inset (int w, int h) + { + if (size.width > w*2) + size.width -= w*2; + origin.x += w; + + if (size.height > h*2) + size.height -= h*2; + origin.y += h; + } + // Return a status bar rectangle which is the last line of + // this rectangle. This rectangle will be modified to not + // include the status bar area. + Rect + MakeStatusBar () + { + Rect status_bar; + if (size.height > 1) + { + status_bar.origin.x = origin.x; + status_bar.origin.y = size.height; + status_bar.size.width = size.width; + status_bar.size.height = 1; + --size.height; + } + return status_bar; + } + + // Return a menubar rectangle which is the first line of + // this rectangle. This rectangle will be modified to not + // include the menubar area. + Rect + MakeMenuBar () + { + Rect menubar; + if (size.height > 1) + { + menubar.origin.x = origin.x; + menubar.origin.y = origin.y; + menubar.size.width = size.width; + menubar.size.height = 1; + ++origin.y; + --size.height; + } + return menubar; + } + + void + HorizontalSplitPercentage (float top_percentage, Rect &top, Rect &bottom) const + { + float top_height = top_percentage * size.height; + HorizontalSplit (top_height, top, bottom); + } + + void + HorizontalSplit (int top_height, Rect &top, Rect &bottom) const + { + top = *this; + if (top_height < size.height) + { + top.size.height = top_height; + bottom.origin.x = origin.x; + bottom.origin.y = origin.y + top.size.height; + bottom.size.width = size.width; + bottom.size.height = size.height - top.size.height; + } + else + { + bottom.Clear(); + } + } + + void + VerticalSplitPercentage (float left_percentage, Rect &left, Rect &right) const + { + float left_width = left_percentage * size.width; + VerticalSplit (left_width, left, right); + } + + + void + VerticalSplit (int left_width, Rect &left, Rect &right) const + { + left = *this; + if (left_width < size.width) + { + left.size.width = left_width; + right.origin.x = origin.x + left.size.width; + right.origin.y = origin.y; + right.size.width = size.width - left.size.width; + right.size.height = size.height; + } + else + { + right.Clear(); + } + } + }; + + bool operator == (const Rect &lhs, const Rect &rhs) + { + return lhs.origin == rhs.origin && lhs.size == rhs.size; + } + bool operator != (const Rect &lhs, const Rect &rhs) + { + return lhs.origin != rhs.origin || lhs.size != rhs.size; + } + + enum HandleCharResult + { + eKeyNotHandled = 0, + eKeyHandled = 1, + eQuitApplication = 2 + }; + + enum class MenuActionResult + { + Handled, + NotHandled, + Quit // Exit all menus and quit + }; + + struct KeyHelp + { + int ch; + const char *description; + }; + + class WindowDelegate + { + public: + virtual + ~WindowDelegate() + { + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + return false; // Drawing not handled + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key) + { + return eKeyNotHandled; + } + + virtual const char * + WindowDelegateGetHelpText () + { + return NULL; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + return NULL; + } + }; + + class HelpDialogDelegate : + public WindowDelegate + { + public: + HelpDialogDelegate (const char *text, KeyHelp *key_help_array); + + virtual + ~HelpDialogDelegate(); + + virtual bool + WindowDelegateDraw (Window &window, bool force); + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key); + + size_t + GetNumLines() const + { + return m_text.GetSize(); + } + + size_t + GetMaxLineLength () const + { + return m_text.GetMaxStringLength(); + } + + protected: + StringList m_text; + int m_first_visible_line; + }; + + + class Window + { + public: + + Window (const char *name) : + m_name (name), + m_window (NULL), + m_panel (NULL), + m_parent (NULL), + m_subwindows (), + m_delegate_sp (), + m_curr_active_window_idx (UINT32_MAX), + m_prev_active_window_idx (UINT32_MAX), + m_delete (false), + m_needs_update (true), + m_can_activate (true), + m_is_subwin (false) + { + } + + Window (const char *name, WINDOW *w, bool del = true) : + m_name (name), + m_window (NULL), + m_panel (NULL), + m_parent (NULL), + m_subwindows (), + m_delegate_sp (), + m_curr_active_window_idx (UINT32_MAX), + m_prev_active_window_idx (UINT32_MAX), + m_delete (del), + m_needs_update (true), + m_can_activate (true), + m_is_subwin (false) + { + if (w) + Reset(w); + } + + Window (const char *name, const Rect &bounds) : + m_name (name), + m_window (NULL), + m_parent (NULL), + m_subwindows (), + m_delegate_sp (), + m_curr_active_window_idx (UINT32_MAX), + m_prev_active_window_idx (UINT32_MAX), + m_delete (true), + m_needs_update (true), + m_can_activate (true), + m_is_subwin (false) + { + Reset (::newwin (bounds.size.height, bounds.size.width, bounds.origin.y, bounds.origin.y)); + } + + virtual + ~Window () + { + RemoveSubWindows (); + Reset (); + } + + void + Reset (WINDOW *w = NULL, bool del = true) + { + if (m_window == w) + return; + + if (m_panel) + { + ::del_panel (m_panel); + m_panel = NULL; + } + if (m_window && m_delete) + { + ::delwin (m_window); + m_window = NULL; + m_delete = false; + } + if (w) + { + m_window = w; + m_panel = ::new_panel (m_window); + m_delete = del; + } + } + + void AttributeOn (attr_t attr) { ::wattron (m_window, attr); } + void AttributeOff (attr_t attr) { ::wattroff (m_window, attr); } + void Box (chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { ::box(m_window, v_char, h_char); } + void Clear () { ::wclear (m_window); } + void Erase () { ::werase (m_window); } + Rect GetBounds () { return Rect (GetParentOrigin(), GetSize()); } // Get the rectangle in our parent window + int GetChar () { return ::wgetch (m_window); } + int GetCursorX () { return getcurx (m_window); } + int GetCursorY () { return getcury (m_window); } + Rect GetFrame () { return Rect (Point(), GetSize()); } // Get our rectangle in our own coordinate system + Point GetParentOrigin() { return Point (GetParentX(), GetParentY()); } + Size GetSize() { return Size (GetWidth(), GetHeight()); } + int GetParentX () { return getparx (m_window); } + int GetParentY () { return getpary (m_window); } + int GetMaxX() { return getmaxx (m_window); } + int GetMaxY() { return getmaxy (m_window); } + int GetWidth() { return GetMaxX(); } + int GetHeight() { return GetMaxY(); } + void MoveCursor (int x, int y) { ::wmove (m_window, y, x); } + void MoveWindow (int x, int y) { MoveWindow(Point(x,y)); } + void Resize (int w, int h) { ::wresize(m_window, h, w); } + void Resize (const Size &size) { ::wresize(m_window, size.height, size.width); } + void PutChar (int ch) { ::waddch (m_window, ch); } + void PutCString (const char *s, int len = -1) { ::waddnstr (m_window, s, len); } + void Refresh () { ::wrefresh (m_window); } + void DeferredRefresh () + { + // We are using panels, so we don't need to call this... + //::wnoutrefresh(m_window); + } + void SetBackground (int color_pair_idx) { ::wbkgd (m_window,COLOR_PAIR(color_pair_idx)); } + void UnderlineOn () { AttributeOn(A_UNDERLINE); } + void UnderlineOff () { AttributeOff(A_UNDERLINE); } + + void PutCStringTruncated (const char *s, int right_pad) + { + int bytes_left = GetWidth() - GetCursorX(); + if (bytes_left > right_pad) + { + bytes_left -= right_pad; + ::waddnstr (m_window, s, bytes_left); + } + } + + void + MoveWindow (const Point &origin) + { + const bool moving_window = origin != GetParentOrigin(); + if (m_is_subwin && moving_window) + { + // Can't move subwindows, must delete and re-create + Size size = GetSize(); + Reset (::subwin (m_parent->m_window, + size.height, + size.width, + origin.y, + origin.x), true); + } + else + { + ::mvwin (m_window, origin.y, origin.x); + } + } + + void + SetBounds (const Rect &bounds) + { + const bool moving_window = bounds.origin != GetParentOrigin(); + if (m_is_subwin && moving_window) + { + // Can't move subwindows, must delete and re-create + Reset (::subwin (m_parent->m_window, + bounds.size.height, + bounds.size.width, + bounds.origin.y, + bounds.origin.x), true); + } + else + { + if (moving_window) + MoveWindow(bounds.origin); + Resize (bounds.size); + } + } + + void + Printf (const char *format, ...) __attribute__ ((format (printf, 2, 3))) + { + va_list args; + va_start (args, format); + vwprintw(m_window, format, args); + va_end (args); + } + + void + Touch () + { + ::touchwin (m_window); + if (m_parent) + m_parent->Touch(); + } + + WindowSP + CreateSubWindow (const char *name, const Rect &bounds, bool make_active) + { + WindowSP subwindow_sp; + if (m_window) + { + subwindow_sp.reset(new Window(name, ::subwin (m_window, + bounds.size.height, + bounds.size.width, + bounds.origin.y, + bounds.origin.x), true)); + subwindow_sp->m_is_subwin = true; + } + else + { + subwindow_sp.reset(new Window(name, ::newwin (bounds.size.height, + bounds.size.width, + bounds.origin.y, + bounds.origin.x), true)); + subwindow_sp->m_is_subwin = false; + } + subwindow_sp->m_parent = this; + if (make_active) + { + m_prev_active_window_idx = m_curr_active_window_idx; + m_curr_active_window_idx = m_subwindows.size(); + } + m_subwindows.push_back(subwindow_sp); + ::top_panel (subwindow_sp->m_panel); + m_needs_update = true; + return subwindow_sp; + } + + bool + RemoveSubWindow (Window *window) + { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) + { + if ((*pos).get() == window) + { + if (m_prev_active_window_idx == i) + m_prev_active_window_idx = UINT32_MAX; + else if (m_prev_active_window_idx != UINT32_MAX && m_prev_active_window_idx > i) + --m_prev_active_window_idx; + + if (m_curr_active_window_idx == i) + m_curr_active_window_idx = UINT32_MAX; + else if (m_curr_active_window_idx != UINT32_MAX && m_curr_active_window_idx > i) + --m_curr_active_window_idx; + window->Erase(); + m_subwindows.erase(pos); + m_needs_update = true; + if (m_parent) + m_parent->Touch(); + else + ::touchwin (stdscr); + return true; + } + } + return false; + } + + WindowSP + FindSubWindow (const char *name) + { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) + { + if ((*pos)->m_name.compare(name) == 0) + return *pos; + } + return WindowSP(); + } + + void + RemoveSubWindows () + { + m_curr_active_window_idx = UINT32_MAX; + m_prev_active_window_idx = UINT32_MAX; + for (Windows::iterator pos = m_subwindows.begin(); + pos != m_subwindows.end(); + pos = m_subwindows.erase(pos)) + { + (*pos)->Erase(); + } + if (m_parent) + m_parent->Touch(); + else + ::touchwin (stdscr); + } + + WINDOW * + get() + { + return m_window; + } + + operator WINDOW *() + { + return m_window; + } + + //---------------------------------------------------------------------- + // Window drawing utilities + //---------------------------------------------------------------------- + void + DrawTitleBox (const char *title, const char *bottom_message = NULL) + { + attr_t attr = 0; + if (IsActive()) + attr = A_BOLD | COLOR_PAIR(2); + else + attr = 0; + if (attr) + AttributeOn(attr); + + Box(); + MoveCursor(3, 0); + + if (title && title[0]) + { + PutChar ('<'); + PutCString (title); + PutChar ('>'); + } + + if (bottom_message && bottom_message[0]) + { + int bottom_message_length = strlen(bottom_message); + int x = GetWidth() - 3 - (bottom_message_length + 2); + + if (x > 0) + { + MoveCursor (x, GetHeight() - 1); + PutChar ('['); + PutCString(bottom_message); + PutChar (']'); + } + else + { + MoveCursor (1, GetHeight() - 1); + PutChar ('['); + PutCStringTruncated (bottom_message, 1); + } + } + if (attr) + AttributeOff(attr); + + } + + virtual void + Draw (bool force) + { + if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw (*this, force)) + return; + + for (auto &subwindow_sp : m_subwindows) + subwindow_sp->Draw(force); + } + + bool + CreateHelpSubwindow () + { + if (m_delegate_sp) + { + const char *text = m_delegate_sp->WindowDelegateGetHelpText (); + KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp (); + if ((text && text[0]) || key_help) + { + std::auto_ptr<HelpDialogDelegate> help_delegate_ap(new HelpDialogDelegate(text, key_help)); + const size_t num_lines = help_delegate_ap->GetNumLines(); + const size_t max_length = help_delegate_ap->GetMaxLineLength(); + Rect bounds = GetBounds(); + bounds.Inset(1, 1); + if (max_length + 4 < bounds.size.width) + { + bounds.origin.x += (bounds.size.width - max_length + 4)/2; + bounds.size.width = max_length + 4; + } + else + { + if (bounds.size.width > 100) + { + const int inset_w = bounds.size.width / 4; + bounds.origin.x += inset_w; + bounds.size.width -= 2*inset_w; + } + } + + if (num_lines + 2 < bounds.size.height) + { + bounds.origin.y += (bounds.size.height - num_lines + 2)/2; + bounds.size.height = num_lines + 2; + } + else + { + if (bounds.size.height > 100) + { + const int inset_h = bounds.size.height / 4; + bounds.origin.y += inset_h; + bounds.size.height -= 2*inset_h; + } + } + WindowSP help_window_sp; + Window *parent_window = GetParent(); + if (parent_window) + help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); + else + help_window_sp = CreateSubWindow("Help", bounds, true); + help_window_sp->SetDelegate(WindowDelegateSP(help_delegate_ap.release())); + return true; + } + } + return false; + } + + virtual HandleCharResult + HandleChar (int key) + { + // Always check the active window first + HandleCharResult result = eKeyNotHandled; + WindowSP active_window_sp = GetActiveWindow (); + if (active_window_sp) + { + result = active_window_sp->HandleChar (key); + if (result != eKeyNotHandled) + return result; + } + + if (m_delegate_sp) + { + result = m_delegate_sp->WindowDelegateHandleChar (*this, key); + if (result != eKeyNotHandled) + return result; + } + + // Then check for any windows that want any keys + // that weren't handled. This is typically only + // for a menubar. + // Make a copy of the subwindows in case any HandleChar() + // functions muck with the subwindows. If we don't do this, + // we can crash when iterating over the subwindows. + Windows subwindows (m_subwindows); + for (auto subwindow_sp : subwindows) + { + if (subwindow_sp->m_can_activate == false) + { + HandleCharResult result = subwindow_sp->HandleChar(key); + if (result != eKeyNotHandled) + return result; + } + } + + return eKeyNotHandled; + } + + bool + SetActiveWindow (Window *window) + { + const size_t num_subwindows = m_subwindows.size(); + for (size_t i=0; i<num_subwindows; ++i) + { + if (m_subwindows[i].get() == window) + { + m_prev_active_window_idx = m_curr_active_window_idx; + ::top_panel (window->m_panel); + m_curr_active_window_idx = i; + return true; + } + } + return false; + } + + WindowSP + GetActiveWindow () + { + if (!m_subwindows.empty()) + { + if (m_curr_active_window_idx >= m_subwindows.size()) + { + if (m_prev_active_window_idx < m_subwindows.size()) + { + m_curr_active_window_idx = m_prev_active_window_idx; + m_prev_active_window_idx = UINT32_MAX; + } + else if (IsActive()) + { + m_prev_active_window_idx = UINT32_MAX; + m_curr_active_window_idx = UINT32_MAX; + + // Find first window that wants to be active if this window is active + const size_t num_subwindows = m_subwindows.size(); + for (size_t i=0; i<num_subwindows; ++i) + { + if (m_subwindows[i]->GetCanBeActive()) + { + m_curr_active_window_idx = i; + break; + } + } + } + } + + if (m_curr_active_window_idx < m_subwindows.size()) + return m_subwindows[m_curr_active_window_idx]; + } + return WindowSP(); + } + + bool + GetCanBeActive () const + { + return m_can_activate; + } + + void + SetCanBeActive (bool b) + { + m_can_activate = b; + } + + const WindowDelegateSP & + GetDelegate () const + { + return m_delegate_sp; + } + + void + SetDelegate (const WindowDelegateSP &delegate_sp) + { + m_delegate_sp = delegate_sp; + } + + Window * + GetParent () const + { + return m_parent; + } + + bool + IsActive () const + { + if (m_parent) + return m_parent->GetActiveWindow().get() == this; + else + return true; // Top level window is always active + } + + void + SelectNextWindowAsActive () + { + // Move active focus to next window + const size_t num_subwindows = m_subwindows.size(); + if (m_curr_active_window_idx == UINT32_MAX) + { + uint32_t idx = 0; + for (auto subwindow_sp : m_subwindows) + { + if (subwindow_sp->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + break; + } + ++idx; + } + } + else if (m_curr_active_window_idx + 1 < num_subwindows) + { + bool handled = false; + m_prev_active_window_idx = m_curr_active_window_idx; + for (size_t idx=m_curr_active_window_idx + 1; idx<num_subwindows; ++idx) + { + if (m_subwindows[idx]->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + handled = true; + break; + } + } + if (!handled) + { + for (size_t idx=0; idx<=m_prev_active_window_idx; ++idx) + { + if (m_subwindows[idx]->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + break; + } + } + } + } + else + { + m_prev_active_window_idx = m_curr_active_window_idx; + for (size_t idx=0; idx<num_subwindows; ++idx) + { + if (m_subwindows[idx]->GetCanBeActive()) + { + m_curr_active_window_idx = idx; + break; + } + } + } + } + + const char * + GetName () const + { + return m_name.c_str(); + } + protected: + std::string m_name; + WINDOW *m_window; + PANEL *m_panel; + Window *m_parent; + Windows m_subwindows; + WindowDelegateSP m_delegate_sp; + uint32_t m_curr_active_window_idx; + uint32_t m_prev_active_window_idx; + bool m_delete; + bool m_needs_update; + bool m_can_activate; + bool m_is_subwin; + + private: + DISALLOW_COPY_AND_ASSIGN(Window); + }; + + class MenuDelegate + { + public: + virtual ~MenuDelegate() {} + + virtual MenuActionResult + MenuDelegateAction (Menu &menu) = 0; + }; + + class Menu : public WindowDelegate + { + public: + enum class Type + { + Invalid, + Bar, + Item, + Separator + }; + + // Menubar or separator constructor + Menu (Type type); + + // Menuitem constructor + Menu (const char *name, + const char *key_name, + int key_value, + uint64_t identifier); + + virtual ~ + Menu () + { + } + + const MenuDelegateSP & + GetDelegate () const + { + return m_delegate_sp; + } + + void + SetDelegate (const MenuDelegateSP &delegate_sp) + { + m_delegate_sp = delegate_sp; + } + + void + RecalculateNameLengths(); + + void + AddSubmenu (const MenuSP &menu_sp); + + int + DrawAndRunMenu (Window &window); + + void + DrawMenuTitle (Window &window, bool highlight); + + virtual bool + WindowDelegateDraw (Window &window, bool force); + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key); + + MenuActionResult + ActionPrivate (Menu &menu) + { + MenuActionResult result = MenuActionResult::NotHandled; + if (m_delegate_sp) + { + result = m_delegate_sp->MenuDelegateAction (menu); + if (result != MenuActionResult::NotHandled) + return result; + } + else if (m_parent) + { + result = m_parent->ActionPrivate(menu); + if (result != MenuActionResult::NotHandled) + return result; + } + return m_canned_result; + } + + MenuActionResult + Action () + { + // Call the recursive action so it can try to handle it + // with the menu delegate, and if not, try our parent menu + return ActionPrivate (*this); + } + + void + SetCannedResult (MenuActionResult result) + { + m_canned_result = result; + } + + Menus & + GetSubmenus() + { + return m_submenus; + } + + const Menus & + GetSubmenus() const + { + return m_submenus; + } + + int + GetSelectedSubmenuIndex () const + { + return m_selected; + } + + void + SetSelectedSubmenuIndex (int idx) + { + m_selected = idx; + } + + Type + GetType () const + { + return m_type; + } + + int + GetStartingColumn() const + { + return m_start_col; + } + + void + SetStartingColumn(int col) + { + m_start_col = col; + } + + int + GetKeyValue() const + { + return m_key_value; + } + + void + SetKeyValue(int key_value) + { + m_key_value = key_value; + } + + std::string & + GetName() + { + return m_name; + } + + std::string & + GetKeyName() + { + return m_key_name; + } + + int + GetDrawWidth () const + { + return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; + } + + + uint64_t + GetIdentifier() const + { + return m_identifier; + } + + void + SetIdentifier (uint64_t identifier) + { + m_identifier = identifier; + } + + protected: + std::string m_name; + std::string m_key_name; + uint64_t m_identifier; + Type m_type; + int m_key_value; + int m_start_col; + int m_max_submenu_name_length; + int m_max_submenu_key_name_length; + int m_selected; + Menu *m_parent; + Menus m_submenus; + WindowSP m_menu_window_sp; + MenuActionResult m_canned_result; + MenuDelegateSP m_delegate_sp; + }; + + // Menubar or separator constructor + Menu::Menu (Type type) : + m_name (), + m_key_name (), + m_identifier (0), + m_type (type), + m_key_value (0), + m_start_col (0), + m_max_submenu_name_length (0), + m_max_submenu_key_name_length (0), + m_selected (0), + m_parent (NULL), + m_submenus (), + m_canned_result (MenuActionResult::NotHandled), + m_delegate_sp() + { + } + + // Menuitem constructor + Menu::Menu (const char *name, + const char *key_name, + int key_value, + uint64_t identifier) : + m_name (), + m_key_name (), + m_identifier (identifier), + m_type (Type::Invalid), + m_key_value (key_value), + m_start_col (0), + m_max_submenu_name_length (0), + m_max_submenu_key_name_length (0), + m_selected (0), + m_parent (NULL), + m_submenus (), + m_canned_result (MenuActionResult::NotHandled), + m_delegate_sp() + { + if (name && name[0]) + { + m_name = name; + m_type = Type::Item; + if (key_name && key_name[0]) + m_key_name = key_name; + } + else + { + m_type = Type::Separator; + } + } + + void + Menu::RecalculateNameLengths() + { + m_max_submenu_name_length = 0; + m_max_submenu_key_name_length = 0; + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + for (size_t i=0; i<num_submenus; ++i) + { + Menu *submenu = submenus[i].get(); + if (m_max_submenu_name_length < submenu->m_name.size()) + m_max_submenu_name_length = submenu->m_name.size(); + if (m_max_submenu_key_name_length < submenu->m_key_name.size()) + m_max_submenu_key_name_length = submenu->m_key_name.size(); + } + } + + void + Menu::AddSubmenu (const MenuSP &menu_sp) + { + menu_sp->m_parent = this; + if (m_max_submenu_name_length < menu_sp->m_name.size()) + m_max_submenu_name_length = menu_sp->m_name.size(); + if (m_max_submenu_key_name_length < menu_sp->m_key_name.size()) + m_max_submenu_key_name_length = menu_sp->m_key_name.size(); + m_submenus.push_back(menu_sp); + } + + void + Menu::DrawMenuTitle (Window &window, bool highlight) + { + if (m_type == Type::Separator) + { + window.MoveCursor(0, window.GetCursorY()); + window.PutChar(ACS_LTEE); + int width = window.GetWidth(); + if (width > 2) + { + width -= 2; + for (size_t i=0; i< width; ++i) + window.PutChar(ACS_HLINE); + } + window.PutChar(ACS_RTEE); + } + else + { + const int shortcut_key = m_key_value; + bool underlined_shortcut = false; + const attr_t hilgight_attr = A_REVERSE; + if (highlight) + window.AttributeOn(hilgight_attr); + if (isprint(shortcut_key)) + { + size_t lower_pos = m_name.find(tolower(shortcut_key)); + size_t upper_pos = m_name.find(toupper(shortcut_key)); + const char *name = m_name.c_str(); + size_t pos = std::min<size_t>(lower_pos, upper_pos); + if (pos != std::string::npos) + { + underlined_shortcut = true; + if (pos > 0) + { + window.PutCString(name, pos); + name += pos; + } + const attr_t shortcut_attr = A_UNDERLINE|A_BOLD; + window.AttributeOn (shortcut_attr); + window.PutChar(name[0]); + window.AttributeOff(shortcut_attr); + name++; + if (name[0]) + window.PutCString(name); + } + } + + if (!underlined_shortcut) + { + window.PutCString(m_name.c_str()); + } + + if (highlight) + window.AttributeOff(hilgight_attr); + + if (m_key_name.empty()) + { + if (!underlined_shortcut && isprint(m_key_value)) + { + window.AttributeOn (COLOR_PAIR(3)); + window.Printf (" (%c)", m_key_value); + window.AttributeOff (COLOR_PAIR(3)); + } + } + else + { + window.AttributeOn (COLOR_PAIR(3)); + window.Printf (" (%s)", m_key_name.c_str()); + window.AttributeOff (COLOR_PAIR(3)); + } + } + } + + bool + Menu::WindowDelegateDraw (Window &window, bool force) + { + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType (); + switch (menu_type) + { + case Menu::Type::Bar: + { + window.SetBackground(2); + window.MoveCursor(0, 0); + for (size_t i=0; i<num_submenus; ++i) + { + Menu *menu = submenus[i].get(); + if (i > 0) + window.PutChar(' '); + menu->SetStartingColumn (window.GetCursorX()); + window.PutCString("| "); + menu->DrawMenuTitle (window, false); + } + window.PutCString(" |"); + window.DeferredRefresh(); + } + break; + + case Menu::Type::Item: + { + int y = 1; + int x = 3; + // Draw the menu + int cursor_x = 0; + int cursor_y = 0; + window.Erase(); + window.SetBackground(2); + window.Box(); + for (size_t i=0; i<num_submenus; ++i) + { + const bool is_selected = i == selected_idx; + window.MoveCursor(x, y + i); + if (is_selected) + { + // Remember where we want the cursor to be + cursor_x = x-1; + cursor_y = y+i; + } + submenus[i]->DrawMenuTitle (window, is_selected); + } + window.MoveCursor(cursor_x, cursor_y); + window.DeferredRefresh(); + } + break; + + default: + case Menu::Type::Separator: + break; + } + return true; // Drawing handled... + } + + HandleCharResult + Menu::WindowDelegateHandleChar (Window &window, int key) + { + HandleCharResult result = eKeyNotHandled; + + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType (); + if (menu_type == Menu::Type::Bar) + { + MenuSP run_menu_sp; + switch (key) + { + case KEY_DOWN: + case KEY_UP: + // Show last menu or first menu + if (selected_idx < num_submenus) + run_menu_sp = submenus[selected_idx]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + case KEY_RIGHT: + { + ++m_selected; + if (m_selected >= num_submenus) + m_selected = 0; + if (m_selected < num_submenus) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + } + break; + + case KEY_LEFT: + { + --m_selected; + if (m_selected < 0) + m_selected = num_submenus - 1; + if (m_selected < num_submenus) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + } + break; + + default: + for (size_t i=0; i<num_submenus; ++i) + { + if (submenus[i]->GetKeyValue() == key) + { + SetSelectedSubmenuIndex(i); + run_menu_sp = submenus[i]; + result = eKeyHandled; + break; + } + } + break; + } + + if (run_menu_sp) + { + // Run the action on this menu in case we need to populate the + // menu with dynamic content and also in case check marks, and + // any other menu decorations need to be caclulated + if (run_menu_sp->Action() == MenuActionResult::Quit) + return eQuitApplication; + + Rect menu_bounds; + menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); + menu_bounds.origin.y = 1; + menu_bounds.size.width = run_menu_sp->GetDrawWidth(); + menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; + if (m_menu_window_sp) + window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); + + m_menu_window_sp = window.GetParent()->CreateSubWindow (run_menu_sp->GetName().c_str(), + menu_bounds, + true); + m_menu_window_sp->SetDelegate (run_menu_sp); + } + } + else if (menu_type == Menu::Type::Item) + { + switch (key) + { + case KEY_DOWN: + if (m_submenus.size() > 1) + { + const int start_select = m_selected; + while (++m_selected != start_select) + { + if (m_selected >= num_submenus) + m_selected = 0; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_UP: + if (m_submenus.size() > 1) + { + const int start_select = m_selected; + while (--m_selected != start_select) + { + if (m_selected < 0) + m_selected = num_submenus - 1; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_RETURN: + if (selected_idx < num_submenus) + { + if (submenus[selected_idx]->Action() == MenuActionResult::Quit) + return eQuitApplication; + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + } + break; + + case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in case other chars are entered for escaped sequences + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + + default: + { + bool handled = false; + for (size_t i=0; i<num_submenus; ++i) + { + Menu *menu = submenus[i].get(); + if (menu->GetKeyValue() == key) + { + handled = true; + SetSelectedSubmenuIndex(i); + window.GetParent()->RemoveSubWindow(&window); + if (menu->Action() == MenuActionResult::Quit) + return eQuitApplication; + return eKeyHandled; + } + } + } + break; + + } + } + else if (menu_type == Menu::Type::Separator) + { + + } + return result; + } + + + class Application + { + public: + Application (FILE *in, FILE *out) : + m_window_sp(), + m_screen (NULL), + m_in (in), + m_out (out) + { + + } + + ~Application () + { + m_window_delegates.clear(); + m_window_sp.reset(); + if (m_screen) + { + ::delscreen(m_screen); + m_screen = NULL; + } + } + + void + Initialize () + { + ::setlocale(LC_ALL, ""); + ::setlocale(LC_CTYPE, ""); +#if 0 + ::initscr(); +#else + m_screen = ::newterm(NULL, m_out, m_in); +#endif + ::start_color(); + ::curs_set(0); + ::noecho(); + ::keypad(stdscr,TRUE); + } + + void + Terminate () + { + ::endwin(); + } + + void + Run (Debugger &debugger) + { + bool done = false; + int delay_in_tenths_of_a_second = 1; + + // Alas the threading model in curses is a bit lame so we need to + // resort to polling every 0.5 seconds. We could poll for stdin + // ourselves and then pass the keys down but then we need to + // translate all of the escape sequences ourselves. So we resort to + // polling for input because we need to receive async process events + // while in this loop. + + halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths of seconds seconds when calling Window::GetChar() + + ListenerSP listener_sp (new Listener ("lldb.IOHandler.curses.Application")); + ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); + debugger.EnableForwardEvents (listener_sp); + + bool update = true; +#if defined(__APPLE__) + std::deque<int> escape_chars; +#endif + + while (!done) + { + if (update) + { + m_window_sp->Draw(false); + // All windows should be calling Window::DeferredRefresh() instead + // of Window::Refresh() so we can do a single update and avoid + // any screen blinking + update_panels(); + + // Cursor hiding isn't working on MacOSX, so hide it in the top left corner + m_window_sp->MoveCursor(0, 0); + + doupdate(); + update = false; + } + +#if defined(__APPLE__) + // Terminal.app doesn't map its function keys correctly, F1-F4 default to: + // \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if possible + int ch; + if (escape_chars.empty()) + ch = m_window_sp->GetChar(); + else + { + ch = escape_chars.front(); + escape_chars.pop_front(); + } + if (ch == KEY_ESCAPE) + { + int ch2 = m_window_sp->GetChar(); + if (ch2 == 'O') + { + int ch3 = m_window_sp->GetChar(); + switch (ch3) + { + case 'P': ch = KEY_F(1); break; + case 'Q': ch = KEY_F(2); break; + case 'R': ch = KEY_F(3); break; + case 'S': ch = KEY_F(4); break; + default: + escape_chars.push_back(ch2); + if (ch3 != -1) + escape_chars.push_back(ch3); + break; + } + } + else if (ch2 != -1) + escape_chars.push_back(ch2); + } +#else + int ch = m_window_sp->GetChar(); + +#endif + if (ch == -1) + { + if (feof(m_in) || ferror(m_in)) + { + done = true; + } + else + { + // Just a timeout from using halfdelay(), check for events + EventSP event_sp; + while (listener_sp->PeekAtNextEvent()) + { + listener_sp->GetNextEvent(event_sp); + + if (event_sp) + { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) + { + //uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class (broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) + { + update = true; + continue; // Don't get any key, just update our view + } + } + } + } + } + } + else + { + HandleCharResult key_result = m_window_sp->HandleChar(ch); + switch (key_result) + { + case eKeyHandled: + update = true; + break; + case eKeyNotHandled: + break; + case eQuitApplication: + done = true; + break; + } + } + } + + debugger.CancelForwardEvents (listener_sp); + + } + + WindowSP & + GetMainWindow () + { + if (!m_window_sp) + m_window_sp.reset (new Window ("main", stdscr, false)); + return m_window_sp; + } + + WindowDelegates & + GetWindowDelegates () + { + return m_window_delegates; + } + + protected: + WindowSP m_window_sp; + WindowDelegates m_window_delegates; + SCREEN *m_screen; + FILE *m_in; + FILE *m_out; + }; + + +} // namespace curses + + +using namespace curses; + +struct Row +{ + ValueObjectSP valobj; + Row *parent; + int row_idx; + int x; + int y; + bool might_have_children; + bool expanded; + bool calculated_children; + std::vector<Row> children; + + Row (const ValueObjectSP &v, Row *p) : + valobj (v), + parent (p), + row_idx(0), + x(1), + y(1), + might_have_children (v ? v->MightHaveChildren() : false), + expanded (false), + calculated_children (false), + children() + { + } + + size_t + GetDepth () const + { + if (parent) + return 1 + parent->GetDepth(); + return 0; + } + + void + Expand() + { + expanded = true; + if (!calculated_children) + { + calculated_children = true; + if (valobj) + { + const size_t num_children = valobj->GetNumChildren(); + for (size_t i=0; i<num_children; ++i) + { + children.push_back(Row (valobj->GetChildAtIndex(i, true), this)); + } + } + } + } + + void + Unexpand () + { + expanded = false; + } + + void + DrawTree (Window &window) + { + if (parent) + parent->DrawTreeForChild (window, this, 0); + + if (might_have_children) + { + // It we can get UTF8 characters to work we should try to use the "symbol" + // UTF8 string below +// const char *symbol = ""; +// if (row.expanded) +// symbol = "\xe2\x96\xbd "; +// else +// symbol = "\xe2\x96\xb7 "; +// window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a + // 'v' or '>' character... +// if (expanded) +// window.PutChar (ACS_DARROW); +// else +// window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow + // symbols, just use a diamond... + window.PutChar (ACS_DIAMOND); + window.PutChar (ACS_HLINE); + } + } + + void + DrawTreeForChild (Window &window, Row *child, uint32_t reverse_depth) + { + if (parent) + parent->DrawTreeForChild (window, this, reverse_depth + 1); + + if (&children.back() == child) + { + // Last child + if (reverse_depth == 0) + { + window.PutChar (ACS_LLCORNER); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (' '); + window.PutChar (' '); + } + } + else + { + if (reverse_depth == 0) + { + window.PutChar (ACS_LTEE); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (ACS_VLINE); + window.PutChar (' '); + } + } + } +}; + +struct DisplayOptions +{ + bool show_types; +}; + +class TreeItem; + +class TreeDelegate +{ +public: + TreeDelegate() {} + virtual ~TreeDelegate() {} + virtual void TreeDelegateDrawTreeItem (TreeItem &item, Window &window) = 0; + virtual void TreeDelegateGenerateChildren (TreeItem &item) = 0; + virtual bool TreeDelegateItemSelected (TreeItem &item) = 0; // Return true if we need to update views +}; +typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; + +class TreeItem +{ +public: + + TreeItem (TreeItem *parent, TreeDelegate &delegate, bool might_have_children) : + m_parent (parent), + m_delegate (delegate), + m_identifier (0), + m_row_idx (-1), + m_children (), + m_might_have_children (might_have_children), + m_is_expanded (false) + { + } + + TreeItem & + operator=(const TreeItem &rhs) + { + if (this != &rhs) + { + m_parent = rhs.m_parent; + m_delegate = rhs.m_delegate; + m_identifier = rhs.m_identifier; + m_row_idx = rhs.m_row_idx; + m_children = rhs.m_children; + m_might_have_children = rhs.m_might_have_children; + m_is_expanded = rhs.m_is_expanded; + } + return *this; + } + + size_t + GetDepth () const + { + if (m_parent) + return 1 + m_parent->GetDepth(); + return 0; + } + + int + GetRowIndex () const + { + return m_row_idx; + } + + void + ClearChildren () + { + m_children.clear(); + } + + void + Resize (size_t n, const TreeItem &t) + { + m_children.resize(n, t); + } + + TreeItem & + operator [](size_t i) + { + return m_children[i]; + } + + void + SetRowIndex (int row_idx) + { + m_row_idx = row_idx; + } + + size_t + GetNumChildren () + { + m_delegate.TreeDelegateGenerateChildren (*this); + return m_children.size(); + } + + void + ItemWasSelected () + { + m_delegate.TreeDelegateItemSelected(*this); + } + void + CalculateRowIndexes (int &row_idx) + { + SetRowIndex(row_idx); + ++row_idx; + + // The root item must calculate its children + if (m_parent == NULL) + GetNumChildren(); + + const bool expanded = IsExpanded(); + for (auto &item : m_children) + { + if (expanded) + item.CalculateRowIndexes(row_idx); + else + item.SetRowIndex(-1); + } + } + + TreeItem * + GetParent () + { + return m_parent; + } + + bool + IsExpanded () const + { + return m_is_expanded; + } + + void + Expand() + { + m_is_expanded = true; + } + + void + Unexpand () + { + m_is_expanded = false; + } + + bool + Draw (Window &window, + const int first_visible_row, + const uint32_t selected_row_idx, + int &row_idx, + int &num_rows_left) + { + if (num_rows_left <= 0) + return false; + + if (m_row_idx >= first_visible_row) + { + window.MoveCursor(2, row_idx + 1); + + if (m_parent) + m_parent->DrawTreeForChild (window, this, 0); + + if (m_might_have_children) + { + // It we can get UTF8 characters to work we should try to use the "symbol" + // UTF8 string below + // const char *symbol = ""; + // if (row.expanded) + // symbol = "\xe2\x96\xbd "; + // else + // symbol = "\xe2\x96\xb7 "; + // window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a + // 'v' or '>' character... + // if (expanded) + // window.PutChar (ACS_DARROW); + // else + // window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow + // symbols, just use a diamond... + window.PutChar (ACS_DIAMOND); + window.PutChar (ACS_HLINE); + } + bool highlight = (selected_row_idx == m_row_idx) && window.IsActive(); + + if (highlight) + window.AttributeOn(A_REVERSE); + + m_delegate.TreeDelegateDrawTreeItem(*this, window); + + if (highlight) + window.AttributeOff(A_REVERSE); + ++row_idx; + --num_rows_left; + } + + if (num_rows_left <= 0) + return false; // We are done drawing... + + if (IsExpanded()) + { + for (auto &item : m_children) + { + // If we displayed all the rows and item.Draw() returns + // false we are done drawing and can exit this for loop + if (item.Draw(window, first_visible_row, selected_row_idx, row_idx, num_rows_left) == false) + break; + } + } + return num_rows_left >= 0; // Return true if not done drawing yet + } + + void + DrawTreeForChild (Window &window, TreeItem *child, uint32_t reverse_depth) + { + if (m_parent) + m_parent->DrawTreeForChild (window, this, reverse_depth + 1); + + if (&m_children.back() == child) + { + // Last child + if (reverse_depth == 0) + { + window.PutChar (ACS_LLCORNER); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (' '); + window.PutChar (' '); + } + } + else + { + if (reverse_depth == 0) + { + window.PutChar (ACS_LTEE); + window.PutChar (ACS_HLINE); + } + else + { + window.PutChar (ACS_VLINE); + window.PutChar (' '); + } + } + } + + TreeItem * + GetItemForRowIndex (uint32_t row_idx) + { + if (m_row_idx == row_idx) + return this; + if (m_children.empty()) + return NULL; + if (m_children.back().m_row_idx < row_idx) + return NULL; + if (IsExpanded()) + { + for (auto &item : m_children) + { + TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); + if (selected_item_ptr) + return selected_item_ptr; + } + } + return NULL; + } + +// void * +// GetUserData() const +// { +// return m_user_data; +// } +// +// void +// SetUserData (void *user_data) +// { +// m_user_data = user_data; +// } + uint64_t + GetIdentifier() const + { + return m_identifier; + } + + void + SetIdentifier (uint64_t identifier) + { + m_identifier = identifier; + } + + +protected: + TreeItem *m_parent; + TreeDelegate &m_delegate; + //void *m_user_data; + uint64_t m_identifier; + int m_row_idx; // Zero based visible row index, -1 if not visible or for the root item + std::vector<TreeItem> m_children; + bool m_might_have_children; + bool m_is_expanded; + +}; + +class TreeWindowDelegate : public WindowDelegate +{ +public: + TreeWindowDelegate (Debugger &debugger, const TreeDelegateSP &delegate_sp) : + m_debugger (debugger), + m_delegate_sp (delegate_sp), + m_root (NULL, *delegate_sp, true), + m_selected_item (NULL), + m_num_rows (0), + m_selected_row_idx (0), + m_first_visible_row (0), + m_min_x (0), + m_min_y (0), + m_max_x (0), + m_max_y (0) + { + } + + int + NumVisibleRows () const + { + return m_max_y - m_min_y; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); + Process *process = exe_ctx.GetProcessPtr(); + + bool display_content = false; + if (process) + { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) + { + // We are stopped, so it is ok to + display_content = true; + } + else if (StateIsRunningState(state)) + { + return true; // Don't do any updating when we are running + } + } + + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox (window.GetName()); + + if (display_content) + { + const int num_visible_rows = NumVisibleRows(); + m_num_rows = 0; + m_root.CalculateRowIndexes(m_num_rows); + + // If we unexpanded while having something selected our + // total number of rows is less than the num visible rows, + // then make sure we show all the rows by setting the first + // visible row accordingly. + if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + int row_idx = 0; + int num_rows_left = num_visible_rows; + m_root.Draw (window, m_first_visible_row, m_selected_row_idx, row_idx, num_rows_left); + // Get the selected row + m_selected_item = m_root.GetItemForRowIndex (m_selected_row_idx); + } + else + { + m_selected_item = NULL; + } + + window.DeferredRefresh(); + + + return true; // Drawing handled + } + + + virtual const char * + WindowDelegateGetHelpText () + { + return "Thread window keyboard shortcuts:"; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { KEY_UP, "Select previous item" }, + { KEY_DOWN, "Select next item" }, + { KEY_RIGHT, "Expand the selected item" }, + { KEY_LEFT, "Unexpand the selected item or select parent if not expanded" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { 'h', "Show help dialog" }, + { ' ', "Toggle item expansion" }, + { ',', "Page up" }, + { '.', "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int c) + { + switch(c) + { + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) + { + if (m_first_visible_row > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > m_max_y) + { + if (m_first_visible_row + m_max_y < m_num_rows) + { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) + { + --m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + return eKeyHandled; + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) + { + ++m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_item) + { + if (!m_selected_item->IsExpanded()) + m_selected_item->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_item) + { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else if (m_selected_item->GetParent()) + { + m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected (); + } + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_item) + { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else + m_selected_item->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow (); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + Debugger &m_debugger; + TreeDelegateSP m_delegate_sp; + TreeItem m_root; + TreeItem *m_selected_item; + int m_num_rows; + int m_selected_row_idx; + int m_first_visible_row; + int m_min_x; + int m_min_y; + int m_max_x; + int m_max_y; + +}; + +class FrameTreeDelegate : public TreeDelegate +{ +public: + FrameTreeDelegate (const ThreadSP &thread_sp) : + TreeDelegate(), + m_thread_wp() + { + if (thread_sp) + m_thread_wp = thread_sp; + } + + virtual ~FrameTreeDelegate() + { + } + + virtual void + TreeDelegateDrawTreeItem (TreeItem &item, Window &window) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + const uint64_t frame_idx = item.GetIdentifier(); + StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(frame_idx); + if (frame_sp) + { + StreamString strm; + const SymbolContext &sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + ExecutionContext exe_ctx (frame_sp); + //const char *frame_format = "frame #${frame.index}: ${module.file.basename}{`${function.name}${function.pc-offset}}}"; + const char *frame_format = "frame #${frame.index}: {${function.name}${function.pc-offset}}}"; + if (Debugger::FormatPrompt (frame_format, &sc, &exe_ctx, NULL, strm)) + { + int right_pad = 1; + window.PutCStringTruncated(strm.GetString().c_str(), right_pad); + } + } + } + } + virtual void + TreeDelegateGenerateChildren (TreeItem &item) + { + // No children for frames yet... + } + + virtual bool + TreeDelegateItemSelected (TreeItem &item) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + const uint64_t frame_idx = item.GetIdentifier(); + thread_sp->SetSelectedFrameByIndex(frame_idx); + return true; + } + return false; + } + void + SetThread (ThreadSP thread_sp) + { + m_thread_wp = thread_sp; + } + +protected: + ThreadWP m_thread_wp; +}; + +class ThreadTreeDelegate : public TreeDelegate +{ +public: + ThreadTreeDelegate (Debugger &debugger) : + TreeDelegate(), + m_debugger (debugger), + m_thread_wp (), + m_tid (LLDB_INVALID_THREAD_ID), + m_stop_id (UINT32_MAX) + { + } + + virtual + ~ThreadTreeDelegate() + { + } + + virtual void + TreeDelegateDrawTreeItem (TreeItem &item, Window &window) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + StreamString strm; + ExecutionContext exe_ctx (thread_sp); + const char *format = "thread #${thread.index}: tid = ${thread.id}{, stop reason = ${thread.stop-reason}}"; + if (Debugger::FormatPrompt (format, NULL, &exe_ctx, NULL, strm)) + { + int right_pad = 1; + window.PutCStringTruncated(strm.GetString().c_str(), right_pad); + } + } + } + virtual void + TreeDelegateGenerateChildren (TreeItem &item) + { + TargetSP target_sp (m_debugger.GetSelectedTarget()); + if (target_sp) + { + ProcessSP process_sp = target_sp->GetProcessSP(); + if (process_sp && process_sp->IsAlive()) + { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) + { + ThreadSP thread_sp = process_sp->GetThreadList().GetSelectedThread(); + if (thread_sp) + { + if (m_stop_id == process_sp->GetStopID() && thread_sp->GetID() == m_tid) + return; // Children are already up to date + if (m_frame_delegate_sp) + m_frame_delegate_sp->SetThread(thread_sp); + else + { + // Always expand the thread item the first time we show it + item.Expand(); + m_frame_delegate_sp.reset (new FrameTreeDelegate(thread_sp)); + } + + m_stop_id = process_sp->GetStopID(); + m_thread_wp = thread_sp; + m_tid = thread_sp->GetID(); + + TreeItem t (&item, *m_frame_delegate_sp, false); + size_t num_frames = thread_sp->GetStackFrameCount(); + item.Resize (num_frames, t); + for (size_t i=0; i<num_frames; ++i) + { + item[i].SetIdentifier(i); + } + } + return; + } + } + } + item.ClearChildren(); + } + + virtual bool + TreeDelegateItemSelected (TreeItem &item) + { + ThreadSP thread_sp = m_thread_wp.lock(); + if (thread_sp) + { + ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); + Mutex::Locker locker (thread_list.GetMutex()); + ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); + if (selected_thread_sp->GetID() != thread_sp->GetID()) + { + thread_list.SetSelectedThreadByID(thread_sp->GetID()); + return true; + } + } + return false; + } + +protected: + Debugger &m_debugger; + ThreadWP m_thread_wp; + std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; + lldb::user_id_t m_tid; + uint32_t m_stop_id; +}; + +class ValueObjectListDelegate : public WindowDelegate +{ +public: + ValueObjectListDelegate () : + m_valobj_list (), + m_rows (), + m_selected_row (NULL), + m_selected_row_idx (0), + m_first_visible_row (0), + m_num_rows (0), + m_max_x (0), + m_max_y (0) + { + } + + ValueObjectListDelegate (ValueObjectList &valobj_list) : + m_valobj_list (valobj_list), + m_rows (), + m_selected_row (NULL), + m_selected_row_idx (0), + m_first_visible_row (0), + m_num_rows (0), + m_max_x (0), + m_max_y (0) + { + SetValues (valobj_list); + } + + virtual + ~ValueObjectListDelegate() + { + } + + void + SetValues (ValueObjectList &valobj_list) + { + m_selected_row = NULL; + m_selected_row_idx = 0; + m_first_visible_row = 0; + m_num_rows = 0; + m_rows.clear(); + m_valobj_list = valobj_list; + const size_t num_values = m_valobj_list.GetSize(); + for (size_t i=0; i<num_values; ++i) + m_rows.push_back(Row(m_valobj_list.GetValueObjectAtIndex(i), NULL)); + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + m_num_rows = 0; + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox (window.GetName()); + + const int num_visible_rows = NumVisibleRows(); + const int num_rows = CalculateTotalNumberRows (m_rows); + + // If we unexpanded while having something selected our + // total number of rows is less than the num visible rows, + // then make sure we show all the rows by setting the first + // visible row accordingly. + if (m_first_visible_row > 0 && num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + DisplayRows (window, m_rows, g_options); + + window.DeferredRefresh(); + + // Get the selected row + m_selected_row = GetRowForRowIndex (m_selected_row_idx); + // Keep the cursor on the selected row so the highlight and the cursor + // are always on the same line + if (m_selected_row) + window.MoveCursor (m_selected_row->x, + m_selected_row->y); + + return true; // Drawing handled + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { KEY_UP, "Select previous item" }, + { KEY_DOWN, "Select next item" }, + { KEY_RIGHT, "Expand selected item" }, + { KEY_LEFT, "Unexpand selected item or select parent if not expanded" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { 'A', "Format as annotated address" }, + { 'b', "Format as binary" }, + { 'B', "Format as hex bytes with ASCII" }, + { 'c', "Format as character" }, + { 'd', "Format as a signed integer" }, + { 'D', "Format selected value using the default format for the type" }, + { 'f', "Format as float" }, + { 'h', "Show help dialog" }, + { 'i', "Format as instructions" }, + { 'o', "Format as octal" }, + { 'p', "Format as pointer" }, + { 's', "Format as C string" }, + { 't', "Toggle showing/hiding type names" }, + { 'u', "Format as an unsigned integer" }, + { 'x', "Format as hex" }, + { 'X', "Format as uppercase hex" }, + { ' ', "Toggle item expansion" }, + { ',', "Page up" }, + { '.', "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int c) + { + switch(c) + { + case 'x': + case 'X': + case 'o': + case 's': + case 'u': + case 'd': + case 'D': + case 'i': + case 'A': + case 'p': + case 'c': + case 'b': + case 'B': + case 'f': + // Change the format for the currently selected item + if (m_selected_row) + m_selected_row->valobj->SetFormat (FormatForChar (c)); + return eKeyHandled; + + case 't': + // Toggle showing type names + g_options.show_types = !g_options.show_types; + return eKeyHandled; + + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) + { + if (m_first_visible_row > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > m_max_y) + { + if (m_first_visible_row + m_max_y < m_num_rows) + { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) + --m_selected_row_idx; + return eKeyHandled; + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) + ++m_selected_row_idx; + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_row) + { + if (!m_selected_row->expanded) + m_selected_row->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_row) + { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else if (m_selected_row->parent) + m_selected_row_idx = m_selected_row->parent->row_idx; + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_row) + { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else + m_selected_row->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow (); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + ValueObjectList m_valobj_list; + std::vector<Row> m_rows; + Row *m_selected_row; + uint32_t m_selected_row_idx; + uint32_t m_first_visible_row; + uint32_t m_num_rows; + int m_min_x; + int m_min_y; + int m_max_x; + int m_max_y; + + static Format + FormatForChar (int c) + { + switch (c) + { + case 'x': return eFormatHex; + case 'X': return eFormatHexUppercase; + case 'o': return eFormatOctal; + case 's': return eFormatCString; + case 'u': return eFormatUnsigned; + case 'd': return eFormatDecimal; + case 'D': return eFormatDefault; + case 'i': return eFormatInstruction; + case 'A': return eFormatAddressInfo; + case 'p': return eFormatPointer; + case 'c': return eFormatChar; + case 'b': return eFormatBinary; + case 'B': return eFormatBytesWithASCII; + case 'f': return eFormatFloat; + } + return eFormatDefault; + } + + bool + DisplayRowObject (Window &window, + Row &row, + DisplayOptions &options, + bool highlight, + bool last_child) + { + ValueObject *valobj = row.valobj.get(); + + if (valobj == NULL) + return false; + + const char *type_name = options.show_types ? valobj->GetTypeName().GetCString() : NULL; + const char *name = valobj->GetName().GetCString(); + const char *value = valobj->GetValueAsCString (); + const char *summary = valobj->GetSummaryAsCString (); + + window.MoveCursor (row.x, row.y); + + row.DrawTree (window); + + if (highlight) + window.AttributeOn(A_REVERSE); + + if (type_name && type_name[0]) + window.Printf ("(%s) ", type_name); + + if (name && name[0]) + window.PutCString(name); + + attr_t changd_attr = 0; + if (valobj->GetValueDidChange()) + changd_attr = COLOR_PAIR(5) | A_BOLD; + + if (value && value[0]) + { + window.PutCString(" = "); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCString (value); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (summary && summary[0]) + { + window.PutChar(' '); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCString(summary); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (highlight) + window.AttributeOff (A_REVERSE); + + return true; + } + void + DisplayRows (Window &window, + std::vector<Row> &rows, + DisplayOptions &options) + { + // > 0x25B7 + // \/ 0x25BD + + bool window_is_active = window.IsActive(); + for (auto &row : rows) + { + const bool last_child = row.parent && &rows[rows.size()-1] == &row; + // Save the row index in each Row structure + row.row_idx = m_num_rows; + if ((m_num_rows >= m_first_visible_row) && + ((m_num_rows - m_first_visible_row) < NumVisibleRows())) + { + row.x = m_min_x; + row.y = m_num_rows - m_first_visible_row + 1; + if (DisplayRowObject (window, + row, + options, + window_is_active && m_num_rows == m_selected_row_idx, + last_child)) + { + ++m_num_rows; + } + else + { + row.x = 0; + row.y = 0; + } + } + else + { + row.x = 0; + row.y = 0; + ++m_num_rows; + } + + if (row.expanded && !row.children.empty()) + { + DisplayRows (window, + row.children, + options); + } + } + } + + int + CalculateTotalNumberRows (const std::vector<Row> &rows) + { + int row_count = 0; + for (const auto &row : rows) + { + ++row_count; + if (row.expanded) + row_count += CalculateTotalNumberRows(row.children); + } + return row_count; + } + static Row * + GetRowForRowIndexImpl (std::vector<Row> &rows, size_t &row_index) + { + for (auto &row : rows) + { + if (row_index == 0) + return &row; + else + { + --row_index; + if (row.expanded && !row.children.empty()) + { + Row *result = GetRowForRowIndexImpl (row.children, row_index); + if (result) + return result; + } + } + } + return NULL; + } + + Row * + GetRowForRowIndex (size_t row_index) + { + return GetRowForRowIndexImpl (m_rows, row_index); + } + + int + NumVisibleRows () const + { + return m_max_y - m_min_y; + } + + static DisplayOptions g_options; +}; + +class FrameVariablesWindowDelegate : public ValueObjectListDelegate +{ +public: + FrameVariablesWindowDelegate (Debugger &debugger) : + ValueObjectListDelegate (), + m_debugger (debugger), + m_frame_block (NULL) + { + } + + virtual + ~FrameVariablesWindowDelegate() + { + } + + virtual const char * + WindowDelegateGetHelpText () + { + return "Frame variable window keyboard shortcuts:"; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); + Process *process = exe_ctx.GetProcessPtr(); + Block *frame_block = NULL; + StackFrame *frame = NULL; + + if (process) + { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) + { + frame = exe_ctx.GetFramePtr(); + if (frame) + frame_block = frame->GetFrameBlock (); + } + else if (StateIsRunningState(state)) + { + return true; // Don't do any updating when we are running + } + } + + ValueObjectList local_values; + if (frame_block) + { + // Only update the variables if they have changed + if (m_frame_block != frame_block) + { + m_frame_block = frame_block; + + VariableList *locals = frame->GetVariableList(true); + if (locals) + { + const DynamicValueType use_dynamic = eDynamicDontRunTarget; + const size_t num_locals = locals->GetSize(); + for (size_t i=0; i<num_locals; ++i) + local_values.Append(frame->GetValueObjectForFrameVariable (locals->GetVariableAtIndex(i), use_dynamic)); + // Update the values + SetValues(local_values); + } + } + } + else + { + m_frame_block = NULL; + // Update the values with an empty list if there is no frame + SetValues(local_values); + } + + return ValueObjectListDelegate::WindowDelegateDraw (window, force); + + } + +protected: + Debugger &m_debugger; + Block *m_frame_block; +}; + + +class RegistersWindowDelegate : public ValueObjectListDelegate +{ +public: + RegistersWindowDelegate (Debugger &debugger) : + ValueObjectListDelegate (), + m_debugger (debugger) + { + } + + virtual + ~RegistersWindowDelegate() + { + } + + virtual const char * + WindowDelegateGetHelpText () + { + return "Register window keyboard shortcuts:"; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); + StackFrame *frame = exe_ctx.GetFramePtr(); + + ValueObjectList value_list; + if (frame) + { + if (frame->GetStackID() != m_stack_id) + { + m_stack_id = frame->GetStackID(); + RegisterContextSP reg_ctx (frame->GetRegisterContext()); + if (reg_ctx) + { + const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); + for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) + { + value_list.Append(ValueObjectRegisterSet::Create (frame, reg_ctx, set_idx)); + } + } + SetValues(value_list); + } + } + else + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + return true; // Don't do any updating if we are running + else + { + // Update the values with an empty list if there + // is no process or the process isn't alive anymore + SetValues(value_list); + } + } + return ValueObjectListDelegate::WindowDelegateDraw (window, force); + } + +protected: + Debugger &m_debugger; + StackID m_stack_id; +}; + +static const char * +CursesKeyToCString (int ch) +{ + static char g_desc[32]; + if (ch >= KEY_F0 && ch < KEY_F0 + 64) + { + snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); + return g_desc; + } + switch (ch) + { + case KEY_DOWN: return "down"; + case KEY_UP: return "up"; + case KEY_LEFT: return "left"; + case KEY_RIGHT: return "right"; + case KEY_HOME: return "home"; + case KEY_BACKSPACE: return "backspace"; + case KEY_DL: return "delete-line"; + case KEY_IL: return "insert-line"; + case KEY_DC: return "delete-char"; + case KEY_IC: return "insert-char"; + case KEY_CLEAR: return "clear"; + case KEY_EOS: return "clear-to-eos"; + case KEY_EOL: return "clear-to-eol"; + case KEY_SF: return "scroll-forward"; + case KEY_SR: return "scroll-backward"; + case KEY_NPAGE: return "page-down"; + case KEY_PPAGE: return "page-up"; + case KEY_STAB: return "set-tab"; + case KEY_CTAB: return "clear-tab"; + case KEY_CATAB: return "clear-all-tabs"; + case KEY_ENTER: return "enter"; + case KEY_PRINT: return "print"; + case KEY_LL: return "lower-left key"; + case KEY_A1: return "upper left of keypad"; + case KEY_A3: return "upper right of keypad"; + case KEY_B2: return "center of keypad"; + case KEY_C1: return "lower left of keypad"; + case KEY_C3: return "lower right of keypad"; + case KEY_BTAB: return "back-tab key"; + case KEY_BEG: return "begin key"; + case KEY_CANCEL: return "cancel key"; + case KEY_CLOSE: return "close key"; + case KEY_COMMAND: return "command key"; + case KEY_COPY: return "copy key"; + case KEY_CREATE: return "create key"; + case KEY_END: return "end key"; + case KEY_EXIT: return "exit key"; + case KEY_FIND: return "find key"; + case KEY_HELP: return "help key"; + case KEY_MARK: return "mark key"; + case KEY_MESSAGE: return "message key"; + case KEY_MOVE: return "move key"; + case KEY_NEXT: return "next key"; + case KEY_OPEN: return "open key"; + case KEY_OPTIONS: return "options key"; + case KEY_PREVIOUS: return "previous key"; + case KEY_REDO: return "redo key"; + case KEY_REFERENCE: return "reference key"; + case KEY_REFRESH: return "refresh key"; + case KEY_REPLACE: return "replace key"; + case KEY_RESTART: return "restart key"; + case KEY_RESUME: return "resume key"; + case KEY_SAVE: return "save key"; + case KEY_SBEG: return "shifted begin key"; + case KEY_SCANCEL: return "shifted cancel key"; + case KEY_SCOMMAND: return "shifted command key"; + case KEY_SCOPY: return "shifted copy key"; + case KEY_SCREATE: return "shifted create key"; + case KEY_SDC: return "shifted delete-character key"; + case KEY_SDL: return "shifted delete-line key"; + case KEY_SELECT: return "select key"; + case KEY_SEND: return "shifted end key"; + case KEY_SEOL: return "shifted clear-to-end-of-line key"; + case KEY_SEXIT: return "shifted exit key"; + case KEY_SFIND: return "shifted find key"; + case KEY_SHELP: return "shifted help key"; + case KEY_SHOME: return "shifted home key"; + case KEY_SIC: return "shifted insert-character key"; + case KEY_SLEFT: return "shifted left-arrow key"; + case KEY_SMESSAGE: return "shifted message key"; + case KEY_SMOVE: return "shifted move key"; + case KEY_SNEXT: return "shifted next key"; + case KEY_SOPTIONS: return "shifted options key"; + case KEY_SPREVIOUS: return "shifted previous key"; + case KEY_SPRINT: return "shifted print key"; + case KEY_SREDO: return "shifted redo key"; + case KEY_SREPLACE: return "shifted replace key"; + case KEY_SRIGHT: return "shifted right-arrow key"; + case KEY_SRSUME: return "shifted resume key"; + case KEY_SSAVE: return "shifted save key"; + case KEY_SSUSPEND: return "shifted suspend key"; + case KEY_SUNDO: return "shifted undo key"; + case KEY_SUSPEND: return "suspend key"; + case KEY_UNDO: return "undo key"; + case KEY_MOUSE: return "Mouse event has occurred"; + case KEY_RESIZE: return "Terminal resize event"; + case KEY_EVENT: return "We were interrupted by an event"; + case KEY_RETURN: return "return"; + case ' ': return "space"; + case '\t': return "tab"; + case KEY_ESCAPE: return "escape"; + default: + if (isprint(ch)) + snprintf(g_desc, sizeof(g_desc), "%c", ch); + else + snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); + return g_desc; + } + return NULL; +} + +HelpDialogDelegate::HelpDialogDelegate (const char *text, KeyHelp *key_help_array) : + m_text (), + m_first_visible_line (0) +{ + if (text && text[0]) + { + m_text.SplitIntoLines(text); + m_text.AppendString(""); + } + if (key_help_array) + { + for (KeyHelp *key = key_help_array; key->ch; ++key) + { + StreamString key_description; + key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), key->description); + m_text.AppendString(std::move(key_description.GetString())); + } + } +} + +HelpDialogDelegate::~HelpDialogDelegate() +{ +} + +bool +HelpDialogDelegate::WindowDelegateDraw (Window &window, bool force) +{ + window.Erase(); + const int window_height = window.GetHeight(); + int x = 2; + int y = 1; + const int min_y = y; + const int max_y = window_height - 1 - y; + const int num_visible_lines = max_y - min_y + 1; + const size_t num_lines = m_text.GetSize(); + const char *bottom_message; + if (num_lines <= num_visible_lines) + bottom_message = "Press any key to exit"; + else + bottom_message = "Use arrows to scroll, any other key to exit"; + window.DrawTitleBox(window.GetName(), bottom_message); + while (y <= max_y) + { + window.MoveCursor(x, y); + window.PutCStringTruncated(m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1); + ++y; + } + return true; +} + +HandleCharResult +HelpDialogDelegate::WindowDelegateHandleChar (Window &window, int key) +{ + bool done = false; + const size_t num_lines = m_text.GetSize(); + const size_t num_visible_lines = window.GetHeight() - 2; + + if (num_lines <= num_visible_lines) + { + done = true; + // If we have all lines visible and don't need scrolling, then any + // key press will cause us to exit + } + else + { + switch (key) + { + case KEY_UP: + if (m_first_visible_line > 0) + --m_first_visible_line; + break; + + case KEY_DOWN: + if (m_first_visible_line + num_visible_lines < num_lines) + ++m_first_visible_line; + break; + + case KEY_PPAGE: + case ',': + if (m_first_visible_line > 0) + { + if (m_first_visible_line >= num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + } + break; + case KEY_NPAGE: + case '.': + if (m_first_visible_line + num_visible_lines < num_lines) + { + m_first_visible_line += num_visible_lines; + if (m_first_visible_line > num_lines) + m_first_visible_line = num_lines - num_visible_lines; + } + break; + default: + done = true; + break; + } + } + if (done) + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; +} + +class ApplicationDelegate : + public WindowDelegate, + public MenuDelegate +{ +public: + enum { + eMenuID_LLDB = 1, + eMenuID_LLDBAbout, + eMenuID_LLDBExit, + + eMenuID_Target, + eMenuID_TargetCreate, + eMenuID_TargetDelete, + + eMenuID_Process, + eMenuID_ProcessAttach, + eMenuID_ProcessDetach, + eMenuID_ProcessLaunch, + eMenuID_ProcessContinue, + eMenuID_ProcessHalt, + eMenuID_ProcessKill, + + eMenuID_Thread, + eMenuID_ThreadStepIn, + eMenuID_ThreadStepOver, + eMenuID_ThreadStepOut, + + eMenuID_View, + eMenuID_ViewBacktrace, + eMenuID_ViewRegisters, + eMenuID_ViewSource, + eMenuID_ViewVariables, + + eMenuID_Help, + eMenuID_HelpGUIHelp + }; + + ApplicationDelegate (Application &app, Debugger &debugger) : + WindowDelegate (), + MenuDelegate (), + m_app (app), + m_debugger (debugger) + { + } + + virtual + ~ApplicationDelegate () + { + } + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + return false; // Drawing not handled, let standard window drawing happen + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int key) + { + switch (key) + { + case '\t': + window.SelectNextWindowAsActive(); + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + case KEY_ESCAPE: + return eQuitApplication; + + default: + break; + } + return eKeyNotHandled; + } + + + virtual const char * + WindowDelegateGetHelpText () + { + return "Welcome to the LLDB curses GUI.\n\n" + "Press the TAB key to change the selected view.\n" + "Each view has its own keyboard shortcuts, press 'h' to open a dialog to display them.\n\n" + "Common key bindings for all views:"; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { '\t', "Select next view" }, + { 'h', "Show help dialog with view specific key bindings" }, + { ',', "Page up" }, + { '.', "Page down" }, + { KEY_UP, "Select previous" }, + { KEY_DOWN, "Select next" }, + { KEY_LEFT, "Unexpand or select parent" }, + { KEY_RIGHT, "Expand" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + virtual MenuActionResult + MenuDelegateAction (Menu &menu) + { + switch (menu.GetIdentifier()) + { + case eMenuID_ThreadStepIn: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + exe_ctx.GetThreadRef().StepIn(true, true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOut: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + exe_ctx.GetThreadRef().StepOut(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOver: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + exe_ctx.GetThreadRef().StepOver(true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessContinue: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + process->Resume(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessKill: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Destroy(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessHalt: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Halt(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessDetach: + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Detach(false); + } + } + return MenuActionResult::Handled; + + case eMenuID_Process: + { + // Populate the menu with all of the threads if the process is stopped when + // the Process menu gets selected and is about to display its submenu. + Menus &submenus = menu.GetSubmenus(); + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) + { + if (submenus.size() == 7) + menu.AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); + else if (submenus.size() > 8) + submenus.erase (submenus.begin() + 8, submenus.end()); + + ThreadList &threads = process->GetThreadList(); + Mutex::Locker locker (threads.GetMutex()); + size_t num_threads = threads.GetSize(); + for (size_t i=0; i<num_threads; ++i) + { + ThreadSP thread_sp = threads.GetThreadAtIndex(i); + char menu_char = '\0'; + if (i < 9) + menu_char = '1' + i; + StreamString thread_menu_title; + thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); + const char *thread_name = thread_sp->GetName(); + if (thread_name && thread_name[0]) + thread_menu_title.Printf (" %s", thread_name); + else + { + const char *queue_name = thread_sp->GetQueueName(); + if (queue_name && queue_name[0]) + thread_menu_title.Printf (" %s", queue_name); + } + menu.AddSubmenu (MenuSP (new Menu(thread_menu_title.GetString().c_str(), NULL, menu_char, thread_sp->GetID()))); + } + } + else if (submenus.size() > 7) + { + // Remove the separator and any other thread submenu items + // that were previously added + submenus.erase (submenus.begin() + 7, submenus.end()); + } + // Since we are adding and removing items we need to recalculate the name lengths + menu.RecalculateNameLengths(); + } + return MenuActionResult::Handled; + + case eMenuID_ViewVariables: + { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (variables_window_sp) + { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + main_window_sp->RemoveSubWindow(variables_window_sp.get()); + + if (registers_window_sp) + { + // We have a registers window, so give all the area back to the registers window + Rect registers_bounds = variables_bounds; + registers_bounds.size.width = source_bounds.size.width; + registers_window_sp->SetBounds(registers_bounds); + } + else + { + // We have no registers window showing so give the bottom + // area back to the source view + source_window_sp->Resize (source_bounds.size.width, + source_bounds.size.height + variables_bounds.size.height); + } + } + else + { + Rect new_variables_rect; + if (registers_window_sp) + { + // We have a registers window so split the area of the registers + // window into two columns where the left hand side will be the + // variables and the right hand side will be the registers + const Rect variables_bounds = registers_window_sp->GetBounds(); + Rect new_registers_rect; + variables_bounds.VerticalSplitPercentage (0.50, new_variables_rect, new_registers_rect); + registers_window_sp->SetBounds (new_registers_rect); + } + else + { + // No variables window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_variables_rect); + source_window_sp->SetBounds (new_source_rect); + } + WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Variables", + new_variables_rect, + false); + new_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_ViewRegisters: + { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (registers_window_sp) + { + if (variables_window_sp) + { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + // We have a variables window, so give all the area back to the variables window + variables_window_sp->Resize (variables_bounds.size.width + registers_window_sp->GetWidth(), + variables_bounds.size.height); + } + else + { + // We have no variables window showing so give the bottom + // area back to the source view + source_window_sp->Resize (source_bounds.size.width, + source_bounds.size.height + registers_window_sp->GetHeight()); + } + main_window_sp->RemoveSubWindow(registers_window_sp.get()); + } + else + { + Rect new_regs_rect; + if (variables_window_sp) + { + // We have a variables window, split it into two columns + // where the left hand side will be the variables and the + // right hand side will be the registers + const Rect variables_bounds = variables_window_sp->GetBounds(); + Rect new_vars_rect; + variables_bounds.VerticalSplitPercentage (0.50, new_vars_rect, new_regs_rect); + variables_window_sp->SetBounds (new_vars_rect); + } + else + { + // No registers window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_regs_rect); + source_window_sp->SetBounds (new_source_rect); + } + WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Registers", + new_regs_rect, + false); + new_window_sp->SetDelegate (WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_HelpGUIHelp: + m_app.GetMainWindow ()->CreateHelpSubwindow(); + return MenuActionResult::Handled; + + default: + break; + } + + return MenuActionResult::NotHandled; + } +protected: + Application &m_app; + Debugger &m_debugger; +}; + + +class StatusBarWindowDelegate : public WindowDelegate +{ +public: + StatusBarWindowDelegate (Debugger &debugger) : + m_debugger (debugger) + { + } + + virtual + ~StatusBarWindowDelegate () + { + } + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = exe_ctx.GetThreadPtr(); + StackFrame *frame = exe_ctx.GetFramePtr(); + window.Erase(); + window.SetBackground(2); + window.MoveCursor (0, 0); + if (process) + { + const StateType state = process->GetState(); + window.Printf ("Process: %5" PRIu64 " %10s", process->GetID(), StateAsCString(state)); + + if (StateIsStoppedState(state, true)) + { + window.MoveCursor (40, 0); + if (thread) + window.Printf ("Thread: 0x%4.4" PRIx64, thread->GetID()); + + window.MoveCursor (60, 0); + if (frame) + window.Printf ("Frame: %3u PC = 0x%16.16" PRIx64, frame->GetFrameIndex(), frame->GetFrameCodeAddress().GetOpcodeLoadAddress (exe_ctx.GetTargetPtr())); + } + else if (state == eStateExited) + { + const char *exit_desc = process->GetExitDescription(); + const int exit_status = process->GetExitStatus(); + if (exit_desc && exit_desc[0]) + window.Printf (" with status = %i (%s)", exit_status, exit_desc); + else + window.Printf (" with status = %i", exit_status); + } + } + window.DeferredRefresh(); + return true; + } + +protected: + Debugger &m_debugger; +}; + +class SourceFileWindowDelegate : public WindowDelegate +{ +public: + SourceFileWindowDelegate (Debugger &debugger) : + WindowDelegate (), + m_debugger (debugger), + m_sc (), + m_file_sp (), + m_disassembly_scope (NULL), + m_disassembly_sp (), + m_disassembly_range (), + m_line_width (4), + m_selected_line (0), + m_pc_line (0), + m_stop_id (0), + m_frame_idx (UINT32_MAX), + m_first_visible_line (0), + m_min_x (0), + m_min_y (0), + m_max_x (0), + m_max_y (0) + { + } + + + virtual + ~SourceFileWindowDelegate() + { + } + + void + Update (const SymbolContext &sc) + { + m_sc = sc; + } + + uint32_t + NumVisibleLines () const + { + return m_max_y - m_min_y; + } + + virtual const char * + WindowDelegateGetHelpText () + { + return "Source/Disassembly window keyboard shortcuts:"; + } + + virtual KeyHelp * + WindowDelegateGetKeyHelp () + { + static curses::KeyHelp g_source_view_key_help[] = { + { KEY_RETURN, "Run to selected line with one shot breakpoint" }, + { KEY_UP, "Select previous source line" }, + { KEY_DOWN, "Select next source line" }, + { KEY_PPAGE, "Page up" }, + { KEY_NPAGE, "Page down" }, + { 'b', "Set breakpoint on selected source/disassembly line" }, + { 'c', "Continue process" }, + { 'd', "Detach and resume process" }, + { 'D', "Detach with process suspended" }, + { 'h', "Show help dialog" }, + { 'k', "Kill process" }, + { 'n', "Step over (source line)" }, + { 'N', "Step over (single instruction)" }, + { 'o', "Step out" }, + { 's', "Step in (source line)" }, + { 'S', "Step in (single instruction)" }, + { ',', "Page up" }, + { '.', "Page down" }, + { '\0', NULL } + }; + return g_source_view_key_help; + } + + virtual bool + WindowDelegateDraw (Window &window, bool force) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = NULL; + + bool update_location = false; + if (process) + { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) + { + // We are stopped, so it is ok to + update_location = true; + } + } + + m_min_x = 1; + m_min_y = 1; + m_max_x = window.GetMaxX()-1; + m_max_y = window.GetMaxY()-1; + + const uint32_t num_visible_lines = NumVisibleLines(); + StackFrameSP frame_sp; + bool set_selected_line_to_pc = false; + + + if (update_location) + { + + const bool process_alive = process ? process->IsAlive() : false; + bool thread_changed = false; + if (process_alive) + { + thread = exe_ctx.GetThreadPtr(); + if (thread) + { + frame_sp = thread->GetSelectedFrame(); + auto tid = thread->GetID(); + thread_changed = tid != m_tid; + m_tid = tid; + } + else + { + if (m_tid != LLDB_INVALID_THREAD_ID) + { + thread_changed = true; + m_tid = LLDB_INVALID_THREAD_ID; + } + } + } + const uint32_t stop_id = process ? process->GetStopID() : 0; + const bool stop_id_changed = stop_id != m_stop_id; + bool frame_changed = false; + m_stop_id = stop_id; + if (frame_sp) + { + m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + const uint32_t frame_idx = frame_sp->GetFrameIndex(); + frame_changed = frame_idx != m_frame_idx; + m_frame_idx = frame_idx; + } + else + { + m_sc.Clear(true); + frame_changed = m_frame_idx != UINT32_MAX; + m_frame_idx = UINT32_MAX; + } + + const bool context_changed = thread_changed || frame_changed || stop_id_changed; + + if (process_alive) + { + if (m_sc.line_entry.IsValid()) + { + m_pc_line = m_sc.line_entry.line; + if (m_pc_line != UINT32_MAX) + --m_pc_line; // Convert to zero based line number... + // Update the selected line if the stop ID changed... + if (context_changed) + m_selected_line = m_pc_line; + + if (m_file_sp && m_file_sp->FileSpecMatches(m_sc.line_entry.file)) + { + // Same file, nothing to do, we should either have the + // lines or not (source file missing) + if (m_selected_line >= m_first_visible_line) + { + if (m_selected_line >= m_first_visible_line + num_visible_lines) + m_first_visible_line = m_selected_line - 10; + } + else + { + if (m_selected_line > 10) + m_first_visible_line = m_selected_line - 10; + else + m_first_visible_line = 0; + } + } + else + { + // File changed, set selected line to the line with the PC + m_selected_line = m_pc_line; + m_file_sp = m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); + if (m_file_sp) + { + const size_t num_lines = m_file_sp->GetNumLines(); + int m_line_width = 1; + for (size_t n = num_lines; n >= 10; n = n / 10) + ++m_line_width; + + snprintf (m_line_format, sizeof(m_line_format), " %%%iu ", m_line_width); + if (num_lines < num_visible_lines || m_selected_line < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = m_selected_line - 10; + } + } + } + else + { + m_file_sp.reset(); + } + + if (!m_file_sp || m_file_sp->GetNumLines() == 0) + { + // Show disassembly + bool prefer_file_cache = false; + if (m_sc.function) + { + if (m_disassembly_scope != m_sc.function) + { + m_disassembly_scope = m_sc.function; + m_disassembly_sp = m_sc.function->GetInstructions (exe_ctx, NULL, prefer_file_cache); + if (m_disassembly_sp) + { + set_selected_line_to_pc = true; + m_disassembly_range = m_sc.function->GetAddressRange(); + } + else + { + m_disassembly_range.Clear(); + } + } + else + { + set_selected_line_to_pc = context_changed; + } + } + else if (m_sc.symbol) + { + if (m_disassembly_scope != m_sc.symbol) + { + m_disassembly_scope = m_sc.symbol; + m_disassembly_sp = m_sc.symbol->GetInstructions (exe_ctx, NULL, prefer_file_cache); + if (m_disassembly_sp) + { + set_selected_line_to_pc = true; + m_disassembly_range.GetBaseAddress() = m_sc.symbol->GetAddress(); + m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); + } + else + { + m_disassembly_range.Clear(); + } + } + else + { + set_selected_line_to_pc = context_changed; + } + } + } + } + else + { + m_pc_line = UINT32_MAX; + } + } + + + window.Erase(); + window.DrawTitleBox ("Sources"); + + + Target *target = exe_ctx.GetTargetPtr(); + const size_t num_source_lines = GetNumSourceLines(); + if (num_source_lines > 0) + { + // Display source + BreakpointLines bp_lines; + if (target) + { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx=0; bp_idx<num_bps; ++bp_idx) + { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx=0; bp_loc_idx<num_bps_locs; ++bp_loc_idx) + { + BreakpointLocationSP bp_loc_sp = bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry (bp_loc_line_entry)) + { + if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) + { + bp_lines.insert(bp_loc_line_entry.line); + } + } + } + } + } + + + const attr_t selected_highlight_attr = A_REVERSE; + const attr_t pc_highlight_attr = COLOR_PAIR(1); + + for (int i=0; i<num_visible_lines; ++i) + { + const uint32_t curr_line = m_first_visible_line + i; + if (curr_line < num_source_lines) + { + const int line_y = 1+i; + window.MoveCursor(1, line_y); + const bool is_pc_line = curr_line == m_pc_line; + const bool line_is_selected = m_selected_line == curr_line; + // Highlight the line as the PC line first, then if the selected line + // isn't the same as the PC line, highlight it differently + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (is_pc_line) + highlight_attr = pc_highlight_attr; + else if (line_is_selected) + highlight_attr = selected_highlight_attr; + + if (bp_lines.find(curr_line+1) != bp_lines.end()) + bp_attr = COLOR_PAIR(2); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf (m_line_format, curr_line + 1); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + const uint32_t line_len = m_file_sp->GetLineLength(curr_line + 1, false); + if (line_len > 0) + window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len); + + if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) + { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) + { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) + { + size_t stop_description_len = strlen(stop_description); + int desc_x = window.GetWidth() - stop_description_len - 16; + window.Printf ("%*s", desc_x - window.GetCursorX(), ""); + //window.MoveCursor(window.GetWidth() - stop_description_len - 15, line_y); + window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); + } + } + else + { + window.Printf ("%*s", window.GetWidth() - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + + } + else + { + break; + } + } + } + else + { + size_t num_disassembly_lines = GetNumDisassemblyLines(); + if (num_disassembly_lines > 0) + { + // Display disassembly + BreakpointAddrs bp_file_addrs; + Target *target = exe_ctx.GetTargetPtr(); + if (target) + { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx=0; bp_idx<num_bps; ++bp_idx) + { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx=0; bp_loc_idx<num_bps_locs; ++bp_loc_idx) + { + BreakpointLocationSP bp_loc_sp = bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + const lldb::addr_t file_addr = bp_loc_sp->GetAddress().GetFileAddress(); + if (file_addr != LLDB_INVALID_ADDRESS) + { + if (m_disassembly_range.ContainsFileAddress(file_addr)) + bp_file_addrs.insert(file_addr); + } + } + } + } + + + const attr_t selected_highlight_attr = A_REVERSE; + const attr_t pc_highlight_attr = COLOR_PAIR(1); + + StreamString strm; + + InstructionList &insts = m_disassembly_sp->GetInstructionList(); + Address pc_address; + + if (frame_sp) + pc_address = frame_sp->GetFrameCodeAddress(); + const uint32_t pc_idx = pc_address.IsValid() ? insts.GetIndexOfInstructionAtAddress (pc_address) : UINT32_MAX; + if (set_selected_line_to_pc) + { + m_selected_line = pc_idx; + } + + const uint32_t non_visible_pc_offset = (num_visible_lines / 5); + if (m_first_visible_line >= num_disassembly_lines) + m_first_visible_line = 0; + + if (pc_idx < num_disassembly_lines) + { + if (pc_idx < m_first_visible_line || + pc_idx >= m_first_visible_line + num_visible_lines) + m_first_visible_line = pc_idx - non_visible_pc_offset; + } + + for (size_t i=0; i<num_visible_lines; ++i) + { + const uint32_t inst_idx = m_first_visible_line + i; + Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); + if (!inst) + break; + + window.MoveCursor(1, i+1); + const bool is_pc_line = frame_sp && inst_idx == pc_idx; + const bool line_is_selected = m_selected_line == inst_idx; + // Highlight the line as the PC line first, then if the selected line + // isn't the same as the PC line, highlight it differently + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (is_pc_line) + highlight_attr = pc_highlight_attr; + else if (line_is_selected) + highlight_attr = selected_highlight_attr; + + if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != bp_file_addrs.end()) + bp_attr = COLOR_PAIR(2); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf (" 0x%16.16llx ", inst->GetAddress().GetLoadAddress(target)); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + + const char *mnemonic = inst->GetMnemonic(&exe_ctx); + const char *operands = inst->GetOperands(&exe_ctx); + const char *comment = inst->GetComment(&exe_ctx); + + if (mnemonic && mnemonic[0] == '\0') + mnemonic = NULL; + if (operands && operands[0] == '\0') + operands = NULL; + if (comment && comment[0] == '\0') + comment = NULL; + + strm.Clear(); + + if (mnemonic && operands && comment) + strm.Printf ("%-8s %-25s ; %s", mnemonic, operands, comment); + else if (mnemonic && operands) + strm.Printf ("%-8s %s", mnemonic, operands); + else if (mnemonic) + strm.Printf ("%s", mnemonic); + + int right_pad = 1; + window.PutCStringTruncated(strm.GetString().c_str(), right_pad); + + if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) + { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) + { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) + { + size_t stop_description_len = strlen(stop_description); + int desc_x = window.GetWidth() - stop_description_len - 16; + window.Printf ("%*s", desc_x - window.GetCursorX(), ""); + //window.MoveCursor(window.GetWidth() - stop_description_len - 15, line_y); + window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); + } + } + else + { + window.Printf ("%*s", window.GetWidth() - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + } + } + } + window.DeferredRefresh(); + return true; // Drawing handled + } + + size_t + GetNumLines () + { + size_t num_lines = GetNumSourceLines(); + if (num_lines == 0) + num_lines = GetNumDisassemblyLines(); + return num_lines; + } + + size_t + GetNumSourceLines () const + { + if (m_file_sp) + return m_file_sp->GetNumLines(); + return 0; + } + size_t + GetNumDisassemblyLines () const + { + if (m_disassembly_sp) + return m_disassembly_sp->GetInstructionList().GetSize(); + return 0; + } + + virtual HandleCharResult + WindowDelegateHandleChar (Window &window, int c) + { + const uint32_t num_visible_lines = NumVisibleLines(); + const size_t num_lines = GetNumLines (); + + switch (c) + { + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_line > num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + m_selected_line = m_first_visible_line; + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + { + if (m_first_visible_line + num_visible_lines < num_lines) + m_first_visible_line += num_visible_lines; + else if (num_lines < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = num_lines - num_visible_lines; + m_selected_line = m_first_visible_line; + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_line > 0) + { + m_selected_line--; + if (m_first_visible_line > m_selected_line) + m_first_visible_line = m_selected_line; + } + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_line + 1 < num_lines) + { + m_selected_line++; + if (m_first_visible_line + num_visible_lines < m_selected_line) + m_first_visible_line++; + } + return eKeyHandled; + + case '\r': + case '\n': + case KEY_ENTER: + // Set a breakpoint and run to the line using a one shot breakpoint + if (GetNumSourceLines() > 0) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) + { + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + 1, // Source line number (m_selected_line is zero based) + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false); // request_hardware + // Make breakpoint one shot + bp_sp->GetOptions()->SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } + else if (m_selected_line < GetNumDisassemblyLines()) + { + const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) + { + Address addr = inst->GetAddress(); + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address + false, // internal + false); // request_hardware + // Make breakpoint one shot + bp_sp->GetOptions()->SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } + return eKeyHandled; + + case 'b': // 'b' == toggle breakpoint on currently selected line + if (m_selected_line < GetNumSourceLines()) + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) + { + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + 1, // Source line number (m_selected_line is zero based) + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false); // request_hardware + } + } + else if (m_selected_line < GetNumDisassemblyLines()) + { + const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) + { + Address addr = inst->GetAddress(); + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address + false, // internal + false); // request_hardware + } + } + return eKeyHandled; + + case 'd': // 'd' == detach and let run + case 'D': // 'D' == detach and keep stopped + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Detach(c == 'D'); + } + return eKeyHandled; + + case 'k': + // 'k' == kill + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Destroy(); + } + return eKeyHandled; + + case 'c': + // 'c' == continue + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Resume(); + } + return eKeyHandled; + + case 'o': + // 'o' == step out + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) + { + exe_ctx.GetThreadRef().StepOut(); + } + } + return eKeyHandled; + case 'n': // 'n' == step over + case 'N': // 'N' == step over instruction + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) + { + bool source_step = (c == 'n'); + exe_ctx.GetThreadRef().StepOver(source_step); + } + } + return eKeyHandled; + case 's': // 's' == step into + case 'S': // 'S' == step into instruction + { + ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) + { + bool source_step = (c == 's'); + bool avoid_code_without_debug_info = true; + exe_ctx.GetThreadRef().StepIn(source_step, avoid_code_without_debug_info); + } + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow (); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + typedef std::set<uint32_t> BreakpointLines; + typedef std::set<lldb::addr_t> BreakpointAddrs; + + Debugger &m_debugger; + SymbolContext m_sc; + SourceManager::FileSP m_file_sp; + SymbolContextScope *m_disassembly_scope; + lldb::DisassemblerSP m_disassembly_sp; + AddressRange m_disassembly_range; + lldb::user_id_t m_tid; + char m_line_format[8]; + int m_line_width; + uint32_t m_selected_line; // The selected line + uint32_t m_pc_line; // The line with the PC + uint32_t m_stop_id; + uint32_t m_frame_idx; + int m_first_visible_line; + int m_min_x; + int m_min_y; + int m_max_x; + int m_max_y; + +}; + +DisplayOptions ValueObjectListDelegate::g_options = { true }; + +IOHandlerCursesGUI::IOHandlerCursesGUI (Debugger &debugger) : + IOHandler (debugger) +{ +} + +void +IOHandlerCursesGUI::Activate () +{ + IOHandler::Activate(); + if (!m_app_ap) + { + m_app_ap.reset (new Application (GetInputFILE(), GetOutputFILE())); + + + // This is both a window and a menu delegate + std::shared_ptr<ApplicationDelegate> app_delegate_sp(new ApplicationDelegate(*m_app_ap, m_debugger)); + + MenuDelegateSP app_menu_delegate_sp = std::static_pointer_cast<MenuDelegate>(app_delegate_sp); + MenuSP lldb_menu_sp(new Menu("LLDB" , "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); + MenuSP exit_menuitem_sp(new Menu("Exit", NULL, 'x', ApplicationDelegate::eMenuID_LLDBExit)); + exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); + lldb_menu_sp->AddSubmenu (MenuSP (new Menu("About LLDB", NULL, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); + lldb_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); + lldb_menu_sp->AddSubmenu (exit_menuitem_sp); + + MenuSP target_menu_sp(new Menu("Target" ,"F2", KEY_F(2), ApplicationDelegate::eMenuID_Target)); + target_menu_sp->AddSubmenu (MenuSP (new Menu("Create", NULL, 'c', ApplicationDelegate::eMenuID_TargetCreate))); + target_menu_sp->AddSubmenu (MenuSP (new Menu("Delete", NULL, 'd', ApplicationDelegate::eMenuID_TargetDelete))); + + MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), ApplicationDelegate::eMenuID_Process)); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Attach" , NULL, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Detach" , NULL, 'd', ApplicationDelegate::eMenuID_ProcessDetach))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Launch" , NULL, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); + process_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Continue", NULL, 'c', ApplicationDelegate::eMenuID_ProcessContinue))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Halt" , NULL, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); + process_menu_sp->AddSubmenu (MenuSP (new Menu("Kill" , NULL, 'k', ApplicationDelegate::eMenuID_ProcessKill))); + + MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), ApplicationDelegate::eMenuID_Thread)); + thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step In" , NULL, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); + thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Over", NULL, 'v', ApplicationDelegate::eMenuID_ThreadStepOver))); + thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Out" , NULL, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); + + MenuSP view_menu_sp(new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Backtrace", NULL, 'b', ApplicationDelegate::eMenuID_ViewBacktrace))); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Registers", NULL, 'r', ApplicationDelegate::eMenuID_ViewRegisters))); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Source" , NULL, 's', ApplicationDelegate::eMenuID_ViewSource))); + view_menu_sp->AddSubmenu (MenuSP (new Menu("Variables", NULL, 'v', ApplicationDelegate::eMenuID_ViewVariables))); + + MenuSP help_menu_sp(new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); + help_menu_sp->AddSubmenu (MenuSP (new Menu("GUI Help", NULL, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); + + m_app_ap->Initialize(); + WindowSP &main_window_sp = m_app_ap->GetMainWindow(); + + MenuSP menubar_sp(new Menu(Menu::Type::Bar)); + menubar_sp->AddSubmenu (lldb_menu_sp); + menubar_sp->AddSubmenu (target_menu_sp); + menubar_sp->AddSubmenu (process_menu_sp); + menubar_sp->AddSubmenu (thread_menu_sp); + menubar_sp->AddSubmenu (view_menu_sp); + menubar_sp->AddSubmenu (help_menu_sp); + menubar_sp->SetDelegate(app_menu_delegate_sp); + + Rect content_bounds = main_window_sp->GetFrame(); + Rect menubar_bounds = content_bounds.MakeMenuBar(); + Rect status_bounds = content_bounds.MakeStatusBar(); + Rect source_bounds; + Rect variables_bounds; + Rect threads_bounds; + Rect source_variables_bounds; + content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, threads_bounds); + source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, variables_bounds); + + WindowSP menubar_window_sp = main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); + // Let the menubar get keys if the active window doesn't handle the + // keys that are typed so it can respond to menubar key presses. + menubar_window_sp->SetCanBeActive(false); // Don't let the menubar become the active window + menubar_window_sp->SetDelegate(menubar_sp); + + WindowSP source_window_sp (main_window_sp->CreateSubWindow("Source", + source_bounds, + true)); + WindowSP variables_window_sp (main_window_sp->CreateSubWindow("Variables", + variables_bounds, + false)); + WindowSP threads_window_sp (main_window_sp->CreateSubWindow("Threads", + threads_bounds, + false)); + WindowSP status_window_sp (main_window_sp->CreateSubWindow("Status", + status_bounds, + false)); + status_window_sp->SetCanBeActive(false); // Don't let the status bar become the active window + main_window_sp->SetDelegate (std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); + source_window_sp->SetDelegate (WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); + variables_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + TreeDelegateSP thread_delegate_sp (new ThreadTreeDelegate(m_debugger)); + threads_window_sp->SetDelegate (WindowDelegateSP(new TreeWindowDelegate(m_debugger, thread_delegate_sp))); + status_window_sp->SetDelegate (WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); + + // Show the main help window once the first time the curses GUI is launched + static bool g_showed_help = false; + if (!g_showed_help) + { + g_showed_help = true; + main_window_sp->CreateHelpSubwindow(); + } + + init_pair (1, COLOR_WHITE , COLOR_BLUE ); + init_pair (2, COLOR_BLACK , COLOR_WHITE ); + init_pair (3, COLOR_MAGENTA , COLOR_WHITE ); + init_pair (4, COLOR_MAGENTA , COLOR_BLACK ); + init_pair (5, COLOR_RED , COLOR_BLACK ); + + } +} + +void +IOHandlerCursesGUI::Deactivate () +{ + m_app_ap->Terminate(); +} + +void +IOHandlerCursesGUI::Run () +{ + m_app_ap->Run(m_debugger); + SetIsDone(true); +} + + +IOHandlerCursesGUI::~IOHandlerCursesGUI () +{ + +} + +void +IOHandlerCursesGUI::Hide () +{ +} + + +void +IOHandlerCursesGUI::Refresh () +{ +} + + +void +IOHandlerCursesGUI::Interrupt () +{ +} + + +void +IOHandlerCursesGUI::GotEOF() +{ +} + +#endif // #ifndef LLDB_DISABLE_CURSES
\ No newline at end of file diff --git a/source/Core/InputReader.cpp b/source/Core/InputReader.cpp deleted file mode 100644 index cbaa671..0000000 --- a/source/Core/InputReader.cpp +++ /dev/null @@ -1,387 +0,0 @@ -//===-- InputReader.cpp -----------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/lldb-python.h" - -#include <string> - -#include "lldb/Core/InputReader.h" -#include "lldb/Core/Debugger.h" -#include "lldb/Interpreter/CommandInterpreter.h" - -using namespace lldb; -using namespace lldb_private; - -InputReader::InputReader (Debugger &debugger) : - m_debugger (debugger), - m_callback (NULL), - m_callback_baton (NULL), - m_end_token (), - m_granularity (eInputReaderGranularityInvalid), - m_done (true), - m_echo (true), - m_active (false), - m_reader_done (false), - m_user_input(), - m_save_user_input(false) -{ -} - -InputReader::~InputReader () -{ -} - -Error -InputReader::Initialize -( - Callback callback, - void *baton, - lldb::InputReaderGranularity granularity, - const char *end_token, - const char *prompt, - bool echo -) -{ - Error err; - m_callback = callback; - m_callback_baton = baton, - m_granularity = granularity; - if (end_token != NULL) - m_end_token = end_token; - if (prompt != NULL) - m_prompt = prompt; - m_done = true; - m_echo = echo; - - if (m_granularity == eInputReaderGranularityInvalid) - { - err.SetErrorString ("Invalid read token size: Reader must be initialized with a token size other than 'eInputReaderGranularityInvalid'."); - } - else - if (end_token != NULL && granularity != eInputReaderGranularityInvalid) - { - if (granularity == eInputReaderGranularityByte) - { - // Check to see if end_token is longer than one byte. - - if (strlen (end_token) > 1) - { - err.SetErrorString ("Invalid end token: End token cannot be larger than specified token size (byte)."); - } - } - else if (granularity == eInputReaderGranularityWord) - { - // Check to see if m_end_token contains any white space (i.e. is multiple words). - - const char *white_space = " \t\n"; - size_t pos = m_end_token.find_first_of (white_space); - if (pos != std::string::npos) - { - err.SetErrorString ("Invalid end token: End token cannot be larger than specified token size (word)."); - } - } - else - { - // Check to see if m_end_token contains any newlines; cannot handle multi-line end tokens. - - size_t pos = m_end_token.find_first_of ('\n'); - if (pos != std::string::npos) - { - err.SetErrorString ("Invalid end token: End token cannot contain a newline."); - } - } - } - - m_done = err.Fail(); - - return err; -} - -size_t -InputReader::HandleRawBytes (const char *bytes, size_t bytes_len) -{ - const char *end_token = NULL; - - if (m_end_token.empty() == false) - { - end_token = ::strstr (bytes, m_end_token.c_str()); - if (end_token >= bytes + bytes_len) - end_token = NULL; - } - - const char *p = bytes; - const char *end = bytes + bytes_len; - - switch (m_granularity) - { - case eInputReaderGranularityInvalid: - break; - - case eInputReaderGranularityByte: - while (p < end) - { - if (end_token == p) - { - p += m_end_token.size(); - SetIsDone(true); - break; - } - - if (m_callback (m_callback_baton, *this, eInputReaderGotToken, p, 1) == 0) - break; - ++p; - if (IsDone()) - break; - } - // Return how many bytes were handled. - return p - bytes; - break; - - - case eInputReaderGranularityWord: - { - char quote = '\0'; - const char *word_start = NULL; - bool send_word = false; - for (; p < end; ++p, send_word = false) - { - if (end_token && end_token == p) - { - m_end_token.size(); - SetIsDone(true); - break; - } - - const char ch = *p; - if (isspace(ch) && (!quote || (quote == ch && p[-1] != '\\'))) - { - // We have a space character or the terminating quote - send_word = word_start != NULL; - quote = '\0'; - } - else if (quote) - { - // We are in the middle of a quoted character - continue; - } - else if (ch == '"' || ch == '\'' || ch == '`') - quote = ch; - else if (word_start == NULL) - { - // We have the first character in a word - word_start = p; - } - - if (send_word) - { - const size_t word_len = p - word_start; - size_t bytes_handled = m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - word_start, - word_len); - - if (bytes_handled != word_len) - return word_start - bytes + bytes_handled; - - if (IsDone()) - return p - bytes; - } - } - } - break; - - - case eInputReaderGranularityLine: - { - const char *line_start = bytes; - const char *end_line = NULL; - while (p < end) - { - const char ch = *p; - if (ch == '\n' || ch == '\r') - { - size_t line_length = p - line_start; - // Now skip the newline character - ++p; - // Skip a complete DOS newline if we run into one - if (ch == 0xd && p < end && *p == 0xa) - ++p; - - if (line_start <= end_token && end_token < line_start + line_length) - { - SetIsDone(true); - m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - line_start, - end_token - line_start); - - return p - bytes; - } - - size_t bytes_handled = m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - line_start, - line_length); - - end_line = p; - - if (bytes_handled != line_length) - { - // The input reader wasn't able to handle all the data - return line_start - bytes + bytes_handled; - } - - - if (IsDone()) - return p - bytes; - - line_start = p; - } - else - { - ++p; - } - } - - if (end_line) - return end_line - bytes; - } - break; - - - case eInputReaderGranularityAll: - { - // Nothing should be handle unless we see our end token - if (end_token) - { - size_t length = end_token - bytes; - size_t bytes_handled = m_callback (m_callback_baton, - *this, - eInputReaderGotToken, - bytes, - length); - m_done = true; - - p += bytes_handled + m_end_token.size(); - - // Consume any white space, such as newlines, beyond the end token - - while (p < end && isspace(*p)) - ++p; - - if (bytes_handled != length) - return bytes_handled; - else - { - return p - bytes; - //return bytes_handled + m_end_token.size(); - } - } - return 0; - } - break; - } - return 0; -} - -const char * -InputReader::GetPrompt () const -{ - if (!m_prompt.empty()) - return m_prompt.c_str(); - else - return NULL; -} - -void -InputReader::RefreshPrompt () -{ - if (m_debugger.GetCommandInterpreter().GetBatchCommandMode()) - return; - - if (!m_prompt.empty()) - { - File &out_file = m_debugger.GetOutputFile(); - if (out_file.IsValid()) - { - out_file.Printf ("%s", m_prompt.c_str()); - out_file.Flush(); - } - } -} - -void -InputReader::Notify (InputReaderAction notification) -{ - switch (notification) - { - case eInputReaderActivate: - case eInputReaderReactivate: - m_active = true; - m_reader_done.SetValue(false, eBroadcastAlways); - break; - - case eInputReaderDeactivate: - case eInputReaderDone: - m_active = false; - break; - - case eInputReaderAsynchronousOutputWritten: - break; - - case eInputReaderInterrupt: - case eInputReaderEndOfFile: - break; - - case eInputReaderGotToken: - return; // We don't notify the tokens here, it is done in HandleRawBytes - } - if (m_callback) - m_callback (m_callback_baton, *this, notification, NULL, 0); - if (notification == eInputReaderDone) - m_reader_done.SetValue(true, eBroadcastAlways); -} - -void -InputReader::WaitOnReaderIsDone () -{ - m_reader_done.WaitForValueEqualTo (true); -} - -const char * -InputReader::GranularityAsCString (lldb::InputReaderGranularity granularity) -{ - switch (granularity) - { - case eInputReaderGranularityInvalid: return "invalid"; - case eInputReaderGranularityByte: return "byte"; - case eInputReaderGranularityWord: return "word"; - case eInputReaderGranularityLine: return "line"; - case eInputReaderGranularityAll: return "all"; - } - - static char unknown_state_string[64]; - snprintf(unknown_state_string, sizeof (unknown_state_string), "InputReaderGranularity = %i", granularity); - return unknown_state_string; -} - -bool -InputReader::HandlerData::GetBatchMode() -{ - return reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode(); -} - -lldb::StreamSP -InputReader::HandlerData::GetOutStream() -{ - return reader.GetDebugger().GetAsyncOutputStream(); -} diff --git a/source/Core/InputReaderEZ.cpp b/source/Core/InputReaderEZ.cpp deleted file mode 100644 index 7a865bd..0000000 --- a/source/Core/InputReaderEZ.cpp +++ /dev/null @@ -1,91 +0,0 @@ -//===-- InputReaderEZ.cpp ---------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/Core/InputReaderEZ.h" - -using namespace lldb; -using namespace lldb_private; - -size_t -InputReaderEZ::Callback_Impl(void *baton, - InputReader &reader, - lldb::InputReaderAction notification, - const char *bytes, - size_t bytes_len) - -{ - HandlerData hand_data(reader, - bytes, - bytes_len, - baton); - - switch (notification) - { - case eInputReaderActivate: - reader.ActivateHandler(hand_data); - break; - case eInputReaderDeactivate: - reader.DeactivateHandler(hand_data); - break; - case eInputReaderReactivate: - reader.ReactivateHandler(hand_data); - break; - case eInputReaderAsynchronousOutputWritten: - reader.AsynchronousOutputWrittenHandler(hand_data); - break; - case eInputReaderGotToken: - { - if (reader.GetSaveUserInput()) - reader.GetUserInput().AppendString(bytes, bytes_len); - reader.GotTokenHandler(hand_data); - } - break; - case eInputReaderInterrupt: - reader.InterruptHandler(hand_data); - break; - case eInputReaderEndOfFile: - reader.EOFHandler(hand_data); - break; - case eInputReaderDone: - reader.DoneHandler(hand_data); - break; - } - return bytes_len; -} - -Error -InputReaderEZ::Initialize(void* baton, - lldb::InputReaderGranularity token_size, - const char* end_token, - const char *prompt, - bool echo) -{ - return InputReader::Initialize(Callback_Impl, - baton, - token_size, - end_token, - prompt, - echo); -} - -Error -InputReaderEZ::Initialize(InitializationParameters& params) -{ - Error ret = Initialize(params.m_baton, - params.m_token_size, - params.m_end_token, - params.m_prompt, - params.m_echo); - m_save_user_input = params.m_save_user_input; - return ret; -} - -InputReaderEZ::~InputReaderEZ () -{ -} diff --git a/source/Core/InputReaderStack.cpp b/source/Core/InputReaderStack.cpp deleted file mode 100644 index 764ea26..0000000 --- a/source/Core/InputReaderStack.cpp +++ /dev/null @@ -1,80 +0,0 @@ -//===-- InputReaderStack.cpp ------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/Core/InputReaderStack.h" - -// C Includes -// C++ Includes -// Other libraries and framework includes -// Project includes - - -using namespace lldb; -using namespace lldb_private; - -InputReaderStack::InputReaderStack () : - m_input_readers (), - m_input_readers_mutex (Mutex::eMutexTypeRecursive) -{ -} - -InputReaderStack::~InputReaderStack () -{ -} - -size_t -InputReaderStack::GetSize () const -{ - Mutex::Locker locker (m_input_readers_mutex); - return m_input_readers.size(); -} - -void -InputReaderStack::Push (const lldb::InputReaderSP& reader_sp) -{ - if (reader_sp) - { - Mutex::Locker locker (m_input_readers_mutex); - m_input_readers.push (reader_sp); - } -} - -bool -InputReaderStack::IsEmpty () const -{ - Mutex::Locker locker (m_input_readers_mutex); - return m_input_readers.empty(); -} - -InputReaderSP -InputReaderStack::Top () -{ - InputReaderSP input_reader_sp; - { - Mutex::Locker locker (m_input_readers_mutex); - if (!m_input_readers.empty()) - input_reader_sp = m_input_readers.top(); - } - - return input_reader_sp; -} - -void -InputReaderStack::Pop () -{ - Mutex::Locker locker (m_input_readers_mutex); - if (!m_input_readers.empty()) - m_input_readers.pop(); -} - -Mutex & -InputReaderStack::GetStackMutex () -{ - return m_input_readers_mutex; -} diff --git a/source/Core/Log.cpp b/source/Core/Log.cpp index 8d659cf..b7dc056 100644 --- a/source/Core/Log.cpp +++ b/source/Core/Log.cpp @@ -83,7 +83,10 @@ Log::GetMask() const void Log::PrintfWithFlagsVarArg (uint32_t flags, const char *format, va_list args) { - if (m_stream_sp) + // Make a copy of our stream shared pointer in case someone disables our + // log while we are logging and releases the stream + StreamSP stream_sp(m_stream_sp); + if (stream_sp) { static uint32_t g_sequence_id = 0; StreamString header; @@ -116,11 +119,11 @@ Log::PrintfWithFlagsVarArg (uint32_t flags, const char *format, va_list args) } header.PrintfVarArg (format, args); - m_stream_sp->Printf("%s\n", header.GetData()); + stream_sp->Printf("%s\n", header.GetData()); if (m_options.Test (LLDB_LOG_OPTION_BACKTRACE)) - Host::Backtrace (*m_stream_sp, 1024); - m_stream_sp->Flush(); + Host::Backtrace (*stream_sp, 1024); + stream_sp->Flush(); } } @@ -467,8 +470,11 @@ Log::GetVerbose() const if (m_options.Test(LLDB_LOG_OPTION_VERBOSE)) return true; - if (m_stream_sp) - return m_stream_sp->GetVerbose(); + // Make a copy of our stream shared pointer in case someone disables our + // log while we are logging and releases the stream + StreamSP stream_sp(m_stream_sp); + if (stream_sp) + return stream_sp->GetVerbose(); return false; } @@ -478,8 +484,11 @@ Log::GetVerbose() const bool Log::GetDebug() const { - if (m_stream_sp) - return m_stream_sp->GetDebug(); + // Make a copy of our stream shared pointer in case someone disables our + // log while we are logging and releases the stream + StreamSP stream_sp(m_stream_sp); + if (stream_sp) + return stream_sp->GetDebug(); return false; } diff --git a/source/Core/Mangled.cpp b/source/Core/Mangled.cpp index a41986d..55bd3cb 100644 --- a/source/Core/Mangled.cpp +++ b/source/Core/Mangled.cpp @@ -20,17 +20,10 @@ #ifdef LLDB_USE_BUILTIN_DEMANGLER -#include <vector> -#include <algorithm> -#include <string> -#include <numeric> -#include <cstdlib> -#include <cstring> -#include <cctype> //---------------------------------------------------------------------- // Inlined copy of: // http://llvm.org/svn/llvm-project/libcxxabi/trunk/src/cxa_demangle.cpp -// revision 193704. +// revision 199944. // // Changes include: // - remove the "__cxxabiv1" namespace @@ -38,8 +31,32 @@ // - removed extern "C" from the cxa_demangle function // - Changed the scope of the unnamed namespace to include cxa_demangle // function. +// - Added "#undef _LIBCPP_EXTERN_TEMPLATE" to avoid warning //---------------------------------------------------------------------- +#undef _LIBCPP_EXTERN_TEMPLATE // Avoid warning below + +//===-------------------------- cxa_demangle.cpp --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#define _LIBCPP_EXTERN_TEMPLATE(...) +#define _LIBCPP_NO_EXCEPTIONS + +#include <vector> +#include <algorithm> +#include <string> +#include <numeric> +#include <cstdlib> +#include <cstring> +#include <cctype> + + namespace { @@ -558,6 +575,8 @@ parse_template_param(const char* first, const char* last, C& db) { if (first[1] == '_') { + if (db.template_param.empty()) + return first; if (!db.template_param.back().empty()) { for (auto& t : db.template_param.back().front()) @@ -580,7 +599,7 @@ parse_template_param(const char* first, const char* last, C& db) sub *= 10; sub += static_cast<size_t>(*t - '0'); } - if (t == last || *t != '_') + if (t == last || *t != '_' || db.template_param.empty()) return first; ++sub; if (sub < db.template_param.back().size()) @@ -615,6 +634,8 @@ parse_const_cast_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto expr = db.names.back().move_full(); db.names.pop_back(); db.names.back() = "const_cast<" + db.names.back().move_full() + ">(" + expr + ")"; @@ -639,6 +660,8 @@ parse_dynamic_cast_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto expr = db.names.back().move_full(); db.names.pop_back(); db.names.back() = "dynamic_cast<" + db.names.back().move_full() + ">(" + expr + ")"; @@ -663,6 +686,8 @@ parse_reinterpret_cast_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto expr = db.names.back().move_full(); db.names.pop_back(); db.names.back() = "reinterpret_cast<" + db.names.back().move_full() + ">(" + expr + ")"; @@ -687,6 +712,8 @@ parse_static_cast_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto expr = db.names.back().move_full(); db.names.pop_back(); db.names.back() = "static_cast<" + db.names.back().move_full() + ">(" + expr + ")"; @@ -723,6 +750,8 @@ parse_sizeof_type_expr(const char* first, const char* last, C& db) const char* t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back() = "sizeof (" + db.names.back().move_full() + ")"; first = t; } @@ -741,6 +770,8 @@ parse_sizeof_expr_expr(const char* first, const char* last, C& db) const char* t = parse_expression(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back() = "sizeof (" + db.names.back().move_full() + ")"; first = t; } @@ -832,6 +863,8 @@ parse_sizeof_function_param_pack_expr(const char* first, const char* last, C& db const char* t = parse_function_param(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back() = "sizeof...(" + db.names.back().move_full() + ")"; first = t; } @@ -855,6 +888,8 @@ parse_typeid_expr(const char* first, const char* last, C& db) t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back() = "typeid(" + db.names.back().move_full() + ")"; first = t; } @@ -873,6 +908,8 @@ parse_throw_expr(const char* first, const char* last, C& db) const char* t = parse_expression(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back() = "throw " + db.names.back().move_full(); first = t; } @@ -894,6 +931,8 @@ parse_dot_star_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto expr = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += ".*" + expr; @@ -918,6 +957,8 @@ parse_simple_id(const char* first, const char* last, C& db) const char* t1 = parse_template_args(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto args = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += std::move(args); @@ -964,6 +1005,8 @@ parse_unresolved_type(const char* first, const char* last, C& db) t = parse_decltype(first, last, db); if (t != first) { + if (db.names.empty()) + return first; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); first = t; } @@ -979,6 +1022,8 @@ parse_unresolved_type(const char* first, const char* last, C& db) t = parse_unqualified_name(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "std::"); db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); first = t; @@ -1005,6 +1050,8 @@ parse_destructor_name(const char* first, const char* last, C& db) t = parse_simple_id(first, last, db); if (t != first) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "~"); first = t; } @@ -1036,6 +1083,8 @@ parse_base_unresolved_name(const char* first, const char* last, C& db) first = parse_template_args(t, last, db); if (first != t) { + if (db.names.size() < 2) + return first; auto args = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += std::move(args); @@ -1060,6 +1109,8 @@ parse_base_unresolved_name(const char* first, const char* last, C& db) first = parse_template_args(t, last, db); if (first != t) { + if (db.names.size() < 2) + return first; auto args = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += std::move(args); @@ -1109,7 +1160,11 @@ parse_unresolved_name(const char* first, const char* last, C& db) if (t2 != t) { if (global) + { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "::"); + } first = t2; } else if (last - t > 2 && t[0] == 's' && t[1] == 'r') @@ -1124,6 +1179,8 @@ parse_unresolved_name(const char* first, const char* last, C& db) t1 = parse_template_args(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto args = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += std::move(args); @@ -1137,7 +1194,7 @@ parse_unresolved_name(const char* first, const char* last, C& db) while (*t != 'E') { t1 = parse_unresolved_qualifier_level(t, last, db); - if (t1 == t || t1 == last) + if (t1 == t || t1 == last || db.names.size() < 2) return first; auto s = db.names.back().move_full(); db.names.pop_back(); @@ -1148,9 +1205,12 @@ parse_unresolved_name(const char* first, const char* last, C& db) t1 = parse_base_unresolved_name(t, last, db); if (t1 == t) { - db.names.pop_back(); + if (!db.names.empty()) + db.names.pop_back(); return first; } + if (db.names.size() < 2) + return first; auto s = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += "::" + std::move(s); @@ -1166,6 +1226,8 @@ parse_unresolved_name(const char* first, const char* last, C& db) t1 = parse_template_args(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto args = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += std::move(args); @@ -1174,9 +1236,12 @@ parse_unresolved_name(const char* first, const char* last, C& db) t1 = parse_base_unresolved_name(t, last, db); if (t1 == t) { - db.names.pop_back(); + if (!db.names.empty()) + db.names.pop_back(); return first; } + if (db.names.size() < 2) + return first; auto s = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += "::" + std::move(s); @@ -1189,11 +1254,15 @@ parse_unresolved_name(const char* first, const char* last, C& db) return first; t = t1; if (global) + { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "::"); + } while (*t != 'E') { t1 = parse_unresolved_qualifier_level(t, last, db); - if (t1 == t || t1 == last) + if (t1 == t || t1 == last || db.names.size() < 2) return first; auto s = db.names.back().move_full(); db.names.pop_back(); @@ -1204,9 +1273,12 @@ parse_unresolved_name(const char* first, const char* last, C& db) t1 = parse_base_unresolved_name(t, last, db); if (t1 == t) { - db.names.pop_back(); + if (!db.names.empty()) + db.names.pop_back(); return first; } + if (db.names.size() < 2) + return first; auto s = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += "::" + std::move(s); @@ -1232,6 +1304,8 @@ parse_dot_expr(const char* first, const char* last, C& db) const char* t1 = parse_unresolved_name(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto name = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += "." + name; @@ -1255,6 +1329,8 @@ parse_call_expr(const char* first, const char* last, C& db) { if (t == last) return first; + if (db.names.empty()) + return first; db.names.back().first += db.names.back().second; db.names.back().second = typename C::String(); db.names.back().first.append("("); @@ -1264,10 +1340,14 @@ parse_call_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 == t || t1 == last) return first; + if (db.names.empty()) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); if (!tmp.empty()) { + if (db.names.empty()) + return first; if (!first_expr) { db.names.back().first.append(", "); @@ -1278,6 +1358,8 @@ parse_call_expr(const char* first, const char* last, C& db) t = t1; } ++t; + if (db.names.empty()) + return first; db.names.back().first.append(")"); first = t; } @@ -1320,10 +1402,14 @@ parse_new_expr(const char* first, const char* last, C& db) has_expr_list = true; if (!first_expr) { + if (db.names.empty()) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); if (!tmp.empty()) { + if (db.names.empty()) + return first; db.names.back().first.append(", "); db.names.back().first.append(tmp); first_expr = false; @@ -1349,10 +1435,14 @@ parse_new_expr(const char* first, const char* last, C& db) return first; if (!first_expr) { + if (db.names.empty()) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); if (!tmp.empty()) { + if (db.names.empty()) + return first; db.names.back().first.append(", "); db.names.back().first.append(tmp); first_expr = false; @@ -1366,14 +1456,20 @@ parse_new_expr(const char* first, const char* last, C& db) typename C::String init_list; if (has_init) { + if (db.names.empty()) + return first; init_list = db.names.back().move_full(); db.names.pop_back(); } + if (db.names.empty()) + return first; auto type = db.names.back().move_full(); db.names.pop_back(); typename C::String expr_list; if (has_expr_list) { + if (db.names.empty()) + return first; expr_list = db.names.back().move_full(); db.names.pop_back(); } @@ -1435,10 +1531,14 @@ parse_conversion_expr(const char* first, const char* last, C& db) return first; if (!first_expr) { + if (db.names.empty()) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); if (!tmp.empty()) { + if (db.names.empty()) + return first; db.names.back().first.append(", "); db.names.back().first.append(tmp); first_expr = false; @@ -1449,6 +1549,8 @@ parse_conversion_expr(const char* first, const char* last, C& db) } ++t; } + if (db.names.size() < 2) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); db.names.back() = "(" + db.names.back().move_full() + ")(" + tmp + ")"; @@ -1472,6 +1574,8 @@ parse_arrow_expr(const char* first, const char* last, C& db) const char* t1 = parse_expression(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += "->"; @@ -1564,6 +1668,8 @@ parse_function_type(const char* first, const char* last, C& db) sig += " &&"; break; } + if (db.names.empty()) + return first; db.names.back().first += " "; db.names.back().second.insert(0, sig); first = t; @@ -1587,6 +1693,8 @@ parse_pointer_to_member_type(const char* first, const char* last, C& db) const char* t2 = parse_type(t, last, db); if (t2 != t) { + if (db.names.size() < 2) + return first; auto func = std::move(db.names.back()); db.names.pop_back(); auto class_type = std::move(db.names.back()); @@ -1621,6 +1729,8 @@ parse_array_type(const char* first, const char* last, C& db) const char* t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; if (db.names.back().second.substr(0, 2) == " [") db.names.back().second.erase(0, 1); db.names.back().second.insert(0, " []"); @@ -1635,6 +1745,8 @@ parse_array_type(const char* first, const char* last, C& db) const char* t2 = parse_type(t+1, last, db); if (t2 != t+1) { + if (db.names.empty()) + return first; if (db.names.back().second.substr(0, 2) == " [") db.names.back().second.erase(0, 1); db.names.back().second.insert(0, " [" + typename C::String(first+1, t) + "]"); @@ -1650,6 +1762,8 @@ parse_array_type(const char* first, const char* last, C& db) const char* t2 = parse_type(++t, last, db); if (t2 != t) { + if (db.names.size() < 2) + return first; auto type = std::move(db.names.back()); db.names.pop_back(); auto expr = std::move(db.names.back()); @@ -1682,6 +1796,8 @@ parse_decltype(const char* first, const char* last, C& db) const char* t = parse_expression(first+2, last, db); if (t != first+2 && t != last && *t == 'E') { + if (db.names.empty()) + return first; db.names.back() = "decltype(" + db.names.back().move_full() + ")"; first = t+1; } @@ -1719,6 +1835,8 @@ parse_vector_type(const char* first, const char* last, C& db) const char* t1 = parse_type(t, last, db); if (t1 != t) { + if (db.names.empty()) + return first; db.names.back().first += " vector[" + typename C::String(num, sz) + "]"; first = t1; } @@ -1740,6 +1858,8 @@ parse_vector_type(const char* first, const char* last, C& db) const char* t = parse_expression(t1, last, db); if (t != t1) { + if (db.names.empty()) + return first; num = db.names.back().move_full(); db.names.pop_back(); t1 = t; @@ -1750,6 +1870,8 @@ parse_vector_type(const char* first, const char* last, C& db) const char* t = parse_type(t1, last, db); if (t != t1) { + if (db.names.empty()) + return first; db.names.back().first += " vector[" + num + "]"; first = t; } @@ -1860,6 +1982,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_array_type(first, last, db); if (t != first) { + if (db.names.empty()) + return first; first = t; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); } @@ -1868,6 +1992,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_type(first+1, last, db); if (t != first+1) { + if (db.names.empty()) + return first; db.names.back().first.append(" complex"); first = t; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); @@ -1877,6 +2003,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_function_type(first, last, db); if (t != first) { + if (db.names.empty()) + return first; first = t; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); } @@ -1885,6 +2013,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_type(first+1, last, db); if (t != first+1) { + if (db.names.empty()) + return first; db.names.back().first.append(" imaginary"); first = t; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); @@ -1894,6 +2024,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_pointer_to_member_type(first, last, db); if (t != first) { + if (db.names.empty()) + return first; first = t; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); } @@ -2021,6 +2153,8 @@ parse_type(const char* first, const char* last, C& db) const char* t2 = parse_type(t, last, db); if (t2 != t) { + if (db.names.size() < 2) + return first; auto type = db.names.back().move_full(); db.names.pop_back(); if (db.names.back().first.substr(0, 9) != "objcproto") @@ -2053,6 +2187,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_name(first, last, db); if (t != first) { + if (db.names.empty()) + return first; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); first = t; } @@ -2068,6 +2204,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_template_args(first, last, db); if (t != first) { + if (db.names.size() < 2) + return first; auto template_args = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += template_args; @@ -2103,6 +2241,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_decltype(first, last, db); if (t != first) { + if (db.names.empty()) + return first; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); first = t; return first; @@ -2112,6 +2252,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_vector_type(first, last, db); if (t != first) { + if (db.names.empty()) + return first; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); first = t; return first; @@ -2133,6 +2275,8 @@ parse_type(const char* first, const char* last, C& db) t = parse_name(first, last, db); if (t != first) { + if (db.names.empty()) + return first; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); first = t; } @@ -2169,6 +2313,7 @@ parse_type(const char* first, const char* last, C& db) // ::= gt # > // ::= ix # [] // ::= le # <= +// ::= li <source-name> # operator "" // ::= ls # << // ::= lS # <<= // ::= lt # < @@ -2251,6 +2396,8 @@ parse_operator_name(const char* first, const char* last, C& db) db.try_to_parse_template_args = try_to_parse_template_args; if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "operator "); db.parsed_ctor_dtor_cv = true; first = t; @@ -2328,6 +2475,18 @@ parse_operator_name(const char* first, const char* last, C& db) db.names.push_back("operator<="); first += 2; break; + case 'i': + { + const char* t = parse_source_name(first+2, last, db); + if (t != first+2) + { + if (db.names.empty()) + return first; + db.names.back().first.insert(0, "operator\"\" "); + first = t; + } + } + break; case 's': db.names.push_back("operator<<"); first += 2; @@ -2472,6 +2631,8 @@ parse_operator_name(const char* first, const char* last, C& db) const char* t = parse_source_name(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "operator "); first = t; } @@ -2681,6 +2842,8 @@ parse_expr_primary(const char* first, const char* last, C& db) ; if (n != t && n != last && *n == 'E') { + if (db.names.empty()) + return first; db.names.back() = "(" + db.names.back().move_full() + ")" + typename C::String(t, n); first = n+1; break; @@ -2781,6 +2944,8 @@ parse_ctor_dtor_name(const char* first, const char* last, C& db) case '2': case '3': case '5': + if (db.names.empty()) + return first; db.names.push_back(base_name(db.names.back().first)); first += 2; db.parsed_ctor_dtor_cv = true; @@ -2794,6 +2959,8 @@ parse_ctor_dtor_name(const char* first, const char* last, C& db) case '1': case '2': case '5': + if (db.names.empty()) + return first; db.names.push_back("~" + base_name(db.names.back().first)); first += 2; db.parsed_ctor_dtor_cv = true; @@ -2864,6 +3031,8 @@ parse_unnamed_type_name(const char* first, const char* last, C& db) db.names.pop_back(); return first; } + if (db.names.size() < 2) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); db.names.back().first.append(tmp); @@ -2873,6 +3042,8 @@ parse_unnamed_type_name(const char* first, const char* last, C& db) t1 = parse_type(t0, last, db); if (t1 == t0) break; + if (db.names.size() < 2) + return first; tmp = db.names.back().move_full(); db.names.pop_back(); if (!tmp.empty()) @@ -2987,7 +3158,11 @@ parse_unscoped_name(const char* first, const char* last, C& db) if (t1 != t0) { if (St) + { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "std::"); + } first = t1; } } @@ -3005,6 +3180,8 @@ parse_alignof_type(const char* first, const char* last, C& db) const char* t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first = "alignof (" + db.names.back().move_full() + ")"; first = t; } @@ -3023,6 +3200,8 @@ parse_alignof_expr(const char* first, const char* last, C& db) const char* t = parse_expression(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first = "alignof (" + db.names.back().move_full() + ")"; first = t; } @@ -3037,6 +3216,8 @@ parse_noexcept_expression(const char* first, const char* last, C& db) const char* t1 = parse_expression(first, last, db); if (t1 != first) { + if (db.names.empty()) + return first; db.names.back().first = "noexcept (" + db.names.back().move_full() + ")"; first = t1; } @@ -3050,6 +3231,8 @@ parse_prefix_expression(const char* first, const char* last, const typename C::S const char* t1 = parse_expression(first, last, db); if (t1 != first) { + if (db.names.empty()) + return first; db.names.back().first = op + "(" + db.names.back().move_full() + ")"; first = t1; } @@ -3066,6 +3249,8 @@ parse_binary_expression(const char* first, const char* last, const typename C::S const char* t2 = parse_expression(t1, last, db); if (t2 != t1) { + if (db.names.size() < 2) + return first; auto op2 = db.names.back().move_full(); db.names.pop_back(); auto op1 = db.names.back().move_full(); @@ -3216,6 +3401,8 @@ parse_expression(const char* first, const char* last, C& db) const char* t1 = parse_expression(t+2, last, db); if (t1 != t+2) { + if (db.names.empty()) + return first; db.names.back().first = (parsed_gs ? typename C::String("::") : typename C::String()) + "delete[] " + db.names.back().move_full(); first = t1; @@ -3235,6 +3422,8 @@ parse_expression(const char* first, const char* last, C& db) const char* t1 = parse_expression(t+2, last, db); if (t1 != t+2) { + if (db.names.empty()) + return first; db.names.back().first = (parsed_gs ? typename C::String("::") : typename C::String()) + "delete " + db.names.back().move_full(); first = t1; @@ -3305,6 +3494,8 @@ parse_expression(const char* first, const char* last, C& db) const char* t2 = parse_expression(t1, last, db); if (t2 != t1) { + if (db.names.size() < 2) + return first; auto op2 = db.names.back().move_full(); db.names.pop_back(); auto op1 = db.names.back().move_full(); @@ -3376,6 +3567,8 @@ parse_expression(const char* first, const char* last, C& db) const char* t1 = parse_expression(first+2, last, db); if (t1 != first+2) { + if (db.names.empty()) + return first; db.names.back() = "(" + db.names.back().move_full() + ")--"; first = t1; } @@ -3464,6 +3657,8 @@ parse_expression(const char* first, const char* last, C& db) const char* t1 = parse_expression(first+2, last, db); if (t1 != first+2) { + if (db.names.empty()) + return first; db.names.back() = "(" + db.names.back().move_full() + ")++"; first = t1; } @@ -3491,6 +3686,8 @@ parse_expression(const char* first, const char* last, C& db) const char* t3 = parse_expression(t2, last, db); if (t3 != t2) { + if (db.names.size() < 3) + return first; auto op3 = db.names.back().move_full(); db.names.pop_back(); auto op2 = db.names.back().move_full(); @@ -3918,6 +4115,8 @@ parse_local_name(const char* first, const char* last, C& db) { case 's': first = parse_discriminator(t+1, last); + if (db.names.empty()) + return first; db.names.back().first.append("::string literal"); break; case 'd': @@ -3930,6 +4129,8 @@ parse_local_name(const char* first, const char* last, C& db) t1 = parse_name(t, last, db); if (t1 != t) { + if (db.names.size() < 2) + return first; auto name = db.names.back().move_full(); db.names.pop_back(); db.names.back().first.append("::"); @@ -3948,6 +4149,8 @@ parse_local_name(const char* first, const char* last, C& db) { // parse but ignore discriminator first = parse_discriminator(t1, last); + if (db.names.size() < 2) + return first; auto name = db.names.back().move_full(); db.names.pop_back(); db.names.back().first.append("::"); @@ -4004,11 +4207,15 @@ parse_name(const char* first, const char* last, C& db) { if (t1 != last && *t1 == 'I') // <unscoped-template-name> <template-args> { + if (db.names.empty()) + return first; db.subs.push_back(typename C::sub_type(1, db.names.back(), db.names.get_allocator())); t0 = t1; t1 = parse_template_args(t0, last, db); if (t1 != t0) { + if (db.names.size() < 2) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += tmp; @@ -4027,6 +4234,8 @@ parse_name(const char* first, const char* last, C& db) t1 = parse_template_args(t0, last, db); if (t1 != t0) { + if (db.names.size() < 2) + return first; auto tmp = db.names.back().move_full(); db.names.pop_back(); db.names.back().first += tmp; @@ -4112,6 +4321,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "vtable for "); first = t; } @@ -4121,6 +4332,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "VTT for "); first = t; } @@ -4130,6 +4343,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "typeinfo for "); first = t; } @@ -4139,6 +4354,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_type(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "typeinfo name for "); first = t; } @@ -4155,6 +4372,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_encoding(t1, last, db); if (t != t1) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "covariant return thunk to "); first = t; } @@ -4171,6 +4390,8 @@ parse_special_name(const char* first, const char* last, C& db) const char* t1 = parse_type(++t0, last, db); if (t1 != t0) { + if (db.names.size() < 2) + return first; auto left = db.names.back().move_full(); db.names.pop_back(); db.names.back().first = "construction vtable for " + @@ -4190,6 +4411,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_encoding(t0, last, db); if (t != t0) { + if (db.names.empty()) + return first; if (first[2] == 'v') { db.names.back().first.insert(0, "virtual thunk to "); @@ -4213,6 +4436,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_name(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "guard variable for "); first = t; } @@ -4222,6 +4447,8 @@ parse_special_name(const char* first, const char* last, C& db) t = parse_name(first+2, last, db); if (t != first+2) { + if (db.names.empty()) + return first; db.names.back().first.insert(0, "reference temporary for "); first = t; } @@ -4233,6 +4460,26 @@ parse_special_name(const char* first, const char* last, C& db) return first; } +template <class T> +class save_value +{ + T& restore_; + T original_value_; +public: + save_value(T& restore) + : restore_(restore), + original_value_(restore) + {} + + ~save_value() + { + restore_ = std::move(original_value_); + } + + save_value(const save_value&) = delete; + save_value& operator=(const save_value&) = delete; +}; + // <encoding> ::= <function name> <bare-function-type> // ::= <data name> // ::= <special-name> @@ -4243,6 +4490,11 @@ parse_encoding(const char* first, const char* last, C& db) { if (first != last) { + save_value<decltype(db.encoding_depth)> su(db.encoding_depth); + ++db.encoding_depth; + save_value<decltype(db.tag_templates)> sb(db.tag_templates); + if (db.encoding_depth > 1) + db.tag_templates = true; switch (*first) { case 'G': @@ -4258,17 +4510,23 @@ parse_encoding(const char* first, const char* last, C& db) { if (t != last && *t != 'E' && *t != '.') { - bool tag_templates = db.tag_templates; + save_value<bool> sb2(db.tag_templates); db.tag_templates = false; const char* t2; typename C::String ret2; + if (db.names.empty()) + return first; const typename C::String& nm = db.names.back().first; + if (nm.empty()) + return first; if (!db.parsed_ctor_dtor_cv && nm.back() == '>' && nm[nm.size()-2] != '-' && nm[nm.size()-2] != '>') { t2 = parse_type(t, last, db); if (t2 == t) return first; + if (db.names.size() < 2) + return first; auto ret1 = std::move(db.names.back().first); ret2 = std::move(db.names.back().second); if (ret2.empty()) @@ -4305,6 +4563,8 @@ parse_encoding(const char* first, const char* last, C& db) db.names.pop_back(); if (!tmp.empty()) { + if (db.names.empty()) + return first; if (!first_arg) db.names.back().first += ", "; else @@ -4315,6 +4575,8 @@ parse_encoding(const char* first, const char* last, C& db) t = t2; } } + if (db.names.empty()) + return first; db.names.back().first += ')'; if (cv & 1) db.names.back().first.append(" const"); @@ -4328,7 +4590,6 @@ parse_encoding(const char* first, const char* last, C& db) db.names.back().first.append(" &&"); db.names.back().first += ret2; first = t; - db.tag_templates = tag_templates; } else first = t; @@ -4370,6 +4631,8 @@ parse_block_invoke(const char* first, const char* last, C& db) while (t != last && isdigit(*t)) ++t; } + if (db.names.empty()) + return first; db.names.back().first.insert(0, "invocation function for block in "); first = t; } @@ -4385,6 +4648,8 @@ parse_dot_suffix(const char* first, const char* last, C& db) { if (first != last && *first == '.') { + if (db.names.empty()) + return first; db.names.back().first += " (" + typename C::String(first, last) + ")"; first = last; } @@ -4620,6 +4885,7 @@ struct Db Vector<template_param_type> template_param; unsigned cv; unsigned ref; + unsigned encoding_depth; bool parsed_ctor_dtor_cv; bool tag_templates; bool fix_forward_references; @@ -4647,6 +4913,7 @@ __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status) Db db(a); db.cv = 0; db.ref = 0; + db.encoding_depth = 0; db.parsed_ctor_dtor_cv = false; db.tag_templates = true; db.template_param.emplace_back(a); @@ -4699,8 +4966,7 @@ __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status) return buf; } -} // unnamed namespace - +} #endif diff --git a/source/Core/Module.cpp b/source/Core/Module.cpp index f90a097..d5758c0 100644 --- a/source/Core/Module.cpp +++ b/source/Core/Module.cpp @@ -33,6 +33,7 @@ #include "lldb/Target/CPPLanguageRuntime.h" #include "lldb/Target/ObjCLanguageRuntime.h" #include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Symbol/SymbolFile.h" @@ -1499,30 +1500,19 @@ Module::SetArchitecture (const ArchSpec &new_arch) } bool -Module::SetLoadAddress (Target &target, lldb::addr_t offset, bool &changed) +Module::SetLoadAddress (Target &target, lldb::addr_t value, bool value_is_offset, bool &changed) { - size_t num_loaded_sections = 0; - SectionList *section_list = GetSectionList (); - if (section_list) + ObjectFile *object_file = GetObjectFile(); + if (object_file) { - const size_t num_sections = section_list->GetSize(); - size_t sect_idx = 0; - for (sect_idx = 0; sect_idx < num_sections; ++sect_idx) - { - // Iterate through the object file sections to find the - // first section that starts of file offset zero and that - // has bytes in the file... - SectionSP section_sp (section_list->GetSectionAtIndex (sect_idx)); - // Only load non-thread specific sections when given a slide - if (section_sp && !section_sp->IsThreadSpecific()) - { - if (target.GetSectionLoadList().SetSectionLoadAddress (section_sp, section_sp->GetFileAddress() + offset)) - ++num_loaded_sections; - } - } + changed = object_file->SetLoadAddress(target, value, value_is_offset); + return true; } - changed = num_loaded_sections > 0; - return num_loaded_sections > 0; + else + { + changed = false; + } + return false; } diff --git a/source/Core/Opcode.cpp b/source/Core/Opcode.cpp index 978a110..2bf7f5e 100644 --- a/source/Core/Opcode.cpp +++ b/source/Core/Opcode.cpp @@ -71,6 +71,10 @@ Opcode::Dump (Stream *s, uint32_t min_byte_width) lldb::ByteOrder Opcode::GetDataByteOrder () const { + if (m_byte_order != eByteOrderInvalid) + { + return m_byte_order; + } switch (m_type) { case Opcode::eTypeInvalid: break; @@ -89,39 +93,66 @@ uint32_t Opcode::GetData (DataExtractor &data) const { uint32_t byte_size = GetByteSize (); + uint8_t swap_buf[8]; + const void *buf = NULL; - DataBufferSP buffer_sp; if (byte_size > 0) { - switch (m_type) + if (!GetEndianSwap()) + { + if (m_type == Opcode::eType16_2) + { + // 32 bit thumb instruction, we need to sizzle this a bit + swap_buf[0] = m_data.inst.bytes[2]; + swap_buf[1] = m_data.inst.bytes[3]; + swap_buf[2] = m_data.inst.bytes[0]; + swap_buf[3] = m_data.inst.bytes[1]; + buf = swap_buf; + } + else + { + buf = GetOpcodeDataBytes(); + } + } + else { - case Opcode::eTypeInvalid: - break; - - case Opcode::eType8: buffer_sp.reset (new DataBufferHeap (&m_data.inst8, byte_size)); break; - case Opcode::eType16: buffer_sp.reset (new DataBufferHeap (&m_data.inst16, byte_size)); break; - case Opcode::eType16_2: - { - // 32 bit thumb instruction, we need to sizzle this a bit - uint8_t buf[4]; - buf[0] = m_data.inst.bytes[2]; - buf[1] = m_data.inst.bytes[3]; - buf[2] = m_data.inst.bytes[0]; - buf[3] = m_data.inst.bytes[1]; - buffer_sp.reset (new DataBufferHeap (buf, byte_size)); - } - break; - case Opcode::eType32: - buffer_sp.reset (new DataBufferHeap (&m_data.inst32, byte_size)); - break; - case Opcode::eType64: buffer_sp.reset (new DataBufferHeap (&m_data.inst64, byte_size)); break; - case Opcode::eTypeBytes:buffer_sp.reset (new DataBufferHeap (GetOpcodeBytes(), byte_size)); break; - break; + switch (m_type) + { + case Opcode::eTypeInvalid: + break; + case Opcode::eType8: + buf = GetOpcodeDataBytes(); + break; + case Opcode::eType16: + *(uint16_t *)swap_buf = llvm::ByteSwap_16(m_data.inst16); + buf = swap_buf; + break; + case Opcode::eType16_2: + swap_buf[0] = m_data.inst.bytes[1]; + swap_buf[1] = m_data.inst.bytes[0]; + swap_buf[2] = m_data.inst.bytes[3]; + swap_buf[3] = m_data.inst.bytes[2]; + buf = swap_buf; + break; + case Opcode::eType32: + *(uint32_t *)swap_buf = llvm::ByteSwap_32(m_data.inst32); + buf = swap_buf; + break; + case Opcode::eType64: + *(uint32_t *)swap_buf = llvm::ByteSwap_64(m_data.inst64); + buf = swap_buf; + break; + case Opcode::eTypeBytes: + buf = GetOpcodeDataBytes(); + break; + } } } - - if (buffer_sp) + if (buf) { + DataBufferSP buffer_sp; + + buffer_sp.reset (new DataBufferHeap (buf, byte_size)); data.SetByteOrder(GetDataByteOrder()); data.SetData (buffer_sp); return byte_size; diff --git a/source/Core/Section.cpp b/source/Core/Section.cpp index e2a084c..28d7d93 100644 --- a/source/Core/Section.cpp +++ b/source/Core/Section.cpp @@ -10,6 +10,7 @@ #include "lldb/Core/Section.h" #include "lldb/Core/Module.h" #include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" using namespace lldb; diff --git a/source/Core/SourceManager.cpp b/source/Core/SourceManager.cpp index 9400346..0a6a804 100644 --- a/source/Core/SourceManager.cpp +++ b/source/Core/SourceManager.cpp @@ -432,6 +432,56 @@ SourceManager::File::GetLineOffset (uint32_t line) return UINT32_MAX; } +uint32_t +SourceManager::File::GetNumLines () +{ + CalculateLineOffsets(); + return m_offsets.size(); +} + +const char * +SourceManager::File::PeekLineData (uint32_t line) +{ + if (!LineIsValid(line)) + return NULL; + + size_t line_offset = GetLineOffset (line); + if (line_offset < m_data_sp->GetByteSize()) + return (const char *)m_data_sp->GetBytes() + line_offset; + return NULL; +} + +uint32_t +SourceManager::File::GetLineLength (uint32_t line, bool include_newline_chars) +{ + if (!LineIsValid(line)) + return false; + + size_t start_offset = GetLineOffset (line); + size_t end_offset = GetLineOffset (line + 1); + if (end_offset == UINT32_MAX) + end_offset = m_data_sp->GetByteSize(); + + if (end_offset > start_offset) + { + uint32_t length = end_offset - start_offset; + if (include_newline_chars == false) + { + const char *line_start = (const char *)m_data_sp->GetBytes() + start_offset; + while (length > 0) + { + const char last_char = line_start[length-1]; + if ((last_char == '\r') || (last_char == '\n')) + --length; + else + break; + } + } + return length; + } + return 0; +} + bool SourceManager::File::LineIsValid (uint32_t line) { diff --git a/source/Core/StreamAsynchronousIO.cpp b/source/Core/StreamAsynchronousIO.cpp index b9e5cdf..257982a 100644 --- a/source/Core/StreamAsynchronousIO.cpp +++ b/source/Core/StreamAsynchronousIO.cpp @@ -28,25 +28,26 @@ StreamAsynchronousIO::StreamAsynchronousIO (Broadcaster &broadcaster, uint32_t b StreamAsynchronousIO::~StreamAsynchronousIO () { + // Flush when we destroy to make sure we display the data + Flush(); } void StreamAsynchronousIO::Flush () { - if (m_accumulated_data.GetSize() > 0) + if (!m_accumulated_data.empty()) { std::unique_ptr<EventDataBytes> data_bytes_ap (new EventDataBytes); // Let's swap the bytes to avoid LARGE string copies. - data_bytes_ap->SwapBytes (m_accumulated_data.GetString()); + data_bytes_ap->SwapBytes (m_accumulated_data); EventSP new_event_sp (new Event (m_broadcast_event_type, data_bytes_ap.release())); m_broadcaster.BroadcastEvent (new_event_sp); - m_accumulated_data.Clear(); } } size_t StreamAsynchronousIO::Write (const void *s, size_t length) { - m_accumulated_data.Write (s, length); + m_accumulated_data.append ((const char *)s, length); return length; } diff --git a/source/Core/StringList.cpp b/source/Core/StringList.cpp index 9949751..d2fa8cf 100644 --- a/source/Core/StringList.cpp +++ b/source/Core/StringList.cpp @@ -56,6 +56,12 @@ StringList::AppendString (const std::string &s) } void +StringList::AppendString (std::string &&s) +{ + m_strings.push_back (s); +} + +void StringList::AppendString (const char *str, size_t str_len) { if (str) @@ -93,6 +99,20 @@ StringList::GetSize () const return m_strings.size(); } +size_t +StringList::GetMaxStringLength () const +{ + size_t max_length = 0; + for (const auto &s : m_strings) + { + const size_t len = s.size(); + if (max_length < len) + max_length = len; + } + return max_length; +} + + const char * StringList::GetStringAtIndex (size_t idx) const { @@ -126,37 +146,39 @@ StringList::Clear () void StringList::LongestCommonPrefix (std::string &common_prefix) { - //arg_sstr_collection::iterator pos, end = m_args.end(); - size_t pos = 0; - size_t end = m_strings.size(); + const size_t num_strings = m_strings.size(); - if (pos == end) + if (num_strings == 0) + { common_prefix.clear(); + } else - common_prefix = m_strings[pos]; - - for (++pos; pos != end; ++pos) { - size_t new_size = strlen (m_strings[pos].c_str()); + common_prefix = m_strings.front(); - // First trim common_prefix if it is longer than the current element: - if (common_prefix.size() > new_size) - common_prefix.erase (new_size); + for (size_t idx = 1; idx < num_strings; ++idx) + { + std::string &curr_string = m_strings[idx]; + size_t new_size = curr_string.size(); - // Then trim it at the first disparity: + // First trim common_prefix if it is longer than the current element: + if (common_prefix.size() > new_size) + common_prefix.erase (new_size); - for (size_t i = 0; i < common_prefix.size(); i++) - { - if (m_strings[pos][i] != common_prefix[i]) + // Then trim it at the first disparity: + for (size_t i = 0; i < common_prefix.size(); i++) { - common_prefix.erase(i); - break; + if (curr_string[i] != common_prefix[i]) + { + common_prefix.erase(i); + break; + } } - } - // If we've emptied the common prefix, we're done. - if (common_prefix.empty()) - break; + // If we've emptied the common prefix, we're done. + if (common_prefix.empty()) + break; + } } } @@ -173,6 +195,24 @@ StringList::InsertStringAtIndex (size_t idx, const char *str) } void +StringList::InsertStringAtIndex (size_t idx, const std::string &str) +{ + if (idx < m_strings.size()) + m_strings.insert (m_strings.begin() + idx, str); + else + m_strings.push_back (str); +} + +void +StringList::InsertStringAtIndex (size_t idx, std::string &&str) +{ + if (idx < m_strings.size()) + m_strings.insert (m_strings.begin() + idx, str); + else + m_strings.push_back (str); +} + +void StringList::DeleteStringAtIndex (size_t idx) { if (idx < m_strings.size()) @@ -180,6 +220,12 @@ StringList::DeleteStringAtIndex (size_t idx) } size_t +StringList::SplitIntoLines (const std::string &lines) +{ + return SplitIntoLines (lines.c_str(), lines.size()); +} + +size_t StringList::SplitIntoLines (const char *lines, size_t len) { const size_t orig_size = m_strings.size(); @@ -231,8 +277,7 @@ StringList::RemoveBlankLines () } std::string -StringList::CopyList(const char* item_preamble, - const char* items_sep) +StringList::CopyList(const char* item_preamble, const char* items_sep) const { StreamString strm; for (size_t i = 0; i < GetSize(); i++) diff --git a/source/Core/Value.cpp b/source/Core/Value.cpp index 9dd72c7..9d42a37 100644 --- a/source/Core/Value.cpp +++ b/source/Core/Value.cpp @@ -26,6 +26,7 @@ #include "lldb/Symbol/Variable.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" using namespace lldb; diff --git a/source/Core/ValueObject.cpp b/source/Core/ValueObject.cpp index d39d21a..10e5ab4 100644 --- a/source/Core/ValueObject.cpp +++ b/source/Core/ValueObject.cpp @@ -50,6 +50,7 @@ #include "lldb/Target/ObjCLanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" @@ -977,14 +978,14 @@ ValueObject::GetPointeeData (DataExtractor& data, ValueObjectSP pointee_sp = Dereference(error); if (error.Fail() || pointee_sp.get() == NULL) return 0; - return pointee_sp->GetDataExtractor().Copy(data); + return pointee_sp->GetData(data); } else { ValueObjectSP child_sp = GetChildAtIndex(0, true); if (child_sp.get() == NULL) return 0; - return child_sp->GetDataExtractor().Copy(data); + return child_sp->GetData(data); } return true; } @@ -1388,99 +1389,33 @@ ValueObject::GetObjectDescription () } bool -ValueObject::GetValueAsCString (lldb::Format format, +ValueObject::GetValueAsCString (const lldb_private::TypeFormatImpl& format, std::string& destination) { - if (GetClangType().IsAggregateType () == false && UpdateValueIfNeeded(false)) - { - const Value::ContextType context_type = m_value.GetContextType(); - - if (context_type == Value::eContextTypeRegisterInfo) - { - const RegisterInfo *reg_info = m_value.GetRegisterInfo(); - if (reg_info) - { - ExecutionContext exe_ctx (GetExecutionContextRef()); - - StreamString reg_sstr; - m_data.Dump (®_sstr, - 0, - format, - reg_info->byte_size, - 1, - UINT32_MAX, - LLDB_INVALID_ADDRESS, - 0, - 0, - exe_ctx.GetBestExecutionContextScope()); - destination.swap(reg_sstr.GetString()); - } - } - else - { - ClangASTType clang_type = GetClangType (); - if (clang_type) - { - // put custom bytes to display in this DataExtractor to override the default value logic - lldb_private::DataExtractor special_format_data; - if (format == eFormatCString) - { - Flags type_flags(clang_type.GetTypeInfo(NULL)); - if (type_flags.Test(ClangASTType::eTypeIsPointer) && !type_flags.Test(ClangASTType::eTypeIsObjC)) - { - // if we are dumping a pointer as a c-string, get the pointee data as a string - TargetSP target_sp(GetTargetSP()); - if (target_sp) - { - size_t max_len = target_sp->GetMaximumSizeOfStringSummary(); - Error error; - DataBufferSP buffer_sp(new DataBufferHeap(max_len+1,0)); - Address address(GetPointerValue()); - if (target_sp->ReadCStringFromMemory(address, (char*)buffer_sp->GetBytes(), max_len, error) && error.Success()) - special_format_data.SetData(buffer_sp); - } - } - } - - StreamString sstr; - ExecutionContext exe_ctx (GetExecutionContextRef()); - clang_type.DumpTypeValue (&sstr, // The stream to use for display - format, // Format to display this type with - special_format_data.GetByteSize() ? - special_format_data: m_data, // Data to extract from - 0, // Byte offset into "m_data" - GetByteSize(), // Byte size of item in "m_data" - GetBitfieldBitSize(), // Bitfield bit size - GetBitfieldBitOffset(), // Bitfield bit offset - exe_ctx.GetBestExecutionContextScope()); - // Don't set the m_error to anything here otherwise - // we won't be able to re-format as anything else. The - // code for ClangASTType::DumpTypeValue() should always - // return something, even if that something contains - // an error messsage. "m_error" is used to detect errors - // when reading the valid object, not for formatting errors. - if (sstr.GetString().empty()) - destination.clear(); - else - destination.swap(sstr.GetString()); - } - } - return !destination.empty(); - } + if (UpdateValueIfNeeded(false)) + return format.FormatObject(this,destination); else return false; } +bool +ValueObject::GetValueAsCString (lldb::Format format, + std::string& destination) +{ + return GetValueAsCString(TypeFormatImpl_Format(format),destination); +} + const char * ValueObject::GetValueAsCString () { if (UpdateValueIfNeeded(true)) { + lldb::TypeFormatImplSP format_sp; lldb::Format my_format = GetFormat(); if (my_format == lldb::eFormatDefault) { if (m_type_format_sp) - my_format = m_type_format_sp->GetFormat(); + format_sp = m_type_format_sp; else { if (m_is_bitfield_for_scalar) @@ -1503,7 +1438,9 @@ ValueObject::GetValueAsCString () if (my_format != m_last_format || m_value_str.empty()) { m_last_format = my_format; - if (GetValueAsCString(my_format, m_value_str)) + if (!format_sp) + format_sp.reset(new TypeFormatImpl_Format(my_format)); + if (GetValueAsCString(*format_sp.get(), m_value_str)) { if (!m_value_did_change && m_old_value_valid) { @@ -1609,7 +1546,8 @@ bool ValueObject::DumpPrintableRepresentation(Stream& s, ValueObjectRepresentationStyle val_obj_display, Format custom_format, - PrintableRepresentationSpecialCases special) + PrintableRepresentationSpecialCases special, + bool do_dump_error) { Flags flags(GetTypeInfo()); @@ -1808,7 +1746,12 @@ ValueObject::DumpPrintableRepresentation(Stream& s, else { if (m_error.Fail()) - s.Printf("<%s>", m_error.AsCString()); + { + if (do_dump_error) + s.Printf("<%s>", m_error.AsCString()); + else + return false; + } else if (val_obj_display == eValueObjectRepresentationStyleSummary) s.PutCString("<no summary available>"); else if (val_obj_display == eValueObjectRepresentationStyleValue) @@ -3515,7 +3458,8 @@ ValueObject::CreateConstantValue (const ConstString &name) if (!valobj_sp) { - valobj_sp = ValueObjectConstResult::Create (NULL, m_error); + ExecutionContext exe_ctx (GetExecutionContextRef()); + valobj_sp = ValueObjectConstResult::Create (exe_ctx.GetBestExecutionContextScope(), m_error); } return valobj_sp; } @@ -3769,7 +3713,8 @@ ValueObject::EvaluationPoint::SyncWithProcessState() { // Start with the target, if it is NULL, then we're obviously not going to get any further: - ExecutionContext exe_ctx(m_exe_ctx_ref.Lock()); + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx(m_exe_ctx_ref.Lock(thread_and_frame_only_if_stopped)); if (exe_ctx.GetTargetPtr() == NULL) return false; diff --git a/source/Core/ValueObjectChild.cpp b/source/Core/ValueObjectChild.cpp index 23add1c..ccf87cd 100644 --- a/source/Core/ValueObjectChild.cpp +++ b/source/Core/ValueObjectChild.cpp @@ -206,8 +206,12 @@ ValueObjectChild::UpdateValue () if (m_error.Success()) { - ExecutionContext exe_ctx (GetExecutionContextRef().Lock()); - m_error = m_value.GetValueAsData (&exe_ctx, m_data, 0, GetModule().get()); + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx (GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped)); + if (GetClangType().GetTypeInfo() & ClangASTType::eTypeHasValue) + m_error = m_value.GetValueAsData (&exe_ctx, m_data, 0, GetModule().get()); + else + m_error.Clear(); // No value so nothing to read... } } else diff --git a/source/Core/ValueObjectVariable.cpp b/source/Core/ValueObjectVariable.cpp index 3d8b07a..2e5bb22 100644 --- a/source/Core/ValueObjectVariable.cpp +++ b/source/Core/ValueObjectVariable.cpp @@ -328,6 +328,12 @@ ValueObjectVariable::GetLocationAsCString () bool ValueObjectVariable::SetValueFromCString (const char *value_str, Error& error) { + if (!UpdateValueIfNeeded()) + { + error.SetErrorString("unable to update value before writing"); + return false; + } + if (m_resolved_value.GetContextType() == Value::eContextTypeRegisterInfo) { RegisterInfo *reg_info = m_resolved_value.GetRegisterInfo(); @@ -360,6 +366,12 @@ ValueObjectVariable::SetValueFromCString (const char *value_str, Error& error) bool ValueObjectVariable::SetData (DataExtractor &data, Error &error) { + if (!UpdateValueIfNeeded()) + { + error.SetErrorString("unable to update value before writing"); + return false; + } + if (m_resolved_value.GetContextType() == Value::eContextTypeRegisterInfo) { RegisterInfo *reg_info = m_resolved_value.GetRegisterInfo(); @@ -371,7 +383,7 @@ ValueObjectVariable::SetData (DataExtractor &data, Error &error) error.SetErrorString("unable to retrieve register info"); return false; } - error = reg_value.SetValueFromData(reg_info, data, 0, false); + error = reg_value.SetValueFromData(reg_info, data, 0, true); if (error.Fail()) return false; if (reg_ctx->WriteRegister (reg_info, reg_value)) |