blob: 9c94af05f00b78861d11f2a013eeb724b5a67e97 [file] [log] [blame]
// 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