| // Copyright (c) 2019, 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. | |
| #include "pe_util.h" | |
| #include <windows.h> | |
| #include <winnt.h> | |
| #include <atlbase.h> | |
| #include <ImageHlp.h> | |
| #include <functional> | |
| #include "common/windows/string_utils-inl.h" | |
| #include "common/windows/guid_string.h" | |
| namespace { | |
| /* | |
| * Not defined in WinNT.h for some reason. Definitions taken from: | |
| * http://uninformed.org/index.cgi?v=4&a=1&p=13 | |
| * | |
| */ | |
| typedef unsigned char UBYTE; | |
| #if !defined(_WIN64) | |
| #define UNW_FLAG_EHANDLER 0x01 | |
| #define UNW_FLAG_UHANDLER 0x02 | |
| #define UNW_FLAG_CHAININFO 0x04 | |
| #endif | |
| union UnwindCode { | |
| struct { | |
| UBYTE offset_in_prolog; | |
| UBYTE unwind_operation_code : 4; | |
| UBYTE operation_info : 4; | |
| }; | |
| USHORT frame_offset; | |
| }; | |
| enum UnwindOperationCodes { | |
| UWOP_PUSH_NONVOL = 0, /* info == register number */ | |
| UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */ | |
| UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */ | |
| UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */ | |
| UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */ | |
| UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */ | |
| // XXX: these are missing from MSDN! | |
| // See: http://www.osronline.com/ddkx/kmarch/64bitamd_4rs7.htm | |
| UWOP_SAVE_XMM, | |
| UWOP_SAVE_XMM_FAR, | |
| UWOP_SAVE_XMM128, /* info == XMM reg number, offset in next slot */ | |
| UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */ | |
| UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */ | |
| }; | |
| // See: http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx | |
| // Note: some fields removed as we don't use them. | |
| struct UnwindInfo { | |
| UBYTE version : 3; | |
| UBYTE flags : 5; | |
| UBYTE size_of_prolog; | |
| UBYTE count_of_codes; | |
| UBYTE frame_register : 4; | |
| UBYTE frame_offset : 4; | |
| UnwindCode unwind_code[1]; | |
| }; | |
| struct CV_INFO_PDB70 { | |
| ULONG cv_signature; | |
| GUID signature; | |
| ULONG age; | |
| CHAR pdb_filename[ANYSIZE_ARRAY]; | |
| }; | |
| #define CV_SIGNATURE_RSDS 'SDSR' | |
| // A helper class to scope a PLOADED_IMAGE. | |
| class AutoImage { | |
| public: | |
| explicit AutoImage(PLOADED_IMAGE img) : img_(img) {} | |
| ~AutoImage() { | |
| if (img_) | |
| ImageUnload(img_); | |
| } | |
| operator PLOADED_IMAGE() { return img_; } | |
| PLOADED_IMAGE operator->() { return img_; } | |
| private: | |
| PLOADED_IMAGE img_; | |
| }; | |
| } // namespace | |
| namespace google_breakpad { | |
| using std::unique_ptr; | |
| using google_breakpad::GUIDString; | |
| bool ReadModuleInfo(const wstring & pe_file, PDBModuleInfo * info) { | |
| // Convert wchar to native charset because ImageLoad only takes | |
| // a PSTR as input. | |
| string img_file; | |
| if (!WindowsStringUtils::safe_wcstombs(pe_file, &img_file)) { | |
| fprintf(stderr, "Image path '%S' contains unrecognized characters.\n", | |
| pe_file.c_str()); | |
| return false; | |
| } | |
| AutoImage img(ImageLoad((PSTR)img_file.c_str(), NULL)); | |
| if (!img) { | |
| fprintf(stderr, "Failed to load %s\n", img_file.c_str()); | |
| return false; | |
| } | |
| info->cpu = FileHeaderMachineToCpuString( | |
| img->FileHeader->FileHeader.Machine); | |
| PIMAGE_OPTIONAL_HEADER64 optional_header = | |
| &(reinterpret_cast<PIMAGE_NT_HEADERS64>(img->FileHeader))->OptionalHeader; | |
| // Search debug directories for a guid signature & age | |
| DWORD debug_rva = optional_header-> | |
| DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; | |
| DWORD debug_size = optional_header-> | |
| DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; | |
| PIMAGE_DEBUG_DIRECTORY debug_directories = | |
| static_cast<PIMAGE_DEBUG_DIRECTORY>( | |
| ImageRvaToVa(img->FileHeader, | |
| img->MappedAddress, | |
| debug_rva, | |
| &img->LastRvaSection)); | |
| for (DWORD i = 0; i < debug_size / sizeof(*debug_directories); i++) { | |
| if (debug_directories[i].Type != IMAGE_DEBUG_TYPE_CODEVIEW || | |
| debug_directories[i].SizeOfData < sizeof(CV_INFO_PDB70)) { | |
| continue; | |
| } | |
| struct CV_INFO_PDB70* cv_info = static_cast<CV_INFO_PDB70*>(ImageRvaToVa( | |
| img->FileHeader, | |
| img->MappedAddress, | |
| debug_directories[i].AddressOfRawData, | |
| &img->LastRvaSection)); | |
| if (cv_info->cv_signature != CV_SIGNATURE_RSDS) { | |
| continue; | |
| } | |
| info->debug_identifier = GenerateDebugIdentifier(cv_info->age, | |
| cv_info->signature); | |
| // This code assumes that the pdb_filename is stored as ASCII without | |
| // multibyte characters, but it's not clear if that's true. | |
| size_t debug_file_length = strnlen_s(cv_info->pdb_filename, MAX_PATH); | |
| if (debug_file_length < 0 || debug_file_length >= MAX_PATH) { | |
| fprintf(stderr, "PE debug directory is corrupt.\n"); | |
| return false; | |
| } | |
| std::string debug_file(cv_info->pdb_filename, debug_file_length); | |
| if (!WindowsStringUtils::safe_mbstowcs(debug_file, &info->debug_file)) { | |
| fprintf(stderr, "PDB filename '%s' contains unrecognized characters.\n", | |
| debug_file.c_str()); | |
| return false; | |
| } | |
| info->debug_file = WindowsStringUtils::GetBaseName(info->debug_file); | |
| return true; | |
| } | |
| fprintf(stderr, "Image is missing debug information.\n"); | |
| return false; | |
| } | |
| bool ReadPEInfo(const wstring & pe_file, PEModuleInfo * info) { | |
| // Convert wchar to native charset because ImageLoad only takes | |
| // a PSTR as input. | |
| string img_file; | |
| if (!WindowsStringUtils::safe_wcstombs(pe_file, &img_file)) { | |
| fprintf(stderr, "Image path '%S' contains unrecognized characters.\n", | |
| pe_file.c_str()); | |
| return false; | |
| } | |
| AutoImage img(ImageLoad((PSTR)img_file.c_str(), NULL)); | |
| if (!img) { | |
| fprintf(stderr, "Failed to open PE file: %S\n", pe_file.c_str()); | |
| return false; | |
| } | |
| info->code_file = WindowsStringUtils::GetBaseName(pe_file); | |
| // The date and time that the file was created by the linker. | |
| DWORD TimeDateStamp = img->FileHeader->FileHeader.TimeDateStamp; | |
| // The size of the file in bytes, including all headers. | |
| DWORD SizeOfImage = 0; | |
| PIMAGE_OPTIONAL_HEADER64 opt = | |
| &((PIMAGE_NT_HEADERS64)img->FileHeader)->OptionalHeader; | |
| if (opt->Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { | |
| // 64-bit PE file. | |
| SizeOfImage = opt->SizeOfImage; | |
| } | |
| else { | |
| // 32-bit PE file. | |
| SizeOfImage = img->FileHeader->OptionalHeader.SizeOfImage; | |
| } | |
| wchar_t code_identifier[32]; | |
| swprintf(code_identifier, | |
| sizeof(code_identifier) / sizeof(code_identifier[0]), | |
| L"%08X%X", TimeDateStamp, SizeOfImage); | |
| info->code_identifier = code_identifier; | |
| return true; | |
| } | |
| bool PrintPEFrameData(const wstring & pe_file, FILE * out_file) | |
| { | |
| // Convert wchar to native charset because ImageLoad only takes | |
| // a PSTR as input. | |
| string img_file; | |
| if (!WindowsStringUtils::safe_wcstombs(pe_file, &img_file)) { | |
| fprintf(stderr, "Image path '%S' contains unrecognized characters.\n", | |
| pe_file.c_str()); | |
| return false; | |
| } | |
| AutoImage img(ImageLoad((PSTR)img_file.c_str(), NULL)); | |
| if (!img) { | |
| fprintf(stderr, "Failed to load %s\n", img_file.c_str()); | |
| return false; | |
| } | |
| PIMAGE_OPTIONAL_HEADER64 optional_header = | |
| &(reinterpret_cast<PIMAGE_NT_HEADERS64>(img->FileHeader))->OptionalHeader; | |
| if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { | |
| fprintf(stderr, "Not a PE32+ image\n"); | |
| return false; | |
| } | |
| // Read Exception Directory | |
| DWORD exception_rva = optional_header-> | |
| DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; | |
| DWORD exception_size = optional_header-> | |
| DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; | |
| PIMAGE_RUNTIME_FUNCTION_ENTRY funcs = | |
| static_cast<PIMAGE_RUNTIME_FUNCTION_ENTRY>( | |
| ImageRvaToVa(img->FileHeader, | |
| img->MappedAddress, | |
| exception_rva, | |
| &img->LastRvaSection)); | |
| for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) { | |
| DWORD unwind_rva = funcs[i].UnwindInfoAddress; | |
| // handle chaining | |
| while (unwind_rva & 0x1) { | |
| unwind_rva ^= 0x1; | |
| PIMAGE_RUNTIME_FUNCTION_ENTRY chained_func = | |
| static_cast<PIMAGE_RUNTIME_FUNCTION_ENTRY>( | |
| ImageRvaToVa(img->FileHeader, | |
| img->MappedAddress, | |
| unwind_rva, | |
| &img->LastRvaSection)); | |
| unwind_rva = chained_func->UnwindInfoAddress; | |
| } | |
| UnwindInfo *unwind_info = static_cast<UnwindInfo *>( | |
| ImageRvaToVa(img->FileHeader, | |
| img->MappedAddress, | |
| unwind_rva, | |
| &img->LastRvaSection)); | |
| DWORD stack_size = 8; // minimal stack size is 8 for RIP | |
| DWORD rip_offset = 8; | |
| do { | |
| for (UBYTE c = 0; c < unwind_info->count_of_codes; c++) { | |
| UnwindCode *unwind_code = &unwind_info->unwind_code[c]; | |
| switch (unwind_code->unwind_operation_code) { | |
| case UWOP_PUSH_NONVOL: { | |
| stack_size += 8; | |
| break; | |
| } | |
| case UWOP_ALLOC_LARGE: { | |
| if (unwind_code->operation_info == 0) { | |
| c++; | |
| if (c < unwind_info->count_of_codes) | |
| stack_size += (unwind_code + 1)->frame_offset * 8; | |
| } | |
| else { | |
| c += 2; | |
| if (c < unwind_info->count_of_codes) | |
| stack_size += (unwind_code + 1)->frame_offset | | |
| ((unwind_code + 2)->frame_offset << 16); | |
| } | |
| break; | |
| } | |
| case UWOP_ALLOC_SMALL: { | |
| stack_size += unwind_code->operation_info * 8 + 8; | |
| break; | |
| } | |
| case UWOP_SET_FPREG: | |
| case UWOP_SAVE_XMM: | |
| case UWOP_SAVE_XMM_FAR: | |
| break; | |
| case UWOP_SAVE_NONVOL: | |
| case UWOP_SAVE_XMM128: { | |
| c++; // skip slot with offset | |
| break; | |
| } | |
| case UWOP_SAVE_NONVOL_FAR: | |
| case UWOP_SAVE_XMM128_FAR: { | |
| c += 2; // skip 2 slots with offset | |
| break; | |
| } | |
| case UWOP_PUSH_MACHFRAME: { | |
| if (unwind_code->operation_info) { | |
| stack_size += 88; | |
| } | |
| else { | |
| stack_size += 80; | |
| } | |
| rip_offset += 80; | |
| break; | |
| } | |
| } | |
| } | |
| if (unwind_info->flags & UNW_FLAG_CHAININFO) { | |
| PIMAGE_RUNTIME_FUNCTION_ENTRY chained_func = | |
| reinterpret_cast<PIMAGE_RUNTIME_FUNCTION_ENTRY>( | |
| (unwind_info->unwind_code + | |
| ((unwind_info->count_of_codes + 1) & ~1))); | |
| unwind_info = static_cast<UnwindInfo *>( | |
| ImageRvaToVa(img->FileHeader, | |
| img->MappedAddress, | |
| chained_func->UnwindInfoAddress, | |
| &img->LastRvaSection)); | |
| } | |
| else { | |
| unwind_info = NULL; | |
| } | |
| } while (unwind_info); | |
| fprintf(out_file, "STACK CFI INIT %lx %lx .cfa: $rsp .ra: .cfa %lu - ^\n", | |
| funcs[i].BeginAddress, | |
| funcs[i].EndAddress - funcs[i].BeginAddress, rip_offset); | |
| fprintf(out_file, "STACK CFI %lx .cfa: $rsp %lu +\n", | |
| funcs[i].BeginAddress, stack_size); | |
| } | |
| return true; | |
| } | |
| wstring GenerateDebugIdentifier(DWORD age, GUID signature) | |
| { | |
| // Use the same format that the MS symbol server uses in filesystem | |
| // hierarchies. | |
| wchar_t age_string[9]; | |
| swprintf(age_string, sizeof(age_string) / sizeof(age_string[0]), | |
| L"%x", age); | |
| // remove when VC++7.1 is no longer supported | |
| age_string[sizeof(age_string) / sizeof(age_string[0]) - 1] = L'\0'; | |
| wstring debug_identifier = GUIDString::GUIDToSymbolServerWString(&signature); | |
| debug_identifier.append(age_string); | |
| return debug_identifier; | |
| } | |
| wstring GenerateDebugIdentifier(DWORD age, DWORD signature) | |
| { | |
| // Use the same format that the MS symbol server uses in filesystem | |
| // hierarchies. | |
| wchar_t identifier_string[17]; | |
| swprintf(identifier_string, | |
| sizeof(identifier_string) / sizeof(identifier_string[0]), | |
| L"%08X%x", signature, age); | |
| // remove when VC++7.1 is no longer supported | |
| identifier_string[sizeof(identifier_string) / | |
| sizeof(identifier_string[0]) - 1] = L'\0'; | |
| return wstring(identifier_string); | |
| } | |
| } // namespace google_breakpad |