Add the capability to include an arbitrary data stream within minidumps

This is supplied via a custom field "custom-data-stream"
Review URL: https://breakpad.appspot.com/408002

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@984 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
cdn@chromium.org 2012-07-10 18:52:07 +00:00
parent 0bed408b15
commit e05aab7b6b
8 changed files with 108 additions and 12 deletions

View file

@ -82,6 +82,11 @@ struct CustomInfoEntry {
wchar_t value[kValueMaxLength]; wchar_t value[kValueMaxLength];
}; };
struct CustomDataStream {
size_t size;
u_int8_t stream[1];
};
// Constants for the protocol between client and the server. // Constants for the protocol between client and the server.
// Tags sent with each message indicating the purpose of // Tags sent with each message indicating the purpose of

View file

@ -31,6 +31,8 @@
#include "client/windows/common/ipc_protocol.h" #include "client/windows/common/ipc_protocol.h"
static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime"; static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime";
static const wchar_t kCustomDataStreamCustomFieldName[] = L"custom-data-stream";
static const size_t kMaxCustomDataStreamSize = 100 * 1024 * 1024;
static const size_t kMaxCustomInfoEntries = 4096; static const size_t kMaxCustomInfoEntries = 4096;
namespace google_breakpad { namespace google_breakpad {
@ -48,6 +50,7 @@ ClientInfo::ClientInfo(CrashGenerationServer* crash_server,
ex_info_(ex_info), ex_info_(ex_info),
assert_info_(assert_info), assert_info_(assert_info),
custom_client_info_(custom_client_info), custom_client_info_(custom_client_info),
custom_data_stream_(NULL),
thread_id_(thread_id), thread_id_(thread_id),
process_handle_(NULL), process_handle_(NULL),
dump_requested_handle_(NULL), dump_requested_handle_(NULL),
@ -86,6 +89,11 @@ bool ClientInfo::Initialize() {
} }
ClientInfo::~ClientInfo() { ClientInfo::~ClientInfo() {
if (custom_data_stream_) {
delete custom_data_stream_;
custom_data_stream_ = NULL;
}
if (dump_request_wait_handle_) { if (dump_request_wait_handle_) {
// Wait for callbacks that might already be running to finish. // Wait for callbacks that might already be running to finish.
UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE); UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE);
@ -199,6 +207,43 @@ bool ClientInfo::PopulateCustomInfo() {
return (bytes_count != read_count); return (bytes_count != read_count);
} }
bool ClientInfo::PopulateCustomDataStream() {
for (SIZE_T i = 0; i < custom_client_info_.count; ++i) {
if (_wcsicmp(kCustomDataStreamCustomFieldName,
custom_client_info_.entries[i].name) != 0) {
continue;
}
wchar_t address_str[CustomInfoEntry::kValueMaxLength];
memcpy(address_str, custom_client_info_.entries[i].value,
CustomInfoEntry::kValueMaxLength);
wchar_t* size_str = wcschr(address_str, ':');
if (!size_str)
return false;
size_str[0] = 0;
++size_str;
void* address = reinterpret_cast<void*>(_wcstoi64(address_str, NULL, 16));
long size = wcstol(size_str, NULL, 16);
if (size <= 0 || size > kMaxCustomDataStreamSize)
return false;
custom_data_stream_ = reinterpret_cast<CustomDataStream*>(
new u_int8_t[sizeof(CustomDataStream) + size - 1]);
SIZE_T bytes_count = 0;
if (!ReadProcessMemory(process_handle_, address,
custom_data_stream_->stream, size, &bytes_count)) {
delete custom_data_stream_;
custom_data_stream_ = NULL;
return false;
}
return true;
}
return false;
}
CustomClientInfo ClientInfo::GetCustomInfo() const { CustomClientInfo ClientInfo::GetCustomInfo() const {
CustomClientInfo custom_info; CustomClientInfo custom_info;
custom_info.entries = custom_info_entries_.get(); custom_info.entries = custom_info_entries_.get();

View file

@ -61,6 +61,7 @@ class ClientInfo {
MINIDUMP_TYPE dump_type() const { return dump_type_; } MINIDUMP_TYPE dump_type() const { return dump_type_; }
EXCEPTION_POINTERS** ex_info() const { return ex_info_; } EXCEPTION_POINTERS** ex_info() const { return ex_info_; }
MDRawAssertionInfo* assert_info() const { return assert_info_; } MDRawAssertionInfo* assert_info() const { return assert_info_; }
CustomDataStream* custom_data_stream() const { return custom_data_stream_; }
DWORD* thread_id() const { return thread_id_; } DWORD* thread_id() const { return thread_id_; }
HANDLE process_handle() const { return process_handle_; } HANDLE process_handle() const { return process_handle_; }
HANDLE dump_requested_handle() const { return dump_requested_handle_; } HANDLE dump_requested_handle() const { return dump_requested_handle_; }
@ -90,6 +91,10 @@ class ClientInfo {
bool GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const; bool GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const;
bool GetClientThreadId(DWORD* thread_id) const; bool GetClientThreadId(DWORD* thread_id) const;
// Reads the custom data stream (if supplied) from the client process
// address space.
bool PopulateCustomDataStream();
// Reads the custom information from the client process address space. // Reads the custom information from the client process address space.
bool PopulateCustomInfo(); bool PopulateCustomInfo();
@ -130,6 +135,9 @@ class ClientInfo {
// Custom information about the client. // Custom information about the client.
CustomClientInfo custom_client_info_; CustomClientInfo custom_client_info_;
// Custom data stream supplied by the client.
CustomDataStream* custom_data_stream_;
// Contains the custom client info entries read from the client process // Contains the custom client info entries read from the client process
// memory. This will be populated only if the method GetClientCustomInfo // memory. This will be populated only if the method GetClientCustomInfo
// is called. // is called.

View file

@ -796,6 +796,7 @@ void CALLBACK CrashGenerationServer::OnDumpRequest(void* context, BOOLEAN) {
assert(context); assert(context);
ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context); ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context);
client_info->PopulateCustomInfo(); client_info->PopulateCustomInfo();
client_info->PopulateCustomDataStream();
CrashGenerationServer* crash_server = client_info->crash_server(); CrashGenerationServer* crash_server = client_info->crash_server();
assert(crash_server); assert(crash_server);
@ -892,6 +893,7 @@ bool CrashGenerationServer::GenerateDump(const ClientInfo& client,
GetCurrentThreadId(), GetCurrentThreadId(),
client_ex_info, client_ex_info,
client.assert_info(), client.assert_info(),
client.custom_data_stream(),
client.dump_type(), client.dump_type(),
true, true,
dump_path); dump_path);

View file

@ -271,14 +271,15 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
DWORD requesting_thread_id, DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers, EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info, MDRawAssertionInfo* assert_info,
CustomDataStream* custom_data_stream,
MINIDUMP_TYPE dump_type, MINIDUMP_TYPE dump_type,
bool is_client_pointers, bool is_client_pointers,
wstring* dump_path) { wstring* dump_path) {
// Just call the full WriteMinidump with NULL as the full_dump_path. // Just call the full WriteMinidump with NULL as the full_dump_path.
return this->WriteMinidump(process_handle, process_id, thread_id, return this->WriteMinidump(process_handle, process_id, thread_id,
requesting_thread_id, exception_pointers, requesting_thread_id, exception_pointers,
assert_info, dump_type, is_client_pointers, assert_info, custom_data_stream, dump_type,
dump_path, NULL); is_client_pointers, dump_path, NULL);
} }
bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
@ -287,6 +288,7 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
DWORD requesting_thread_id, DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers, EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info, MDRawAssertionInfo* assert_info,
CustomDataStream* custom_data_stream,
MINIDUMP_TYPE dump_type, MINIDUMP_TYPE dump_type,
bool is_client_pointers, bool is_client_pointers,
wstring* dump_path, wstring* dump_path,
@ -368,9 +370,9 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
breakpad_info.requesting_thread_id = requesting_thread_id; breakpad_info.requesting_thread_id = requesting_thread_id;
} }
// Leave room in user_stream_array for possible assertion info and handle // Leave room in user_stream_array for possible assertion info, handle
// operations streams. // operations, and custom data streams.
MINIDUMP_USER_STREAM user_stream_array[3]; MINIDUMP_USER_STREAM user_stream_array[4];
user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM; user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM;
user_stream_array[0].BufferSize = sizeof(breakpad_info); user_stream_array[0].BufferSize = sizeof(breakpad_info);
user_stream_array[0].Buffer = &breakpad_info; user_stream_array[0].Buffer = &breakpad_info;
@ -414,6 +416,16 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle,
++user_streams.UserStreamCount; ++user_streams.UserStreamCount;
} }
if (custom_data_stream) {
user_stream_array[user_streams.UserStreamCount].Type =
MD_CUSTOM_DATA_STREAM;
user_stream_array[user_streams.UserStreamCount].BufferSize =
custom_data_stream->size;
user_stream_array[user_streams.UserStreamCount].Buffer =
custom_data_stream->stream;
++user_streams.UserStreamCount;
}
// If the process is terminated by STATUS_INVALID_HANDLE exception store // If the process is terminated by STATUS_INVALID_HANDLE exception store
// the trace of operatios for the offending handle value. Do nothing special // the trace of operatios for the offending handle value. Do nothing special
// if the client already requested the handle trace to be stored in the dump. // if the client already requested the handle trace to be stored in the dump.

View file

@ -33,6 +33,7 @@
#include <windows.h> #include <windows.h>
#include <dbghelp.h> #include <dbghelp.h>
#include <list> #include <list>
#include "client/windows/common/ipc_protocol.h"
#include "google_breakpad/common/minidump_format.h" #include "google_breakpad/common/minidump_format.h"
namespace google_breakpad { namespace google_breakpad {
@ -57,6 +58,7 @@ class MinidumpGenerator {
DWORD requesting_thread_id, DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers, EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info, MDRawAssertionInfo* assert_info,
CustomDataStream* custom_data_stream,
MINIDUMP_TYPE dump_type, MINIDUMP_TYPE dump_type,
bool is_client_pointers, bool is_client_pointers,
std::wstring* dump_path); std::wstring* dump_path);
@ -70,6 +72,7 @@ class MinidumpGenerator {
DWORD requesting_thread_id, DWORD requesting_thread_id,
EXCEPTION_POINTERS* exception_pointers, EXCEPTION_POINTERS* exception_pointers,
MDRawAssertionInfo* assert_info, MDRawAssertionInfo* assert_info,
CustomDataStream* custom_data_stream,
MINIDUMP_TYPE dump_type, MINIDUMP_TYPE dump_type,
bool is_client_pointers, bool is_client_pointers,
std::wstring* dump_path, std::wstring* dump_path,

View file

@ -31,6 +31,7 @@
#include <objbase.h> #include <objbase.h>
#include <dbghelp.h> #include <dbghelp.h>
#include "../common/ipc_protocol.h"
#include "../crash_generation/minidump_generator.h" #include "../crash_generation/minidump_generator.h"
#include "dump_analysis.h" // NOLINT #include "dump_analysis.h" // NOLINT
@ -86,7 +87,8 @@ class MinidumpTest: public testing::Test {
} }
} }
bool WriteDump(ULONG flags) { bool WriteDump(ULONG flags, MDRawAssertionInfo* assert,
google_breakpad::CustomDataStream* custom_data) {
using google_breakpad::MinidumpGenerator; using google_breakpad::MinidumpGenerator;
// Fake exception is access violation on write to this. // Fake exception is access violation on write to this.
@ -112,7 +114,8 @@ class MinidumpTest: public testing::Test {
::GetCurrentThreadId(), ::GetCurrentThreadId(),
::GetCurrentThreadId(), ::GetCurrentThreadId(),
&ex_ptrs, &ex_ptrs,
NULL, assert,
custom_data,
static_cast<MINIDUMP_TYPE>(flags), static_cast<MINIDUMP_TYPE>(flags),
TRUE, TRUE,
&dump_file_, &dump_file_,
@ -177,7 +180,7 @@ TEST_F(MinidumpTest, Version) {
} }
TEST_F(MinidumpTest, Normal) { TEST_F(MinidumpTest, Normal) {
EXPECT_TRUE(WriteDump(MiniDumpNormal)); EXPECT_TRUE(WriteDump(MiniDumpNormal, NULL, NULL));
DumpAnalysis mini(dump_file_); DumpAnalysis mini(dump_file_);
// We expect threads, modules and some memory. // We expect threads, modules and some memory.
@ -206,10 +209,13 @@ TEST_F(MinidumpTest, Normal) {
// We expect no off-stack memory in this dump. // We expect no off-stack memory in this dump.
EXPECT_FALSE(mini.HasMemory(this)); EXPECT_FALSE(mini.HasMemory(this));
// We do not expect a custom data stream.
EXPECT_FALSE(mini.HasStream(MD_CUSTOM_DATA_STREAM));
} }
TEST_F(MinidumpTest, SmallDump) { TEST_F(MinidumpTest, SmallDump) {
ASSERT_TRUE(WriteDump(kSmallDumpType)); ASSERT_TRUE(WriteDump(kSmallDumpType, NULL, NULL));
DumpAnalysis mini(dump_file_); DumpAnalysis mini(dump_file_);
EXPECT_TRUE(mini.HasStream(ThreadListStream)); EXPECT_TRUE(mini.HasStream(ThreadListStream));
@ -240,7 +246,7 @@ TEST_F(MinidumpTest, SmallDump) {
} }
TEST_F(MinidumpTest, LargerDump) { TEST_F(MinidumpTest, LargerDump) {
ASSERT_TRUE(WriteDump(kLargerDumpType)); ASSERT_TRUE(WriteDump(kLargerDumpType, NULL, NULL));
DumpAnalysis mini(dump_file_); DumpAnalysis mini(dump_file_);
// The dump should have all of these streams. // The dump should have all of these streams.
@ -272,7 +278,7 @@ TEST_F(MinidumpTest, LargerDump) {
} }
TEST_F(MinidumpTest, FullDump) { TEST_F(MinidumpTest, FullDump) {
ASSERT_TRUE(WriteDump(kFullDumpType)); ASSERT_TRUE(WriteDump(kFullDumpType, NULL, NULL));
ASSERT_TRUE(dump_file_ != L""); ASSERT_TRUE(dump_file_ != L"");
ASSERT_TRUE(full_dump_file_ != L""); ASSERT_TRUE(full_dump_file_ != L"");
DumpAnalysis mini(dump_file_); DumpAnalysis mini(dump_file_);
@ -329,4 +335,15 @@ TEST_F(MinidumpTest, FullDump) {
EXPECT_FALSE(full.HasStream(TokenStream)); EXPECT_FALSE(full.HasStream(TokenStream));
} }
TEST_F(MinidumpTest, CustomData) {
google_breakpad::CustomDataStream custom_data;
custom_data.size = 1;
custom_data.stream[0] = 'A';
EXPECT_TRUE(WriteDump(MiniDumpNormal, NULL, &custom_data));
DumpAnalysis mini(dump_file_);
// We expect a custom data stream.
EXPECT_TRUE(mini.HasStream(MD_CUSTOM_DATA_STREAM));
}
} // namespace } // namespace

View file

@ -331,7 +331,8 @@ typedef enum {
/* Breakpad extension types. 0x4767 = "Gg" */ /* Breakpad extension types. 0x4767 = "Gg" */
MD_BREAKPAD_INFO_STREAM = 0x47670001, /* MDRawBreakpadInfo */ MD_BREAKPAD_INFO_STREAM = 0x47670001, /* MDRawBreakpadInfo */
MD_ASSERTION_INFO_STREAM = 0x47670002 /* MDRawAssertionInfo */ MD_ASSERTION_INFO_STREAM = 0x47670002, /* MDRawAssertionInfo */
MD_CUSTOM_DATA_STREAM = 0x47670003 /* MDRawCustomDataStream */
} MDStreamType; /* MINIDUMP_STREAM_TYPE */ } MDStreamType; /* MINIDUMP_STREAM_TYPE */
@ -724,6 +725,9 @@ typedef enum {
* Breakpad extension types * Breakpad extension types
*/ */
typedef struct {
u_int8_t stream[1];
} MDRawCustomDataStream;
typedef struct { typedef struct {
/* validity is a bitmask with values from MDBreakpadInfoValidity, indicating /* validity is a bitmask with values from MDBreakpadInfoValidity, indicating