Skip to content

Instantly share code, notes, and snippets.

@frodo821
Last active December 18, 2022 02:24
Show Gist options
  • Save frodo821/460ed4273c8db49aa1d2452847169496 to your computer and use it in GitHub Desktop.
Save frodo821/460ed4273c8db49aa1d2452847169496 to your computer and use it in GitHub Desktop.
Windows Hardware Error Architecture (WHEA) Raw Report Analyser
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "whea_types.hpp"
const char *CPER_BIN_FILE = "CPER";
const char *CPER_ASCII_FILE = "43504552";
/**
* @brief base16エンコードされたファイルであるか、生のバイナリ形式のファイルであるかをチェックする
*
* @param fp ファイルポインタ
* @return int バイナリ形式なら CPER_FILE_TYPE_BINARY を返す。base16エンコード済みなら CPER_FILE_TYPE_ASCII を返す。無効なファイル形式の場合は CPER_FILE_TYPE_INVALID を返す。
*/
int check_file_magic(FILE *fp)
{
char buffer[9];
if (fread(buffer, 1, 8, fp) == 8)
{
if (fseek(fp, 0L, SEEK_SET))
{
perror("fseek");
return CPER_FILE_TYPE_INVALID;
}
buffer[8] = 0;
if (strcmp(buffer, CPER_ASCII_FILE) == 0)
{
return CPER_FILE_TYPE_ASCII;
}
buffer[4] = 0;
if (strcmp(buffer, CPER_BIN_FILE) == 0)
{
return CPER_FILE_TYPE_BINARY;
}
return CPER_FILE_TYPE_INVALID;
}
return CPER_FILE_TYPE_INVALID;
}
/**
* @brief base16文字列を2文字読み込んで1バイトに変換する
*
* @param buffer base16エンコードされた2文字(1バイト分)
* @return uint8_t デコードされた1バイト
*/
uint8_t read_base16_byte(char buffer[2])
{
uint8_t result = 0;
for (int i = 0; i < 2; i++)
{
result = result << 4;
if (buffer[i] >= '0' && buffer[i] <= '9')
{
result += buffer[i] - '0';
}
else if (buffer[i] >= 'a' && buffer[i] <= 'f')
{
result += buffer[i] - 'a' + 10;
}
else if (buffer[i] >= 'A' && buffer[i] <= 'F')
{
result += buffer[i] - 'A' + 10;
}
else
{
return 0;
}
}
return result;
}
/**
* @brief base16エンコードされたファイルから、エラーレポートのヘッダを読み込む
*
* @param fp ファイルポインタ
* @param header ヘッダを格納するメモリ領域へのポインタ
* @return int ヘッダが正常に読み込まれた場合は 0 を返す。それ以外の場合は 1 を返す。
*/
int read_header_in_ascii(FILE *fp, whea_header_t *header)
{
whea_header_in_t header_in;
char buffer[sizeof(whea_header_t) * 2];
if (fread(buffer, 1, sizeof(whea_header_t) * 2, fp) != sizeof(whea_header_t) * 2)
{
return 1;
}
for (int i = 0; i < sizeof(whea_header_t); i++)
{
header_in.bytes[i] = read_base16_byte(&buffer[i * 2]);
}
memcpy(header, &header_in.header, sizeof(whea_header_t));
return 0;
}
/**
* @brief base16エンコードされたファイルから、エラーレポートのセクションデスクリプタを読み込む
*
* @param fp ファイルポインタ
* @param section セクションデスクリプタを格納するメモリ領域へのポインタ
* @return int セクションデスクリプタが正常に読み込まれた場合は 0 を返す。それ以外の場合は 1 を返す。
*/
int read_section_in_ascii(FILE *fp, whea_error_section_descriptor_t *section)
{
whea_error_section_descriptor_in_t section_in;
char buffer[sizeof(whea_error_section_descriptor_t) * 2];
if (fread(buffer, 1, sizeof(whea_error_section_descriptor_t) * 2, fp) != sizeof(whea_error_section_descriptor_t) * 2)
{
return 1;
}
for (int i = 0; i < sizeof(whea_error_section_descriptor_t); i++)
{
section_in.bytes[i] = read_base16_byte(&buffer[i * 2]);
}
memcpy(section, &section_in.desc, sizeof(whea_error_section_descriptor_t));
return 0;
}
int main(int argc, char *argv[])
{
FILE *fp;
whea_header_t header;
if (argc != 2)
{
printf("Usage: %s <file>\n", argv[0]);
return 1;
}
fp = fopen(argv[1], "rb");
if (fp == NULL)
{
printf("Failed to open file %s\n", argv[1]);
return 1;
}
size_t len;
int file_type = check_file_magic(fp);
if (file_type == CPER_FILE_TYPE_INVALID)
{
printf("Invalid file type\n");
return 1;
}
if (file_type == CPER_FILE_TYPE_ASCII)
{
if (read_header_in_ascii(fp, &header))
{
printf("Failed to read header\n");
return 1;
}
}
else
{
if ((len = fread(&header, sizeof(char), sizeof(whea_header_t), fp)) < sizeof(whea_header_t))
{
printf("Failed to read header, expected %zd bytes; actual %zd bytes.\n", sizeof(whea_header_t), len);
fclose(fp);
return 1;
}
}
printf("Signature +%04x: %.4s\n", offsets_h(Signature), header.Signature.AsCHAR);
printf("Revision +%04x: %u.%u (%u)\n", offsets_h(Revision), header.Revision.MajorRevision, header.Revision.MinorRevision, header.Revision.AsUSHORT);
printf("SignatureEnd +%04x: %lx\n", offsets_h(SignatureEnd), header.SignatureEnd);
printf("SectionCount +%04x: %lu\n", offsets_h(SectionCount), header.SectionCount);
printf("ErrorSeverity +%04x: %u\n", offsets_h(Severity), header.Severity);
printf("ValidationBits +%04x: %lu\n", offsets_h(ValidBits), header.ValidBits.AsULONG);
printf("Length +%04x: %lu\n", offsets_h(Length), header.Length);
printf("Timestamp +%04x: 20%02lld-%02lld-%02lld %02lld:%02lld:%02lld\n",
offsets_h(Timestamp),
header.Timestamp.Year,
header.Timestamp.Month,
header.Timestamp.Day,
header.Timestamp.Hours,
header.Timestamp.Minutes,
header.Timestamp.Seconds);
printf("PlatformId +%04x: " format_guid "\n", offsets_h(PlatformId), format_guid_args(header.PlatformId));
printf("PartitionId +%04x: " format_guid "\n", offsets_h(PartitionId), format_guid_args(header.PartitionId));
printf("CreatorId +%04x: " format_guid "\n", offsets_h(CreatorId), format_guid_args(header.CreatorId));
printf("NotifyType +%04x: " format_guid "\n", offsets_h(NotifyType), format_guid_args(header.NotifyType));
printf("RecordId +%04x: %llu\n", offsets_h(RecordId), header.RecordId);
printf("Flags +%04x: %04lx\n", offsets_h(Flags), header.Flags.AsULONG);
printf("PersistenceInfo +%04x: %08llx\n", offsets_h(PersistenceInfo), header.PersistenceInfo.AsULONGLONG);
printf("\n===============================\n");
whea_error_section_descriptor_t descriptor;
for (int i = 0; i < header.SectionCount; i++)
{
if (file_type == CPER_FILE_TYPE_ASCII)
{
if (read_section_in_ascii(fp, &descriptor))
{
printf("Failed to read section descriptor\n");
return 1;
}
}
else
{
if ((len = fread(&descriptor, sizeof(char), sizeof(whea_error_section_descriptor_t), fp)) < sizeof(whea_error_section_descriptor_t))
{
printf("Failed to read descriptor, expected %zd bytes; actual %zd bytes.\n", sizeof(whea_error_section_descriptor_t), len);
fclose(fp);
return 1;
}
}
printf("---> Section %d\n", i);
printf("SectionOffset +%04x: %lu\n", offsets_s(SectionOffset, i), descriptor.SectionOffset);
printf("SectionLength +%04x: %lu\n", offsets_s(SectionLength, i), descriptor.SectionLength);
printf("Revision +%04x: %u.%u (%u)\n", offsets_s(Revision, i), descriptor.Revision.MajorRevision, descriptor.Revision.MinorRevision, descriptor.Revision.AsUSHORT);
printf("ValidationBits +%04x: %u\n", offsets_s(ValidBits, i), descriptor.ValidBits.AsUCHAR);
printf("Flags +%04x: %lu\n", offsets_s(Flags, i), descriptor.Flags.AsULONG);
printf("SectionType +%04x: " format_guid "\n", offsets_s(SectionType, i), format_guid_args(descriptor.SectionType));
printf("FRUId +%04x: " format_guid "\n", offsets_s(FRUId, i), format_guid_args(descriptor.FRUId));
printf("SectionSeverity +%04x: %u\n", offsets_s(SectionSeverity, i), descriptor.SectionSeverity);
printf("FRUText +%04x: '%.20s'\n\n", offsets_s(FRUText, i), descriptor.FRUText);
}
}
#ifndef CPER_TYPES_HPP
#define CPER_TYPES_HPP
/**
* @brief 構造体のメンバのオフセットを計算する
*/
#define offsetof(s, m) ((size_t) & (((s *)0)->m))
/**
* @brief whea_header_t のメンバのオフセットを計算する
*/
#define offsets_h(x) ((unsigned)offsetof(whea_header_t, x))
/**
* @brief whea_error_section_descriptor_t のメンバのファイルの先頭からのオフセットを計算する
*/
#define offsets_s(x, i) (unsigned)(offsetof(whea_error_section_descriptor_t, x) + sizeof(whea_error_section_descriptor_t) * (i) + sizeof(whea_header_t))
/**
* @brief バイナリ形式のレポートファイルを表す
*/
#define CPER_FILE_TYPE_BINARY 0
/**
* @brief base16エンコードされたレポートファイルを表す
*/
#define CPER_FILE_TYPE_ASCII 1
/**
* @brief 無効な形式のレポートファイルを表す
*/
#define CPER_FILE_TYPE_INVALID -1
#define _h2_bytes_of_last_8_bytes(data) ((((uint16_t)data[0]) << 8) | (((uint16_t)data[1])))
#define _l6_bytes_of_last_8_bytes(data) ((((uint64_t)data[2]) << 40) | (((uint64_t)data[3]) << 32) | \
(((uint64_t)data[4]) << 24) | (((uint64_t)data[5]) << 16) | \
(((uint64_t)data[6]) << 8) | (((uint64_t)data[7])))
#define format_guid "%08x-%04x-%04x-%04x-%012llx"
#define format_guid_args(guid) (guid).data1, \
(guid).data2, \
(guid).data3, \
_h2_bytes_of_last_8_bytes((guid).data4), \
_l6_bytes_of_last_8_bytes((guid).data4)
#pragma pack(1)
/**
* とりあえず使いそうな整数型をサイズ別に定義しておく。
* 本来的には stdint.h を使うべきだが、cl.exe でビルドしようとしたときにエラーが出たので一応念のため。
*/
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef struct
{
uint32_t data1;
uint16_t data2;
uint16_t data3;
uint8_t data4[8];
} guid_t;
typedef union
{
struct
{
uint32_t PlatformId : 1;
uint32_t Timestamp : 1;
uint32_t PartitionId : 1;
uint32_t Reserved : 29;
};
uint32_t AsULONG;
} whea_erh_valid_bits_t;
typedef union
{
struct
{
uint8_t MinorRevision;
uint8_t MajorRevision;
};
uint16_t AsUSHORT;
} whea_revision_t;
typedef enum
{
WheaErrSevRecoverable = 0,
WheaErrSevFatal = 1,
WheaErrSevCorrected = 2,
WheaErrSevInformational = 3
} whea_error_severity_t;
typedef union
{
struct
{
uint64_t Seconds : 8;
uint64_t Minutes : 8;
uint64_t Hours : 8;
uint64_t Precise : 1;
uint64_t Reserved : 7;
uint64_t Day : 8;
uint64_t Month : 8;
uint64_t Year : 8;
uint64_t Century : 8;
};
uint64_t AsULONGLONG;
} whea_timestamp_t;
typedef union
{
struct
{
uint64_t Signature : 16;
uint64_t Length : 24;
uint64_t Identifier : 16;
uint64_t Attributes : 2;
uint64_t DoNotLog : 1;
uint64_t Reserved : 5;
};
uint64_t AsULONGLONG;
} whea_persistence_info_t;
typedef union
{
struct
{
uint32_t Recovered : 1;
uint32_t PreviousError : 1;
uint32_t Simulated : 1;
uint32_t DeviceDriver : 1;
uint32_t CriticalEvent : 1;
uint32_t PersistPfn : 1;
uint32_t SectionsTruncated : 1;
uint32_t RecoveryInProgress : 1;
uint32_t Throttle : 1;
uint32_t Reserved : 23;
};
uint32_t AsULONG;
} whea_erh_flags_t;
typedef struct
{
union
{
uint32_t AsLONG;
char AsCHAR[4];
} Signature;
whea_revision_t Revision;
uint32_t SignatureEnd;
uint16_t SectionCount;
whea_error_severity_t Severity;
whea_erh_valid_bits_t ValidBits;
uint32_t Length;
whea_timestamp_t Timestamp;
guid_t PlatformId;
guid_t PartitionId;
guid_t CreatorId;
guid_t NotifyType;
uint64_t RecordId;
whea_erh_flags_t Flags;
whea_persistence_info_t PersistenceInfo;
union
{
struct
{
uint32_t OsBuildNumber;
uint8_t Reserved2[8];
};
uint8_t Reserved[12];
};
} whea_header_t;
typedef union
{
struct
{
uint8_t FRUId : 1;
uint8_t FRUText : 1;
uint8_t Reserved : 6;
};
uint8_t AsUCHAR;
} whea_ersd_valid_bits_t;
typedef union
{
struct
{
uint32_t Primary : 1;
uint32_t ContainmentWarning : 1;
uint32_t Reset : 1;
uint32_t ThresholdExceeded : 1;
uint32_t ResourceNotAvailable : 1;
uint32_t LatentError : 1;
uint32_t Propagated : 1;
uint32_t FruTextByPlugin : 1;
uint32_t Reserved : 24;
};
uint32_t AsULONG;
} whea_ersd_flags_t;
typedef struct
{
uint32_t SectionOffset;
uint32_t SectionLength;
whea_revision_t Revision;
whea_ersd_valid_bits_t ValidBits;
uint8_t Reserved;
whea_ersd_flags_t Flags;
guid_t SectionType;
guid_t FRUId;
whea_error_severity_t SectionSeverity;
char FRUText[20];
} whea_error_section_descriptor_t;
typedef union
{
whea_header_t header;
uint8_t bytes[sizeof(whea_header_t)];
} whea_header_in_t;
typedef union
{
whea_error_section_descriptor_t desc;
uint8_t bytes[sizeof(whea_error_section_descriptor_t)];
} whea_error_section_descriptor_in_t;
#pragma pack()
#endif
@frodo821
Copy link
Author

Windowsイベントビューアで見ることができる、BASE16エンコードされた形式のレポートをそのまま読み込めるように変更した

@frodo821
Copy link
Author

GUIDの定義を含めた。これによって Windows.h への依存を消せたため、どの環境の gcc でも大抵コンパイル可能なコードとなった。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment