mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
[log minidump] LogOutputStream implementation
Emit the received data to Android logcat in Android, and noop for other platforms. Bug: crashpad:308 Change-Id: I6e46e2fa8bd61f93f614ad0bfb6441a79139b04b Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/1958711 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
parent
097395dfca
commit
fa28ef896c
@ -168,6 +168,8 @@ static_library("util") {
|
||||
"stdlib/thread_safe_vector.h",
|
||||
"stream/base94_output_stream.cc",
|
||||
"stream/base94_output_stream.h",
|
||||
"stream/log_output_stream.cc",
|
||||
"stream/log_output_stream.h",
|
||||
"stream/output_stream_interface.h",
|
||||
"stream/zlib_output_stream.cc",
|
||||
"stream/zlib_output_stream.h",
|
||||
@ -591,6 +593,7 @@ source_set("util_test") {
|
||||
"stdlib/strnlen_test.cc",
|
||||
"stdlib/thread_safe_vector_test.cc",
|
||||
"stream/base94_output_stream_test.cc",
|
||||
"stream/log_output_stream_test.cc",
|
||||
"stream/test_output_stream.cc",
|
||||
"stream/test_output_stream.h",
|
||||
"stream/zlib_output_stream_test.cc",
|
||||
|
130
util/stream/log_output_stream.cc
Normal file
130
util/stream/log_output_stream.cc
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2019 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "util/stream/log_output_stream.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
// Most minidumps are expected to be compressed and encoded into less than 128k.
|
||||
constexpr size_t kOutputCap = 128 * 1024;
|
||||
|
||||
// From Android NDK r20 <android/log.h>, log message text may be truncated to
|
||||
// less than an implementation-specific limit (1023 bytes), for sake of safe
|
||||
// and being easy to read in logcat, choose 512.
|
||||
constexpr size_t kLineBufferSize = 512;
|
||||
|
||||
} // namespace
|
||||
|
||||
LogOutputStream::LogOutputStream()
|
||||
: output_count_(0), flush_needed_(false), flushed_(false) {
|
||||
buffer_.reserve(kLineBufferSize);
|
||||
}
|
||||
|
||||
LogOutputStream::~LogOutputStream() {
|
||||
DCHECK(!flush_needed_);
|
||||
}
|
||||
|
||||
bool LogOutputStream::Write(const uint8_t* data, size_t size) {
|
||||
DCHECK(!flushed_);
|
||||
flush_needed_ = true;
|
||||
while (size > 0) {
|
||||
size_t m = std::min(kLineBufferSize - buffer_.size(), size);
|
||||
buffer_.append(reinterpret_cast<const char*>(data), m);
|
||||
data += m;
|
||||
size -= m;
|
||||
if (buffer_.size() == kLineBufferSize && !WriteBuffer()) {
|
||||
flush_needed_ = false;
|
||||
LOG(ERROR) << "Write: exceeds cap.";
|
||||
if (output_stream_for_testing_)
|
||||
output_stream_for_testing_->Flush();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogOutputStream::WriteBuffer() {
|
||||
if (output_count_ == 0) {
|
||||
if (!WriteToLog("-----BEGIN CRASHPAD MINIDUMP-----"))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer_.empty())
|
||||
return true;
|
||||
|
||||
output_count_ += buffer_.size();
|
||||
if (output_count_ > kOutputCap) {
|
||||
WriteToLog("-----ABORT CRASHPAD MINIDUMP-----");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = WriteToLog(buffer_.c_str());
|
||||
buffer_.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LogOutputStream::WriteToLog(const char* buf) {
|
||||
#if defined(OS_ANDROID)
|
||||
int ret =
|
||||
__android_log_buf_write(LOG_ID_CRASH, ANDROID_LOG_FATAL, "crashpad", buf);
|
||||
if (ret < 0) {
|
||||
errno = -ret;
|
||||
PLOG(ERROR) << "__android_log_buf_write";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// For testing.
|
||||
if (output_stream_for_testing_) {
|
||||
return output_stream_for_testing_->Write(
|
||||
reinterpret_cast<const uint8_t*>(buf), strlen(buf));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogOutputStream::Flush() {
|
||||
flush_needed_ = false;
|
||||
flushed_ = true;
|
||||
|
||||
bool result = true;
|
||||
if (WriteBuffer()) {
|
||||
result = WriteToLog("-----END CRASHPAD MINIDUMP-----");
|
||||
} else {
|
||||
LOG(ERROR) << "Flush: exceeds cap.";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Since output_stream_for_testing_'s Write() method has been called, its
|
||||
// Flush() shall always be invoked.
|
||||
if (output_stream_for_testing_)
|
||||
output_stream_for_testing_->Flush();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LogOutputStream::SetOutputStreamForTesting(
|
||||
std::unique_ptr<OutputStreamInterface> stream) {
|
||||
output_stream_for_testing_ = std::move(stream);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
61
util/stream/log_output_stream.h
Normal file
61
util/stream/log_output_stream.h
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2019 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_
|
||||
#define CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "util/stream/output_stream_interface.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief This class output the received data to Android log, NOP in other
|
||||
//! platform.
|
||||
//!
|
||||
//! To avoid overflowing Android log, total 128k log data is allowed, after
|
||||
//! that cap, the output is aborted.
|
||||
class LogOutputStream : public OutputStreamInterface {
|
||||
public:
|
||||
LogOutputStream();
|
||||
~LogOutputStream() override;
|
||||
|
||||
// OutputStreamInterface:
|
||||
bool Write(const uint8_t* data, size_t size) override;
|
||||
bool Flush() override;
|
||||
|
||||
void SetOutputStreamForTesting(std::unique_ptr<OutputStreamInterface> stream);
|
||||
|
||||
private:
|
||||
// Flush the |buffer_|, return false if kOutputCap meet.
|
||||
bool WriteBuffer();
|
||||
bool WriteToLog(const char* buf);
|
||||
|
||||
std::string buffer_;
|
||||
size_t output_count_;
|
||||
bool flush_needed_;
|
||||
bool flushed_;
|
||||
std::unique_ptr<OutputStreamInterface> output_stream_for_testing_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LogOutputStream);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_
|
126
util/stream/log_output_stream_test.cc
Normal file
126
util/stream/log_output_stream_test.cc
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2019 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "util/stream/log_output_stream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/stream/test_output_stream.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kOutputCap = 128 * 1024;
|
||||
constexpr size_t kLineBufferSize = 512;
|
||||
const char* kBeginGuard = "-----BEGIN CRASHPAD MINIDUMP-----";
|
||||
const char* kEndGuard = "-----END CRASHPAD MINIDUMP-----";
|
||||
const char* kAbortGuard = "-----ABORT CRASHPAD MINIDUMP-----";
|
||||
|
||||
std::string ConvertToString(const std::vector<uint8_t>& src) {
|
||||
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
|
||||
}
|
||||
|
||||
class LogOutputStreamTest : public testing::Test {
|
||||
public:
|
||||
LogOutputStreamTest() : test_output_stream_(nullptr) {}
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
std::unique_ptr<TestOutputStream> output_stream =
|
||||
std::make_unique<TestOutputStream>();
|
||||
test_output_stream_ = output_stream.get();
|
||||
log_stream_ = std::make_unique<LogOutputStream>();
|
||||
log_stream_->SetOutputStreamForTesting(std::move(output_stream));
|
||||
}
|
||||
|
||||
const uint8_t* BuildDeterministicInput(size_t size) {
|
||||
deterministic_input_ = std::make_unique<uint8_t[]>(size);
|
||||
uint8_t* deterministic_input_base = deterministic_input_.get();
|
||||
while (size-- > 0)
|
||||
deterministic_input_base[size] = static_cast<uint8_t>('a');
|
||||
return deterministic_input_base;
|
||||
}
|
||||
|
||||
TestOutputStream* test_output_stream() const { return test_output_stream_; }
|
||||
|
||||
LogOutputStream* log_stream() const { return log_stream_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<LogOutputStream> log_stream_;
|
||||
TestOutputStream* test_output_stream_;
|
||||
std::unique_ptr<uint8_t[]> deterministic_input_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LogOutputStreamTest);
|
||||
};
|
||||
|
||||
TEST_F(LogOutputStreamTest, VerifyGuards) {
|
||||
log_stream()->Flush();
|
||||
// Verify OutputStream wrote 2 guards.
|
||||
EXPECT_EQ(test_output_stream()->write_count(), 2u);
|
||||
EXPECT_EQ(test_output_stream()->flush_count(), 1u);
|
||||
EXPECT_FALSE(test_output_stream()->all_data().empty());
|
||||
EXPECT_EQ(ConvertToString(test_output_stream()->all_data()),
|
||||
std::string(kBeginGuard).append(kEndGuard));
|
||||
}
|
||||
|
||||
TEST_F(LogOutputStreamTest, WriteShortLog) {
|
||||
const uint8_t* input = BuildDeterministicInput(2);
|
||||
EXPECT_TRUE(log_stream()->Write(input, 2));
|
||||
EXPECT_TRUE(log_stream()->Flush());
|
||||
// Verify OutputStream wrote 2 guards and data.
|
||||
EXPECT_EQ(test_output_stream()->write_count(), 3u);
|
||||
EXPECT_EQ(test_output_stream()->flush_count(), 1u);
|
||||
EXPECT_FALSE(test_output_stream()->all_data().empty());
|
||||
EXPECT_EQ(ConvertToString(test_output_stream()->all_data()),
|
||||
std::string(kBeginGuard).append("aa").append(kEndGuard));
|
||||
}
|
||||
|
||||
TEST_F(LogOutputStreamTest, WriteLongLog) {
|
||||
size_t input_length = kLineBufferSize + kLineBufferSize / 2;
|
||||
const uint8_t* input = BuildDeterministicInput(input_length);
|
||||
// Verify OutputStream wrote 2 guards and data.
|
||||
EXPECT_TRUE(log_stream()->Write(input, input_length));
|
||||
EXPECT_TRUE(log_stream()->Flush());
|
||||
EXPECT_EQ(test_output_stream()->write_count(),
|
||||
2 + input_length / kLineBufferSize + 1);
|
||||
EXPECT_EQ(test_output_stream()->flush_count(), 1u);
|
||||
EXPECT_EQ(test_output_stream()->all_data().size(),
|
||||
strlen(kBeginGuard) + strlen(kEndGuard) + input_length);
|
||||
}
|
||||
|
||||
TEST_F(LogOutputStreamTest, WriteAbort) {
|
||||
size_t input_length = kOutputCap + kLineBufferSize;
|
||||
const uint8_t* input = BuildDeterministicInput(input_length);
|
||||
EXPECT_FALSE(log_stream()->Write(input, input_length));
|
||||
std::string data(ConvertToString(test_output_stream()->all_data()));
|
||||
EXPECT_EQ(data.substr(data.size() - strlen(kAbortGuard)), kAbortGuard);
|
||||
}
|
||||
|
||||
TEST_F(LogOutputStreamTest, FlushAbort) {
|
||||
size_t input_length = kOutputCap + kLineBufferSize / 2;
|
||||
const uint8_t* input = BuildDeterministicInput(input_length);
|
||||
EXPECT_TRUE(log_stream()->Write(input, input_length));
|
||||
EXPECT_FALSE(log_stream()->Flush());
|
||||
std::string data(ConvertToString(test_output_stream()->all_data()));
|
||||
EXPECT_EQ(data.substr(data.size() - strlen(kAbortGuard)), kAbortGuard);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
Loading…
x
Reference in New Issue
Block a user