| // 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 <mach-o/loader.h> |
| #include <mach-o/nlist.h> |
| #include <sys/types.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #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<process_types::nlist[]> 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<ProcessMemoryMac::MappedMemory> 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 |