| // Copyright 2019 Google LLC |
| // |
| // 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 LLC 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. |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> // Must come first |
| #endif |
| |
| #include "pe_util.h" |
| |
| #include <windows.h> |
| #include <winnt.h> |
| #include <atlbase.h> |
| #include <ImageHlp.h> |
| |
| #include <functional> |
| #include <memory> |
| |
| #include "common/windows/string_utils-inl.h" |
| #include "common/windows/guid_string.h" |
| |
| namespace { |
| |
| /* |
| * Not defined in WinNT.h prior to SDK 10.0.20348.0 for some reason. |
| * Definitions taken from: http://uninformed.org/index.cgi?v=4&a=1&p=13 |
| * |
| */ |
| typedef unsigned char UBYTE; |
| |
| #if !defined(UNW_FLAG_EHANDLER) |
| #define UNW_FLAG_EHANDLER 0x01 |
| #endif |
| #if !defined(UNW_FLAG_UHANDLER) |
| #define UNW_FLAG_UHANDLER 0x02 |
| #endif |
| #if !defined(UNW_FLAG_CHAININFO) |
| #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 |