|  | /* | 
|  | * Copyright (C) 2015 The Android Open Source Project | 
|  | * All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * are met: | 
|  | *  * Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | *  * Redistributions in binary form must reproduce the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer in | 
|  | *    the documentation and/or other materials provided with the | 
|  | *    distribution. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | 
|  | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | 
|  | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | 
|  | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | 
|  | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS | 
|  | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED | 
|  | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
|  | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | 
|  | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | 
|  | * SUCH DAMAGE. | 
|  | */ | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <ctype.h> | 
|  | #include <errno.h> | 
|  | #include <limits.h> | 
|  | #include <signal.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/cdefs.h> | 
|  |  | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include <platform/bionic/macros.h> | 
|  |  | 
|  | #include "Config.h" | 
|  | #include "debug_log.h" | 
|  |  | 
|  | // Config constants | 
|  | static constexpr uint8_t DEFAULT_FILL_ALLOC_VALUE = 0xeb; | 
|  | static constexpr uint8_t DEFAULT_FILL_FREE_VALUE = 0xef; | 
|  |  | 
|  | static constexpr uint8_t DEFAULT_FRONT_GUARD_VALUE = 0xaa; | 
|  | static constexpr uint8_t DEFAULT_REAR_GUARD_VALUE = 0xbb; | 
|  |  | 
|  | // Used as the default for all guard values. | 
|  | static constexpr size_t DEFAULT_GUARD_BYTES = 32; | 
|  | static constexpr size_t MAX_GUARD_BYTES = 16384; | 
|  |  | 
|  | static constexpr size_t DEFAULT_BACKTRACE_FRAMES = 16; | 
|  | static constexpr size_t MAX_BACKTRACE_FRAMES = 256; | 
|  | static constexpr const char DEFAULT_BACKTRACE_DUMP_PREFIX[] = "/data/local/tmp/backtrace_heap"; | 
|  |  | 
|  | static constexpr size_t DEFAULT_EXPAND_BYTES = 16; | 
|  | static constexpr size_t MAX_EXPAND_BYTES = 16384; | 
|  |  | 
|  | static constexpr size_t DEFAULT_FREE_TRACK_ALLOCATIONS = 100; | 
|  | static constexpr size_t MAX_FREE_TRACK_ALLOCATIONS = 16384; | 
|  |  | 
|  | static constexpr size_t DEFAULT_RECORD_ALLOCS = 8000000; | 
|  | static constexpr size_t MAX_RECORD_ALLOCS = 50000000; | 
|  | static constexpr const char DEFAULT_RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt"; | 
|  |  | 
|  | const std::unordered_map<std::string, Config::OptionInfo> Config::kOptions = { | 
|  | { | 
|  | "guard", | 
|  | {FRONT_GUARD | REAR_GUARD | TRACK_ALLOCS, &Config::SetGuard}, | 
|  | }, | 
|  | { | 
|  | "front_guard", | 
|  | {FRONT_GUARD | TRACK_ALLOCS, &Config::SetFrontGuard}, | 
|  | }, | 
|  | { | 
|  | "rear_guard", | 
|  | {REAR_GUARD | TRACK_ALLOCS, &Config::SetRearGuard}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "backtrace_size", | 
|  | {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceSize}, | 
|  | }, | 
|  | { | 
|  | "bt_sz", | 
|  | {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceSize}, | 
|  | }, | 
|  | { | 
|  | "backtrace_min_size", | 
|  | {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceMinSize}, | 
|  | }, | 
|  | { | 
|  | "bt_min_sz", | 
|  | {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceMinSize}, | 
|  | }, | 
|  | { | 
|  | "backtrace_max_size", | 
|  | {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceMaxSize}, | 
|  | }, | 
|  | { | 
|  | "bt_max_sz", | 
|  | {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceMaxSize}, | 
|  | }, | 
|  | { | 
|  | "backtrace", | 
|  | {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktrace}, | 
|  | }, | 
|  | { | 
|  | "bt", | 
|  | {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktrace}, | 
|  | }, | 
|  | { | 
|  | "backtrace_enable_on_signal", | 
|  | {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktraceEnableOnSignal}, | 
|  | }, | 
|  | { | 
|  | "bt_en_on_sig", | 
|  | {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktraceEnableOnSignal}, | 
|  | }, | 
|  | { | 
|  | "backtrace_dump_on_exit", | 
|  | {0, &Config::SetBacktraceDumpOnExit}, | 
|  | }, | 
|  | { | 
|  | "bt_dmp_on_ex", | 
|  | {0, &Config::SetBacktraceDumpOnExit}, | 
|  | }, | 
|  | { | 
|  | "backtrace_dump_prefix", | 
|  | {0, &Config::SetBacktraceDumpPrefix}, | 
|  | }, | 
|  | { | 
|  | "bt_dmp_pre", | 
|  | {0, &Config::SetBacktraceDumpPrefix}, | 
|  | }, | 
|  | { | 
|  | "backtrace_full", | 
|  | {BACKTRACE_FULL, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | { | 
|  | "bt_full", | 
|  | {BACKTRACE_FULL, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "fill", | 
|  | {FILL_ON_ALLOC | FILL_ON_FREE, &Config::SetFill}, | 
|  | }, | 
|  | { | 
|  | "fill_on_alloc", | 
|  | {FILL_ON_ALLOC, &Config::SetFillOnAlloc}, | 
|  | }, | 
|  | { | 
|  | "fill_on_free", | 
|  | {FILL_ON_FREE, &Config::SetFillOnFree}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "expand_alloc", | 
|  | {EXPAND_ALLOC, &Config::SetExpandAlloc}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "free_track", | 
|  | {FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, &Config::SetFreeTrack}, | 
|  | }, | 
|  | { | 
|  | "free_track_backtrace_num_frames", | 
|  | {0, &Config::SetFreeTrackBacktraceNumFrames}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "leak_track", | 
|  | {LEAK_TRACK | TRACK_ALLOCS, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "record_allocs", | 
|  | {RECORD_ALLOCS, &Config::SetRecordAllocs}, | 
|  | }, | 
|  | { | 
|  | "record_allocs_file", | 
|  | {0, &Config::SetRecordAllocsFile}, | 
|  | }, | 
|  | { | 
|  | "record_allocs_on_exit", | 
|  | {0, &Config::SetRecordAllocsOnExit}, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "verify_pointers", | 
|  | {TRACK_ALLOCS, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | { | 
|  | "abort_on_error", | 
|  | {ABORT_ON_ERROR, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | { | 
|  | "verbose", | 
|  | {VERBOSE, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | { | 
|  | "check_unreachable_on_signal", | 
|  | {CHECK_UNREACHABLE_ON_SIGNAL, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | { | 
|  | "log_allocator_stats_on_signal", | 
|  | {LOG_ALLOCATOR_STATS_ON_SIGNAL, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | { | 
|  | "log_allocator_stats_on_exit", | 
|  | {LOG_ALLOCATOR_STATS_ON_EXIT, &Config::VerifyValueEmpty}, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | bool Config::ParseValue(const std::string& option, const std::string& value, size_t min_value, | 
|  | size_t max_value, size_t* parsed_value) const { | 
|  | assert(!value.empty()); | 
|  |  | 
|  | // Parse the value into a size_t value. | 
|  | errno = 0; | 
|  | char* end; | 
|  | long long_value = strtol(value.c_str(), &end, 10); | 
|  | if (errno != 0) { | 
|  | error_log("%s: bad value for option '%s': %s", getprogname(), option.c_str(), strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | if (end == value.c_str()) { | 
|  | error_log("%s: bad value for option '%s'", getprogname(), option.c_str()); | 
|  | return false; | 
|  | } | 
|  | if (static_cast<size_t>(end - value.c_str()) != value.size()) { | 
|  | error_log("%s: bad value for option '%s', non space found after option: %s", getprogname(), | 
|  | option.c_str(), end); | 
|  | return false; | 
|  | } | 
|  | if (long_value < 0) { | 
|  | error_log("%s: bad value for option '%s', value cannot be negative: %ld", getprogname(), | 
|  | option.c_str(), long_value); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (static_cast<size_t>(long_value) < min_value) { | 
|  | error_log("%s: bad value for option '%s', value must be >= %zu: %ld", getprogname(), | 
|  | option.c_str(), min_value, long_value); | 
|  | return false; | 
|  | } | 
|  | if (static_cast<size_t>(long_value) > max_value) { | 
|  | error_log("%s: bad value for option '%s', value must be <= %zu: %ld", getprogname(), | 
|  | option.c_str(), max_value, long_value); | 
|  | return false; | 
|  | } | 
|  | *parsed_value = static_cast<size_t>(long_value); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::ParseValue(const std::string& option, const std::string& value, size_t default_value, | 
|  | size_t min_value, size_t max_value, size_t* new_value) const { | 
|  | if (value.empty()) { | 
|  | *new_value = default_value; | 
|  | return true; | 
|  | } | 
|  | return ParseValue(option, value, min_value, max_value, new_value); | 
|  | } | 
|  |  | 
|  | bool Config::SetGuard(const std::string& option, const std::string& value) { | 
|  | if (value.empty()) { | 
|  | // Set the defaults. | 
|  | front_guard_bytes_ = DEFAULT_GUARD_BYTES; | 
|  | rear_guard_bytes_ = DEFAULT_GUARD_BYTES; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!ParseValue(option, value, 1, MAX_GUARD_BYTES, &rear_guard_bytes_)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // It's necessary to align the front guard to MINIMUM_ALIGNMENT_BYTES to | 
|  | // make sure that the header is aligned properly. | 
|  | front_guard_bytes_ = __BIONIC_ALIGN(rear_guard_bytes_, MINIMUM_ALIGNMENT_BYTES); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::SetFrontGuard(const std::string& option, const std::string& value) { | 
|  | if (!ParseValue(option, value, DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, &front_guard_bytes_)) { | 
|  | return false; | 
|  | } | 
|  | // It's necessary to align the front guard to MINIMUM_ALIGNMENT_BYTES to | 
|  | // make sure that the header is aligned properly. | 
|  | front_guard_bytes_ = __BIONIC_ALIGN(front_guard_bytes_, MINIMUM_ALIGNMENT_BYTES); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::SetRearGuard(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, &rear_guard_bytes_); | 
|  | } | 
|  |  | 
|  | bool Config::SetFill(const std::string& option, const std::string& value) { | 
|  | if (value.empty()) { | 
|  | // Set the defaults. | 
|  | fill_on_alloc_bytes_ = SIZE_MAX; | 
|  | fill_on_free_bytes_ = SIZE_MAX; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!ParseValue(option, value, 1, SIZE_MAX, &fill_on_alloc_bytes_)) { | 
|  | return false; | 
|  | } | 
|  | fill_on_free_bytes_ = fill_on_alloc_bytes_; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::SetFillOnAlloc(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, SIZE_MAX, 1, SIZE_MAX, &fill_on_alloc_bytes_); | 
|  | } | 
|  |  | 
|  | bool Config::SetFillOnFree(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, SIZE_MAX, 1, SIZE_MAX, &fill_on_free_bytes_); | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktrace(const std::string& option, const std::string& value) { | 
|  | backtrace_enabled_ = true; | 
|  | return ParseValue(option, value, DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES, | 
|  | &backtrace_frames_); | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktraceEnableOnSignal(const std::string& option, const std::string& value) { | 
|  | backtrace_enable_on_signal_ = true; | 
|  | return ParseValue(option, value, DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES, | 
|  | &backtrace_frames_); | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktraceDumpOnExit(const std::string& option, const std::string& value) { | 
|  | if (Config::VerifyValueEmpty(option, value)) { | 
|  | backtrace_dump_on_exit_ = true; | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktraceDumpPrefix(const std::string&, const std::string& value) { | 
|  | if (value.empty()) { | 
|  | backtrace_dump_prefix_ = DEFAULT_BACKTRACE_DUMP_PREFIX; | 
|  | } else { | 
|  | backtrace_dump_prefix_ = value; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktraceSize(const std::string& option, const std::string& value) { | 
|  | if (!ParseValue(option, value, 1, SIZE_MAX, &backtrace_min_size_bytes_)) { | 
|  | return false; | 
|  | } | 
|  | backtrace_max_size_bytes_ = backtrace_min_size_bytes_; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktraceMinSize(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, 1, SIZE_MAX, &backtrace_min_size_bytes_); | 
|  | } | 
|  |  | 
|  | bool Config::SetBacktraceMaxSize(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, 1, SIZE_MAX, &backtrace_max_size_bytes_); | 
|  | } | 
|  |  | 
|  | bool Config::SetExpandAlloc(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, &expand_alloc_bytes_); | 
|  | } | 
|  |  | 
|  | bool Config::SetFreeTrack(const std::string& option, const std::string& value) { | 
|  | // This option enables fill on free, so set the bytes to the default value. | 
|  | if (fill_on_free_bytes_ == 0) { | 
|  | fill_on_free_bytes_ = SIZE_MAX; | 
|  | } | 
|  | if (free_track_backtrace_num_frames_ == 0) { | 
|  | free_track_backtrace_num_frames_ = DEFAULT_BACKTRACE_FRAMES; | 
|  | } | 
|  |  | 
|  | return ParseValue(option, value, DEFAULT_FREE_TRACK_ALLOCATIONS, 1, MAX_FREE_TRACK_ALLOCATIONS, | 
|  | &free_track_allocations_); | 
|  | } | 
|  |  | 
|  | bool Config::SetFreeTrackBacktraceNumFrames(const std::string& option, const std::string& value) { | 
|  | return ParseValue(option, value, DEFAULT_BACKTRACE_FRAMES, 0, MAX_BACKTRACE_FRAMES, | 
|  | &free_track_backtrace_num_frames_); | 
|  | } | 
|  |  | 
|  | bool Config::SetRecordAllocs(const std::string& option, const std::string& value) { | 
|  | if (record_allocs_file_.empty()) { | 
|  | record_allocs_file_ = DEFAULT_RECORD_ALLOCS_FILE; | 
|  | } | 
|  | return ParseValue(option, value, DEFAULT_RECORD_ALLOCS, 1, MAX_RECORD_ALLOCS, | 
|  | &record_allocs_num_entries_); | 
|  | } | 
|  |  | 
|  | bool Config::SetRecordAllocsFile(const std::string&, const std::string& value) { | 
|  | if (value.empty()) { | 
|  | // Set the default. | 
|  | record_allocs_file_ = DEFAULT_RECORD_ALLOCS_FILE; | 
|  | return true; | 
|  | } | 
|  | record_allocs_file_ = value; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::SetRecordAllocsOnExit(const std::string& option, const std::string& value) { | 
|  | if (Config::VerifyValueEmpty(option, value)) { | 
|  | record_allocs_on_exit_ = true; | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Config::VerifyValueEmpty(const std::string& option, const std::string& value) { | 
|  | if (!value.empty()) { | 
|  | // This is not valid. | 
|  | error_log("%s: value set for option '%s' which does not take a value", getprogname(), | 
|  | option.c_str()); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Config::LogUsage() const { | 
|  | error_log("For malloc debug option descriptions go to:"); | 
|  | error_log( | 
|  | "  https://android.googlesource.com/platform/bionic/+/main/libc/malloc_debug/README.md"); | 
|  | } | 
|  |  | 
|  | bool Config::GetOption(const char** options_str, std::string* option, std::string* value) { | 
|  | const char* cur = *options_str; | 
|  | // Process each property name we can find. | 
|  | while (isspace(*cur)) ++cur; | 
|  |  | 
|  | if (*cur == '\0') { | 
|  | *options_str = cur; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const char* start = cur; | 
|  | while (!isspace(*cur) && *cur != '=' && *cur != '\0') ++cur; | 
|  |  | 
|  | *option = std::string(start, cur - start); | 
|  |  | 
|  | // Skip any spaces after the name. | 
|  | while (isspace(*cur)) ++cur; | 
|  |  | 
|  | value->clear(); | 
|  | if (*cur == '=') { | 
|  | ++cur; | 
|  | // Skip the space after the equal. | 
|  | while (isspace(*cur)) ++cur; | 
|  |  | 
|  | start = cur; | 
|  | while (!isspace(*cur) && *cur != '\0') ++cur; | 
|  |  | 
|  | if (cur != start) { | 
|  | *value = std::string(start, cur - start); | 
|  | } | 
|  | } | 
|  | *options_str = cur; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Config::Init(const char* options_str) { | 
|  | // Initialize a few default values. | 
|  | fill_alloc_value_ = DEFAULT_FILL_ALLOC_VALUE; | 
|  | fill_free_value_ = DEFAULT_FILL_FREE_VALUE; | 
|  | front_guard_value_ = DEFAULT_FRONT_GUARD_VALUE; | 
|  | rear_guard_value_ = DEFAULT_REAR_GUARD_VALUE; | 
|  | backtrace_signal_ = SIGRTMAX - 19; | 
|  | backtrace_dump_signal_ = SIGRTMAX - 17; | 
|  | record_allocs_signal_ = SIGRTMAX - 18; | 
|  | free_track_backtrace_num_frames_ = 0; | 
|  | record_allocs_file_.clear(); | 
|  | fill_on_free_bytes_ = 0; | 
|  | backtrace_enable_on_signal_ = false; | 
|  | backtrace_enabled_ = false; | 
|  | backtrace_dump_on_exit_ = false; | 
|  | backtrace_dump_prefix_ = DEFAULT_BACKTRACE_DUMP_PREFIX; | 
|  | backtrace_min_size_bytes_ = 0; | 
|  | backtrace_max_size_bytes_ = SIZE_MAX; | 
|  | check_unreachable_signal_ = SIGRTMAX - 16; | 
|  | log_allocator_stats_signal_ = SIGRTMAX - 15; | 
|  |  | 
|  | // Process each option name we can find. | 
|  | std::string option; | 
|  | std::string value; | 
|  | bool valid = true; | 
|  | while (GetOption(&options_str, &option, &value)) { | 
|  | auto entry = kOptions.find(option); | 
|  | if (entry == kOptions.end()) { | 
|  | error_log("%s: unknown option %s", getprogname(), option.c_str()); | 
|  | valid = false; | 
|  | break; | 
|  | } | 
|  |  | 
|  | const OptionInfo* info = &entry->second; | 
|  | auto process_func = info->process_func; | 
|  | if (process_func != nullptr && !(this->*process_func)(option, value)) { | 
|  | valid = false; | 
|  | break; | 
|  | } | 
|  | options_ |= info->option; | 
|  | } | 
|  |  | 
|  | if (!valid || *options_str != '\0') { | 
|  | LogUsage(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } |