// 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
