1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/immediate_crash.h"
#include <stdint.h>
#include "base/base_paths.h"
#include "base/clang_profiling_buildflags.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/scoped_native_library.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base {
namespace {
// If ImmediateCrash() is not treated as noreturn by the compiler, the compiler
// will complain that not all paths through this function return a value.
[[maybe_unused]] int TestImmediateCrashTreatedAsNoReturn() {
ImmediateCrash();
}
#if defined(ARCH_CPU_X86_FAMILY)
// This is tricksy and false, since x86 instructions are not all one byte long,
// but there is no better alternative short of implementing an x86 instruction
// decoder.
using Instruction = uint8_t;
// https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4
// Look for RET opcode (0xc3). Note that 0xC3 is a substring of several
// other opcodes (VMRESUME, MOVNTI), and can also be encoded as part of an
// argument to another opcode. None of these other cases are expected to be
// present, so a simple byte scan should be Good Enoughâ„¢.
constexpr Instruction kRet = 0xc3;
// INT3 ; UD2
constexpr Instruction kRequiredBody[] = {0xcc, 0x0f, 0x0b};
constexpr Instruction kOptionalFooter[] = {};
#elif defined(ARCH_CPU_ARMEL)
using Instruction = uint16_t;
// T32 opcode reference: https://developer.arm.com/docs/ddi0487/latest
// Actually BX LR, canonical encoding:
constexpr Instruction kRet = 0x4770;
// BKPT #0; UDF #0
constexpr Instruction kRequiredBody[] = {0xbe00, 0xde00};
constexpr Instruction kOptionalFooter[] = {};
#elif defined(ARCH_CPU_ARM64)
using Instruction = uint32_t;
// A64 opcode reference: https://developer.arm.com/docs/ddi0487/latest
// Use an enum here rather than separate constexpr vars because otherwise some
// of the vars will end up unused on each platform, upsetting
// -Wunused-const-variable.
enum : Instruction {
// There are multiple valid encodings of return (which is really a special
// form of branch). This is the one clang seems to use:
kRet = 0xd65f03c0,
kBrk0 = 0xd4200000,
kBrk1 = 0xd4200020,
kBrkF000 = 0xd43e0000,
kHlt0 = 0xd4400000,
};
#if BUILDFLAG(IS_WIN)
constexpr Instruction kRequiredBody[] = {kBrkF000, kBrk1};
constexpr Instruction kOptionalFooter[] = {};
#elif BUILDFLAG(IS_MAC)
constexpr Instruction kRequiredBody[] = {kBrk0, kHlt0};
// Some clangs emit a BRK #1 for __builtin_unreachable(), but some do not, so
// it is allowed but not required to occur.
constexpr Instruction kOptionalFooter[] = {kBrk1};
#else
constexpr Instruction kRequiredBody[] = {kBrk0, kHlt0};
constexpr Instruction kOptionalFooter[] = {};
#endif
#endif
// This function loads a shared library that defines two functions,
// TestFunction1 and TestFunction2. It then returns the bytes of the body of
// whichever of those functions happens to come first in the library.
void GetTestFunctionInstructions(std::vector<Instruction>* body) {
FilePath helper_library_path;
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA)
// On Android M, DIR_EXE == /system/bin when running base_unittests.
// On Fuchsia, NativeLibrary understands the native convention that libraries
// are not colocated with the binary.
ASSERT_TRUE(PathService::Get(DIR_EXE, &helper_library_path));
#endif
helper_library_path = helper_library_path.AppendASCII(
GetNativeLibraryName("immediate_crash_test_helper"));
#if BUILDFLAG(IS_ANDROID) && defined(COMPONENT_BUILD)
helper_library_path = helper_library_path.ReplaceExtension(".cr.so");
#endif
ScopedNativeLibrary helper_library(helper_library_path);
ASSERT_TRUE(helper_library.is_valid())
<< "shared library load failed: "
<< helper_library.GetError()->ToString();
void* a = helper_library.GetFunctionPointer("TestFunction1");
ASSERT_TRUE(a);
void* b = helper_library.GetFunctionPointer("TestFunction2");
ASSERT_TRUE(b);
#if defined(ARCH_CPU_ARMEL)
// Routines loaded from a shared library will have the LSB in the pointer set
// if encoded as T32 instructions. The rest of this test assumes T32.
ASSERT_TRUE(reinterpret_cast<uintptr_t>(a) & 0x1)
<< "Expected T32 opcodes but found A32 opcodes instead.";
ASSERT_TRUE(reinterpret_cast<uintptr_t>(b) & 0x1)
<< "Expected T32 opcodes but found A32 opcodes instead.";
// Mask off the lowest bit.
a = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(a) & ~uintptr_t{0x1});
b = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(b) & ~uintptr_t{0x1});
#endif
// There are two identical test functions starting at a and b, which may
// occur in the library in either order. Grab whichever one comes first,
// and use the address of the other to figure out where it ends.
const Instruction* const start = static_cast<Instruction*>(std::min(a, b));
const Instruction* const end = static_cast<Instruction*>(std::max(a, b));
for (const Instruction& instruction : make_span(start, end))
body->push_back(instruction);
}
absl::optional<std::vector<Instruction>> ExpectImmediateCrashInvocation(
std::vector<Instruction> instructions) {
auto iter = instructions.begin();
for (const auto inst : kRequiredBody) {
if (iter == instructions.end())
return absl::nullopt;
EXPECT_EQ(inst, *iter);
iter++;
}
return absl::make_optional(
std::vector<Instruction>(iter, instructions.end()));
}
std::vector<Instruction> MaybeSkipOptionalFooter(
std::vector<Instruction> instructions) {
auto iter = instructions.begin();
for (const auto inst : kOptionalFooter) {
if (iter == instructions.end() || *iter != inst)
break;
iter++;
}
return std::vector<Instruction>(iter, instructions.end());
}
#if BUILDFLAG(USE_CLANG_COVERAGE) || BUILDFLAG(CLANG_PROFILING)
bool MatchPrefix(const std::vector<Instruction>& haystack,
const base::span<const Instruction>& needle) {
for (size_t i = 0; i < needle.size(); i++) {
if (i >= haystack.size() || needle[i] != haystack[i])
return false;
}
return true;
}
std::vector<Instruction> DropUntilMatch(
std::vector<Instruction> haystack,
const base::span<const Instruction>& needle) {
while (!haystack.empty() && !MatchPrefix(haystack, needle))
haystack.erase(haystack.begin());
return haystack;
}
#endif // USE_CLANG_COVERAGE || BUILDFLAG(CLANG_PROFILING)
std::vector<Instruction> MaybeSkipCoverageHook(
std::vector<Instruction> instructions) {
#if BUILDFLAG(USE_CLANG_COVERAGE) || BUILDFLAG(CLANG_PROFILING)
// Warning: it is not illegal for the entirety of the expected crash sequence
// to appear as a subsequence of the coverage hook code. If that happens, this
// code will falsely exit early, having not found the real expected crash
// sequence, so this may not adequately ensure that the immediate crash
// sequence is present. We do check when not under coverage, at least.
return DropUntilMatch(instructions, base::make_span(kRequiredBody));
#else
return instructions;
#endif // USE_CLANG_COVERAGE || BUILDFLAG(CLANG_PROFILING)
}
} // namespace
// Attempts to verify the actual instructions emitted by ImmediateCrash().
// While the test results are highly implementation-specific, this allows macro
// changes (e.g. CLs like https://crrev.com/671123) to be verified using the
// trybots/waterfall, without having to build and disassemble Chrome on
// multiple platforms. This makes it easier to evaluate changes to
// ImmediateCrash() against its requirements (e.g. size of emitted sequence,
// whether or not multiple ImmediateCrash sequences can be folded together, et
// cetera). Please see immediate_crash.h for more details about the
// requirements.
//
// Note that C++ provides no way to get the size of a function. Instead, the
// test relies on a shared library which defines only two functions and assumes
// the two functions will be laid out contiguously as a heuristic for finding
// the size of the function.
TEST(ImmediateCrashTest, ExpectedOpcodeSequence) {
std::vector<Instruction> body;
ASSERT_NO_FATAL_FAILURE(GetTestFunctionInstructions(&body));
SCOPED_TRACE(HexEncode(body.data(), body.size() * sizeof(Instruction)));
auto it = ranges::find(body, kRet);
ASSERT_NE(body.end(), it) << "Failed to find return opcode";
it++;
body = std::vector<Instruction>(it, body.end());
absl::optional<std::vector<Instruction>> result = MaybeSkipCoverageHook(body);
result = ExpectImmediateCrashInvocation(result.value());
result = MaybeSkipOptionalFooter(result.value());
result = MaybeSkipCoverageHook(result.value());
result = ExpectImmediateCrashInvocation(result.value());
ASSERT_TRUE(result);
}
} // namespace base
|