| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "MachOTrie.h" | |
| #include "lldb/Utility/DataExtractor.h" | |
| #include "lldb/Utility/Flags.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/BinaryFormat/MachO.h" | |
| #include <cstdio> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace llvm::MachO; | |
| /// Upper bound on the length of a symbol name assembled from export-trie edge | |
| /// labels. A corrupt trie can encode an edge label whose terminator is far | |
| /// away in the trie data, so a single label is many megabytes long; appending | |
| /// it to the running name would otherwise request an unbounded allocation. No | |
| /// legitimate symbol name comes close to this size. Also 1 MiB is the | |
| /// symbol length limit in ld. | |
| static constexpr size_t kMaxTrieSymbolNameLength = 1 << 20; // 1 MiB | |
| void TrieEntry::Dump() const { | |
| printf("0x%16.16llx 0x%16.16llx 0x%16.16llx \"%s\"", | |
| static_cast<unsigned long long>(address), | |
| static_cast<unsigned long long>(flags), | |
| static_cast<unsigned long long>(other), name.GetCString()); | |
| if (import_name) | |
| printf(" -> \"%s\"\n", import_name.GetCString()); | |
| else | |
| printf("\n"); | |
| } | |
| void TrieEntryWithOffset::Dump(uint32_t idx) const { | |
| printf("[%3u] 0x%16.16llx: ", idx, | |
| static_cast<unsigned long long>(nodeOffset)); | |
| entry.Dump(); | |
| } | |
| namespace { | |
| bool ParseTrieEntriesImpl(DataExtractor &data, lldb::offset_t offset, | |
| const bool is_arm, addr_t text_seg_base_addr, | |
| std::string &prefix, | |
| std::set<lldb::addr_t> &resolver_addresses, | |
| std::vector<TrieEntryWithOffset> &reexports, | |
| std::vector<TrieEntryWithOffset> &ext_symbols, | |
| std::set<lldb::offset_t> &visited_nodes) { | |
| if (!data.ValidOffset(offset)) | |
| return true; | |
| // Every node in a well-formed trie is reached by exactly one path, so a node | |
| // offset seen twice means the trie is corrupt. | |
| if (!visited_nodes.insert(offset).second) | |
| return false; | |
| // Terminal node -- end of a branch, possibly add this to | |
| // the symbol table or resolver table. | |
| const uint64_t terminalSize = data.GetULEB128(&offset); | |
| lldb::offset_t children_offset = offset + terminalSize; | |
| if (terminalSize != 0) { | |
| TrieEntryWithOffset e(offset); | |
| e.entry.flags = data.GetULEB128(&offset); | |
| const char *import_name = nullptr; | |
| if (e.entry.flags & EXPORT_SYMBOL_FLAGS_REEXPORT) { | |
| e.entry.address = 0; | |
| e.entry.other = data.GetULEB128(&offset); // dylib ordinal | |
| import_name = data.GetCStr(&offset); | |
| } else { | |
| e.entry.address = data.GetULEB128(&offset); | |
| if (text_seg_base_addr != LLDB_INVALID_ADDRESS) | |
| e.entry.address += text_seg_base_addr; | |
| if (e.entry.flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) { | |
| e.entry.other = data.GetULEB128(&offset); | |
| uint64_t resolver_addr = e.entry.other; | |
| if (text_seg_base_addr != LLDB_INVALID_ADDRESS) | |
| resolver_addr += text_seg_base_addr; | |
| if (is_arm) | |
| resolver_addr &= THUMB_ADDRESS_BIT_MASK; | |
| resolver_addresses.insert(resolver_addr); | |
| } else | |
| e.entry.other = 0; | |
| } | |
| bool add_this_entry = false; | |
| if (Flags(e.entry.flags).Test(EXPORT_SYMBOL_FLAGS_REEXPORT) && | |
| import_name && import_name[0]) { | |
| // add symbols that are reexport symbols with a valid import name. | |
| add_this_entry = true; | |
| } else if (e.entry.flags == 0 && | |
| (import_name == nullptr || import_name[0] == '\0')) { | |
| // add externally visible symbols, in case the nlist record has | |
| // been stripped/omitted. | |
| add_this_entry = true; | |
| } | |
| if (add_this_entry) { | |
| if (prefix.size() > 1) { | |
| // Skip the leading '_' | |
| e.entry.name.SetString(llvm::StringRef(prefix).drop_front()); | |
| } | |
| if (import_name) { | |
| // Skip the leading '_' | |
| e.entry.import_name.SetCString(import_name + 1); | |
| } | |
| if (Flags(e.entry.flags).Test(EXPORT_SYMBOL_FLAGS_REEXPORT)) { | |
| reexports.push_back(e); | |
| } else { | |
| if (is_arm && (e.entry.address & 1)) { | |
| e.entry.flags |= TRIE_SYMBOL_IS_THUMB; | |
| e.entry.address &= THUMB_ADDRESS_BIT_MASK; | |
| } | |
| ext_symbols.push_back(e); | |
| } | |
| } | |
| } | |
| const uint8_t childrenCount = data.GetU8(&children_offset); | |
| for (uint8_t i = 0; i < childrenCount; ++i) { | |
| const char *cstr = data.GetCStr(&children_offset); | |
| if (!cstr) | |
| return false; // Corrupt data | |
| if (prefix.size() + llvm::StringRef(cstr).size() > kMaxTrieSymbolNameLength) | |
| return false; // Corrupt data: implausibly long symbol name. | |
| const size_t prevSize = prefix.size(); | |
| prefix.append(cstr); | |
| lldb::offset_t childNodeOffset = data.GetULEB128(&children_offset); | |
| // A child offset of 0 points back at the root; like any other repeated | |
| // offset it is a cycle, which ParseTrieEntriesImpl rejects as corrupt. | |
| if (!ParseTrieEntriesImpl(data, childNodeOffset, is_arm, text_seg_base_addr, | |
| prefix, resolver_addresses, reexports, | |
| ext_symbols, visited_nodes)) | |
| return false; | |
| prefix.resize(prevSize); | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| bool lldb_private::ParseTrieEntries( | |
| DataExtractor &data, const bool is_arm, lldb::addr_t text_seg_base_addr, | |
| std::set<lldb::addr_t> &resolver_addresses, | |
| std::vector<TrieEntryWithOffset> &reexports, | |
| std::vector<TrieEntryWithOffset> &ext_symbols) { | |
| lldb::offset_t offset = 0; | |
| std::set<lldb::offset_t> visited_nodes; | |
| std::string prefix; | |
| return ParseTrieEntriesImpl(data, offset, is_arm, text_seg_base_addr, prefix, | |
| resolver_addresses, reexports, ext_symbols, | |
| visited_nodes); | |
| } |