From 777e36014f766f245f9ea3f37b6d02e4054588ef Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Fri, 3 Mar 2017 14:47:03 -0500 Subject: [PATCH 1/6] linux: Add HTTPTransportLibcurl for Linux (but not Android) BUG=crashpad:30 TEST=crashpad_util_test HTTPTransport.* Change-Id: Ifef812830fe2d778f400467d93771dc166cef390 --- DEPS | 2 +- util/net/http_transport_libcurl.cc | 366 +++++++++++++++++++++++++++++ util/util.gyp | 12 + 3 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 util/net/http_transport_libcurl.cc diff --git a/DEPS b/DEPS index a2def66c..190f5a30 100644 --- a/DEPS +++ b/DEPS @@ -38,7 +38,7 @@ deps = { 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + - '3a2d52d74c9af5277bf6456cc00ae728f89c4898', + '9e0d322ae9f87acbe17c4ced025319b4964bf0b7', 'crashpad/third_party/zlib/zlib': Var('chromium_git') + '/chromium/src/third_party/zlib@' + '13dc246a58e4b72104d35f9b1809af95221ebda7', diff --git a/util/net/http_transport_libcurl.cc b/util/net/http_transport_libcurl.cc new file mode 100644 index 00000000..96ea7502 --- /dev/null +++ b/util/net/http_transport_libcurl.cc @@ -0,0 +1,366 @@ +// Copyright 2017 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/net/http_transport.h" + +#include +#include +#include + +#include +#include + +#include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "base/scoped_generic.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "package.h" +#include "util/net/http_body.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +namespace { + +std::string UserAgent() { + std::string user_agent = base::StringPrintf( + "%s/%s %s", PACKAGE_NAME, PACKAGE_VERSION, curl_version()); + + utsname os; + if (uname(&os) != 0) { + PLOG(WARNING) << "uname"; + } else { + // Match the architecture name that would be used by the kernel, so that the + // strcmp() below can omit the kernel’s architecture name if it’s the same + // as the user process’ architecture. On Linux, these names are normally + // defined in each architecture’s Makefile as UTS_MACHINE, but can be + // overridden in architecture-specific configuration as COMPAT_UTS_MACHINE. + // See linux-4.4.52/arch/*/Makefile and + // linux-4.4.52/arch/*/include/asm/compat.h. In turn, on some systems, these + // names are further overridden or refined in early kernel startup code by + // modifying the string returned by linux-4.4.52/include/linux/utsname.h + // init_utsname() as noted. +#if defined(ARCH_CPU_X86) + // linux-4.4.52/arch/x86/kernel/cpu/bugs.c check_bugs() sets the first digit + // to 4, 5, or 6, but no higher. Assume 6. + const char arch[] = "i686"; +#elif defined(ARCH_CPU_X86_64) + const char arch[] = "x86_64"; +#elif defined(ARCH_CPU_ARMEL) + // linux-4.4.52/arch/arm/kernel/setup.c setup_processor() bases the string + // on the ARM processor name and a character identifying little- or + // big-endian. The processor name comes from a definition in + // arch/arm/mm/proc-*.S. Assume armv7, little-endian. + const char arch[] = "armv7l"; +#elif defined(ARCH_CPU_ARM64) + // ARM64 uses aarch64 or aarch64_be as directed by ELF_PLATFORM. See + // linux-4.4.52/arch/arm64/kernel/setup.c setup_arch(). Assume + // little-endian. + const char arch[] = "aarch64"; +#elif defined(ARCH_CPU_MIPSEL) + const char arch[] = "mips"; +#elif defined(ARCH_CPU_MIPS64EL) + const char arch[] = "mips64"; +#else +#error Port +#endif + + user_agent.append( + base::StringPrintf(" %s/%s (%s", os.sysname, os.release, arch)); + if (strcmp(arch, os.machine) != 0) { + user_agent.append(base::StringPrintf("; %s", os.machine)); + } + user_agent.append(1, ')'); + } + + return user_agent; +} + +std::string CurlErrorMessage(CURLcode curl_err, const std::string& base) { + return base::StringPrintf( + "%s: %s (%d)", base.c_str(), curl_easy_strerror(curl_err), curl_err); +} + +struct ScopedCURLTraits { + static CURL* InvalidValue() { return nullptr; } + static void Free(CURL* curl) { + if (curl) { + curl_easy_cleanup(curl); + } + } +}; +using ScopedCURL = base::ScopedGeneric; + +class CurlSList { + public: + CurlSList() : list_(nullptr) {} + ~CurlSList() { + if (list_) { + curl_slist_free_all(list_); + } + } + + curl_slist* get() const { return list_; } + + bool Append(const char* data) { + curl_slist* list = curl_slist_append(list_, data); + if (!list_) { + list_ = list; + } + return list != nullptr; + } + + private: + curl_slist* list_; + + DISALLOW_COPY_AND_ASSIGN(CurlSList); +}; + +class ScopedClearString { + public: + explicit ScopedClearString(std::string* string) : string_(string) {} + + ~ScopedClearString() { + if (string_) { + string_->clear(); + } + } + + void Disarm() { string_ = nullptr; } + + private: + std::string* string_; + + DISALLOW_COPY_AND_ASSIGN(ScopedClearString); +}; + +class HTTPTransportLibcurl final : public HTTPTransport { + public: + HTTPTransportLibcurl(); + ~HTTPTransportLibcurl() override; + + // HTTPTransport: + bool ExecuteSynchronously(std::string* response_body) override; + + private: + static size_t ReadRequestBody(char* buffer, + size_t size, + size_t nitems, + void* userdata); + static size_t WriteResponseBody(char* buffer, + size_t size, + size_t nitems, + void* userdata); + + DISALLOW_COPY_AND_ASSIGN(HTTPTransportLibcurl); +}; + +HTTPTransportLibcurl::HTTPTransportLibcurl() : HTTPTransport() {} + +HTTPTransportLibcurl::~HTTPTransportLibcurl() {} + +bool HTTPTransportLibcurl::ExecuteSynchronously(std::string* response_body) { + DCHECK(body_stream()); + + response_body->clear(); + + // curl_easy_init() will do this on the first call if it hasn’t been done yet, + // but not in a thread-safe way as is done here. + static CURLcode curl_global_init_err = []() { + return curl_global_init(CURL_GLOBAL_DEFAULT); + }(); + if (curl_global_init_err != CURLE_OK) { + LOG(ERROR) << CurlErrorMessage(curl_global_init_err, "curl_global_init"); + return false; + } + + CurlSList curl_headers; + ScopedCURL curl(curl_easy_init()); + if (!curl.get()) { + LOG(ERROR) << "curl_easy_init"; + return false; + } + +// These macros wrap the repetitive “try something, log an error and return +// false on failure” pattern. Macros are convenient because the log messages +// will point to the correct line number, which can help pinpoint a problem when +// there are as many calls to these functions as there are here. +#define TRY_CURL_EASY_SETOPT(curl, option, parameter) \ + do { \ + CURLcode curl_err = curl_easy_setopt((curl), (option), (parameter)); \ + if (curl_err != CURLE_OK) { \ + LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_setopt"); \ + return false; \ + } \ + } while (false) +#define TRY_CURL_SLIST_APPEND(slist, data) \ + do { \ + if (!(slist).Append(data)) { \ + LOG(ERROR) << "curl_slist_append"; \ + return false; \ + } \ + } while (false) + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_USERAGENT, UserAgent().c_str()); + + // Accept and automatically decode any encoding that libcurl understands. + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_ACCEPT_ENCODING, ""); + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_URL, url().c_str()); + + const int kMillisecondsPerSecond = 1E3; + TRY_CURL_EASY_SETOPT(curl.get(), + CURLOPT_TIMEOUT_MS, + static_cast(timeout() * kMillisecondsPerSecond)); + + // If the request body size is known ahead of time, a Content-Length header + // field will be present. Store that to use as CURLOPT_POSTFIELDSIZE_LARGE, + // which will both set the Content-Length field in the request header and + // inform libcurl of the request body size. Otherwise, use Transfer-Encoding: + // chunked, which does not require advance knowledge of the request body size. + bool chunked = true; + size_t content_length; + for (const auto& pair : headers()) { + if (pair.first == kContentLength) { + chunked = !base::StringToSizeT(pair.second, &content_length); + DCHECK(!chunked); + } else { + TRY_CURL_SLIST_APPEND(curl_headers, + (pair.first + ": " + pair.second).c_str()); + } + } + + if (method() == "POST") { + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_POST, 1l); + + // By default when sending a POST request, libcurl includes an “Expect: + // 100-continue” header field. Althogh this header is specified in HTTP/1.1 + // (RFC 2616 §8.2.3, RFC 7231 §5.1.1), even collection servers that claim to + // speak HTTP/1.1 may not respond to it. When sending this header field, + // libcurl will wait for one second for the server to respond with a “100 + // Continue” status before continuing to transmit the request body. This + // delay is avoided by telling libcurl not to send this header field at all. + // The drawback is that certain HTTP error statuses may not be received + // until after substantial amounts of data have been sent to the server. + TRY_CURL_SLIST_APPEND(curl_headers, "Expect:"); + + if (chunked) { + TRY_CURL_SLIST_APPEND(curl_headers, "Transfer-Encoding: chunked"); + } else { + curl_off_t content_length_curl; + if (!AssignIfInRange(&content_length_curl, content_length)) { + LOG(ERROR) << base::StringPrintf("Content-Length %zu too large", + content_length); + return false; + } + TRY_CURL_EASY_SETOPT( + curl.get(), CURLOPT_POSTFIELDSIZE_LARGE, content_length_curl); + } + } else if (method() != "GET") { + // Untested. + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_CUSTOMREQUEST, method().c_str()); + } + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get()); + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READFUNCTION, ReadRequestBody); + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READDATA, this); + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEFUNCTION, WriteResponseBody); + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEDATA, response_body); + +#undef TRY_CURL_EASY_SETOPT +#undef TRY_CURL_SLIST_APPEND + + // If a partial response body is received and then a failure occurs, ensure + // that response_body is cleared. + ScopedClearString clear_response_body(response_body); + + // Do it. + CURLcode curl_err = curl_easy_perform(curl.get()); + if (curl_err != CURLE_OK) { + LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_perform"); + return false; + } + + long status; + curl_err = curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &status); + if (curl_err != CURLE_OK) { + LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_getinfo"); + return false; + } + + if (status != 200) { + LOG(ERROR) << base::StringPrintf("HTTP status %ld", status); + return false; + } + + // The response body is complete. Don’t clear it. + clear_response_body.Disarm(); + + return true; +} + +// static +size_t HTTPTransportLibcurl::ReadRequestBody(char* buffer, + size_t size, + size_t nitems, + void* userdata) { + HTTPTransportLibcurl* self = + reinterpret_cast(userdata); + + // This libcurl callback mimics the silly stdio-style fread() interface: size + // and nitems have been separated and must be multiplied. + base::CheckedNumeric checked_len = base::CheckMul(size, nitems); + size_t len = checked_len.ValueOrDefault(std::numeric_limits::max()); + + // Limit the read to what can be expressed in a FileOperationResult. + len = std::min( + len, + static_cast(std::numeric_limits::max())); + + FileOperationResult bytes_read = self->body_stream()->GetBytesBuffer( + reinterpret_cast(buffer), len); + if (bytes_read < 0) { + return CURL_READFUNC_ABORT; + } + + return bytes_read; +} + +// static +size_t HTTPTransportLibcurl::WriteResponseBody(char* buffer, + size_t size, + size_t nitems, + void* userdata) { + std::string* response_body = reinterpret_cast(userdata); + + // This libcurl callback mimics the silly stdio-style fread() interface: size + // and nitems have been separated and must be multiplied. + base::CheckedNumeric checked_len = base::CheckMul(size, nitems); + size_t len = checked_len.ValueOrDefault(std::numeric_limits::max()); + + response_body->append(buffer, len); + return len; +} + +} // namespace + +// static +std::unique_ptr HTTPTransport::Create() { + return std::unique_ptr(new HTTPTransportLibcurl()); +} + +} // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index 9e57b442..f1f004c7 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -119,6 +119,7 @@ 'net/http_multipart_builder.h', 'net/http_transport.cc', 'net/http_transport.h', + 'net/http_transport_libcurl.cc', 'net/http_transport_mac.mm', 'net/http_transport_win.cc', 'numeric/checked_address_range.cc', @@ -300,6 +301,17 @@ 'win/capture_context.asm', ], }], + ['OS=="linux"', { + 'link_settings': { + 'libraries': [ + '-lcurl', + ], + }, + }, { # else: OS!="linux" + 'sources!': [ + 'net/http_transport_libcurl.cc', + ], + }], ], }, ], From 6a5695967fda48d40771764caff1458cc39450fb Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Fri, 3 Mar 2017 15:11:50 -0500 Subject: [PATCH 2/6] Introduce the crashpad_http_upload tool crashpad_http_upload sends HTTP POST multipart/form-data requests and receives responses in exactly the same manner that crashpad_handler does for crash report uploads, but separates it out for more general testing and debugging. Change-Id: I5c5919f9b1dc1e6be1e43b15a35b31f51add8a46 --- tools/crashpad_http_upload.cc | 206 ++++++++++++++++++++++++++++++++++ tools/crashpad_http_upload.md | 132 ++++++++++++++++++++++ tools/generate_dump.cc | 13 +-- tools/tool_support.cc | 10 ++ tools/tool_support.h | 9 ++ tools/tools.gyp | 18 ++- util/file/file_reader.cc | 6 +- util/file/file_writer.cc | 67 +++++++++++ util/file/file_writer.h | 35 ++++++ 9 files changed, 486 insertions(+), 10 deletions(-) create mode 100644 tools/crashpad_http_upload.cc create mode 100644 tools/crashpad_http_upload.md diff --git a/tools/crashpad_http_upload.cc b/tools/crashpad_http_upload.cc new file mode 100644 index 00000000..55ab9550 --- /dev/null +++ b/tools/crashpad_http_upload.cc @@ -0,0 +1,206 @@ +// Copyright 2017 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 +#include +#include +#include + +#include +#include + +#include "base/files/file_path.h" +#include "tools/tool_support.h" +#include "util/file/file_writer.h" +#include "util/net/http_body.h" +#include "util/net/http_multipart_builder.h" +#include "util/net/http_transport.h" +#include "util/string/split_string.h" + +namespace crashpad { +namespace { + +void Usage(const base::FilePath& me) { + fprintf(stderr, +"Usage: %" PRFilePath " [OPTION]...\n" +"Send an HTTP POST request.\n" +" -f, --file=KEY=PATH upload the file at PATH for the HTTP KEY parameter\n" +" --no-upload-gzip don't use gzip compression when uploading\n" +" -o, --output=FILE write the response body to FILE instead of stdout\n" +" -s, --string=KEY=VALUE set the HTTP KEY parameter to VALUE\n" +" -u, --url=URL send the request to URL\n" +" --help display this help and exit\n" +" --version output version information and exit\n", + me.value().c_str()); + ToolSupport::UsageTail(me); +} + +int HTTPUploadMain(int argc, char* argv[]) { + const base::FilePath argv0( + ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); + const base::FilePath me(argv0.BaseName()); + + enum OptionFlags { + // “Short” (single-character) options. + kOptionFile = 'f', + kOptionOutput = 'o', + kOptionString = 's', + kOptionURL = 'u', + + // Long options without short equivalents. + kOptionLastChar = 255, + kOptionNoUploadGzip, + + // Standard options. + kOptionHelp = -2, + kOptionVersion = -3, + }; + + struct { + std::string url; + const char* output; + bool upload_gzip; + } options = {}; + options.upload_gzip = true; + + const option long_options[] = { + {"file", required_argument, nullptr, kOptionFile}, + {"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip}, + {"output", required_argument, nullptr, kOptionOutput}, + {"string", required_argument, nullptr, kOptionString}, + {"url", required_argument, nullptr, kOptionURL}, + {"help", no_argument, nullptr, kOptionHelp}, + {"version", no_argument, nullptr, kOptionVersion}, + {nullptr, 0, nullptr, 0}, + }; + + HTTPMultipartBuilder http_multipart_builder; + + int opt; + while ((opt = getopt_long(argc, argv, "f:o:s:u:", long_options, nullptr)) != + -1) { + switch (opt) { + case kOptionFile: { + std::string key; + std::string path; + if (!SplitStringFirst(optarg, '=', &key, &path)) { + ToolSupport::UsageHint(me, "--file requires KEY=STRING"); + return EXIT_FAILURE; + } + base::FilePath file_path( + ToolSupport::CommandLineArgumentToFilePathStringType(path)); + std::string file_name( + ToolSupport::FilePathToCommandLineArgument(file_path.BaseName())); + http_multipart_builder.SetFileAttachment( + key, file_name, file_path, "application/octet-stream"); + break; + } + case kOptionNoUploadGzip: { + options.upload_gzip = false; + break; + } + case kOptionOutput: { + options.output = optarg; + break; + } + case kOptionString: { + std::string key; + std::string value; + if (!SplitStringFirst(optarg, '=', &key, &value)) { + ToolSupport::UsageHint(me, "--string requires KEY=VALUE"); + return EXIT_FAILURE; + } + http_multipart_builder.SetFormData(key, value); + break; + } + case kOptionURL: + options.url = optarg; + break; + case kOptionHelp: + Usage(me); + return EXIT_SUCCESS; + case kOptionVersion: + ToolSupport::Version(me); + return EXIT_SUCCESS; + default: + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + if (options.url.empty()) { + ToolSupport::UsageHint(me, "--url is required"); + return EXIT_FAILURE; + } + + if (argc) { + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + + std::unique_ptr file_writer; + if (options.output) { + FileWriter* file_writer_impl = new FileWriter(); + file_writer.reset(file_writer_impl); + base::FilePath output_path( + ToolSupport::CommandLineArgumentToFilePathStringType(options.output)); + if (!file_writer_impl->Open(output_path, + FileWriteMode::kTruncateOrCreate, + FilePermissions::kWorldReadable)) { + return EXIT_FAILURE; + } + } else { + file_writer.reset(new WeakStdioFileWriter(stdout)); + } + + http_multipart_builder.SetGzipEnabled(options.upload_gzip); + + std::unique_ptr http_transport(HTTPTransport::Create()); + http_transport->SetURL(options.url); + + HTTPHeaders content_headers; + http_multipart_builder.PopulateContentHeaders(&content_headers); + for (const auto& content_header : content_headers) { + http_transport->SetHeader(content_header.first, content_header.second); + } + + http_transport->SetBodyStream(http_multipart_builder.GetBodyStream()); + + std::string response_body; + if (!http_transport->ExecuteSynchronously(&response_body)) { + return EXIT_FAILURE; + } + + if (!response_body.empty() && + !file_writer->Write(&response_body[0], response_body.size())) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +} // namespace +} // namespace crashpad + +#if defined(OS_POSIX) +int main(int argc, char* argv[]) { + return crashpad::HTTPUploadMain(argc, argv); +} +#elif defined(OS_WIN) +int wmain(int argc, wchar_t* argv[]) { + return crashpad::ToolSupport::Wmain(argc, argv, crashpad::HTTPUploadMain); +} +#endif // OS_POSIX diff --git a/tools/crashpad_http_upload.md b/tools/crashpad_http_upload.md new file mode 100644 index 00000000..93b87522 --- /dev/null +++ b/tools/crashpad_http_upload.md @@ -0,0 +1,132 @@ + + +# crashpad_http_upload(1) + +## Name + +crashpad_http_upload—Send an HTTP POST request + +## Synopsis + +**crashpad_http_uplaod** [_OPTION…_] + +## Description + +Performs an HTTP or HTTPS POST, building a `multipart/form-data` request from +key-value pairs and files in the manner of an HTML `
` with a POST action. +Provides the response. + +Programs that use the Crashpad client library directly will not normally use +this tool. This tool is provided for debugging and testing as it isolates +Crashpad’s networking implementation normally used to upload crash reports to +a crash report collection server, making it available for more general use. + +## Options + + * **-f**, **--file**=_KEY_=_PATH_ + + Include _PATH_ in the request as a file upload, in the manner of an HTML + `` element. _KEY_ is used as the field name. + + * **--no-upload-gzip** + + Do not use `gzip` compression. Normally, the entire request body is + compressed into a `gzip` stream and transmitted with `Content-Encoding: + gzip`. This option disables compression, and is intended for use with servers + that don’t accept uploads compressed in this way. + + * **-o**, **--output**=_FILE_ + + The response body will be written to _FILE_ instead of standard output. + + * **-s**, **--string**=_KEY_=_VALUE_ + + Include _KEY_ and _VALUE_ in the request as an ordinary form field, in the + manner of an HTML `` element. _KEY_ is used as the field + name, and _VALUE_ is used as its value. + + * **-u**, **--url**=_URL_ + + Send the request to _URL_. This option is required. + + * **--help** + + Display help and exit. + + * **--version** + + Output version information and exit. + +## Examples + +Uploads a file to an HTTP server running on `localhost`. + +``` +$ crashpad_http-upload --url http://localhost/upload_test \ + --string=when=now --file=what=1040.pdf +Thanks for the upload! +``` + +This example corresponds to the HTML form: + +``` + + + + +
+``` + +## Exit Status + + * **0** + + Success. + + * **1** + + Failure, with a message printed to the standard error stream. HTTP error + statuses such as 404 (Not Found) are included in the definition of failure. + +## See Also + +[crashpad_handler(8)](../handler/crashpad_handler.md) + +## Resources + +Crashpad home page: https://crashpad.chromium.org/. + +Report bugs at https://crashpad.chromium.org/bug/new. + +## Copyright + +Copyright 2017 [The Crashpad +Authors](https://chromium.googlesource.com/crashpad/crashpad/+/master/AUTHORS). + +## License + +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. diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc index 11fe0ca5..b98b1f4f 100644 --- a/tools/generate_dump.cc +++ b/tools/generate_dump.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include #include #include @@ -48,12 +47,6 @@ namespace crashpad { namespace { -struct Options { - std::string dump_path; - pid_t pid; - bool suspend; -}; - void Usage(const base::FilePath& me) { fprintf(stderr, "Usage: %" PRFilePath " [OPTION]... PID\n" @@ -85,7 +78,11 @@ int GenerateDumpMain(int argc, char* argv[]) { kOptionVersion = -3, }; - Options options = {}; + struct { + std::string dump_path; + pid_t pid; + bool suspend; + } options = {}; options.suspend = true; const option long_options[] = { diff --git a/tools/tool_support.cc b/tools/tool_support.cc index f6d4f84c..15deead0 100644 --- a/tools/tool_support.cc +++ b/tools/tool_support.cc @@ -98,4 +98,14 @@ base::FilePath::StringType ToolSupport::CommandLineArgumentToFilePathStringType( #endif // OS_POSIX } +// static +std::string ToolSupport::FilePathToCommandLineArgument( + const base::FilePath& file_path) { +#if defined(OS_POSIX) + return file_path.value(); +#elif defined(OS_WIN) + return base::UTF16ToUTF8(file_path.value()); +#endif // OS_POSIX +} + } // namespace crashpad diff --git a/tools/tool_support.h b/tools/tool_support.h index e3a50614..48f412f3 100644 --- a/tools/tool_support.h +++ b/tools/tool_support.h @@ -72,9 +72,18 @@ class ToolSupport { //! `char*`. This undoes that transformation. //! //! \sa Wmain() + //! \sa FilePathToCommandLineArgument() static base::FilePath::StringType CommandLineArgumentToFilePathStringType( const base::StringPiece& arg); + //! \brief Converts a base::FilePath to a command line argument. + //! + //! On POSIX, this is a no-op. On Windows, this undoes the transformation done + //! by CommandLineArgumentToFilePathStringType() in the same manner as + //! Wmain(). + static std::string FilePathToCommandLineArgument( + const base::FilePath& file_path); + private: DISALLOW_IMPLICIT_CONSTRUCTORS(ToolSupport); }; diff --git a/tools/tools.gyp b/tools/tools.gyp index 48763005..3639b531 100644 --- a/tools/tools.gyp +++ b/tools/tools.gyp @@ -48,6 +48,22 @@ 'crashpad_database_util.cc', ], }, + { + 'target_name': 'crashpad_http_upload', + 'type': 'executable', + 'dependencies': [ + 'crashpad_tool_support', + '../compat/compat.gyp:crashpad_compat', + '../third_party/mini_chromium/mini_chromium.gyp:base', + '../util/util.gyp:crashpad_util', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'crashpad_http_upload.cc', + ], + }, { 'target_name': 'generate_dump', 'type': 'executable', @@ -77,7 +93,7 @@ }, }], ], - } + }, ], 'conditions': [ ['OS=="mac"', { diff --git a/util/file/file_reader.cc b/util/file/file_reader.cc index 14d06c74..4f5ae773 100644 --- a/util/file/file_reader.cc +++ b/util/file/file_reader.cc @@ -109,10 +109,14 @@ FileOperationResult WeakStdioFileReader::Read(void* data, size_t size) { DCHECK(file_); size_t rv = fread(data, 1, size, file_); - if (rv < size && ferror(file_)) { + if (rv != size && ferror(file_)) { STDIO_PLOG(ERROR) << "fread"; return -1; } + if (rv > size) { + LOG(ERROR) << "fread: expected " << size << ", observed " << rv; + return -1; + } return rv; } diff --git a/util/file/file_writer.cc b/util/file/file_writer.cc index 94f41f14..d22a1a3b 100644 --- a/util/file/file_writer.cc +++ b/util/file/file_writer.cc @@ -56,6 +56,11 @@ bool WeakFileHandleFileWriter::Write(const void* data, size_t size) { bool WeakFileHandleFileWriter::WriteIoVec(std::vector* iovecs) { DCHECK_NE(file_handle_, kInvalidFileHandle); + if (iovecs->empty()) { + LOG(ERROR) << "WriteIoVec(): no iovecs"; + return false; + } + #if defined(OS_POSIX) ssize_t size = 0; @@ -187,4 +192,66 @@ FileOffset FileWriter::Seek(FileOffset offset, int whence) { return weak_file_handle_file_writer_.Seek(offset, whence); } +WeakStdioFileWriter::WeakStdioFileWriter(FILE* file) + : file_(file) { +} + +WeakStdioFileWriter::~WeakStdioFileWriter() { +} + +bool WeakStdioFileWriter::Write(const void* data, size_t size) { + DCHECK(file_); + + size_t rv = fwrite(data, 1, size, file_); + if (rv != size) { + if (ferror(file_)) { + STDIO_PLOG(ERROR) << "fwrite"; + } else { + LOG(ERROR) << "fwrite: expected " << size << ", observed " << rv; + } + return false; + } + + return true; +} + +bool WeakStdioFileWriter::WriteIoVec(std::vector* iovecs) { + DCHECK(file_); + + if (iovecs->empty()) { + LOG(ERROR) << "WriteIoVec(): no iovecs"; + return false; + } + + for (const WritableIoVec& iov : *iovecs) { + if (!Write(iov.iov_base, iov.iov_len)) { + return false; + } + } + +#ifndef NDEBUG + // The interface says that |iovecs| is not sacred, so scramble it to make sure + // that nobody depends on it. + memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size()); +#endif + + return true; +} + +FileOffset WeakStdioFileWriter::Seek(FileOffset offset, int whence) { + DCHECK(file_); + if (fseeko(file_, offset, whence) == -1) { + STDIO_PLOG(ERROR) << "fseeko"; + return -1; + } + + FileOffset new_offset = ftello(file_); + if (new_offset == -1) { + STDIO_PLOG(ERROR) << "ftello"; + return -1; + } + + return new_offset; +} + } // namespace crashpad diff --git a/util/file/file_writer.h b/util/file/file_writer.h index ed261ec5..007e5f3a 100644 --- a/util/file/file_writer.h +++ b/util/file/file_writer.h @@ -167,6 +167,41 @@ class FileWriter : public FileWriterInterface { DISALLOW_COPY_AND_ASSIGN(FileWriter); }; +//! \brief A file writer backed by a standard input/output `FILE*`. +//! +//! This class accepts an already-open `FILE*`. It is not responsible for +//! opening or closing this `FILE*`. Users of this class must ensure that the +//! `FILE*` is closed appropriately elsewhere. Objects of this class may be used +//! to write to `FILE*` objects not associated with filesystem-based files, +//! although special attention should be paid to the Seek() method, which may +//! not function on `FILE*` objects that do not refer to disk-based files. +//! +//! This class is expected to be used when other code is responsible for +//! opening `FILE*` objects and already provides `FILE*` objects. A good use +//! would be a WeakStdioFileWriter for `stdout`. +class WeakStdioFileWriter : public FileWriterInterface { + public: + explicit WeakStdioFileWriter(FILE* file); + ~WeakStdioFileWriter() override; + + // FileWriterInterface: + bool Write(const void* data, size_t size) override; + bool WriteIoVec(std::vector* iovecs) override; + + // FileSeekerInterface: + + //! \copydoc FileWriterInterface::Seek() + //! + //! \note This method is only guaranteed to function on `FILE*` objects + //! referring to disk-based files. + FileOffset Seek(FileOffset offset, int whence) override; + + private: + FILE* file_; // weak + + DISALLOW_COPY_AND_ASSIGN(WeakStdioFileWriter); +}; + } // namespace crashpad #endif // CRASHPAD_UTIL_FILE_FILE_WRITER_H_ From 8e82f6fde05499eb8385d25ea5f4c3c5cab77e24 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Tue, 7 Mar 2017 17:12:22 -0500 Subject: [PATCH 3/6] mac: Update test and comments with feedback from Apple bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple has responded to their bug 29079442 with a resolution stating that these are not corpse ports but task ports that have changed after execve(), as part of the large task port and execve() strategy rewrite from 10.12.1. The comments being replaced were written before we had 10.12.1 source code. Now that we can see what’s going on, revise the comments, and re-enable the task port check for the non-execve() test variants. https://openradar.appspot.com/29079442 https://googleprojectzero.blogspot.com/2016/10/taskt-considered-harmful.html Bug: crashpad:137 Test: crashpad_snapshot_test MachOImageAnnotationsReader.CrashDyld Change-Id: I463637816085f4165b92b85a5b98bfeddcdf4094 Reviewed-on: https://chromium-review.googlesource.com/451120 Reviewed-by: Robert Sesek Commit-Queue: Mark Mentovai --- .../mac/mach_o_image_annotations_reader_test.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc index f4f53494..7a94eb2c 100644 --- a/snapshot/mac/mach_o_image_annotations_reader_test.cc +++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc @@ -117,14 +117,14 @@ class TestMachOImageAnnotationsReader final bool* destroy_complex_request) override { *destroy_complex_request = true; - // In 10.12, dyld fatal errors as tested by test_type_ = kCrashDyld are via - // abort_with_payload(). In 10.12.1, the task port delivered in an exception - // message for this termination type is a corpse, even when the exception is - // EXC_CRASH and not EXC_CORPSE_NOTIFY. The corpse task port (here, |task|) - // is distinct from the process’ original task port (ChildTask()). This is - // filed as https://openradar.appspot.com/29079442. - // - // Instead of comparing task ports, compare PIDs. + if (test_type_ != kCrashDyld) { + // In 10.12.1 and later, the task port will not match ChildTask() in the + // kCrashDyld case, because kCrashDyld uses execl(), which results in a + // new task port being assigned. + EXPECT_EQ(ChildTask(), task); + } + + // The process ID should always compare favorably. pid_t task_pid; kern_return_t kr = pid_for_task(task, &task_pid); EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task"); From b5284cdcba93d2ea4a47ac298bdef16b869cf9d7 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 8 Mar 2017 17:38:27 -0500 Subject: [PATCH 4/6] mac: Add CrashpadClient::GetHandlerMachPort() Bug: chromium:699607 Change-Id: Ib1886550fe81787cb1ffc8d8853f6969cc96831e Reviewed-on: https://chromium-review.googlesource.com/451127 Reviewed-by: Robert Sesek Commit-Queue: Mark Mentovai --- client/crashpad_client.h | 34 ++++++++++++++++++++++++++----- client/crashpad_client_mac.cc | 38 +++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/client/crashpad_client.h b/client/crashpad_client.h index f6f3395c..7799bd9c 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -132,6 +132,27 @@ class CrashpadClient { //! //! \return `true` on success, `false` on failure with a message logged. bool SetHandlerMachPort(base::mac::ScopedMachSendRight exception_port); + + //! \brief Retrieves a send right to the process’ crash handler Mach port. + //! + //! This method is only defined on macOS. + //! + //! This method can be used to obtain the crash handler Mach port when a + //! Crashpad client process wishes to provide a send right to this port to + //! another process. The IPC mechanism used to convey the right is under the + //! application’s control. If the other process wishes to become a client of + //! the same crash handler, it can provide the transferred right to + //! SetHandlerMachPort(). + //! + //! See StartHandler() for more detail on how the port and handler are + //! configured. + //! + //! \return The Mach port set by SetHandlerMachPort(), possibly indirectly by + //! a call to another method such as StartHandler() or + //! SetHandlerMachService(). This method must only be called after a + //! successful call to one of those methods. `MACH_PORT_NULL` on failure + //! with a message logged. + base::mac::ScopedMachSendRight GetHandlerMachPort() const; #endif #if defined(OS_WIN) || DOXYGEN @@ -155,14 +176,15 @@ class CrashpadClient { //! \brief Retrieves the IPC pipe name used to register with the Crashpad //! handler. //! + //! This method is only defined on Windows. + //! //! This method retrieves the IPC pipe name set by SetHandlerIPCPipe(), or a - //! suitable IPC pipe name chosen by StartHandler(). It is intended to be used + //! suitable IPC pipe name chosen by StartHandler(). It must only be called + //! after a successful call to one of those methods. It is intended to be used //! to obtain the IPC pipe name so that it may be passed to other processes, //! so that they may register with an existing Crashpad handler by calling //! SetHandlerIPCPipe(). //! - //! This method is only defined on Windows. - //! //! \return The full name of the crash handler IPC pipe, a string of the form //! `"\\.\pipe\NAME"`. std::wstring GetHandlerIPCPipe() const; @@ -257,10 +279,12 @@ class CrashpadClient { #endif private: -#if defined(OS_WIN) +#if defined(OS_MACOSX) + base::mac::ScopedMachSendRight exception_port_; +#elif defined(OS_WIN) std::wstring ipc_pipe_; ScopedKernelHANDLE handler_start_thread_; -#endif +#endif // OS_MACOSX DISALLOW_COPY_AND_ASSIGN(CrashpadClient); }; diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index cc50969e..978024e5 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -523,7 +523,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface { } // namespace -CrashpadClient::CrashpadClient() { +CrashpadClient::CrashpadClient() : exception_port_(MACH_PORT_NULL) { } CrashpadClient::~CrashpadClient() { @@ -569,8 +569,42 @@ bool CrashpadClient::SetHandlerMachService(const std::string& service_name) { bool CrashpadClient::SetHandlerMachPort( base::mac::ScopedMachSendRight exception_port) { + DCHECK(!exception_port_.is_valid()); DCHECK(exception_port.is_valid()); - return SetCrashExceptionPorts(exception_port.get()); + + if (!SetCrashExceptionPorts(exception_port.get())) { + return false; + } + + exception_port_.swap(exception_port); + return true; +} + +base::mac::ScopedMachSendRight CrashpadClient::GetHandlerMachPort() const { + DCHECK(exception_port_.is_valid()); + + // For the purposes of this method, only return a port set by + // SetHandlerMachPort(). + // + // It would be possible to use task_get_exception_ports() to look up the + // EXC_CRASH task exception port, but that’s probably not what users of this + // interface really want. If CrashpadClient is asked for the handler Mach + // port, it should only return a port that it knows about by virtue of having + // set it. It shouldn’t return any EXC_CRASH task exception port in effect if + // SetHandlerMachPort() was never called, and it shouldn’t return any + // EXC_CRASH task exception port that might be set by other code after + // SetHandlerMachPort() is called. + // + // The caller is accepting its own new ScopedMachSendRight, so increment the + // reference count of the underlying right. + kern_return_t kr = mach_port_mod_refs( + mach_task_self(), exception_port_.get(), MACH_PORT_RIGHT_SEND, 1); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_port_mod_refs"; + return base::mac::ScopedMachSendRight(MACH_PORT_NULL); + } + + return base::mac::ScopedMachSendRight(exception_port_.get()); } // static From a47a512ea60f2a448019b69fbc302fdfb0a52f75 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Thu, 9 Mar 2017 11:25:52 -0500 Subject: [PATCH 5/6] =?UTF-8?q?Use=20=E2=80=9CBug:=20crashpad:###=E2=80=9D?= =?UTF-8?q?=20git=20footer=20format=20in=20change=20descriptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This requires depot_tools 57c4721d81da or later. Run “gclient” to update. When git-cl opens an editor for a change description that doesn’t already have a bug line, the default bug line will now be a “Bug: crashpad:” git footer field. git footers are a more Gerrit-y way of handling things. It didn’t make sense to have two distinct metadata footer sections (or more, if they wound up interleaved). Standardize on the newer format. Bye-bye, BUG=. Change-Id: I7dade51703f9eff471a49510793d37686ce5fc97 Reviewed-on: https://chromium-review.googlesource.com/452557 Reviewed-by: Robert Sesek Commit-Queue: Mark Mentovai --- codereview.settings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codereview.settings b/codereview.settings index 685c904d..616a3be0 100644 --- a/codereview.settings +++ b/codereview.settings @@ -17,3 +17,5 @@ GERRIT_SQUASH_UPLOADS: True CODE_REVIEW_SERVER: https://canary-chromium-review.googlesource.com/ VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/ PROJECT: crashpad +BUG_LINE_FORMAT: Bug: %s +BUG_PREFIX: crashpad: From f590383096514dcbe81a9cf0ee7bac307cc4b3ba Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Thu, 9 Mar 2017 12:25:24 -0500 Subject: [PATCH 6/6] doc: Document the try server and commit queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: crashpad:162 Change-Id: I9b5dd8c281117c24d79ce6b81269c87d42a21e13 Reviewed-on: https://chromium-review.googlesource.com/452438 Reviewed-by: Sigurður Ásgeirsson Commit-Queue: Mark Mentovai --- doc/developing.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/doc/developing.md b/doc/developing.md index 9f881678..4505d610 100644 --- a/doc/developing.md +++ b/doc/developing.md @@ -278,10 +278,25 @@ committing your changes locally with `git commit`. You can then upload a new patch set with `git cl upload` and let your reviewer know you’ve addressed the feedback. +The most recently uploaded patch set on a review may be tested on a [try +server](https://dev.chromium.org/developers/testing/try-server-usage) by running +`git cl try` or by clicking the “CQ Dry Run” button in Gerrit. These set the +“Commit-Queue: +1” label. This does not mean that the patch will be committed, +but the try server and commit queue share infrastructure and a Gerrit label. The +patch will be tested on try bots in a variety of configurations. Status +information will be available on Gerrit. + ### Landing Changes After code review is complete and “Code-Review: +1” has been received from all -reviewers, project members can commit the patch themselves: +reviewers, the patch can be submitted to Crashpad’s [commit +queue](https://dev.chromium.org/developers/testing/commit-queue) by clicking the +“Submit to CQ” button in Gerrit. This sets the “Commit-Queue: +2” label, which +tests the patch on the try server before landing it. + +Although the commit queue is recommended, if needed, project members can bypass +the commit queue and land patches without testing by using the “Submit” button +in Gerrit or by committing via `git cl land`: ``` $ cd ~/crashpad/crashpad @@ -289,15 +304,6 @@ $ git checkout work_branch $ git cl land ``` -Alternatively, patches can be committed by clicking the “Submit” button in the -Gerrit UI. - -Crashpad does not currently have a [commit -queue](https://dev.chromium.org/developers/testing/commit-queue), so -contributors who are not project members will have to ask a project member to -commit the patch for them. Project members can commit changes on behalf of -external contributors by clicking the “Submit” button in the Gerrit UI. - ### External Contributions Copyright holders must complete the [Individual Contributor License