f8e61ad148
TEST=tsan Change-Id: I44456d685737c0fe59c25d0f2cc9f6fc8a4ded56 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/456106 Reviewed-by: Liam Appelbe <liama@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
252 lines
7.9 KiB
C++
252 lines
7.9 KiB
C++
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/tsan_symbolize.h"
|
|
|
|
#include "platform/atomic.h"
|
|
#include "platform/thread_sanitizer.h"
|
|
#include "vm/code_descriptors.h"
|
|
#include "vm/object.h"
|
|
|
|
namespace dart {
|
|
|
|
#if defined(USING_THREAD_SANITIZER) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
struct TsanLineNumberProgram {
|
|
TsanLineNumberProgram* next;
|
|
uintptr_t pc;
|
|
uint32_t size;
|
|
uint8_t stream[];
|
|
};
|
|
static std::atomic<TsanLineNumberProgram*> head = nullptr;
|
|
|
|
enum : uint8_t {
|
|
OP_STOP = 0,
|
|
OP_PUSH_FUNCTION,
|
|
OP_POP_FUNCTION,
|
|
OP_ADVANCE_PC,
|
|
OP_CHANGE_POSITION,
|
|
};
|
|
|
|
static void WriteString(GrowableArray<uint8_t>& out, const char* str) {
|
|
for (intptr_t i = 0, len = strlen(str); i <= len; i++) {
|
|
out.Add(str[i]);
|
|
}
|
|
}
|
|
|
|
void RegisterTsanSymbolize(const Code& code) {
|
|
GrowableArray<uint8_t> out(256);
|
|
|
|
if (code.IsFunctionCode()) {
|
|
const CodeSourceMap& map = CodeSourceMap::Handle(code.code_source_map());
|
|
RELEASE_ASSERT(!map.IsNull());
|
|
const Array& functions = Array::Handle(code.inlined_id_to_function());
|
|
Function& function = Function::Handle(code.function());
|
|
Script& script = Script::Handle(function.script());
|
|
String& url = String::Handle(script.url());
|
|
GrowableArray<Function*> function_stack(8);
|
|
GrowableArray<TokenPosition> token_positions(8);
|
|
function_stack.Add(&Function::Handle(function.ptr()));
|
|
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
|
|
|
|
out.Add(OP_PUSH_FUNCTION);
|
|
WriteString(out, function.QualifiedUserVisibleNameCString());
|
|
WriteString(out, url.ToCString());
|
|
|
|
NoSafepointScope no_safepoint;
|
|
ReadStream stream(map.Data(), map.Length());
|
|
while (stream.PendingBytes() > 0) {
|
|
int32_t arg;
|
|
switch (CodeSourceMapOps::Read(&stream, &arg)) {
|
|
case CodeSourceMapOps::kChangePosition: {
|
|
const TokenPosition& old_token = token_positions.Last();
|
|
token_positions.Last() = TokenPosition::Deserialize(
|
|
Utils::AddWithWrapAround(arg, old_token.Serialize()));
|
|
|
|
TokenPosition pos = token_positions.Last();
|
|
if (pos.IsNoSource()) {
|
|
pos = function_stack.Last()->token_pos();
|
|
}
|
|
|
|
intptr_t line = -1;
|
|
intptr_t column = -1;
|
|
script = function_stack.Last()->script();
|
|
script.GetTokenLocation(pos, &line, &column);
|
|
|
|
out.Add(OP_CHANGE_POSITION);
|
|
out.Add((line >> 0) & 0xFF);
|
|
out.Add((line >> 8) & 0xFF);
|
|
out.Add((column >> 0) & 0xFF);
|
|
out.Add((column >> 8) & 0xFF);
|
|
break;
|
|
}
|
|
case CodeSourceMapOps::kAdvancePC: {
|
|
out.Add(OP_ADVANCE_PC);
|
|
out.Add((arg >> 0) & 0xFF);
|
|
out.Add((arg >> 8) & 0xFF);
|
|
out.Add((arg >> 16) & 0xFF);
|
|
out.Add((arg >> 24) & 0xFF);
|
|
break;
|
|
}
|
|
case CodeSourceMapOps::kPushFunction: {
|
|
function ^= functions.At(arg);
|
|
function_stack.Add(&Function::Handle(function.ptr()));
|
|
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
|
|
|
|
out.Add(OP_PUSH_FUNCTION);
|
|
WriteString(out, function.QualifiedUserVisibleNameCString());
|
|
script = function_stack.Last()->script();
|
|
url = script.url();
|
|
WriteString(out, url.ToCString());
|
|
break;
|
|
}
|
|
case CodeSourceMapOps::kPopFunction: {
|
|
function_stack.RemoveLast();
|
|
token_positions.RemoveLast();
|
|
out.Add(OP_POP_FUNCTION);
|
|
break;
|
|
}
|
|
case CodeSourceMapOps::kNullCheck: {
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
} else {
|
|
out.Add(OP_PUSH_FUNCTION);
|
|
WriteString(out, code.Name());
|
|
WriteString(out, "stub");
|
|
|
|
int32_t arg = code.Size();
|
|
out.Add(OP_ADVANCE_PC);
|
|
out.Add((arg >> 0) & 0xFF);
|
|
out.Add((arg >> 8) & 0xFF);
|
|
out.Add((arg >> 16) & 0xFF);
|
|
out.Add((arg >> 24) & 0xFF);
|
|
}
|
|
out.Add(OP_STOP);
|
|
|
|
TsanLineNumberProgram* lnp = reinterpret_cast<TsanLineNumberProgram*>(
|
|
malloc(sizeof(TsanLineNumberProgram) + out.length()));
|
|
lnp->next = nullptr;
|
|
lnp->pc = code.PayloadStart();
|
|
lnp->size = code.Size();
|
|
memcpy(&lnp->stream, out.data(), out.length()); // NOLINT
|
|
|
|
TsanLineNumberProgram* old_head = head.load(std::memory_order_acquire);
|
|
do {
|
|
lnp->next = old_head;
|
|
} while (
|
|
!head.compare_exchange_weak(old_head, lnp, std::memory_order_acq_rel));
|
|
}
|
|
|
|
typedef void (*AddFrame)(void* ctxt,
|
|
const char* function_name,
|
|
const char* file,
|
|
int line,
|
|
int column);
|
|
|
|
// It would be nice to implement this by a heap walk to find the Code object and
|
|
// then symbolize using our normal PC descriptors, etc, but this function must
|
|
// not call any function that has been instrumented by TSAN or it might deadlock
|
|
// during __tsan_func_entry.
|
|
extern "C" __attribute__((visibility("default")))
|
|
__attribute((used)) NO_SANITIZE_THREAD DISABLE_SANITIZER_INSTRUMENTATION void
|
|
__tsan_symbolize_external_ex(uintptr_t pc, AddFrame add_frame, void* ctxt) {
|
|
const uword lookup_pc = pc & ~kExternalPCBit;
|
|
|
|
for (TsanLineNumberProgram* lnp = head.load(std::memory_order_acquire);
|
|
lnp != nullptr; lnp = lnp->next) {
|
|
if ((lookup_pc >= lnp->pc) && ((lookup_pc - lnp->pc) < lnp->size)) {
|
|
// Greater than the default value of --inlining_depth_threshold.
|
|
constexpr intptr_t kMaxDepth = 32;
|
|
const char* names[kMaxDepth];
|
|
const char* files[kMaxDepth];
|
|
int32_t lines[kMaxDepth];
|
|
int32_t columns[kMaxDepth];
|
|
intptr_t depth = 0;
|
|
uword lnp_pc = lnp->pc;
|
|
|
|
uint8_t* cursor = &lnp->stream[0];
|
|
for (;;) {
|
|
switch (*cursor++) {
|
|
case OP_PUSH_FUNCTION: {
|
|
const char* name = reinterpret_cast<const char*>(cursor);
|
|
while (*cursor++ != 0) {
|
|
}
|
|
const char* file = reinterpret_cast<const char*>(cursor);
|
|
while (*cursor++ != 0) {
|
|
}
|
|
|
|
names[depth] = name;
|
|
files[depth] = file;
|
|
lines[depth] = -1;
|
|
columns[depth] = -1;
|
|
|
|
depth++;
|
|
if (depth >= kMaxDepth) {
|
|
FATAL("LNP overflow");
|
|
}
|
|
break;
|
|
}
|
|
case OP_POP_FUNCTION: {
|
|
depth--;
|
|
break;
|
|
}
|
|
case OP_CHANGE_POSITION: {
|
|
uint8_t a = *cursor++;
|
|
uint8_t b = *cursor++;
|
|
uint8_t c = *cursor++;
|
|
uint8_t d = *cursor++;
|
|
lines[depth - 1] = a | (b << 8);
|
|
columns[depth - 1] = c | (d << 8);
|
|
break;
|
|
}
|
|
case OP_ADVANCE_PC: {
|
|
uint8_t a = *cursor++;
|
|
uint8_t b = *cursor++;
|
|
uint8_t c = *cursor++;
|
|
uint8_t d = *cursor++;
|
|
uint32_t disp = a | (b << 8) | (c << 16) | (d << 24);
|
|
lnp_pc += disp;
|
|
if (lookup_pc <= lnp_pc) {
|
|
for (intptr_t i = depth - 1; i >= 0; i--) {
|
|
add_frame(ctxt, names[i], files[i], lines[i], columns[i]);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case OP_STOP: {
|
|
FATAL("pc in function but outside LNP");
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
// TSAN doesn't fall back to reporting the PC if we provide no frame or a
|
|
// frame with a null location.
|
|
char pc_string[17];
|
|
for (intptr_t i = 0; i < 16; i++) {
|
|
pc_string[i] = "0123456789abcdef"[(lookup_pc >> ((15 - i) << 2)) & 0xF];
|
|
}
|
|
pc_string[16] = 0;
|
|
|
|
if (lookup_pc == 42) {
|
|
add_frame(ctxt, "dart-deoptimized-code", pc_string, 0, 0);
|
|
} else {
|
|
add_frame(ctxt, "dart-code-lookup-failed", pc_string, 0, 0);
|
|
}
|
|
}
|
|
#else
|
|
void RegisterTsanSymbolize(const Code& code) {}
|
|
#endif
|
|
|
|
} // namespace dart
|