Author: jmolenda Date: Wed Jul 6 20:09:23 2016 New Revision: 274718 URL: http://llvm.org/viewvc/llvm-project?rev=274718&view=rev Log: Add support to debugserver for some new ways to interact with dyld to find the solibs loaded in a process. Support two new ways of sending the jGetLoadedDynamicLibrariesInfos packet to debugserver and add a new jGetSharedCacheInfo packet. Update the documentation for these packets as well. The changes to lldb to use these will be a separate commit.
<rdar://problem/25251243> Modified: lldb/trunk/docs/lldb-gdb-remote.txt lldb/trunk/tools/debugserver/source/DNB.cpp lldb/trunk/tools/debugserver/source/DNB.h lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.h lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.mm lldb/trunk/tools/debugserver/source/RNBRemote.cpp lldb/trunk/tools/debugserver/source/RNBRemote.h Modified: lldb/trunk/docs/lldb-gdb-remote.txt URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/docs/lldb-gdb-remote.txt?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/docs/lldb-gdb-remote.txt (original) +++ lldb/trunk/docs/lldb-gdb-remote.txt Wed Jul 6 20:09:23 2016 @@ -1508,6 +1508,28 @@ for this region. // This packet asks the remote debug stub to send the details about libraries // being added/removed from the process as a performance optimization. // +// There are three ways this packet can be used. All three return a dictionary of +// binary images formatted the same way. +// +// On MacOS X 10.11, iOS 9, tvOS 9, watchOS 2 and earlier, the packet is used like +// jGetLoadedDynamicLibrariesInfos:{"image_count":1,"image_list_address":140734800075128} +// where the image_list_address is an array of {void* load_addr, void* mod_date, void* pathname} +// in the inferior process memory (and image_count is the number of elements in this array). +// lldb is using information from the dyld_all_image_infos structure to make these requests to +// debugserver. This use is not supported on macOS 10.12, iOS 10, tvOS 10, watchOS 3 or newer. +// +// On macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer, there are two calls. One requests information +// on all shared libraries: +// jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true} +// And the second requests information about a list of shared libraries, given their load addresses: +// jGetLoadedDynamicLibrariesInfos:{"solib_addresses":[8382824135,3258302053,830202858503]} +// +// The second call is both a performance optimization (instead of having lldb read the mach-o header/load commands +// out of memory with generic read packets) but also adds additional information in the form of the +// filename of the shared libraries (which is not available in the mach-o header/load commands.) +// +// An example using the Mac OS X 10.11 style call: +// // LLDB SENDS: jGetLoadedDynamicLibrariesInfos:{"image_count":1,"image_list_address":140734800075128} // STUB REPLIES: ${"images":[{"load_address":4294967296,"mod_date":0,"pathname":"/tmp/a.out","uuid":"02CF262C-ED6F-3965-9E14-63538B465CFF","mach_header":{"magic":4277009103,"cputype":16777223,"cpusubtype":18446744071562067971,"filetype":2},"segments":{"name":"__PAGEZERO","vmaddr":0,"vmsize":4294967296,"fileoff":0,"filesize":0,"maxprot":0},{"name":"__TEXT","vmaddr":4294967296,"vmsize":4096,"fileoff":0,"filesize":4096,"maxprot":7},{"name":"__LINKEDIT","vmaddr":4294971392,"vmsize":4096,"fileoff":4096,"filesize":152,"maxprot":7}}]}#00 // @@ -1562,26 +1584,12 @@ for this region. // quite a bit to provide all the information that the DynamicLoaderMacOSX // would need to work correctly on this platform. // -// On Mac OS X / iOS, when libraries are added or removed, a stub -// function is called which lldb puts a breakpoint on. The arguments -// to the stub function include the number of libraries being added -// or removed and the address where the list of libraries can be -// found. The information at this address is the load address of the -// library, the filename, and the mod date of the library if available. -// DynamicLoaderMacOSX then parses the load commands in the Mach-O header -// at the load address before it can decide what action to take. -// -// The purpose of this packet is to eliminate all of the memory reads needed -// to read the Mach-O header and load commands for these libraries. -// On a typical GUI app, there can be a couple hundred shared libraries -// which results in megabytes of read packets. That same information can -// be returned in a couple hundred kilobytes in JSON format from the remote -// debugserver. -// -// // PRIORITY TO IMPLEMENT -// Low. If this packet is absent, lldb will read the Mach-O headers/load -// commands out of memory. +// On Mac OS X 10.11, iOS 9, tvOS 9, watchOS 2 and older: Low. If this packet is absent, +// lldb will read the Mach-O headers/load commands out of memory. +// On macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer: High. If this packet is absent, +// lldb will not know anything about shared libraries in the inferior, or where the main +// executable loaded. //---------------------------------------------------------------------- //---------------------------------------------------------------------- @@ -1649,6 +1657,26 @@ the previous FP and PC), and follow the iOS now don't require us to read any memory! //---------------------------------------------------------------------- +// "jGetSharedCacheInfo" +// +// BRIEF +// This packet asks the remote debug stub to send the details about the inferior's +// shared cache. The shared cache is a collection of common libraries/frameworks that +// are mapped into every process at the same address on Darwin systems, and can be +// identified by a load address and UUID. +// +// +// LLDB SENDS: jGetSharedCacheInfo:{} +// STUB REPLIES: ${"shared_cache_base_address":140735683125248,"shared_cache_uuid":"DDB8D70C-C9A2-3561-B2C8-BE48A4F33F96","no_shared_cache":false,"shared_cache_private_cache":false]}#00 +// +// PRIORITY TO IMPLEMENT +// Low. When both lldb and the inferior process are running on the same computer, and lldb +// and the inferior process have the same shared cache, lldb may (as an optimization) read +// the shared cache out of its own memory instead of using gdb-remote read packets to read +// them from the inferior process. +//---------------------------------------------------------------------- + +//---------------------------------------------------------------------- // "qQueryGDBServer" // // BRIEF Modified: lldb/trunk/tools/debugserver/source/DNB.cpp URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/DNB.cpp?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/tools/debugserver/source/DNB.cpp (original) +++ lldb/trunk/tools/debugserver/source/DNB.cpp Wed Jul 6 20:09:23 2016 @@ -1083,6 +1083,39 @@ DNBGetLoadedDynamicLibrariesInfos (nub_p return JSONGenerator::ObjectSP(); } +JSONGenerator::ObjectSP +DNBGetAllLoadedLibrariesInfos (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->GetAllLoadedLibrariesInfos (pid); + } + return JSONGenerator::ObjectSP(); +} + +JSONGenerator::ObjectSP +DNBGetLibrariesInfoForAddresses (nub_process_t pid, std::vector<uint64_t> &macho_addresses) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->GetLibrariesInfoForAddresses (pid, macho_addresses); + } + return JSONGenerator::ObjectSP(); +} + +JSONGenerator::ObjectSP +DNBGetSharedCacheInfo (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->GetSharedCacheInfo (pid); + } + return JSONGenerator::ObjectSP(); +} + const char * Modified: lldb/trunk/tools/debugserver/source/DNB.h URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/DNB.h?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/tools/debugserver/source/DNB.h (original) +++ lldb/trunk/tools/debugserver/source/DNB.h Wed Jul 6 20:09:23 2016 @@ -144,6 +144,10 @@ nub_addr_t DNBGetPThreadT nub_addr_t DNBGetDispatchQueueT (nub_process_t pid, nub_thread_t tid); nub_addr_t DNBGetTSDAddressForThread (nub_process_t pid, nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset, uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size); JSONGenerator::ObjectSP DNBGetLoadedDynamicLibrariesInfos (nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count); +JSONGenerator::ObjectSP DNBGetAllLoadedLibrariesInfos (nub_process_t pid); +JSONGenerator::ObjectSP DNBGetLibrariesInfoForAddresses (nub_process_t pid, std::vector<uint64_t> &macho_addresses); +JSONGenerator::ObjectSP DNBGetSharedCacheInfo (nub_process_t pid); + // //---------------------------------------------------------------------- // Breakpoint functions Modified: lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.h URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.h?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.h (original) +++ lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.h Wed Jul 6 20:09:23 2016 @@ -15,8 +15,10 @@ #define __MachProcess_h__ #include <mach/mach.h> +#include <mach-o/loader.h> #include <sys/signal.h> #include <pthread.h> +#include <uuid/uuid.h> #include <vector> #include <CoreFoundation/CoreFoundation.h> @@ -46,6 +48,43 @@ public: MachProcess (); ~MachProcess (); + // A structure that can hold everything debugserver needs to know from + // a binary's Mach-O header / load commands. + + struct mach_o_segment + { + std::string name; + uint64_t vmaddr; + uint64_t vmsize; + uint64_t fileoff; + uint64_t filesize; + uint64_t maxprot; + uint64_t initprot; + uint64_t nsects; + uint64_t flags; + }; + + struct mach_o_information + { + struct mach_header_64 mach_header; + std::vector<struct mach_o_segment> segments; + uuid_t uuid; + }; + + struct binary_image_information + { + std::string filename; + uint64_t load_address; + uint64_t mod_date; // may not be available - 0 if so + struct mach_o_information macho_info; + + binary_image_information () : + filename (), + load_address (INVALID_NUB_ADDRESS), + mod_date (0) + { } + }; + //---------------------------------------------------------------------- // Child process control //---------------------------------------------------------------------- @@ -193,7 +232,15 @@ public: nub_addr_t GetPThreadT (nub_thread_t tid); nub_addr_t GetDispatchQueueT (nub_thread_t tid); nub_addr_t GetTSDAddressForThread (nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset, uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size); + + + bool GetMachOInformationFromMemory (nub_addr_t mach_o_header_addr, int wordsize, struct mach_o_information &inf); + JSONGenerator::ObjectSP FormatDynamicLibrariesIntoJSON (const std::vector<struct binary_image_information> &image_infos); + void GetAllLoadedBinariesViaDYLDSPI (std::vector<struct binary_image_information> &image_infos); JSONGenerator::ObjectSP GetLoadedDynamicLibrariesInfos (nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count); + JSONGenerator::ObjectSP GetLibrariesInfoForAddresses (nub_process_t pid, std::vector<uint64_t> &macho_addresses); + JSONGenerator::ObjectSP GetAllLoadedLibrariesInfos (nub_process_t pid); + JSONGenerator::ObjectSP GetSharedCacheInfo (nub_process_t pid); nub_size_t GetNumThreads () const; nub_thread_t GetThreadAtIndex (nub_size_t thread_idx) const; @@ -358,6 +405,11 @@ private: // as the sole reason for the process being stopped, we can auto resume // the process. bool m_did_exec; + + void * (*m_dyld_process_info_create) (task_t task, uint64_t timestamp, kern_return_t* kernelError); + void (*m_dyld_process_info_for_each_image) (void* info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)); + void (*m_dyld_process_info_release) (void* info); + void (*m_dyld_process_info_get_cache) (void* info, void* cacheInfo); }; Modified: lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.mm URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.mm?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.mm (original) +++ lldb/trunk/tools/debugserver/source/MacOSX/MachProcess.mm Wed Jul 6 20:09:23 2016 @@ -12,8 +12,10 @@ //===----------------------------------------------------------------------===// #include "DNB.h" +#include <dlfcn.h> #include <inttypes.h> #include <mach/mach.h> +#include <mach/task.h> #include <signal.h> #include <spawn.h> #include <sys/fcntl.h> @@ -417,8 +419,17 @@ MachProcess::MachProcess() : m_image_infos_baton(NULL), m_sent_interrupt_signo (0), m_auto_resume_signo (0), - m_did_exec (false) -{ + m_did_exec (false), + m_dyld_process_info_create (nullptr), + m_dyld_process_info_for_each_image (nullptr), + m_dyld_process_info_release (nullptr), + m_dyld_process_info_get_cache (nullptr) +{ + m_dyld_process_info_create = (void * (*) (task_t task, uint64_t timestamp, kern_return_t* kernelError)) dlsym (RTLD_DEFAULT, "_dyld_process_info_create"); + m_dyld_process_info_for_each_image = (void (*)(void *info, void (^)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path))) dlsym (RTLD_DEFAULT, "_dyld_process_info_for_each_image"); + m_dyld_process_info_release = (void (*) (void* info)) dlsym (RTLD_DEFAULT, "_dyld_process_info_release"); + m_dyld_process_info_get_cache = (void (*) (void* info, void* cacheInfo)) dlsym (RTLD_DEFAULT, "_dyld_process_info_get_cache"); + DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__); } @@ -520,8 +531,177 @@ MachProcess::GetTSDAddressForThread (nub return m_thread_list.GetTSDAddressForThread (tid, plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset, plo_pthread_tsd_entry_size); } +// Given an address, read the mach-o header and load commands out of memory to fill in +// the mach_o_information "inf" object. +// +// Returns false if there was an error in reading this mach-o file header/load commands. + +bool +MachProcess::GetMachOInformationFromMemory (nub_addr_t mach_o_header_addr, int wordsize, struct mach_o_information &inf) +{ + uint64_t load_cmds_p; + if (wordsize == 4) + { + struct mach_header header; + if (ReadMemory (mach_o_header_addr, sizeof (struct mach_header), &header) != sizeof (struct mach_header)) + { + return false; + } + load_cmds_p = mach_o_header_addr + sizeof (struct mach_header); + inf.mach_header.magic = header.magic; + inf.mach_header.cputype = header.cputype; + // high byte of cpusubtype is used for "capability bits", v. CPU_SUBTYPE_MASK, CPU_SUBTYPE_LIB64 in machine.h + inf.mach_header.cpusubtype = header.cpusubtype & 0x00ffffff; + inf.mach_header.filetype = header.filetype; + inf.mach_header.ncmds = header.ncmds; + inf.mach_header.sizeofcmds = header.sizeofcmds; + inf.mach_header.flags = header.flags; + } + else + { + struct mach_header_64 header; + if (ReadMemory (mach_o_header_addr, sizeof (struct mach_header_64), &header) != sizeof (struct mach_header_64)) + { + return false; + } + load_cmds_p = mach_o_header_addr + sizeof (struct mach_header_64); + inf.mach_header.magic = header.magic; + inf.mach_header.cputype = header.cputype; + // high byte of cpusubtype is used for "capability bits", v. CPU_SUBTYPE_MASK, CPU_SUBTYPE_LIB64 in machine.h + inf.mach_header.cpusubtype = header.cpusubtype & 0x00ffffff; + inf.mach_header.filetype = header.filetype; + inf.mach_header.ncmds = header.ncmds; + inf.mach_header.sizeofcmds = header.sizeofcmds; + inf.mach_header.flags = header.flags; + } + for (uint32_t j = 0; j < inf.mach_header.ncmds; j++) + { + struct load_command lc; + if (ReadMemory (load_cmds_p, sizeof (struct load_command), &lc) != sizeof (struct load_command)) + { + return false; + } + if (lc.cmd == LC_SEGMENT) + { + struct segment_command seg; + if (ReadMemory (load_cmds_p, sizeof (struct segment_command), &seg) != sizeof (struct segment_command)) + { + return false; + } + struct mach_o_segment this_seg; + char name[17]; + ::memset (name, 0, sizeof (name)); + memcpy (name, seg.segname, sizeof (seg.segname)); + this_seg.name = name; + this_seg.vmaddr = seg.vmaddr; + this_seg.vmsize = seg.vmsize; + this_seg.fileoff = seg.fileoff; + this_seg.filesize = seg.filesize; + this_seg.maxprot = seg.maxprot; + this_seg.initprot = seg.initprot; + this_seg.nsects = seg.nsects; + this_seg.flags = seg.flags; + inf.segments.push_back(this_seg); + } + if (lc.cmd == LC_SEGMENT_64) + { + struct segment_command_64 seg; + if (ReadMemory (load_cmds_p, sizeof (struct segment_command_64), &seg) != sizeof (struct segment_command_64)) + { + return false; + } + struct mach_o_segment this_seg; + char name[17]; + ::memset (name, 0, sizeof (name)); + memcpy (name, seg.segname, sizeof (seg.segname)); + this_seg.name = name; + this_seg.vmaddr = seg.vmaddr; + this_seg.vmsize = seg.vmsize; + this_seg.fileoff = seg.fileoff; + this_seg.filesize = seg.filesize; + this_seg.maxprot = seg.maxprot; + this_seg.initprot = seg.initprot; + this_seg.nsects = seg.nsects; + this_seg.flags = seg.flags; + inf.segments.push_back(this_seg); + } + if (lc.cmd == LC_UUID) + { + struct uuid_command uuidcmd; + if (ReadMemory (load_cmds_p, sizeof (struct uuid_command), &uuidcmd) == sizeof (struct uuid_command)) + uuid_copy (inf.uuid, uuidcmd.uuid); + } + load_cmds_p += lc.cmdsize; + } + return true; +} + +// Given completely filled in array of binary_image_information structures, create a JSONGenerator object +// with all the details we want to send to lldb. +JSONGenerator::ObjectSP +MachProcess::FormatDynamicLibrariesIntoJSON (const std::vector<struct binary_image_information> &image_infos) +{ + JSONGenerator::ArraySP image_infos_array_sp (new JSONGenerator::Array()); + const size_t image_count = image_infos.size(); + + for (size_t i = 0; i < image_count; i++) + { + JSONGenerator::DictionarySP image_info_dict_sp (new JSONGenerator::Dictionary()); + image_info_dict_sp->AddIntegerItem ("load_address", image_infos[i].load_address); + image_info_dict_sp->AddIntegerItem ("mod_date", image_infos[i].mod_date); + image_info_dict_sp->AddStringItem ("pathname", image_infos[i].filename); + + uuid_string_t uuidstr; + uuid_unparse_upper (image_infos[i].macho_info.uuid, uuidstr); + image_info_dict_sp->AddStringItem ("uuid", uuidstr); + + JSONGenerator::DictionarySP mach_header_dict_sp (new JSONGenerator::Dictionary()); + mach_header_dict_sp->AddIntegerItem ("magic", image_infos[i].macho_info.mach_header.magic); + mach_header_dict_sp->AddIntegerItem ("cputype", (uint32_t) image_infos[i].macho_info.mach_header.cputype); + mach_header_dict_sp->AddIntegerItem ("cpusubtype", (uint32_t) image_infos[i].macho_info.mach_header.cpusubtype); + mach_header_dict_sp->AddIntegerItem ("filetype", image_infos[i].macho_info.mach_header.filetype); + +// DynamicLoaderMacOSX doesn't currently need these fields, so don't send them. +// mach_header_dict_sp->AddIntegerItem ("ncmds", image_infos[i].macho_info.mach_header.ncmds); +// mach_header_dict_sp->AddIntegerItem ("sizeofcmds", image_infos[i].macho_info.mach_header.sizeofcmds); +// mach_header_dict_sp->AddIntegerItem ("flags", image_infos[i].macho_info.mach_header.flags); + image_info_dict_sp->AddItem ("mach_header", mach_header_dict_sp); + + JSONGenerator::ArraySP segments_sp (new JSONGenerator::Array()); + for (size_t j = 0; j < image_infos[i].macho_info.segments.size(); j++) + { + JSONGenerator::DictionarySP segment_sp (new JSONGenerator::Dictionary()); + segment_sp->AddStringItem ("name", image_infos[i].macho_info.segments[j].name); + segment_sp->AddIntegerItem ("vmaddr", image_infos[i].macho_info.segments[j].vmaddr); + segment_sp->AddIntegerItem ("vmsize", image_infos[i].macho_info.segments[j].vmsize); + segment_sp->AddIntegerItem ("fileoff", image_infos[i].macho_info.segments[j].fileoff); + segment_sp->AddIntegerItem ("filesize", image_infos[i].macho_info.segments[j].filesize); + segment_sp->AddIntegerItem ("maxprot", image_infos[i].macho_info.segments[j].maxprot); + +// DynamicLoaderMacOSX doesn't currently need these fields, so don't send them. +// segment_sp->AddIntegerItem ("initprot", image_infos[i].macho_info.segments[j].initprot); +// segment_sp->AddIntegerItem ("nsects", image_infos[i].macho_info.segments[j].nsects); +// segment_sp->AddIntegerItem ("flags", image_infos[i].macho_info.segments[j].flags); + segments_sp->AddItem (segment_sp); + } + image_info_dict_sp->AddItem ("segments", segments_sp); + + image_infos_array_sp->AddItem (image_info_dict_sp); + } + + JSONGenerator::DictionarySP reply_sp (new JSONGenerator::Dictionary());; + reply_sp->AddItem ("images", image_infos_array_sp); + + return reply_sp; +} + +// Get the shared library information using the old (pre-macOS 10.12, pre-iOS 10, pre-tvOS 10, pre-watchOS 3) +// code path. We'll be given the address of an array of structures in the form +// {void* load_addr, void* mod_date, void* pathname} +// +// In macOS 10.12 etc and newer, we'll use SPI calls into dyld to gather this information. JSONGenerator::ObjectSP MachProcess::GetLoadedDynamicLibrariesInfos (nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count) { @@ -536,29 +716,7 @@ MachProcess::GetLoadedDynamicLibrariesIn if (processInfo.kp_proc.p_flag & P_LP64) pointer_size = 8; - struct segment - { - std::string name; - uint64_t vmaddr; - uint64_t vmsize; - uint64_t fileoff; - uint64_t filesize; - uint64_t maxprot; - uint64_t initprot; - uint64_t nsects; - uint64_t flags; - }; - - struct image_info - { - uint64_t load_address; - std::string pathname; - uint64_t mod_date; - struct mach_header_64 mach_header; - std::vector<struct segment> segments; - uuid_t uuid; - }; - std::vector<image_info> image_infos; + std::vector<struct binary_image_information> image_infos; size_t image_infos_size = image_count * 3 * pointer_size; uint8_t *image_info_buf = (uint8_t *) malloc (image_infos_size); @@ -577,7 +735,7 @@ MachProcess::GetLoadedDynamicLibrariesIn for (size_t i = 0; i < image_count; i++) { - struct image_info info; + struct binary_image_information info; nub_addr_t pathname_address; if (pointer_size == 4) { @@ -604,13 +762,13 @@ MachProcess::GetLoadedDynamicLibrariesIn pathname_address = pathname_address_64; } char strbuf[17]; - info.pathname = ""; + info.filename = ""; uint64_t pathname_ptr = pathname_address; bool still_reading = true; while (still_reading && ReadMemory (pathname_ptr, sizeof (strbuf) - 1, strbuf) == sizeof (strbuf) - 1) { strbuf[sizeof(strbuf) - 1] = '\0'; - info.pathname += strbuf; + info.filename += strbuf; pathname_ptr += sizeof (strbuf) - 1; // Stop if we found nul byte indicating the end of the string for (size_t i = 0; i < sizeof(strbuf) - 1; i++) @@ -622,7 +780,7 @@ MachProcess::GetLoadedDynamicLibrariesIn } } } - uuid_clear (info.uuid); + uuid_clear (info.macho_info.uuid); image_infos.push_back (info); } if (image_infos.size() == 0) @@ -630,157 +788,161 @@ MachProcess::GetLoadedDynamicLibrariesIn return reply_sp; } + free (image_info_buf); //// Second, read the mach header / load commands for all the dylibs - for (size_t i = 0; i < image_count; i++) { - uint64_t load_cmds_p; - if (pointer_size == 4) - { - struct mach_header header; - if (ReadMemory (image_infos[i].load_address, sizeof (struct mach_header), &header) != sizeof (struct mach_header)) - { - return reply_sp; - } - load_cmds_p = image_infos[i].load_address + sizeof (struct mach_header); - image_infos[i].mach_header.magic = header.magic; - image_infos[i].mach_header.cputype = header.cputype; - image_infos[i].mach_header.cpusubtype = header.cpusubtype; - image_infos[i].mach_header.filetype = header.filetype; - image_infos[i].mach_header.ncmds = header.ncmds; - image_infos[i].mach_header.sizeofcmds = header.sizeofcmds; - image_infos[i].mach_header.flags = header.flags; - } - else - { - struct mach_header_64 header; - if (ReadMemory (image_infos[i].load_address, sizeof (struct mach_header_64), &header) != sizeof (struct mach_header_64)) - { - return reply_sp; - } - load_cmds_p = image_infos[i].load_address + sizeof (struct mach_header_64); - image_infos[i].mach_header.magic = header.magic; - image_infos[i].mach_header.cputype = header.cputype; - image_infos[i].mach_header.cpusubtype = header.cpusubtype; - image_infos[i].mach_header.filetype = header.filetype; - image_infos[i].mach_header.ncmds = header.ncmds; - image_infos[i].mach_header.sizeofcmds = header.sizeofcmds; - image_infos[i].mach_header.flags = header.flags; - } - for (uint32_t j = 0; j < image_infos[i].mach_header.ncmds; j++) + if (!GetMachOInformationFromMemory (image_infos[i].load_address, pointer_size, image_infos[i].macho_info)) { - struct load_command lc; - if (ReadMemory (load_cmds_p, sizeof (struct load_command), &lc) != sizeof (struct load_command)) - { - return reply_sp; - } - if (lc.cmd == LC_SEGMENT) - { - struct segment_command seg; - if (ReadMemory (load_cmds_p, sizeof (struct segment_command), &seg) != sizeof (struct segment_command)) - { - return reply_sp; - } - struct segment this_seg; - char name[17]; - ::memset (name, 0, sizeof (name)); - memcpy (name, seg.segname, sizeof (seg.segname)); - this_seg.name = name; - this_seg.vmaddr = seg.vmaddr; - this_seg.vmsize = seg.vmsize; - this_seg.fileoff = seg.fileoff; - this_seg.filesize = seg.filesize; - this_seg.maxprot = seg.maxprot; - this_seg.initprot = seg.initprot; - this_seg.nsects = seg.nsects; - this_seg.flags = seg.flags; - image_infos[i].segments.push_back(this_seg); - } - if (lc.cmd == LC_SEGMENT_64) - { - struct segment_command_64 seg; - if (ReadMemory (load_cmds_p, sizeof (struct segment_command_64), &seg) != sizeof (struct segment_command_64)) - { - return reply_sp; - } - struct segment this_seg; - char name[17]; - ::memset (name, 0, sizeof (name)); - memcpy (name, seg.segname, sizeof (seg.segname)); - this_seg.name = name; - this_seg.vmaddr = seg.vmaddr; - this_seg.vmsize = seg.vmsize; - this_seg.fileoff = seg.fileoff; - this_seg.filesize = seg.filesize; - this_seg.maxprot = seg.maxprot; - this_seg.initprot = seg.initprot; - this_seg.nsects = seg.nsects; - this_seg.flags = seg.flags; - image_infos[i].segments.push_back(this_seg); - } - if (lc.cmd == LC_UUID) - { - struct uuid_command uuidcmd; - if (ReadMemory (load_cmds_p, sizeof (struct uuid_command), &uuidcmd) == sizeof (struct uuid_command)) - uuid_copy (image_infos[i].uuid, uuidcmd.uuid); - } - load_cmds_p += lc.cmdsize; + return reply_sp; } } - //// Thrid, format all of the above in the JSONGenerator object. + //// Third, format all of the above in the JSONGenerator object. - JSONGenerator::ArraySP image_infos_array_sp (new JSONGenerator::Array()); + return FormatDynamicLibrariesIntoJSON (image_infos); + } + + return reply_sp; +} + +// From dyld SPI header dyld_process_info.h +typedef void* dyld_process_info; +struct dyld_process_cache_info +{ + uuid_t cacheUUID; // UUID of cache used by process + uint64_t cacheBaseAddress; // load address of dyld shared cache + bool noCache; // process is running without a dyld cache + bool privateCache; // process is using a private copy of its dyld cache +}; + + +// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer to get +// the load address, uuid, and filenames of all the libraries. +// This only fills in those three fields in the 'struct binary_image_information' - call +// GetMachOInformationFromMemory to fill in the mach-o header/load command details. +void +MachProcess::GetAllLoadedBinariesViaDYLDSPI (std::vector<struct binary_image_information> &image_infos) +{ + kern_return_t kern_ret; + if (m_dyld_process_info_create) + { + dyld_process_info info = m_dyld_process_info_create (m_task.TaskPort(), 0, &kern_ret); + if (info) + { + m_dyld_process_info_for_each_image (info, ^(uint64_t mach_header_addr, const uuid_t uuid, const char *path) { + struct binary_image_information image; + image.filename = path; + uuid_copy (image.macho_info.uuid, uuid); + image.load_address = mach_header_addr; + image_infos.push_back (image); + }); + m_dyld_process_info_release (info); + } + } +} + +// Fetch information about all shared libraries using the dyld SPIs that exist in +// macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer. +JSONGenerator::ObjectSP +MachProcess::GetAllLoadedLibrariesInfos (nub_process_t pid) +{ + JSONGenerator::DictionarySP reply_sp; + + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; + struct kinfo_proc processInfo; + size_t bufsize = sizeof(processInfo); + if (sysctl(mib, (unsigned)(sizeof(mib)/sizeof(int)), &processInfo, &bufsize, NULL, 0) == 0 && bufsize > 0) + { + uint32_t pointer_size = 4; + if (processInfo.kp_proc.p_flag & P_LP64) + pointer_size = 8; + + std::vector<struct binary_image_information> image_infos; + GetAllLoadedBinariesViaDYLDSPI (image_infos); + const size_t image_count = image_infos.size(); for (size_t i = 0; i < image_count; i++) { - JSONGenerator::DictionarySP image_info_dict_sp (new JSONGenerator::Dictionary()); - image_info_dict_sp->AddIntegerItem ("load_address", image_infos[i].load_address); - image_info_dict_sp->AddIntegerItem ("mod_date", image_infos[i].mod_date); - image_info_dict_sp->AddStringItem ("pathname", image_infos[i].pathname); + GetMachOInformationFromMemory (image_infos[i].load_address, pointer_size, image_infos[i].macho_info); + } + return FormatDynamicLibrariesIntoJSON (image_infos); + } + return reply_sp; +} - uuid_string_t uuidstr; - uuid_unparse_upper (image_infos[i].uuid, uuidstr); - image_info_dict_sp->AddStringItem ("uuid", uuidstr); +// Fetch information about the shared libraries at the given load addresses using the +// dyld SPIs that exist in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer. +JSONGenerator::ObjectSP +MachProcess::GetLibrariesInfoForAddresses (nub_process_t pid, std::vector<uint64_t> &macho_addresses) +{ + JSONGenerator::DictionarySP reply_sp; - JSONGenerator::DictionarySP mach_header_dict_sp (new JSONGenerator::Dictionary()); - mach_header_dict_sp->AddIntegerItem ("magic", image_infos[i].mach_header.magic); - mach_header_dict_sp->AddIntegerItem ("cputype", image_infos[i].mach_header.cputype); - mach_header_dict_sp->AddIntegerItem ("cpusubtype", image_infos[i].mach_header.cpusubtype); - mach_header_dict_sp->AddIntegerItem ("filetype", image_infos[i].mach_header.filetype); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; + struct kinfo_proc processInfo; + size_t bufsize = sizeof(processInfo); + if (sysctl(mib, (unsigned)(sizeof(mib)/sizeof(int)), &processInfo, &bufsize, NULL, 0) == 0 && bufsize > 0) + { + uint32_t pointer_size = 4; + if (processInfo.kp_proc.p_flag & P_LP64) + pointer_size = 8; -// DynamicLoaderMacOSX doesn't currently need these fields, so don't send them. -// mach_header_dict_sp->AddIntegerItem ("ncmds", image_infos[i].mach_header.ncmds); -// mach_header_dict_sp->AddIntegerItem ("sizeofcmds", image_infos[i].mach_header.sizeofcmds); -// mach_header_dict_sp->AddIntegerItem ("flags", image_infos[i].mach_header.flags); - image_info_dict_sp->AddItem ("mach_header", mach_header_dict_sp); + std::vector<struct binary_image_information> all_image_infos; + GetAllLoadedBinariesViaDYLDSPI (all_image_infos); - JSONGenerator::ArraySP segments_sp (new JSONGenerator::Array()); - for (size_t j = 0; j < image_infos[i].segments.size(); j++) + std::vector<struct binary_image_information> image_infos; + const size_t macho_addresses_count = macho_addresses.size(); + const size_t all_image_infos_count = all_image_infos.size(); + for (size_t i = 0; i < macho_addresses_count; i++) + { + for (size_t j = 0; j < all_image_infos_count; j++) { - JSONGenerator::DictionarySP segment_sp (new JSONGenerator::Dictionary()); - segment_sp->AddStringItem ("name", image_infos[i].segments[j].name); - segment_sp->AddIntegerItem ("vmaddr", image_infos[i].segments[j].vmaddr); - segment_sp->AddIntegerItem ("vmsize", image_infos[i].segments[j].vmsize); - segment_sp->AddIntegerItem ("fileoff", image_infos[i].segments[j].fileoff); - segment_sp->AddIntegerItem ("filesize", image_infos[i].segments[j].filesize); - segment_sp->AddIntegerItem ("maxprot", image_infos[i].segments[j].maxprot); - -// DynamicLoaderMacOSX doesn't currently need these fields, so don't send them. -// segment_sp->AddIntegerItem ("initprot", image_infos[i].segments[j].initprot); -// segment_sp->AddIntegerItem ("nsects", image_infos[i].segments[j].nsects); -// segment_sp->AddIntegerItem ("flags", image_infos[i].segments[j].flags); - segments_sp->AddItem (segment_sp); + if (all_image_infos[j].load_address == macho_addresses[i]) + { + image_infos.push_back (all_image_infos[j]); + } } - image_info_dict_sp->AddItem ("segments", segments_sp); + } + + const size_t image_infos_count = image_infos.size(); + for (size_t i = 0; i < image_infos_count; i++) + { + GetMachOInformationFromMemory (image_infos[i].load_address, pointer_size, image_infos[i].macho_info); + } + return FormatDynamicLibrariesIntoJSON (image_infos); + } + return reply_sp; +} + +// From dyld's internal podyld_process_info.h: + +JSONGenerator::ObjectSP +MachProcess::GetSharedCacheInfo (nub_process_t pid) +{ + JSONGenerator::DictionarySP reply_sp (new JSONGenerator::Dictionary());; + kern_return_t kern_ret; + if (m_dyld_process_info_create && m_dyld_process_info_get_cache) + { + dyld_process_info info = m_dyld_process_info_create (m_task.TaskPort(), 0, &kern_ret); + if (info) + { + struct dyld_process_cache_info shared_cache_info; + m_dyld_process_info_get_cache (info, &shared_cache_info); + + reply_sp->AddIntegerItem ("shared_cache_base_address", shared_cache_info.cacheBaseAddress); + + uuid_string_t uuidstr; + uuid_unparse_upper (shared_cache_info.cacheUUID, uuidstr); + reply_sp->AddStringItem ("shared_cache_uuid", uuidstr); + + reply_sp->AddBooleanItem ("no_shared_cache", shared_cache_info.noCache); + reply_sp->AddBooleanItem ("shared_cache_private_cache", shared_cache_info.privateCache); - image_infos_array_sp->AddItem (image_info_dict_sp); + m_dyld_process_info_release (info); } - reply_sp.reset (new JSONGenerator::Dictionary()); - reply_sp->AddItem ("images", image_infos_array_sp); } return reply_sp; } Modified: lldb/trunk/tools/debugserver/source/RNBRemote.cpp URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/RNBRemote.cpp?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/tools/debugserver/source/RNBRemote.cpp (original) +++ lldb/trunk/tools/debugserver/source/RNBRemote.cpp Wed Jul 6 20:09:23 2016 @@ -284,6 +284,7 @@ RNBRemote::CreatePacketTable () t.push_back (Packet (json_query_thread_extended_info,&RNBRemote::HandlePacket_jThreadExtendedInfo , NULL, "jThreadExtendedInfo", "Replies with JSON data of thread extended information.")); t.push_back (Packet (json_query_get_loaded_dynamic_libraries_infos, &RNBRemote::HandlePacket_jGetLoadedDynamicLibrariesInfos, NULL, "jGetLoadedDynamicLibrariesInfos", "Replies with JSON data of all the shared libraries loaded in this process.")); t.push_back (Packet (json_query_threads_info, &RNBRemote::HandlePacket_jThreadsInfo , NULL, "jThreadsInfo", "Replies with JSON data with information about all threads.")); + t.push_back (Packet (json_query_get_shared_cache_info, &RNBRemote::HandlePacket_jGetSharedCacheInfo, NULL, "jGetSharedCacheInfo", "Replies with JSON data about the location and uuid of the shared cache in the inferior process.")); t.push_back (Packet (start_noack_mode, &RNBRemote::HandlePacket_QStartNoAckMode , NULL, "QStartNoAckMode", "Request that " DEBUGSERVER_PROGRAM_NAME " stop acking remote protocol packets")); t.push_back (Packet (prefix_reg_packets_with_tid, &RNBRemote::HandlePacket_QThreadSuffixSupported , NULL, "QThreadSuffixSupported", "Check if thread specific packets (register packets 'g', 'G', 'p', and 'P') support having the thread ID appended to the end of the command")); t.push_back (Packet (set_logging_mode, &RNBRemote::HandlePacket_QSetLogging , NULL, "QSetLogging:", "Check if register packets ('g', 'G', 'p', and 'P' support having the thread ID prefix")); @@ -5061,6 +5062,122 @@ get_integer_value_for_key_name_from_json } +// A helper function that retrieves a boolean value from +// a one-level-deep JSON dictionary of key-value pairs. e.g. +// jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true}] + +// Returns true if it was able to find the key name, and sets the 'value' +// argument to the value found. + +bool +get_boolean_value_for_key_name_from_json (const char *key, const char *json_string, bool &value) +{ + std::string key_with_quotes = "\""; + key_with_quotes += key; + key_with_quotes += "\""; + const char *c = strstr (json_string, key_with_quotes.c_str()); + if (c) + { + c += key_with_quotes.size(); + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + + if (*c == ':') + { + c++; + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + + if (strncmp (c, "true", 4) == 0) + { + value = true; + return true; + } else if (strncmp (c, "false", 5) == 0) + { + value = false; + return true; + } + } + } + return false; +} + +// A helper function that reads an array of uint64_t's from +// a one-level-deep JSON dictionary of key-value pairs. e.g. +// jGetLoadedDynamicLibrariesInfos:{"solib_addrs":[31345823,7768020384,7310483024]}] + +// Returns true if it was able to find the key name, false if it did not. +// "ints" will have all integers found in the array appended to it. + +bool +get_array_of_ints_value_for_key_name_from_json (const char *key, const char *json_string, std::vector<uint64_t> &ints) +{ + std::string key_with_quotes = "\""; + key_with_quotes += key; + key_with_quotes += "\""; + const char *c = strstr (json_string, key_with_quotes.c_str()); + if (c) + { + c += key_with_quotes.size(); + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + + if (*c == ':') + { + c++; + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + + if (*c == '[') + { + c++; + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + while (1) + { + if (!isdigit (*c)) + { + return true; + } + + errno = 0; + char *endptr; + uint64_t value = strtoul (c, &endptr, 10); + if (errno == 0) + { + ints.push_back (value); + } + else + { + break; + } + if (endptr == c || endptr == nullptr || *endptr == '\0') + { + break; + } + c = endptr; + + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + if (*c == ',') + c++; + while (*c != '\0' && (*c == ' ' || *c == '\t' || *c == '\n' || *c == '\r')) + c++; + if (*c == ']') + { + return true; + } + } + } + } + } + return false; +} + JSONGenerator::ObjectSP RNBRemote::GetJSONThreadsInfo(bool threads_with_valid_stop_info_only) { @@ -5488,6 +5605,20 @@ RNBRemote::HandlePacket_jThreadExtendedI return SendPacket ("OK"); } +// This packet may be called in one of three ways: +// +// jGetLoadedDynamicLibrariesInfos:{"image_count":40,"image_list_address":4295244704} +// Look for an array of the old dyld_all_image_infos style of binary infos at the image_list_address. +// This an array of {void* load_addr, void* mod_date, void* pathname} +// +// jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true} +// Use the new style (macOS 10.12, tvOS 10, iOS 10, watchOS 3) dyld SPI to get a list of all the +// libraries loaded +// +// jGetLoadedDynamicLibrariesInfos:{"solib_addresses":[8382824135,3258302053,830202858503]} +// Use the new style (macOS 10.12, tvOS 10, iOS 10, watchOS 3) dyld SPI to get the information +// about the libraries loaded at these addresses. +// rnb_err_t RNBRemote::HandlePacket_jGetLoadedDynamicLibrariesInfos (const char *p) { @@ -5505,28 +5636,81 @@ RNBRemote::HandlePacket_jGetLoadedDynami { p += strlen (get_loaded_dynamic_libraries_infos_str); - nub_addr_t image_list_address = get_integer_value_for_key_name_from_json ("image_list_address", p); - nub_addr_t image_count = get_integer_value_for_key_name_from_json ("image_count", p); + JSONGenerator::ObjectSP json_sp; - if (image_list_address != INVALID_NUB_ADDRESS && image_count != INVALID_NUB_ADDRESS) + std::vector<uint64_t> macho_addresses; + bool fetch_all_solibs = false; + if (get_boolean_value_for_key_name_from_json ("fetch_all_solibs", p, fetch_all_solibs) && fetch_all_solibs) + { + json_sp = DNBGetAllLoadedLibrariesInfos (pid); + } + else if (get_array_of_ints_value_for_key_name_from_json ("solib_addresses", p, macho_addresses)) { - JSONGenerator::ObjectSP json_sp; + json_sp = DNBGetLibrariesInfoForAddresses (pid, macho_addresses); + } + else + { + nub_addr_t image_list_address = get_integer_value_for_key_name_from_json ("image_list_address", p); + nub_addr_t image_count = get_integer_value_for_key_name_from_json ("image_count", p); - json_sp = DNBGetLoadedDynamicLibrariesInfos (pid, image_list_address, image_count); + if (image_list_address != INVALID_NUB_ADDRESS && image_count != INVALID_NUB_ADDRESS) + { + json_sp = DNBGetLoadedDynamicLibrariesInfos (pid, image_list_address, image_count); + } + } - if (json_sp.get()) + if (json_sp.get()) + { + std::ostringstream json_str; + json_sp->Dump (json_str); + if (json_str.str().size() > 0) { - std::ostringstream json_str; - json_sp->Dump (json_str); - if (json_str.str().size() > 0) - { - std::string json_str_quoted = binary_encode_string (json_str.str()); - return SendPacket (json_str_quoted.c_str()); - } - else - { - SendPacket ("E84"); - } + std::string json_str_quoted = binary_encode_string (json_str.str()); + return SendPacket (json_str_quoted.c_str()); + } + else + { + SendPacket ("E84"); + } + } + } + return SendPacket ("OK"); +} + +// This packet does not currently take any arguments. So the behavior is +// jGetSharedCacheInfo:{} +// send information about the inferior's shared cache +// jGetSharedCacheInfo: +// send "OK" to indicate that this packet is supported +rnb_err_t +RNBRemote::HandlePacket_jGetSharedCacheInfo (const char *p) +{ + nub_process_t pid; + // If we haven't run the process yet, return an error. + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E85"); + } + + pid = m_ctx.ProcessID(); + + const char get_shared_cache_info_str[] = { "jGetSharedCacheInfo:{" }; + if (strncmp (p, get_shared_cache_info_str, sizeof (get_shared_cache_info_str) - 1) == 0) + { + JSONGenerator::ObjectSP json_sp = DNBGetSharedCacheInfo (pid); + + if (json_sp.get()) + { + std::ostringstream json_str; + json_sp->Dump (json_str); + if (json_str.str().size() > 0) + { + std::string json_str_quoted = binary_encode_string (json_str.str()); + return SendPacket (json_str_quoted.c_str()); + } + else + { + SendPacket ("E86"); } } } Modified: lldb/trunk/tools/debugserver/source/RNBRemote.h URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/RNBRemote.h?rev=274718&r1=274717&r2=274718&view=diff ============================================================================== --- lldb/trunk/tools/debugserver/source/RNBRemote.h (original) +++ lldb/trunk/tools/debugserver/source/RNBRemote.h Wed Jul 6 20:09:23 2016 @@ -105,6 +105,7 @@ public: json_query_thread_extended_info,// 'jThreadExtendedInfo' json_query_get_loaded_dynamic_libraries_infos, // 'jGetLoadedDynamicLibrariesInfos' json_query_threads_info, // 'jThreadsInfo' + json_query_get_shared_cache_info, // 'jGetSharedCacheInfo' pass_signals_to_inferior, // 'QPassSignals' start_noack_mode, // 'QStartNoAckMode' prefix_reg_packets_with_tid, // 'QPrefixRegisterPacketsWithThreadID @@ -194,6 +195,7 @@ public: rnb_err_t HandlePacket_jThreadExtendedInfo (const char *p); rnb_err_t HandlePacket_jGetLoadedDynamicLibrariesInfos (const char *p); rnb_err_t HandlePacket_jThreadsInfo (const char *p); + rnb_err_t HandlePacket_jGetSharedCacheInfo (const char *p); rnb_err_t HandlePacket_qThreadExtraInfo (const char *p); rnb_err_t HandlePacket_qThreadStopInfo (const char *p); rnb_err_t HandlePacket_qHostInfo (const char *p); _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits