Skip to content

[Runtime][Reflection][swift-inspect] Add facilities for tracking and examining backtraces for metadata allocations. #32244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions include/swift/Reflection/ReflectionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
#include "swift/Runtime/Unreachable.h"

#include <set>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <vector>

namespace {

Expand Down Expand Up @@ -794,7 +795,7 @@ class ReflectionContext
llvm::Optional<std::string> iterateConformances(
std::function<void(StoredPointer Type, StoredPointer Proto)> Call) {
std::string ConformancesPointerName =
"__swift_debug_protocolConformanceStatePointer";
"_swift_debug_protocolConformanceStatePointer";
auto ConformancesAddrAddr =
getReader().getSymbolAddress(ConformancesPointerName);
if (!ConformancesAddrAddr)
Expand Down Expand Up @@ -844,9 +845,9 @@ class ReflectionContext
llvm::Optional<std::string> iterateMetadataAllocations(
std::function<void (MetadataAllocation<Runtime>)> Call) {
std::string IterationEnabledName =
"__swift_debug_metadataAllocationIterationEnabled";
"_swift_debug_metadataAllocationIterationEnabled";
std::string AllocationPoolPointerName =
"__swift_debug_allocationPoolPointer";
"_swift_debug_allocationPoolPointer";

auto IterationEnabledAddr =
getReader().getSymbolAddress(IterationEnabledName);
Expand Down Expand Up @@ -922,6 +923,50 @@ class ReflectionContext
return llvm::None;
}

llvm::Optional<std::string> iterateMetadataAllocationBacktraces(
std::function<void(StoredPointer, uint32_t, const StoredPointer *)>
Call) {
std::string BacktraceListName =
"_swift_debug_metadataAllocationBacktraceList";

auto BacktraceListAddr = getReader().getSymbolAddress(BacktraceListName);
if (!BacktraceListAddr)
return "unable to look up debug variable " + BacktraceListName;
auto BacktraceListNextPtr =
getReader().readPointer(BacktraceListAddr, sizeof(StoredPointer));
if (!BacktraceListNextPtr)
return llvm::None;

auto BacktraceListNext = BacktraceListNextPtr->getResolvedAddress();
while (BacktraceListNext) {
auto HeaderBytes = getReader().readBytes(
RemoteAddress(BacktraceListNext),
sizeof(MetadataAllocationBacktraceHeader<Runtime>));
auto HeaderPtr =
reinterpret_cast<const MetadataAllocationBacktraceHeader<Runtime> *>(
HeaderBytes.get());
if (HeaderPtr == nullptr) {
std::stringstream stream;
stream << "unable to read Next pointer 0x" << std::hex
<< BacktraceListNext.getAddressData();
return stream.str();
}
auto BacktraceAddrPtr =
BacktraceListNext +
sizeof(MetadataAllocationBacktraceHeader<Runtime>);
auto BacktraceBytes =
getReader().readBytes(RemoteAddress(BacktraceAddrPtr),
HeaderPtr->Count * sizeof(StoredPointer));
auto BacktracePtr =
reinterpret_cast<const StoredPointer *>(BacktraceBytes.get());

Call(HeaderPtr->Allocation, HeaderPtr->Count, BacktracePtr);

BacktraceListNext = RemoteAddress(HeaderPtr->Next);
}
return llvm::None;
}

private:
const TypeInfo *getClosureContextInfo(StoredPointer Context,
const ClosureContextInfo &Info) {
Expand Down
12 changes: 10 additions & 2 deletions include/swift/Runtime/Debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
#ifndef SWIFT_RUNTIME_DEBUG_HELPERS_H
#define SWIFT_RUNTIME_DEBUG_HELPERS_H

#include "swift/Runtime/Config.h"
#include "swift/Runtime/Unreachable.h"
#include <atomic>
#include <cstdarg>
#include <cstdio>
#include <functional>
#include <stdint.h>
#include "swift/Runtime/Config.h"
#include "swift/Runtime/Unreachable.h"

#ifdef SWIFT_HAVE_CRASHREPORTERCLIENT

Expand Down Expand Up @@ -145,6 +147,9 @@ void swift_abortDynamicReplacementDisabling();
void dumpStackTraceEntry(unsigned index, void *framePC,
bool shortOutput = false);

SWIFT_RUNTIME_ATTRIBUTE_NOINLINE
bool withCurrentBacktrace(std::function<void(void **, int)> call);

SWIFT_RUNTIME_ATTRIBUTE_NOINLINE
void printCurrentBacktrace(unsigned framesToSkip = 1);

Expand Down Expand Up @@ -237,6 +242,9 @@ bool _swift_debug_metadataAllocationIterationEnabled;
SWIFT_RUNTIME_STDLIB_SPI
const void * const _swift_debug_allocationPoolPointer;

SWIFT_RUNTIME_STDLIB_SPI
std::atomic<const void *> _swift_debug_metadataAllocationBacktraceList;

SWIFT_RUNTIME_STDLIB_SPI
const void * const _swift_debug_protocolConformanceStatePointer;

Expand Down
7 changes: 7 additions & 0 deletions include/swift/Runtime/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ enum MetadataAllocatorTags : uint16_t {
GenericValueMetadataTag,
};

template <typename Runtime> struct MetadataAllocationBacktraceHeader {
TargetPointer<Runtime, const void> Next;
TargetPointer<Runtime, void> Allocation;
uint32_t Count;
// Count backtrace pointers immediately follow.
};

/// The buffer used by a yield-once coroutine (such as the generalized
/// accessors `read` and `modify`).
struct YieldOnceBuffer {
Expand Down
22 changes: 22 additions & 0 deletions include/swift/SwiftRemoteMirror/SwiftRemoteMirror.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,28 @@ swift_reflection_ptr_t swift_reflection_allocationMetadataPointer(
SwiftReflectionContextRef ContextRef,
swift_metadata_allocation_t Allocation);

/// Backtrace iterator callback passed to
/// swift_reflection_iterateMetadataAllocationBacktraces
typedef void (*swift_metadataAllocationIterator)(
swift_reflection_ptr_t AllocationPtr, size_t Count,
const swift_reflection_ptr_t Ptrs[], void *ContextPtr);

/// Iterate over all recorded metadata allocation backtraces in the process.
///
/// Calls the passed in Call function for each recorded backtrace. The function
/// is passed the number of backtrace entries and an array of those entries, as
/// pointers. The array is stored from deepest to shallowest, so main() will be
/// somewhere near the end. This array is valid only for the duration of the
/// call.
///
/// Returns NULL on success. On error, returns a pointer to a C string
/// describing the error. This pointer remains valid until the next
/// swift_reflection call on the given context.
SWIFT_REMOTE_MIRROR_LINKAGE
const char *swift_reflection_iterateMetadataAllocationBacktraces(
SwiftReflectionContextRef ContextRef, swift_metadataAllocationIterator Call,
void *ContextPtr);

#ifdef __cplusplus
} // extern "C"
#endif
Expand Down
18 changes: 18 additions & 0 deletions stdlib/public/SwiftRemoteMirror/SwiftRemoteMirror.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -635,3 +635,21 @@ swift_reflection_ptr_t swift_reflection_allocationMetadataPointer(
NativeAllocation.Size = Allocation.Size;
return Context->allocationMetadataPointer(NativeAllocation);
}

const char *swift_reflection_iterateMetadataAllocationBacktraces(
SwiftReflectionContextRef ContextRef, swift_metadataAllocationIterator Call,
void *ContextPtr) {
auto Context = ContextRef->nativeContext;
auto Error = Context->iterateMetadataAllocationBacktraces(
[&](auto AllocationPtr, auto Count, auto Ptrs) {
// Ptrs is an array of StoredPointer, but the callback expects an array
// of swift_reflection_ptr_t. Those may are not always the same type.
// (For example, swift_reflection_ptr_t can be 64-bit on 32-bit systems,
// while StoredPointer is always the pointer size of the target system.)
// Convert the array to an array of swift_reflection_ptr_t.
std::vector<swift_reflection_ptr_t> ConvertedPtrs{&Ptrs[0],
&Ptrs[Count]};
Call(AllocationPtr, Count, ConvertedPtrs.data(), ContextPtr);
});
return convertError(ContextRef, Error);
}
4 changes: 4 additions & 0 deletions stdlib/public/runtime/EnvironmentVariables.def
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ VARIABLE(SWIFT_DEBUG_ENABLE_METADATA_ALLOCATION_ITERATION, bool, false,
"Enable additional metadata allocation tracking for swift-inspect to "
"use.")

VARIABLE(SWIFT_DEBUG_ENABLE_METADATA_BACKTRACE_LOGGING, bool, false,
"Enable logging of backtraces for each metadata allocation. Requires "
"SWIFT_DEBUG_ENABLE_METADATA_ALLOCATION_ITERATION to be enabled.")

VARIABLE(SWIFT_DEBUG_IMPLICIT_OBJC_ENTRYPOINT, uint8_t, 2,
"Print warnings when using implicit @objc entrypoints. Set to "
"desired reporting level, 0-3.")
Expand Down
27 changes: 21 additions & 6 deletions stdlib/public/runtime/Errors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ static _Unwind_Reason_Code SwiftUnwindFrame(struct _Unwind_Context *context, voi
}
#endif

SWIFT_NOINLINE
void swift::printCurrentBacktrace(unsigned framesToSkip) {
SWIFT_ALWAYS_INLINE
static bool withCurrentBacktraceImpl(std::function<void(void **, int)> call) {
#if SWIFT_SUPPORTS_BACKTRACE_REPORTING
constexpr unsigned maxSupportedStackDepth = 128;
void *addrs[maxSupportedStackDepth];
Expand All @@ -237,14 +237,29 @@ void swift::printCurrentBacktrace(unsigned framesToSkip) {
#else
int symbolCount = backtrace(addrs, maxSupportedStackDepth);
#endif
for (int i = framesToSkip; i < symbolCount; ++i) {
dumpStackTraceEntry(i - framesToSkip, addrs[i]);
}
call(addrs, symbolCount);
return true;
#else
fprintf(stderr, "<backtrace unavailable>\n");
return false;
#endif
}

SWIFT_NOINLINE
bool swift::withCurrentBacktrace(std::function<void(void **, int)> call) {
return withCurrentBacktraceImpl(call);
}

SWIFT_NOINLINE
void swift::printCurrentBacktrace(unsigned framesToSkip) {
bool success = withCurrentBacktraceImpl([&](void **addrs, int symbolCount) {
for (int i = framesToSkip; i < symbolCount; ++i) {
dumpStackTraceEntry(i - framesToSkip, addrs[i]);
}
});
if (!success)
fprintf(stderr, "<backtrace unavailable>\n");
}

#ifdef SWIFT_HAVE_CRASHREPORTERCLIENT
#include <malloc/malloc.h>

Expand Down
37 changes: 34 additions & 3 deletions stdlib/public/runtime/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5504,12 +5504,19 @@ AllocationPool{PoolRange{InitialAllocationPool.Pool,

bool swift::_swift_debug_metadataAllocationIterationEnabled = false;
const void * const swift::_swift_debug_allocationPoolPointer = &AllocationPool;
std::atomic<const void *> swift::_swift_debug_metadataAllocationBacktraceList;

static void checkAllocatorDebugEnvironmentVariable(void *context) {
_swift_debug_metadataAllocationIterationEnabled
= runtime::environment::SWIFT_DEBUG_ENABLE_METADATA_ALLOCATION_ITERATION();
if (!_swift_debug_metadataAllocationIterationEnabled)
if (!_swift_debug_metadataAllocationIterationEnabled) {
if (runtime::environment::SWIFT_DEBUG_ENABLE_METADATA_BACKTRACE_LOGGING())
swift::warning(RuntimeErrorFlagNone,
"Warning: SWIFT_DEBUG_ENABLE_METADATA_BACKTRACE_LOGGING "
"without SWIFT_DEBUG_ENABLE_METADATA_ALLOCATION_ITERATION "
"has no effect.\n");
return;
}

// Write a PoolTrailer to the end of InitialAllocationPool and shrink
// the pool accordingly.
Expand All @@ -5523,6 +5530,24 @@ static void checkAllocatorDebugEnvironmentVariable(void *context) {
AllocationPool.store(poolCopy, std::memory_order_relaxed);
}

static void recordBacktrace(void *allocation) {
withCurrentBacktrace([&](void **addrs, int count) {
MetadataAllocationBacktraceHeader<InProcess> *record =
(MetadataAllocationBacktraceHeader<InProcess> *)malloc(
sizeof(*record) + count * sizeof(void *));
record->Allocation = allocation;
record->Count = count;
memcpy(record + 1, addrs, count * sizeof(void *));

record->Next = _swift_debug_metadataAllocationBacktraceList.load(
std::memory_order_relaxed);
while (!_swift_debug_metadataAllocationBacktraceList.compare_exchange_weak(
record->Next, record, std::memory_order_release,
std::memory_order_relaxed))
; // empty
});
}

void *MetadataAllocator::Allocate(size_t size, size_t alignment) {
assert(Tag != 0);
assert(alignment <= alignof(void*));
Expand Down Expand Up @@ -5582,8 +5607,14 @@ void *MetadataAllocator::Allocate(size_t size, size_t alignment) {
AllocationHeader *header = (AllocationHeader *)allocation;
header->Size = size;
header->Tag = Tag;

return allocation + sizeof(AllocationHeader);

auto *returnedAllocation = allocation + sizeof(AllocationHeader);

if (runtime::environment ::
SWIFT_DEBUG_ENABLE_METADATA_BACKTRACE_LOGGING())
recordBacktrace(returnedAllocation);

return returnedAllocation;
} else {
return allocation;
}
Expand Down
56 changes: 56 additions & 0 deletions tools/swift-inspect/Sources/swift-inspect/Backtrace.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftRemoteMirror

struct Backtrace {
enum Style {
case oneLine
case long
}

/// The pointers to the locations in the backtrace. These are stored from
/// deepest to shallowest, so main() will be somewhere near the end.
var ptrs: [swift_reflection_ptr_t]

func symbolString(
ptr: swift_reflection_ptr_t,
inspector: Inspector
) -> String {
let symbol = inspector.getSymbol(address: ptr)
let name = symbol.name ?? "<unknown>"
let library = symbol.library ?? "<unknown>"
return "\(hex: ptr) (\(library)) \(name)"
}

func symbolicatedOneLine(inspector: Inspector) -> String {
return ptrs.reversed().map {
symbolString(ptr: $0, inspector: inspector)
}.joined(separator: " | ")
}

func symbolicatedLong(inspector: Inspector) -> String {
return ptrs.reversed().enumerated().map {
let indent = String(repeating: " ", count: $0 + 1)
return indent + symbolString(ptr: $1, inspector: inspector)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to pass indentation to the symbolString and have the two just be different separators.

}.joined(separator: "\n")
}

func symbolicated(style: Style, inspector: Inspector) -> String {
switch style {
case .oneLine:
return symbolicatedOneLine(inspector: inspector)
case .long:
return symbolicatedLong(inspector: inspector)
}
}
}
Loading