// Copyright 2014 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "snapshot/mac/mach_o_image_symbol_table_reader.h" #include #include #include #include #include #include "base/logging.h" #include "base/strings/stringprintf.h" #include "util/mac/checked_mach_address_range.h" #include "util/process/process_memory_mac.h" namespace crashpad { namespace internal { //! \brief The internal implementation for MachOImageSymbolTableReader. //! //! Initialization is broken into more than one function that needs to share //! data, so member variables are used. However, much of this data is irrelevant //! after initialization is completed, so rather than doing it in //! MachOImageSymbolTableReader, it’s handled by this class, which is a “friend” //! of MachOImageSymbolTableReader. class MachOImageSymbolTableReaderInitializer { public: MachOImageSymbolTableReaderInitializer( ProcessReaderMac* process_reader, const MachOImageSegmentReader* linkedit_segment, const std::string& module_info) : module_info_(module_info), linkedit_range_(), process_reader_(process_reader), linkedit_segment_(linkedit_segment) { linkedit_range_.SetRange(process_reader_->Is64Bit(), linkedit_segment->Address(), linkedit_segment->Size()); DCHECK(linkedit_range_.IsValid()); } ~MachOImageSymbolTableReaderInitializer() {} //! \brief Reads the symbol table from another process. //! //! \sa MachOImageSymbolTableReader::Initialize() bool Initialize(const process_types::symtab_command* symtab_command, const process_types::dysymtab_command* dysymtab_command, MachOImageSymbolTableReader::SymbolInformationMap* external_defined_symbols) { mach_vm_address_t symtab_address = AddressForLinkEditComponent(symtab_command->symoff); uint32_t symbol_count = symtab_command->nsyms; size_t nlist_size = process_types::nlist::ExpectedSize(process_reader_); mach_vm_size_t symtab_size = symbol_count * nlist_size; if (!IsInLinkEditSegment(symtab_address, symtab_size, "symtab")) { return false; } // If a dysymtab is present, use it to filter the symtab for just the // portion used for extdefsym. If no dysymtab is present, the entire symtab // will need to be consulted. uint32_t skip_count = 0; if (dysymtab_command) { if (dysymtab_command->iextdefsym >= symtab_command->nsyms || dysymtab_command->iextdefsym + dysymtab_command->nextdefsym > symtab_command->nsyms) { LOG(WARNING) << base::StringPrintf( "dysymtab extdefsym %u + %u > symtab nsyms %u", dysymtab_command->iextdefsym, dysymtab_command->nextdefsym, symtab_command->nsyms) << module_info_; return false; } skip_count = dysymtab_command->iextdefsym; mach_vm_size_t skip_size = skip_count * nlist_size; symtab_address += skip_size; symtab_size -= skip_size; symbol_count = dysymtab_command->nextdefsym; } mach_vm_address_t strtab_address = AddressForLinkEditComponent(symtab_command->stroff); mach_vm_size_t strtab_size = symtab_command->strsize; if (!IsInLinkEditSegment(strtab_address, strtab_size, "strtab")) { return false; } std::unique_ptr symbols( new process_types::nlist[symtab_command->nsyms]); if (!process_types::nlist::ReadArrayInto( process_reader_, symtab_address, symbol_count, &symbols[0])) { LOG(WARNING) << "could not read symbol table" << module_info_; return false; } std::unique_ptr string_table; for (size_t symbol_index = 0; symbol_index < symbol_count; ++symbol_index) { const process_types::nlist& symbol = symbols[symbol_index]; std::string symbol_info = base::StringPrintf(", symbol index %zu%s", skip_count + symbol_index, module_info_.c_str()); bool valid_symbol = true; if ((symbol.n_type & N_STAB) == 0 && (symbol.n_type & N_PEXT) == 0 && (symbol.n_type & N_EXT)) { uint8_t symbol_type = symbol.n_type & N_TYPE; if (symbol_type == N_ABS || symbol_type == N_SECT) { if (symbol.n_strx >= strtab_size) { LOG(WARNING) << base::StringPrintf( "string at 0x%x out of bounds (0x%llx)", symbol.n_strx, strtab_size) << symbol_info; return false; } if (!string_table) { string_table = process_reader_->Memory()->ReadMapped( strtab_address, strtab_size); if (!string_table) { LOG(WARNING) << "could not read string table" << module_info_; return false; } } std::string name; if (!string_table->ReadCString(symbol.n_strx, &name)) { LOG(WARNING) << "could not read string" << symbol_info; return false; } if (symbol_type == N_ABS && symbol.n_sect != NO_SECT) { LOG(WARNING) << base::StringPrintf("N_ABS symbol %s in section %u", name.c_str(), symbol.n_sect) << symbol_info; return false; } if (symbol_type == N_SECT && symbol.n_sect == NO_SECT) { LOG(WARNING) << base::StringPrintf( "N_SECT symbol %s in section NO_SECT", name.c_str()) << symbol_info; return false; } MachOImageSymbolTableReader::SymbolInformation this_symbol_info; this_symbol_info.value = symbol.n_value; this_symbol_info.section = symbol.n_sect; if (!external_defined_symbols->insert( std::make_pair(name, this_symbol_info)).second) { LOG(WARNING) << "duplicate symbol " << name << symbol_info; return false; } } else { // External indirect symbols may be found in the portion of the symbol // table used for external symbols as opposed to indirect symbols when // the indirect symbols are also external. These can be produced by // Xcode 5.1 ld64-236.3/src/ld/LinkEditClassic.hpp // ld::tool::SymbolTableAtom<>::addGlobal(). Indirect symbols are not // currently supported by this symbol table reader, so ignore them // without failing or logging a message when encountering them. See // https://groups.google.com/a/chromium.org/d/topic/crashpad-dev/k7QkLwO71Zo valid_symbol = symbol_type == N_INDR; } } else { valid_symbol = false; } if (!valid_symbol && dysymtab_command) { LOG(WARNING) << "non-external symbol with type " << symbol.n_type << " in extdefsym" << symbol_info; return false; } } return true; } private: //! \brief Computes the address for data in the `__LINKEDIT` segment //! identified by its file offset in a Mach-O image. //! //! \param[in] fileoff The file offset relative to the beginning of an image’s //! `mach_header` or `mach_header_64` of the data in the `__LINKEDIT` //! segment. //! //! \return The address, in the remote process’ address space, of the //! requested data. mach_vm_address_t AddressForLinkEditComponent(uint32_t fileoff) const { return linkedit_range_.Base() + fileoff - linkedit_segment_->fileoff(); } //! \brief Determines whether an address range is located within the //! `__LINKEDIT` segment. //! //! \param[in] address The base address of the range to check. //! \param[in] size The size of the range to check. //! \param[in] tag A string that identifies the range being checked. This is //! used only for logging. //! //! \return `true` if the range identified by \a address + \a size lies //! entirely within the `__LINKEDIT` segment. `false` if that range is //! invalid, or if that range is not contained by the `__LINKEDIT` //! segment, with an appropriate message logged. bool IsInLinkEditSegment(mach_vm_address_t address, mach_vm_size_t size, const char* tag) const { CheckedMachAddressRange subrange(process_reader_->Is64Bit(), address, size); if (!subrange.IsValid()) { LOG(WARNING) << base::StringPrintf("invalid %s range (0x%llx + 0x%llx)", tag, address, size) << module_info_; return false; } if (!linkedit_range_.ContainsRange(subrange)) { LOG(WARNING) << base::StringPrintf( "%s at 0x%llx + 0x%llx outside of " SEG_LINKEDIT " segment at 0x%llx + 0x%llx", tag, address, size, linkedit_range_.Base(), linkedit_range_.Size()) << module_info_; return false; } return true; } std::string module_info_; CheckedMachAddressRange linkedit_range_; ProcessReaderMac* process_reader_; // weak const MachOImageSegmentReader* linkedit_segment_; // weak DISALLOW_COPY_AND_ASSIGN(MachOImageSymbolTableReaderInitializer); }; } // namespace internal MachOImageSymbolTableReader::MachOImageSymbolTableReader() : external_defined_symbols_(), initialized_() { } MachOImageSymbolTableReader::~MachOImageSymbolTableReader() { } bool MachOImageSymbolTableReader::Initialize( ProcessReaderMac* process_reader, const process_types::symtab_command* symtab_command, const process_types::dysymtab_command* dysymtab_command, const MachOImageSegmentReader* linkedit_segment, const std::string& module_info) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); internal::MachOImageSymbolTableReaderInitializer initializer(process_reader, linkedit_segment, module_info); if (!initializer.Initialize( symtab_command, dysymtab_command, &external_defined_symbols_)) { return false; } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } const MachOImageSymbolTableReader::SymbolInformation* MachOImageSymbolTableReader::LookUpExternalDefinedSymbol( const std::string& name) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); const auto& iterator = external_defined_symbols_.find(name); if (iterator == external_defined_symbols_.end()) { return nullptr; } return &iterator->second; } } // namespace crashpad