blob: 78f6a6e0994eaff48fcdf62d075a86a74d8eb89b [file] [log] [blame] [edit]
// 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) {
if (!code.IsFunctionCode()) {
// TODO(rmacnak): Do something for stubs?
return;
}
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);
GrowableArray<uint8_t> out(256);
out.Add(OP_PUSH_FUNCTION);
WriteString(out, function.QualifiedUserVisibleNameCString());
WriteString(out, url.ToCString());
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();
}
}
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__((disable_sanitizer_instrumentation)) void
__tsan_symbolize_external_ex(uintptr_t pc, AddFrame add_frame, void* ctxt) {
constexpr uintptr_t kExternalPCBit = 1ULL << 60;
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) {
#if 0
char s[17];
s[16] = 0;
for (intptr_t k = 0; k < 16; k++) {
s[k] = "0123456789abcdef"[lookup_pc >> ((15 - k) << 2)) & 0xFF];
}
add_frame(ctxt, "lookup_pc", s, 0, 0);
for (intptr_t k = 0; k < 16; k++) {
s[k] = "0123456789abcdef"[lnp_pc >> ((15 - k) << 2)) & 0xFF];
}
add_frame(ctxt, "lnp_pc", s, 0, 0);
#endif
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();
}
}
add_frame(ctxt, "dart-code-lookup-failed", nullptr, 0, 0);
}
#else
void RegisterTsanSymbolize(const Code& code) {}
#endif
} // namespace dart