// Copyright (c) 2014 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // pecoffutils.c: Utilities for dealing with PECOFF files // #include "common/pecoff/pecoffutils.h" #include #include #include #ifndef _WIN32 #include #endif namespace google_breakpad { bool IsValidPeCoff(const uint8_t* obj_base) { // at offset 0x3c, find the offset to PE signature const uint32_t* peOffsetPtr = reinterpret_cast(obj_base + IMAGE_FILE_HEADER_OFFSET); // TODO: ideally we want to check that the offset is less than the size of the // mapped file, but we don't have that information at the moment // // if (*peOffsetPtr > size) return FALSE; // check PE signature const PeHeader* peHeader = reinterpret_cast(obj_base+*peOffsetPtr); if (peHeader->mMagic != IMAGE_FILE_MAGIC) return false; return true; } int PeCoffClass(const uint8_t* obj_base) { const uint32_t* peOffsetPtr = reinterpret_cast(obj_base + IMAGE_FILE_HEADER_OFFSET); const PeHeader* peHeader = reinterpret_cast(obj_base+*peOffsetPtr); const uint16_t* peOptionalHeader = reinterpret_cast (reinterpret_cast(peHeader) + sizeof(PeHeader)); // We need to read the magic before we know if this a Pe32OptionalHeader or // Pe32PlusOptionalHeader, so we don't use those types here. return *peOptionalHeader; } // // Header information // template const char* PeCoffObjectFileReader::Architecture( ObjectFileBase obj_base) { const PeHeader* peHeader = GetHeader(obj_base); uint16_t arch = peHeader->mMachine; switch (arch) { case IMAGE_FILE_MACHINE_I386: return "x86"; case IMAGE_FILE_MACHINE_ARM: return "arm"; case IMAGE_FILE_MACHINE_MIPS16: case IMAGE_FILE_MACHINE_MIPSFPU: case IMAGE_FILE_MACHINE_MIPSFPU16: case IMAGE_FILE_MACHINE_WCEMIPSV2: return "mips"; case IMAGE_FILE_MACHINE_POWERPC: case IMAGE_FILE_MACHINE_POWERPCFP: return "ppc"; case IMAGE_FILE_MACHINE_AMD64: return "x86_64"; default: fprintf(stderr, "unrecognized machine architecture: %d\n", peHeader->mMachine); return NULL; } } template bool PeCoffObjectFileReader::Endianness( ObjectFileBase obj_base, bool* big_endian) { // TODO: Not sure what big-endian PECOFF looks like: characteristics flag // IMAGE_FILE_BYTES_REVERSED_HI and/or certain machine types are big-endian *big_endian = false; return true; } template typename PeCoffObjectFileReader::Addr PeCoffObjectFileReader::GetLoadingAddress( ObjectFileBase obj_base) { const PeOptionalHeader* peOptionalHeader = GetOptionalHeader(obj_base); return peOptionalHeader->mImageBase; } // // Section enumeration and location // template int PeCoffObjectFileReader::GetNumberOfSections( ObjectFileBase obj_base) { const PeHeader* peHeader = GetHeader(obj_base); return peHeader->mNumberOfSections; } template const typename PeCoffObjectFileReader::Section PeCoffObjectFileReader::FindSectionByIndex( ObjectFileBase obj_base, int i) { const PeSectionHeader* section_table = GetSectionTable(obj_base); return reinterpret_cast(&(section_table[i])); } template const typename PeCoffObjectFileReader::Section PeCoffObjectFileReader::FindSectionByName( const char* section_name, ObjectFileBase obj_base) { const PeHeader* peHeader = GetHeader(obj_base); const PeSectionHeader* section_table = GetSectionTable(obj_base); const char* string_table = GetStringTable(obj_base); uint32_t string_table_length = *(reinterpret_cast(string_table)); for (int s = 0; s < peHeader->mNumberOfSections; s++) { const char* name = section_table[s].mName; // look up long section names in string table if (name[0] == '/') { unsigned int offset = ::strtoul(section_table[s].mName+1, NULL, 10); if (offset > string_table_length) fprintf(stderr, "section name offset %d exceeds string table length", offset); else name = string_table + offset; } if (::strcmp(section_name, name) == 0) { return reinterpret_cast(&(section_table[s])); } } // nothing found return NULL; } // // Section information // template const uint8_t* PeCoffObjectFileReader::GetSectionPointer( ObjectFileBase obj_base, Section section) { return reinterpret_cast(obj_base) + reinterpret_cast(section)->mPointerToRawData; } template typename PeCoffObjectFileReader::Offset PeCoffObjectFileReader::GetSectionSize( ObjectFileBase obj_base, Section section) { // There are mSizeOfRawData bytes of data for the section in the mapped image // file. Return mVirtualSize if it's smaller. // This doesn't handle the case where mVirtualSize is larger and the section // should be zero padded, because we have nowhere to do that. if ((reinterpret_cast(section)->mVirtualSize) < (reinterpret_cast(section)->mSizeOfRawData)) return reinterpret_cast(section)->mVirtualSize; return reinterpret_cast(section)->mSizeOfRawData; } template typename PeCoffObjectFileReader::Offset PeCoffObjectFileReader::GetSectionRVA( ObjectFileBase obj_base, Section section) { return reinterpret_cast(section)->mVirtualAddress; } template const char* PeCoffObjectFileReader::GetSectionName( ObjectFileBase obj_base,Section section) { const char* string_table = GetStringTable(obj_base); uint32_t string_table_length = *(reinterpret_cast(string_table)); const char* name = reinterpret_cast(section)->mName; // look up long section names in string table if (name[0] == '/') { unsigned int offset = ::strtoul(name+1, NULL, 10); if (offset > string_table_length) fprintf(stderr, "section name offset %d exceeds string table length", offset); else name = string_table + offset; } return name; } // // // template bool PeCoffObjectFileReader::ExportedSymbolsToModule( ObjectFileBase obj_base, Module* module) { // locate the export table, if present const PeDataDirectory* data_directory_export_entry = GetDataDirectoryEntry(obj_base, PE_EXPORT_TABLE); if (data_directory_export_entry && data_directory_export_entry->mSize != 0) { const PeExportTable* export_table = reinterpret_cast(ConvertRVAToPointer(obj_base, data_directory_export_entry->mVirtualAddress)); const uint32_t* eat = reinterpret_cast(ConvertRVAToPointer(obj_base, export_table->mExportAddressTableRVA)); const uint32_t* enpt = reinterpret_cast(ConvertRVAToPointer(obj_base, export_table->mNamePointerRVA)); const uint16_t* eot = reinterpret_cast(ConvertRVAToPointer(obj_base, export_table->mOrdinalTableRVA)); // process the export name pointer table for (uint32_t i = 0; i < export_table->mNumberofNamePointers; i++) { // look up the name for the export uint32_t export_name_rva = enpt[i]; if (export_name_rva == 0) continue; const char* export_name = reinterpret_cast(ConvertRVAToPointer(obj_base, export_name_rva)); // find the corresponding export address table entry uint16_t export_ordinal = eot[i]; if ((export_ordinal < export_table->mOrdinalBase) || (export_ordinal >= (export_table->mOrdinalBase + export_table->mAddressTableEntries))) { fprintf(stderr, "exported ordinal %d out of range for EAT!\n", export_ordinal); continue; } uint32_t eat_index = export_ordinal - export_table->mOrdinalBase; uint32_t export_rva = eat[eat_index]; // if the export's address lies inside the export table, it's a forwarded // export, which we can ignore if ((export_rva >= data_directory_export_entry->mVirtualAddress) && (export_rva < (data_directory_export_entry->mVirtualAddress + data_directory_export_entry->mSize))) continue; Module::Extern* ext = new Module::Extern; ext->name = export_name; ext->address = export_rva + GetLoadingAddress(obj_base); module->AddExtern(ext); } return true; } // report if a COFF symbol table exists, but we don't use it (yet) // According to the PECOFF spec. COFF debugging information is deprecated. // We don't know of any tools which produce that and don't produce DWARF or // MS CodeView debug information. const PeHeader* peHeader = GetHeader(obj_base); if (peHeader->mPointerToSymbolTable) { fprintf(stderr, "COFF debug symbols present but are not implemented\n"); } return false; } template string PeCoffObjectFileReader::FileIdentifierFromMappedFile( ObjectFileBase obj_file) { uint8_t identifier[kMDGUIDSize]; uint32_t age; if (!PeCoffFileID::PeCoffFileIdentifierFromMappedFile(obj_file, identifier, &age)) return ""; // Endian-ness swap to match dump processor expectation. uint8_t identifier_swapped[kMDGUIDSize]; memcpy(identifier_swapped, identifier, kMDGUIDSize); uint32_t* data1 = reinterpret_cast(identifier_swapped); *data1 = htonl(*data1); uint16_t* data2 = reinterpret_cast(identifier_swapped + 4); *data2 = htons(*data2); uint16_t* data3 = reinterpret_cast(identifier_swapped + 6); *data3 = htons(*data3); // Format the file identifier in IDENTIFIER as a UUID with the // dashes removed. char identifier_str[40]; int buffer_idx = 0; for (int idx = 0; idx < kMDGUIDSize; ++idx) { int hi = (identifier_swapped[idx] >> 4) & 0x0F; int lo = (identifier_swapped[idx]) & 0x0F; identifier_str[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi; identifier_str[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo; } identifier_str[buffer_idx] = 0; string id = identifier_str; // Append age char age_string[9]; snprintf(age_string, sizeof(age_string) / sizeof(age_string[0]), "%X", age); id += age_string; return id; } // // Helper functions for PeCoffFileID // template bool PeCoffObjectFileReader::GetBuildID( ObjectFileBase obj_base, uint8_t identifier[kMDGUIDSize], uint32_t* age) { // locate the debug directory, if present const PeDataDirectory* data_directory_debug_entry = GetDataDirectoryEntry(obj_base, PE_DEBUG_DATA); if (!data_directory_debug_entry) return false; uint32_t debug_directory_size = data_directory_debug_entry->mSize; if (debug_directory_size == 0) return false; const PeDebugDirectory* debug_directory = reinterpret_cast(ConvertRVAToPointer(obj_base, data_directory_debug_entry->mVirtualAddress)); if (debug_directory == NULL) { fprintf(stderr, "No section containing the debug directory VMA could be found\n"); return false; } // search the debug directory for a codeview entry for (int i = 0; i < debug_directory_size/sizeof(PeDebugDirectory); i++) { if (debug_directory[i].mType == IMAGE_DEBUG_TYPE_CODEVIEW) { // interpret the codeview record to get build-id const CvInfoPbd70* codeview_record = reinterpret_cast (obj_base + debug_directory[i].mPointerToRawData); if ((codeview_record->mCvSignature) == CODEVIEW_PDB70_CVSIGNATURE) { memcpy(identifier, codeview_record->mSignature, kMDGUIDSize); *age = codeview_record->mAge; return true; } else { fprintf(stderr, "Unhandled codeview signature %x\n", codeview_record->mCvSignature); } } } fprintf(stderr, "No codeview entry in debug directory\n"); return false; } template bool PeCoffObjectFileReader::HashTextSection( ObjectFileBase obj_base, uint8_t identifier[kMDGUIDSize]) { Section text_section; Offset text_size; if (!(text_section = FindSectionByName(".text", obj_base)) || ((text_size = GetSectionSize(obj_base, text_section)) == 0)) return false; memset(identifier, 0, kMDGUIDSize); const uint8_t* ptr = GetSectionPointer(obj_base, text_section); const uint8_t* ptr_end = ptr + std::min(text_size, 4096U); while (ptr < ptr_end) { for (unsigned i = 0; i < kMDGUIDSize; i++) identifier[i] ^= ptr[i]; ptr += kMDGUIDSize; } return true; } // // Private implementation helper functions // template const PeHeader* PeCoffObjectFileReader::GetHeader( ObjectFileBase obj_base) { const uint32_t* peOffsetPtr = reinterpret_cast(obj_base + IMAGE_FILE_HEADER_OFFSET); const PeHeader* peHeader = reinterpret_cast(obj_base+*peOffsetPtr); return peHeader; } template const typename PeCoffObjectFileReader::PeOptionalHeader* PeCoffObjectFileReader::GetOptionalHeader( ObjectFileBase obj_base) { const PeHeader* peHeader = GetHeader(obj_base); PeOptionalHeader* peOptionalHeader = (PeOptionalHeader*) ((uint32_t*)peHeader + 6); return peOptionalHeader; } template const PeSectionHeader* PeCoffObjectFileReader::GetSectionTable( ObjectFileBase obj_base) { const PeHeader* peHeader = GetHeader(obj_base); const PeOptionalHeader* peOptionalHeader = GetOptionalHeader(obj_base); // section table immediately follows optional header const PeSectionHeader* section_table = reinterpret_cast (reinterpret_cast(peOptionalHeader) + peHeader->mSizeOfOptionalHeader); return section_table; } template const char* PeCoffObjectFileReader::GetStringTable( ObjectFileBase obj_base) { const PeHeader* peHeader = GetHeader(obj_base); // string table immediately follows symbol table uint32_t string_table_offset = peHeader->mPointerToSymbolTable + peHeader->mNumberOfSymbols*sizeof(PeSymbol); const char* string_table = reinterpret_cast(obj_base) + string_table_offset; return string_table; } template const PeDataDirectory* PeCoffObjectFileReader::GetDataDirectoryEntry( ObjectFileBase obj_base, int entry) { const PeOptionalHeader* peOptionalHeader = GetOptionalHeader(obj_base); // the data directory immediately follows the optional header const PeDataDirectory* data_directory = reinterpret_cast(&peOptionalHeader->mDataDirectory[0]); uint32_t data_directory_size = peOptionalHeader->mNumberOfRvaAndSizes; // locate the required directory entry, if present if (data_directory_size < entry) return NULL; return &data_directory[entry]; } template const uint8_t* PeCoffObjectFileReader::ConvertRVAToPointer( ObjectFileBase obj_base, Offset rva) { // find which section contains the rva to compute it's mapped address const PeSectionHeader* section_table = GetSectionTable(obj_base); for (int s = 0; s < GetNumberOfSections(obj_base); s++) { const PeSectionHeader* section = &(section_table[s]); if ((rva >= section->mVirtualAddress) && (rva < (section->mVirtualAddress + section->mSizeOfRawData))) { uint32_t offset = rva - section->mVirtualAddress; const uint8_t* pointer = GetSectionPointer(obj_base, (Section)section) + offset; return pointer; } } fprintf(stderr, "No section could be found containing RVA %x\n", rva); return NULL; } // instantiation of templated classes template class PeCoffObjectFileReader; template class PeCoffObjectFileReader; }