Commit 4a0b385c authored by ckitagawa's avatar ckitagawa Committed by Commit Bot

[Zucchini] Add support to setup.exe for Zucchini patches

Adds support to setup.exe to both upgrade itself and chrome.7z through
Zucchini patches when "use_zucchini = true" as a gn arg.

Zucchini patches are NOT created at this time! This is primarily for
experimentation with the release infra. A security review will be
conducted before launch.

This has been successfully tested using Zucchini-based versions of
chrome_updater.exe for the following cases:

Patchers
| setup.exe | chrome.7z |
-------------------------
| Courgette | Courgette | Default (can also be used for downgrades)
| Courgette | Zucchini  | Upgrade: once enabled
| Zucchini  | Zucchini  | Target Default
| Zucchini  | Courgette | Downgrade: in the event of issues

Child Changes:
- setup.rc PATCHERTYPE:
https://chromium-review.googlesource.com/937400
- BUILD.gn zucchini.exe:
https://chromium-review.googlesource.com/937396

Bug: 729154
Change-Id: Iec2e514a8b8a5ee311fe210505ac2b31c0418f54
Reviewed-on: https://chromium-review.googlesource.com/916553
Commit-Queue: Calder Kitagawa <ckitagawa@google.com>
Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#539854}
parent a64a1e5f
...@@ -2,9 +2,18 @@ ...@@ -2,9 +2,18 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import("//build/buildflag_header.gni")
import("//chrome/installer/setup/buildflags.gni")
import("//chrome/process_version_rc_template.gni") import("//chrome/process_version_rc_template.gni")
import("//testing/test.gni") import("//testing/test.gni")
buildflag_header("buildflags") {
header = "buildflags.h"
# Use ZUCCHINI since ZUCCHINI_ENABLED is too long a name for setup.rc.
flags = [ "ZUCCHINI=$use_zucchini" ]
}
if (is_win) { if (is_win) {
executable("setup") { executable("setup") {
sources = [ sources = [
...@@ -21,6 +30,7 @@ if (is_win) { ...@@ -21,6 +30,7 @@ if (is_win) {
configs += [ "//build/config/win:windowed" ] configs += [ "//build/config/win:windowed" ]
deps = [ deps = [
":buildflags",
":lib", ":lib",
":setup_exe_version", ":setup_exe_version",
"//build/config:exe_and_shlib_deps", "//build/config:exe_and_shlib_deps",
...@@ -69,12 +79,14 @@ if (is_win) { ...@@ -69,12 +79,14 @@ if (is_win) {
] ]
public_deps = [ public_deps = [
":buildflags",
"//base", "//base",
"//chrome/common:constants", "//chrome/common:constants",
"//chrome/common:version_header", "//chrome/common:version_header",
"//chrome/install_static:install_static_util", "//chrome/install_static:install_static_util",
"//chrome/installer/util:with_rc_strings", "//chrome/installer/util:with_rc_strings",
"//chrome/installer/zucchini:zucchini_io", "//chrome/installer/zucchini:zucchini_io",
"//chrome/installer/zucchini:zucchini_lib",
"//chrome_elf:constants", "//chrome_elf:constants",
"//components/base32", "//components/base32",
"//components/crash/content/app", "//components/crash/content/app",
......
include_rules = [ include_rules = [
"+chrome_elf/chrome_elf_constants.h", "+chrome_elf/chrome_elf_constants.h",
"+chrome/installer/zucchini",
"+chrome/install_static", "+chrome/install_static",
"+components/base32", "+components/base32",
"+courgette", "+courgette",
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "chrome/installer/setup/buildflags.h"
#include "chrome/installer/util/lzma_util.h" #include "chrome/installer/util/lzma_util.h"
#include "chrome/installer/zucchini/zucchini.h"
#include "chrome/installer/zucchini/zucchini_integration.h" #include "chrome/installer/zucchini/zucchini_integration.h"
#include "courgette/courgette.h" #include "courgette/courgette.h"
#include "third_party/bspatch/mbspatch.h" #include "third_party/bspatch/mbspatch.h"
...@@ -37,8 +39,7 @@ bool ArchivePatchHelper::UncompressAndPatch( ...@@ -37,8 +39,7 @@ bool ArchivePatchHelper::UncompressAndPatch(
UnPackConsumer consumer) { UnPackConsumer consumer) {
ArchivePatchHelper instance(working_directory, compressed_archive, ArchivePatchHelper instance(working_directory, compressed_archive,
patch_source, target, consumer); patch_source, target, consumer);
return (instance.Uncompress(NULL) && return (instance.Uncompress(NULL) && instance.ApplyPatch());
(instance.CourgetteEnsemblePatch() || instance.BinaryPatch()));
} }
bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) { bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) {
...@@ -61,6 +62,14 @@ bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) { ...@@ -61,6 +62,14 @@ bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) {
return true; return true;
} }
bool ArchivePatchHelper::ApplyPatch() {
#if BUILDFLAG(ZUCCHINI)
if (ZucchiniEnsemblePatch())
return true;
#endif // BUILDFLAG(ZUCCHINI)
return CourgetteEnsemblePatch() || BinaryPatch();
}
bool ArchivePatchHelper::CourgetteEnsemblePatch() { bool ArchivePatchHelper::CourgetteEnsemblePatch() {
if (last_uncompressed_file_.empty()) { if (last_uncompressed_file_.empty()) {
LOG(ERROR) << "No patch file found in compressed archive."; LOG(ERROR) << "No patch file found in compressed archive.";
......
...@@ -43,8 +43,9 @@ class ArchivePatchHelper { ...@@ -43,8 +43,9 @@ class ArchivePatchHelper {
// Uncompresses |compressed_archive| in |working_directory| then applies the // Uncompresses |compressed_archive| in |working_directory| then applies the
// extracted patch file to |patch_source|, writing the result to |target|. // extracted patch file to |patch_source|, writing the result to |target|.
// Ensemble patching via Courgette is attempted first. If that fails, bspatch // Ensemble patching via Zucchini is attempted first (if it is enabled). If
// is attempted. Returns false if uncompression or both patching steps fail. // that fails Courgette is attempted with fallback to bspatch. Returns false
// if uncompression or all patching steps fail.
static bool UncompressAndPatch(const base::FilePath& working_directory, static bool UncompressAndPatch(const base::FilePath& working_directory,
const base::FilePath& compressed_archive, const base::FilePath& compressed_archive,
const base::FilePath& patch_source, const base::FilePath& patch_source,
...@@ -56,6 +57,13 @@ class ArchivePatchHelper { ...@@ -56,6 +57,13 @@ class ArchivePatchHelper {
// file extracted from the archive. // file extracted from the archive.
bool Uncompress(base::FilePath* last_uncompressed_file); bool Uncompress(base::FilePath* last_uncompressed_file);
// Performs ensemble patching on the uncompressed version of
// |compressed_archive| in |working_directory| as specified in the
// constructor using files from |patch_source|. Ensemble patching via
// Zucchini is attempted first (if it is enabled). If that fails patching via
// Courgette is attempted. Courgette falls back to bspatch if unsuccessful.
bool ApplyPatch();
// Attempts to use Courgette to apply last_uncompressed_file() to // Attempts to use Courgette to apply last_uncompressed_file() to
// patch_source() to generate target(). Returns false if patching fails. // patch_source() to generate target(). Returns false if patching fails.
bool CourgetteEnsemblePatch(); bool CourgetteEnsemblePatch();
......
...@@ -46,9 +46,9 @@ base::FilePath ArchivePatchHelperTest::data_dir_; ...@@ -46,9 +46,9 @@ base::FilePath ArchivePatchHelperTest::data_dir_;
} // namespace } // namespace
// Test that patching works. // Test that patching works.
TEST_F(ArchivePatchHelperTest, Patching) { TEST_F(ArchivePatchHelperTest, CourgettePatching) {
base::FilePath src = data_dir_.AppendASCII("archive1.7z"); base::FilePath src = data_dir_.AppendASCII("archive1.7z");
base::FilePath patch = data_dir_.AppendASCII("archive.diff"); base::FilePath patch = data_dir_.AppendASCII("courgette_archive.diff");
base::FilePath dest = test_dir_.GetPath().AppendASCII("archive2.7z"); base::FilePath dest = test_dir_.GetPath().AppendASCII("archive2.7z");
installer::ArchivePatchHelper archive_helper( installer::ArchivePatchHelper archive_helper(
test_dir_.GetPath(), base::FilePath(), src, dest, test_dir_.GetPath(), base::FilePath(), src, dest,
...@@ -60,6 +60,19 @@ TEST_F(ArchivePatchHelperTest, Patching) { ...@@ -60,6 +60,19 @@ TEST_F(ArchivePatchHelperTest, Patching) {
EXPECT_TRUE(base::ContentsEqual(dest, base)); EXPECT_TRUE(base::ContentsEqual(dest, base));
} }
TEST_F(ArchivePatchHelperTest, ZucchiniPatching) {
base::FilePath src = data_dir_.AppendASCII("archive1.7z");
base::FilePath patch = data_dir_.AppendASCII("zucchini_archive.diff");
base::FilePath dest = test_dir_.GetPath().AppendASCII("archive2.7z");
installer::ArchivePatchHelper archive_helper(
test_dir_.GetPath(), base::FilePath(), src, dest,
installer::UnPackConsumer::SETUP_EXE_PATCH);
archive_helper.set_last_uncompressed_file(patch);
EXPECT_TRUE(archive_helper.ZucchiniEnsemblePatch());
base::FilePath base = data_dir_.AppendASCII("archive2.7z");
EXPECT_TRUE(base::ContentsEqual(dest, base));
}
TEST_F(ArchivePatchHelperTest, InvalidDiff_MisalignedCblen) { TEST_F(ArchivePatchHelperTest, InvalidDiff_MisalignedCblen) {
base::FilePath src = data_dir_.AppendASCII("bin.old"); base::FilePath src = data_dir_.AppendASCII("bin.old");
base::FilePath patch = data_dir_.AppendASCII("misaligned_cblen.diff"); base::FilePath patch = data_dir_.AppendASCII("misaligned_cblen.diff");
......
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
declare_args() {
# Specify if the Zucchini patcher features should be included in setup.exe.
# See //chrome/installer/zucchini for more information.
use_zucchini = false
}
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS #undef APSTUDIO_READONLY_SYMBOLS
#include "chrome/installer/setup/buildflags.h"
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources // English (U.S.) resources
...@@ -135,6 +137,10 @@ IDR_GOOGLE_UPDATE_APP_COMMANDS_MARKUP GOOGLEUPDATEAPPLICATIONCOMMANDS { 1L } ...@@ -135,6 +137,10 @@ IDR_GOOGLE_UPDATE_APP_COMMANDS_MARKUP GOOGLEUPDATEAPPLICATIONCOMMANDS { 1L }
// on their integer resource names. // on their integer resource names.
// //
#define IDR_PATCHER_TYPE_COURGETTE 1 #define IDR_PATCHER_TYPE_COURGETTE 1
#define IDR_PATCHER_TYPE_ZUCCHINI 2
IDR_PATCHER_TYPE_COURGETTE PATCHERTYPE { 0L } IDR_PATCHER_TYPE_COURGETTE PATCHERTYPE { 0L }
#if BUILDFLAG(ZUCCHINI)
IDR_PATCHER_TYPE_ZUCCHINI PATCHERTYPE { 0L }
#endif // BUILDFLAG(ZUCCHINI)
...@@ -19,6 +19,12 @@ const wchar_t kInstallSourceChromeDir[] = L"Chrome-bin"; ...@@ -19,6 +19,12 @@ const wchar_t kInstallSourceChromeDir[] = L"Chrome-bin";
const wchar_t kMediaPlayerRegPath[] = const wchar_t kMediaPlayerRegPath[] =
L"Software\\Microsoft\\MediaPlayer\\ShimInclusionList"; L"Software\\Microsoft\\MediaPlayer\\ShimInclusionList";
const char kCourgette[] = "courgette";
const char kBsdiff[] = "bsdiff";
#if BUILDFLAG(ZUCCHINI)
const char kZucchini[] = "zucchini";
#endif // BUILDFLAG(ZUCCHINI)
namespace switches { namespace switches {
// Setting this will delay the operation of setup by the specified number of // Setting this will delay the operation of setup by the specified number of
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#ifndef CHROME_INSTALLER_SETUP_SETUP_CONSTANTS_H__ #ifndef CHROME_INSTALLER_SETUP_SETUP_CONSTANTS_H__
#define CHROME_INSTALLER_SETUP_SETUP_CONSTANTS_H__ #define CHROME_INSTALLER_SETUP_SETUP_CONSTANTS_H__
#include "chrome/installer/setup/buildflags.h"
namespace installer { namespace installer {
extern const wchar_t kChromeArchive[]; extern const wchar_t kChromeArchive[];
...@@ -19,6 +21,20 @@ extern const wchar_t kInstallSourceChromeDir[]; ...@@ -19,6 +21,20 @@ extern const wchar_t kInstallSourceChromeDir[];
extern const wchar_t kMediaPlayerRegPath[]; extern const wchar_t kMediaPlayerRegPath[];
// The range of error values among the installer, Courgette, BSDiff and
// Zucchini overlap. These offset values disambiguate between different sets
// of errors by shifting the values up with the specified offset.
const int kCourgetteErrorOffset = 300;
const int kBsdiffErrorOffset = 600;
const int kZucchiniErrorOffset = 900;
// Arguments to --patch switch
extern const char kCourgette[];
extern const char kBsdiff[];
#if BUILDFLAG(ZUCCHINI)
extern const char kZucchini[];
#endif // BUILDFLAG(ZUCCHINI)
namespace switches { namespace switches {
extern const char kDelay[]; extern const char kDelay[];
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#include "chrome/install_static/install_details.h" #include "chrome/install_static/install_details.h"
#include "chrome/install_static/install_util.h" #include "chrome/install_static/install_util.h"
#include "chrome/installer/setup/archive_patch_helper.h" #include "chrome/installer/setup/archive_patch_helper.h"
#include "chrome/installer/setup/buildflags.h"
#include "chrome/installer/setup/install.h" #include "chrome/installer/setup/install.h"
#include "chrome/installer/setup/install_worker.h" #include "chrome/installer/setup/install_worker.h"
#include "chrome/installer/setup/installer_crash_reporting.h" #include "chrome/installer/setup/installer_crash_reporting.h"
...@@ -311,18 +312,15 @@ bool UncompressAndPatchChromeArchive( ...@@ -311,18 +312,15 @@ bool UncompressAndPatchChromeArchive(
} }
archive_helper->set_patch_source(patch_source); archive_helper->set_patch_source(patch_source);
// Try courgette first. Failing that, try bspatch.
// Patch application sometimes takes a very long time, so use 100 buckets for // Patch application sometimes takes a very long time, so use 100 buckets for
// up to an hour. // up to an hour.
start_time = base::TimeTicks::Now(); start_time = base::TimeTicks::Now();
installer_state.SetStage(installer::PATCHING); installer_state.SetStage(installer::PATCHING);
if (!archive_helper->CourgetteEnsemblePatch()) { if (!archive_helper->ApplyPatch()) {
if (!archive_helper->BinaryPatch()) { *install_status = installer::APPLY_DIFF_PATCH_FAILED;
*install_status = installer::APPLY_DIFF_PATCH_FAILED; installer_state.WriteInstallerResult(
installer_state.WriteInstallerResult( *install_status, IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, NULL);
*install_status, IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, NULL); return false;
return false;
}
} }
// Record patch time only if it was successful. // Record patch time only if it was successful.
...@@ -1000,6 +998,11 @@ bool HandleNonInstallCmdLineOptions(const base::FilePath& setup_exe, ...@@ -1000,6 +998,11 @@ bool HandleNonInstallCmdLineOptions(const base::FilePath& setup_exe,
*exit_code = installer::BsdiffPatchFiles(input_file, *exit_code = installer::BsdiffPatchFiles(input_file,
patch_file, patch_file,
output_file); output_file);
#if BUILDFLAG(ZUCCHINI)
} else if (patch_type_str == installer::kZucchini) {
*exit_code =
installer::ZucchiniPatchFiles(input_file, patch_file, output_file);
#endif // BUILDFLAG(ZUCCHINI)
} else { } else {
*exit_code = installer::PATCH_INVALID_ARGUMENTS; *exit_code = installer::PATCH_INVALID_ARGUMENTS;
} }
......
...@@ -53,6 +53,8 @@ ...@@ -53,6 +53,8 @@
#include "chrome/installer/util/non_updating_app_registration_data.h" #include "chrome/installer/util/non_updating_app_registration_data.h"
#include "chrome/installer/util/updating_app_registration_data.h" #include "chrome/installer/util/updating_app_registration_data.h"
#include "chrome/installer/util/util_constants.h" #include "chrome/installer/util/util_constants.h"
#include "chrome/installer/zucchini/zucchini.h"
#include "chrome/installer/zucchini/zucchini_integration.h"
#include "courgette/courgette.h" #include "courgette/courgette.h"
#include "courgette/third_party/bsdiff/bsdiff.h" #include "courgette/third_party/bsdiff/bsdiff.h"
...@@ -322,6 +324,29 @@ int BsdiffPatchFiles(const base::FilePath& src, ...@@ -322,6 +324,29 @@ int BsdiffPatchFiles(const base::FilePath& src,
return exit_code; return exit_code;
} }
int ZucchiniPatchFiles(const base::FilePath& src,
const base::FilePath& patch,
const base::FilePath& dest) {
VLOG(1) << "Applying Zucchini patch " << patch.value() << " to file "
<< src.value() << " and generating file " << dest.value();
if (src.empty() || patch.empty() || dest.empty())
return installer::PATCH_INVALID_ARGUMENTS;
const zucchini::status::Code patch_status = zucchini::Apply(src, patch, dest);
const int exit_code =
(patch_status != zucchini::status::kStatusSuccess)
? static_cast<int>(patch_status) + kZucchiniErrorOffset
: 0;
LOG_IF(ERROR, exit_code) << "Failed to apply Zucchini patch " << patch.value()
<< " to file " << src.value()
<< " and generating file " << dest.value()
<< ". err=" << exit_code;
return exit_code;
}
base::Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) { base::Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) {
VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value(); VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value();
base::FileEnumerator version_enum(chrome_path, false, base::FileEnumerator version_enum(chrome_path, false,
......
...@@ -60,6 +60,13 @@ int BsdiffPatchFiles(const base::FilePath& src, ...@@ -60,6 +60,13 @@ int BsdiffPatchFiles(const base::FilePath& src,
const base::FilePath& patch, const base::FilePath& patch,
const base::FilePath& dest); const base::FilePath& dest);
// Applies a patch file to source file using Zucchini. Returns 0 in case of
// success. In case of errors, it returns kZucchiniErrorOffset + a Zucchini
// status code, as defined in chrome/installer/zucchini/zucchini.h
int ZucchiniPatchFiles(const base::FilePath& src,
const base::FilePath& patch,
const base::FilePath& dest);
// Find the version of Chrome from an install source directory. // Find the version of Chrome from an install source directory.
// Chrome_path should contain at least one version folder. // Chrome_path should contain at least one version folder.
// Returns the maximum version found or NULL if no version is found. // Returns the maximum version found or NULL if no version is found.
......
...@@ -220,9 +220,6 @@ const wchar_t kChromeChannelStableExplicit[] = L"stable"; ...@@ -220,9 +220,6 @@ const wchar_t kChromeChannelStableExplicit[] = L"stable";
const size_t kMaxAppModelIdLength = 64U; const size_t kMaxAppModelIdLength = 64U;
const char kCourgette[] = "courgette";
const char kBsdiff[] = "bsdiff";
const char kSetupHistogramAllocatorName[] = "SetupMetrics"; const char kSetupHistogramAllocatorName[] = "SetupMetrics";
} // namespace installer } // namespace installer
...@@ -123,23 +123,23 @@ enum ArchiveType { ...@@ -123,23 +123,23 @@ enum ArchiveType {
// this are the fork-and-join for diff vs. full installers (where there are // this are the fork-and-join for diff vs. full installers (where there are
// additional (costly) stages for the former) and rollback in case of error. // additional (costly) stages for the former) and rollback in case of error.
enum InstallerStage { enum InstallerStage {
NO_STAGE, // No stage to report. NO_STAGE, // No stage to report.
UPDATING_SETUP, // Courgette patching setup.exe (diff). UPDATING_SETUP, // Patching setup.exe with differential update.
PRECONDITIONS, // Evaluating pre-install conditions. PRECONDITIONS, // Evaluating pre-install conditions.
UNCOMPRESSING, // Uncompressing chrome.packed.7z. UNCOMPRESSING, // Uncompressing chrome.packed.7z.
PATCHING, // Patching chrome.7z using Courgette (diff). PATCHING, // Patching chrome.7z with differential update.
UNPACKING, // Unpacking chrome.7z. UNPACKING, // Unpacking chrome.7z.
CREATING_VISUAL_MANIFEST, // Creating VisualElementsManifest.xml. CREATING_VISUAL_MANIFEST, // Creating VisualElementsManifest.xml.
BUILDING, // Building the install work item list. BUILDING, // Building the install work item list.
EXECUTING, // Executing the install work item list. EXECUTING, // Executing the install work item list.
UPDATING_CHANNELS, // Updating channel information. UPDATING_CHANNELS, // Updating channel information.
COPYING_PREFERENCES_FILE, // Copying preferences file. COPYING_PREFERENCES_FILE, // Copying preferences file.
CREATING_SHORTCUTS, // Creating shortcuts. CREATING_SHORTCUTS, // Creating shortcuts.
REGISTERING_CHROME, // Performing Chrome registration. REGISTERING_CHROME, // Performing Chrome registration.
REMOVING_OLD_VERSIONS, // Deleting old version directories. REMOVING_OLD_VERSIONS, // Deleting old version directories.
ROLLINGBACK, // Rolling-back the install work item list. ROLLINGBACK, // Rolling-back the install work item list.
FINISHING, // Finishing the install. FINISHING, // Finishing the install.
NUM_STAGES // The number of stages. NUM_STAGES // The number of stages.
}; };
namespace switches { namespace switches {
...@@ -234,16 +234,6 @@ extern const wchar_t kChromeChannelStableExplicit[]; ...@@ -234,16 +234,6 @@ extern const wchar_t kChromeChannelStableExplicit[];
extern const size_t kMaxAppModelIdLength; extern const size_t kMaxAppModelIdLength;
// The range of error values for the installer, Courgette, and bsdiff is
// overlapping. These offset values disambiguate between different sets
// of errors by shifting the values up with the specified offset.
const int kCourgetteErrorOffset = 300;
const int kBsdiffErrorOffset = 600;
// Arguments to --patch switch
extern const char kCourgette[];
extern const char kBsdiff[];
// Name of the allocator (and associated file) for storing histograms to be // Name of the allocator (and associated file) for storing histograms to be
// reported by Chrome during its next upload. // reported by Chrome during its next upload.
extern const char kSetupHistogramAllocatorName[]; extern const char kSetupHistogramAllocatorName[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment