From 18496c562609fb2f5b8ea8309b5d7ea41eb9ae9c Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 4 Oct 2024 12:25:04 +0100 Subject: [PATCH 01/17] [Backtracing] Remove support for implicit import of _Backtracing. We're going to rename the module to Runtime, and it isn't going to be an implicit import, so we don't need any of this. rdar://124913332 --- CMakeLists.txt | 9 ---- cmake/modules/AddPureSwift.cmake | 8 +--- include/swift/AST/DiagnosticsFrontend.def | 2 - include/swift/Basic/LangOptions.h | 4 -- include/swift/Config.h.in | 2 - include/swift/Frontend/Frontend.h | 12 ------ include/swift/Option/FrontendOptions.td | 8 ---- include/swift/Strings.h | 2 - lib/Frontend/CompilerInvocation.cpp | 5 --- lib/Frontend/Frontend.cpp | 42 ------------------- stdlib/CMakeLists.txt | 6 --- stdlib/cmake/modules/AddSwiftStdlib.cmake | 9 ---- test/Backtracing/SimpleBacktrace.swift | 4 +- .../tools/sourcekitd-test/Options.td | 8 ---- .../tools/sourcekitd-test/TestOptions.cpp | 4 -- .../tools/sourcekitd-test/TestOptions.h | 2 - .../tools/sourcekitd-test/sourcekitd-test.cpp | 14 ------- tools/swift-ide-test/swift-ide-test.cpp | 17 -------- 18 files changed, 4 insertions(+), 154 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ded05a6626e..ae119a36bed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -695,10 +695,6 @@ option(SWIFT_IMPLICIT_CONCURRENCY_IMPORT "Implicitly import the Swift concurrency module" TRUE) -option(SWIFT_IMPLICIT_BACKTRACING_IMPORT - "Implicitly import the Swift backtracing module" - FALSE) - option(SWIFT_ENABLE_EXPERIMENTAL_CONCURRENCY "Enable build of the Swift concurrency module" FALSE) @@ -865,11 +861,6 @@ if (CMAKE_Swift_COMPILER) SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT) message(STATUS " Implicit 'string-processing' import: ${SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT}") - # Same for _Backtracing. - swift_supports_implicit_module("backtracing" - SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT) - message(STATUS " Implicit 'backtracing' import: ${SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT}") - swift_get_package_cmo_support( Swift_COMPILER_PACKAGE_CMO_SUPPORT) message(STATUS " Package CMO: ${Swift_COMPILER_PACKAGE_CMO_SUPPORT}") diff --git a/cmake/modules/AddPureSwift.cmake b/cmake/modules/AddPureSwift.cmake index 28e496a1ba4..647247a0570 100644 --- a/cmake/modules/AddPureSwift.cmake +++ b/cmake/modules/AddPureSwift.cmake @@ -28,13 +28,7 @@ function(_add_host_swift_compile_options name) "$<$:SHELL:-Xfrontend -disable-implicit-string-processing-module-import>") endif() - # Same for backtracing - if (SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT) - target_compile_options(${name} PRIVATE - "$<$:SHELL:-Xfrontend -disable-implicit-backtracing-module-import>") - endif() - - if(SWIFT_ANALYZE_CODE_COVERAGE) + if(SWIFT_ANALYZE_CODE_COVERAGE) set(_cov_flags $<$:-profile-generate -profile-coverage-mapping>) target_compile_options(${name} PRIVATE ${_cov_flags}) target_link_options(${name} PRIVATE ${_cov_flags}) diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index b6fd6274e38..6648a34c62a 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -188,8 +188,6 @@ WARNING(warn_implicit_concurrency_import_failed,none, "unable to perform implicit import of \"_Concurrency\" module: no such module found", ()) REMARK(warn_implicit_string_processing_import_failed,none, "unable to perform implicit import of \"_StringProcessing\" module: no such module found", ()) -REMARK(warn_implicit_backtracing_import_failed,none, - "unable to perform implicit import of \"_Backtracing\" module: no such module found", ()) ERROR(error_bad_module_name,none, "module name \"%0\" is not a valid identifier" diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index f7ec4f15538..1ca5e89f485 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -421,10 +421,6 @@ namespace swift { /// Disable the implicit import of the _StringProcessing module. bool DisableImplicitStringProcessingModuleImport = false; - /// Disable the implicit import of the _Backtracing module. - bool DisableImplicitBacktracingModuleImport = - !SWIFT_IMPLICIT_BACKTRACING_IMPORT; - /// Disable the implicit import of the Cxx module. bool DisableImplicitCxxModuleImport = false; diff --git a/include/swift/Config.h.in b/include/swift/Config.h.in index b0e8282aac0..1884d0f95bc 100644 --- a/include/swift/Config.h.in +++ b/include/swift/Config.h.in @@ -10,8 +10,6 @@ #cmakedefine01 SWIFT_IMPLICIT_CONCURRENCY_IMPORT -#cmakedefine01 SWIFT_IMPLICIT_BACKTRACING_IMPORT - #cmakedefine01 SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED #cmakedefine01 SWIFT_ENABLE_GLOBAL_ISEL_ARM64 diff --git a/include/swift/Frontend/Frontend.h b/include/swift/Frontend/Frontend.h index 83386ce3275..6918fb89cbc 100644 --- a/include/swift/Frontend/Frontend.h +++ b/include/swift/Frontend/Frontend.h @@ -425,10 +425,6 @@ public: /// imported. bool shouldImportSwiftStringProcessing() const; - /// Whether the Swift Backtracing support library should be implicitly - /// imported. - bool shouldImportSwiftBacktracing() const; - /// Whether the CXX module should be implicitly imported. bool shouldImportCxx() const; @@ -679,14 +675,6 @@ public: /// i.e. if it can be found. bool canImportSwiftStringProcessing() const; - /// Verify that if an implicit import of the `Backtracing` module if - /// expected, it can actually be imported. Emit a warning, otherwise. - void verifyImplicitBacktracingImport(); - - /// Whether the Swift Backtracing support library can be imported - /// i.e. if it can be found. - bool canImportSwiftBacktracing() const; - /// Whether the Cxx library can be imported bool canImportCxx() const; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index d52792fd697..1a5d3f84835 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -517,14 +517,6 @@ def disable_implicit_string_processing_module_import : Flag<["-"], "disable-implicit-string-processing-module-import">, HelpText<"Disable the implicit import of the _StringProcessing module.">; -def enable_implicit_backtracing_module_import : Flag<["-"], - "enable-implicit-backtracing-module-import">, - HelpText<"Enable the implicit import of the _Backtracing module.">; - -def disable_implicit_backtracing_module_import : Flag<["-"], - "disable-implicit-backtracing-module-import">, - HelpText<"Disable the implicit import of the _Backtracing module.">; - def disable_implicit_cxx_module_import : Flag<["-"], "disable-implicit-cxx-module-import">, HelpText<"Disable the implicit import of the C++ Standard Library module.">; diff --git a/include/swift/Strings.h b/include/swift/Strings.h index be4791f9a31..f1c2dba3461 100644 --- a/include/swift/Strings.h +++ b/include/swift/Strings.h @@ -33,8 +33,6 @@ constexpr static const StringLiteral SWIFT_MODULE_ABI_NAME_PREFIX = "Compiler"; constexpr static const StringLiteral SWIFT_DISTRIBUTED_NAME = "Distributed"; /// The name of the StringProcessing module, which supports that extension. constexpr static const StringLiteral SWIFT_STRING_PROCESSING_NAME = "_StringProcessing"; -/// The name of the Backtracing module, which supports that extension. -constexpr static const StringLiteral SWIFT_BACKTRACING_NAME = "_Backtracing"; /// The name of the SwiftShims module, which contains private stdlib decls. constexpr static const StringLiteral SWIFT_SHIMS_NAME = "SwiftShims"; /// The name of the CxxShim module, which contains a cxx casting utility. diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 8982ab95ce0..525bb6b4b12 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -939,11 +939,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.DisableImplicitCxxModuleImport |= Args.hasArg(OPT_disable_implicit_cxx_module_import); - Opts.DisableImplicitBacktracingModuleImport = - Args.hasFlag(OPT_disable_implicit_backtracing_module_import, - OPT_enable_implicit_backtracing_module_import, - true); - if (Args.hasArg(OPT_enable_experimental_async_top_level)) Diags.diagnose(SourceLoc(), diag::warn_flag_deprecated, "-enable-experimental-async-top-level"); diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index efb0685d850..530bbc74f8b 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -1109,20 +1109,6 @@ bool CompilerInvocation::shouldImportSwiftStringProcessing() const { FrontendOptions::ParseInputMode::SwiftModuleInterface; } -/// Enable Swift backtracing on a per-target basis -static bool shouldImportSwiftBacktracingByDefault(const llvm::Triple &target) { - if (target.isOSDarwin() || target.isOSWindows() || target.isOSLinux()) - return true; - return false; -} - -bool CompilerInvocation::shouldImportSwiftBacktracing() const { - return shouldImportSwiftBacktracingByDefault(getLangOptions().Target) && - !getLangOptions().DisableImplicitBacktracingModuleImport && - getFrontendOptions().InputMode != - FrontendOptions::ParseInputMode::SwiftModuleInterface; -} - bool CompilerInvocation::shouldImportCxx() const { // C++ Interop is disabled if (!getLangOptions().EnableCXXInterop) @@ -1209,21 +1195,6 @@ bool CompilerInstance::canImportSwiftStringProcessing() const { return getASTContext().testImportModule(modulePath); } -void CompilerInstance::verifyImplicitBacktracingImport() { - if (Invocation.shouldImportSwiftBacktracing() && - !canImportSwiftBacktracing()) { - Diagnostics.diagnose(SourceLoc(), - diag::warn_implicit_backtracing_import_failed); - } -} - -bool CompilerInstance::canImportSwiftBacktracing() const { - ImportPath::Module::Builder builder( - getASTContext().getIdentifier(SWIFT_BACKTRACING_NAME)); - auto modulePath = builder.get(); - return getASTContext().testImportModule(modulePath); -} - bool CompilerInstance::canImportCxx() const { ImportPath::Module::Builder builder( getASTContext().getIdentifier(CXX_MODULE_NAME)); @@ -1321,19 +1292,6 @@ ImplicitImportInfo CompilerInstance::getImplicitImportInfo() const { } } - if (Invocation.shouldImportSwiftBacktracing()) { - switch (imports.StdlibKind) { - case ImplicitStdlibKind::Builtin: - case ImplicitStdlibKind::None: - break; - - case ImplicitStdlibKind::Stdlib: - if (canImportSwiftBacktracing()) - pushImport(SWIFT_BACKTRACING_NAME); - break; - } - } - if (Invocation.getLangOptions().EnableCXXInterop) { if (Invocation.shouldImportCxx() && canImportCxx()) pushImport(CXX_MODULE_NAME); diff --git a/stdlib/CMakeLists.txt b/stdlib/CMakeLists.txt index 1d8e818e793..9768a59a21f 100644 --- a/stdlib/CMakeLists.txt +++ b/stdlib/CMakeLists.txt @@ -228,12 +228,6 @@ swift_create_stdlib_targets("swift-test-stdlib" "" FALSE) swift_create_stdlib_targets("swift-libexec" "" TRUE) swift_create_stdlib_targets("swift-test-libexec" "" FALSE) -# Check whether the Swift compiler we're using supports -# -disable-implicit-backtracing-module-import -include(SwiftCompilerCapability) - -swift_supports_implicit_module("backtracing" SWIFT_COMPILER_SUPPORTS_BACKTRACING) - # FIXME: Include the toolchain directory before the public directory. Otherwise # the clang resource directory symlink stops installing correctly. add_subdirectory(toolchain) diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index 130a1470393..c76cf96aa91 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -2091,11 +2091,6 @@ function(add_swift_target_library name) # Turn off implicit import of _StringProcessing when building libraries list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-disable-implicit-string-processing-module-import") - # Turn off implicit import of _Backtracing when building libraries - if(SWIFT_COMPILER_SUPPORTS_BACKTRACING) - list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-disable-implicit-backtracing-module-import") - endif() - if(SWIFTLIB_IS_STDLIB AND SWIFT_STDLIB_ENABLE_PRESPECIALIZATION) list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-prespecialize-generic-metadata") endif() @@ -3126,10 +3121,6 @@ function(add_swift_target_executable name) "-Xfrontend;-disable-implicit-string-processing-module-import") endif() - if(SWIFT_IMPLICIT_BACKTRACING_IMPORT) - list(APPEND SWIFTEXE_TARGET_COMPILE_FLAGS "-Xfrontend;-disable-implicit-backtracing-module-import") - endif() - if(SWIFT_BUILD_STDLIB) # All Swift executables depend on the standard library. list(APPEND SWIFTEXE_TARGET_SWIFT_MODULE_DEPENDS Core) diff --git a/test/Backtracing/SimpleBacktrace.swift b/test/Backtracing/SimpleBacktrace.swift index 1695ff896b7..da37be4f6dd 100644 --- a/test/Backtracing/SimpleBacktrace.swift +++ b/test/Backtracing/SimpleBacktrace.swift @@ -1,5 +1,5 @@ // RUN: %empty-directory(%t) -// RUN: %target-build-swift %s -Xfrontend -enable-implicit-backtracing-module-import -parse-as-library -Onone -o %t/SimpleBacktrace +// RUN: %target-build-swift %s -Xfrontend -parse-as-library -Onone -o %t/SimpleBacktrace // RUN: %target-codesign %t/SimpleBacktrace // RUN: %target-run %t/SimpleBacktrace | %FileCheck %s @@ -10,6 +10,8 @@ // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime +import _Backtracing + func level1() { level2() } diff --git a/tools/SourceKit/tools/sourcekitd-test/Options.td b/tools/SourceKit/tools/sourcekitd-test/Options.td index ef2f8c6926e..774bfa04c6b 100644 --- a/tools/SourceKit/tools/sourcekitd-test/Options.td +++ b/tools/SourceKit/tools/sourcekitd-test/Options.td @@ -116,14 +116,6 @@ def disable_implicit_string_processing_module_import : Flag<["-"], "disable-implicit-string-processing-module-import">, HelpText<"Disable implicit import of the _StringProcessing module">; -def enable_implicit_backtracing_module_import : Flag<["-"], - "enable-implicit-backtracing-module-import">, - HelpText<"Enable implicit import of the _Backtracing module">; - -def disable_implicit_backtracing_module_import : Flag<["-"], - "disable-implicit-backtracing-module-import">, - HelpText<"Disable implicit import of the _Backtracing module">; - def end_pos : Separate<["-"], "end-pos">, HelpText<"line:col">; def end_pos_EQ : Joined<["-"], "end-pos=">, Alias; diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp index 2fc43eade84..14345dfbb40 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp @@ -464,10 +464,6 @@ bool TestOptions::parseArgs(llvm::ArrayRef Args) { DisableImplicitStringProcessingModuleImport = true; break; - case OPT_disable_implicit_backtracing_module_import: - DisableImplicitBacktracingModuleImport = true; - break; - case OPT_UNKNOWN: llvm::errs() << "error: unknown argument: " << InputArg->getAsString(ParsedArgs) << '\n' diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h index 9f85fab2b76..a962e312825 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h @@ -135,8 +135,6 @@ struct TestOptions { bool measureInstructions = false; bool DisableImplicitConcurrencyModuleImport = false; bool DisableImplicitStringProcessingModuleImport = false; - bool EnableImplicitBacktracingModuleImport = false; - bool DisableImplicitBacktracingModuleImport = false; std::optional CompletionCheckDependencyInterval; unsigned repeatRequest = 1; struct VFSFile { diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp index b2468cc2323..c79a8f297c0 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -1222,20 +1222,6 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) { sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, "-disable-implicit-string-processing-module-import"); } - if (Opts.EnableImplicitBacktracingModuleImport && - !compilerArgsAreClang) { - sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, - "-Xfrontend"); - sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, - "-enable-implicit-backtracing-module-import"); - } - if (Opts.DisableImplicitBacktracingModuleImport && - !compilerArgsAreClang) { - sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, - "-Xfrontend"); - sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, - "-disable-implicit-backtracing-module-import"); - } for (auto Arg : Opts.CompilerArgs) sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, Arg); diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 2ff77fb1193..c8ce3eb92ce 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -833,16 +833,6 @@ DisableImplicitStringProcessingImport("disable-implicit-string-processing-module llvm::cl::desc("Disable implicit import of _StringProcessing module"), llvm::cl::init(false)); -static llvm::cl::opt -EnableImplicitBacktracingImport("enable-implicit-backtracing-module-import", - llvm::cl::desc("Enable implicit import of _Backtracing module"), - llvm::cl::init(false)); - -static llvm::cl::opt -DisableImplicitBacktracingImport("disable-implicit-backtracing-module-import", - llvm::cl::desc("Disable implicit import of _Backtracing module"), - llvm::cl::init(false)); - static llvm::cl::opt EnableExperimentalNamedOpaqueTypes( "enable-experimental-named-opaque-types", llvm::cl::desc("Enable experimental support for named opaque result types"), @@ -4470,13 +4460,6 @@ int main(int argc, char *argv[]) { if (options::DisableImplicitStringProcessingImport) { InitInvok.getLangOptions().DisableImplicitStringProcessingModuleImport = true; } - if (options::DisableImplicitBacktracingImport) { - InitInvok.getLangOptions().DisableImplicitBacktracingModuleImport = true; - } else if (options::EnableImplicitBacktracingImport) { - InitInvok.getLangOptions().DisableImplicitBacktracingModuleImport = false; - } else { - InitInvok.getLangOptions().DisableImplicitBacktracingModuleImport = true; - } if (options::EnableExperimentalNamedOpaqueTypes) { InitInvok.getLangOptions().enableFeature(Feature::NamedOpaqueTypes); From 760cc57befd6cab6d29a5c6e98eb299b8803ac25 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 24 Oct 2024 11:49:38 +0100 Subject: [PATCH 02/17] [Backtracing] Rename _Backtracing to Runtime. Move the backtracing code into a new Runtime module. This means renaming the Swift Runtime's CMake target because otherwise there will be a name clash. rdar://124913332 --- CMakeLists.txt | 6 + docs/Backtracing.rst | 8 + docs/CompactBacktraceFormat.md | 141 ++++ lib/AST/NameLookup.cpp | 16 - lib/DriverTool/autolink_extract_main.cpp | 2 +- .../public/Backtracing/ArrayImageSource.swift | 48 -- .../public/Backtracing/FileImageSource.swift | 81 -- stdlib/public/Backtracing/ImageSource.swift | 158 ---- .../Backtracing/MemoryImageSource.swift | 35 - stdlib/public/CMakeLists.txt | 4 +- .../CompatibilityOverride.h | 4 +- stdlib/public/RuntimeModule/Address.swift | 263 +++++++ .../Backtrace.swift | 603 +++++++-------- .../BacktraceFormatter.swift | 83 +- .../ByteSwapping.swift | 0 .../CMakeLists.txt | 39 +- .../CachingMemoryReader.swift | 45 +- .../RuntimeModule/CompactBacktrace.swift | 439 +++++++++++ .../Compression.swift | 314 +++----- .../Context.swift | 0 .../CoreSymbolication.swift | 0 .../Dwarf.swift | 578 +++++++------- .../RuntimeModule/EightByteBuffer.swift | 60 ++ .../{Backtracing => RuntimeModule}/Elf.swift | 717 ++++++++++-------- .../public/RuntimeModule/ElfImageCache.swift | 95 +++ .../FramePointerUnwinder.swift | 112 ++- .../Image.swift | 8 +- stdlib/public/RuntimeModule/ImageSource.swift | 436 +++++++++++ .../{Backtracing => RuntimeModule}/Libc.swift | 0 .../public/RuntimeModule/LimitSequence.swift | 231 ++++++ .../MemoryReader.swift | 34 +- .../ProcMapsScanner.swift | 0 .../Registers.swift | 0 stdlib/public/RuntimeModule/RichFrame.swift | 146 ++++ .../Runtime.swift | 0 .../SymbolicatedBacktrace.swift | 231 +++--- .../Utils.swift | 29 + .../Win32Extras.cpp | 0 .../get-cpu-context.S | 0 .../get-cpu-context.asm | 0 .../modules/Compression.h | 0 .../modules/FixedLayout.h | 0 .../modules/ImageFormats/Dwarf/dwarf.h | 0 .../modules/ImageFormats/Dwarf/eh_frame_hdr.h | 0 .../modules/ImageFormats/Elf/elf.h | 0 .../ImageFormats/ImageFormats.modulemap | 0 .../modules/OS/Darwin.h | 0 .../modules/OS/Libc.h | 0 .../modules/OS/Linux.h | 0 .../modules/OS/OS.modulemap | 0 .../modules/OS/Windows.h | 0 .../modules/Runtime/Runtime.h | 20 +- .../modules/module.modulemap | 0 stdlib/public/core/CMakeLists.txt | 2 +- .../libexec/swift-backtrace/CMakeLists.txt | 12 +- .../libexec/swift-backtrace/TargetLinux.swift | 66 +- .../libexec/swift-backtrace/TargetMacOS.swift | 62 +- .../libexec/swift-backtrace/Themes.swift | 2 +- .../libexec/swift-backtrace/Utils.swift | 12 + .../public/libexec/swift-backtrace/main.swift | 64 +- stdlib/public/runtime/CMakeLists.txt | 2 +- test/Backtracing/BacktraceWithLimit.swift | 2 +- .../BacktraceWithLimitAndTop.swift | 2 +- test/Backtracing/DwarfReader.swift | 2 +- test/Backtracing/ElfReader.swift | 2 +- test/Backtracing/SimpleAsyncBacktrace.swift | 2 +- test/Backtracing/SimpleBacktrace.swift | 2 +- test/Backtracing/SymbolicatedBacktrace.swift | 6 +- .../SymbolicatedBacktraceInline.swift | 6 +- test/CMakeLists.txt | 10 + test/lit.cfg | 12 +- unittests/runtime/CMakeLists.txt | 2 +- unittests/runtime/LongTests/CMakeLists.txt | 2 +- .../build_swift/driver_arguments.py | 4 + utils/build_swift/tests/expected_options.py | 1 + .../swift_build_support/products/swift.py | 8 + .../tests/products/test_swift.py | 16 + validation-test/SIL/verify_all_overlays.py | 4 +- 78 files changed, 3391 insertions(+), 1900 deletions(-) create mode 100644 docs/CompactBacktraceFormat.md delete mode 100644 stdlib/public/Backtracing/ArrayImageSource.swift delete mode 100644 stdlib/public/Backtracing/FileImageSource.swift delete mode 100644 stdlib/public/Backtracing/ImageSource.swift delete mode 100644 stdlib/public/Backtracing/MemoryImageSource.swift create mode 100644 stdlib/public/RuntimeModule/Address.swift rename stdlib/public/{Backtracing => RuntimeModule}/Backtrace.swift (51%) rename stdlib/public/{Backtracing => RuntimeModule}/BacktraceFormatter.swift (91%) rename stdlib/public/{Backtracing => RuntimeModule}/ByteSwapping.swift (100%) rename stdlib/public/{Backtracing => RuntimeModule}/CMakeLists.txt (73%) rename stdlib/public/{Backtracing => RuntimeModule}/CachingMemoryReader.swift (64%) create mode 100644 stdlib/public/RuntimeModule/CompactBacktrace.swift rename stdlib/public/{Backtracing => RuntimeModule}/Compression.swift (57%) rename stdlib/public/{Backtracing => RuntimeModule}/Context.swift (100%) rename stdlib/public/{Backtracing => RuntimeModule}/CoreSymbolication.swift (100%) rename stdlib/public/{Backtracing => RuntimeModule}/Dwarf.swift (86%) create mode 100644 stdlib/public/RuntimeModule/EightByteBuffer.swift rename stdlib/public/{Backtracing => RuntimeModule}/Elf.swift (71%) create mode 100644 stdlib/public/RuntimeModule/ElfImageCache.swift rename stdlib/public/{Backtracing => RuntimeModule}/FramePointerUnwinder.swift (51%) rename stdlib/public/{Backtracing => RuntimeModule}/Image.swift (96%) create mode 100644 stdlib/public/RuntimeModule/ImageSource.swift rename stdlib/public/{Backtracing => RuntimeModule}/Libc.swift (100%) create mode 100644 stdlib/public/RuntimeModule/LimitSequence.swift rename stdlib/public/{Backtracing => RuntimeModule}/MemoryReader.swift (87%) rename stdlib/public/{Backtracing => RuntimeModule}/ProcMapsScanner.swift (100%) rename stdlib/public/{Backtracing => RuntimeModule}/Registers.swift (100%) create mode 100644 stdlib/public/RuntimeModule/RichFrame.swift rename stdlib/public/{Backtracing => RuntimeModule}/Runtime.swift (100%) rename stdlib/public/{Backtracing => RuntimeModule}/SymbolicatedBacktrace.swift (74%) rename stdlib/public/{Backtracing => RuntimeModule}/Utils.swift (76%) rename stdlib/public/{Backtracing => RuntimeModule}/Win32Extras.cpp (100%) rename stdlib/public/{Backtracing => RuntimeModule}/get-cpu-context.S (100%) rename stdlib/public/{Backtracing => RuntimeModule}/get-cpu-context.asm (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/Compression.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/FixedLayout.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/ImageFormats/Dwarf/dwarf.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/ImageFormats/Dwarf/eh_frame_hdr.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/ImageFormats/Elf/elf.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/ImageFormats/ImageFormats.modulemap (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/OS/Darwin.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/OS/Libc.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/OS/Linux.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/OS/OS.modulemap (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/OS/Windows.h (100%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/Runtime/Runtime.h (70%) rename stdlib/public/{Backtracing => RuntimeModule}/modules/module.modulemap (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae119a36bed..10560bd9f29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -727,6 +727,10 @@ option(SWIFT_ENABLE_SYNCHRONIZATION "Enable build of the Swift Synchronization module" FALSE) +option(SWIFT_ENABLE_RUNTIME_MODULE + "Build the Swift Runtime module" + FALSE) + option(SWIFT_ENABLE_VOLATILE "Enable build of the Swift Volatile module" FALSE) @@ -1384,6 +1388,8 @@ if(SWIFT_BUILD_STDLIB OR SWIFT_BUILD_SDK_OVERLAY) message(STATUS "Observation Support: ${SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION}") message(STATUS "Synchronization Support: ${SWIFT_ENABLE_SYNCHRONIZATION}") message(STATUS "Volatile Support: ${SWIFT_ENABLE_VOLATILE}") + message(STATUS "Pointer Bounds Support: ${SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS}") + message(STATUS "Runtime Support: ${SWIFT_ENABLE_RUNTIME_MODULE}") message(STATUS "") else() message(STATUS "Not building Swift standard library, SDK overlays, and runtime") diff --git a/docs/Backtracing.rst b/docs/Backtracing.rst index 352e7b09997..b2ee7a628e6 100644 --- a/docs/Backtracing.rst +++ b/docs/Backtracing.rst @@ -319,3 +319,11 @@ of the backtracer using If the runtime is unable to locate the backtracer, it will allow your program to crash as it would have done anyway. + +Backtrace Storage +----------------- + +Backtraces are stored internally in a format called :download:`Compact Backtrace +Format `. This provides us with a way to store a +large number of frames in a much smaller space than would otherwise be possible. + diff --git a/docs/CompactBacktraceFormat.md b/docs/CompactBacktraceFormat.md new file mode 100644 index 00000000000..c1746e7c593 --- /dev/null +++ b/docs/CompactBacktraceFormat.md @@ -0,0 +1,141 @@ +Compact Backtrace Format +======================== + +We would like to be able to efficiently store and access backtraces, +but we also wish to minimise the memory used to store them. Since +backtraces typically contain a good deal of redundancy, it should be +possible to compress the data. + +Compact Backtrace Format (CBF) is a binary format for holding a +backtrace; this specification addresses only the storage of the actual +stack backtrace, and it does not consider storage of ancillary data +(register contents, image lists and so on). Those will be dealt with +separately elsewhere. + +## General Format + +Compact Backtrace Format data is byte aligned and starts with an +information byte: + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────┬───────┐ + │ version │ size │ + └───────────────────────┴───────┘ +~~~ + +The `version` field identifies the version of CBF that is in use; this +document describes version `0`. The `size` field is encoded as +follows: + +| `size` | Machine word size | +| :----: | :---------------- | +| 00 | 16-bit | +| 01 | 32-bit | +| 10 | 64-bit | +| 11 | Reserved | + +This is followed by a series of instructions that tell the reader how +to decode subsequent data. + +The first instruction that computes an address _must_ specify an +absolute address (the `a` bit must be set). + +## Instructions + +The following instructions are currently defined + +| `opcode` | Mnemonic | Meaning | +| :--------: | :------- | :---------------------------------------- | +| `00000000` | `end` | Marks the end of the backtrace | +| `00000001` | `trunc` | As above, but the backtrace was truncated | +| `0000xxxx` | reserved | Reserved for future expansion | +| `0001axxx` | `pc` | A program counter value follows | +| `0010axxx` | `ra` | A return address value follows | +| `0011axxx` | `async` | An async resume point follows | +| `01xxxxxx` | `omit` | Indicates frames have been omitted | +| `1xxxxxxx` | reserved | Reserved for future expansion | + +### `end`/`trunc` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────────┬───┐ + │ 0 0 0 0 0 0 0 │ t │ end (or trunc if t is 1) + └───────────────────────────┴───┘ +~~~ + +#### Meaning + +Marks the end of the backtrace data. If `t` is set, it indicates that +the backtrace was truncated at this point (for instance because we hit +a frame limit while capturing). + +It is not strictly necessary to use the `end` instruction if the +CBF data is of a known length. + +### `pc`, `ra`, `async` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌────────────────┬───┬──────────┐ + │ 0 0 0 1 │ a │ count │ pc + └────────────────┴───┴──────────┘ + ┌────────────────┬───┬──────────┐ + │ 0 0 1 0 │ a │ count │ ra + └────────────────┴───┴──────────┘ + ┌────────────────┬───┬──────────┐ + │ 0 0 1 1 │ a │ count │ async + └────────────────┴───┴──────────┘ +~~~ + +#### Meaning + +Each of these instructions represents a frame on the stack. For `pc` +frames, the computed address is an actual program counter (aka +instruction pointer) value. `ra` instructions instead represent a +_return address_, the difference being that the program has not yet +executed that instruction. `async` instructions point at the entry +point of an async resume function, and are used when walking stacks on +systems that support `async`/`await` primitives that are implemented +by function splitting (typically an `async` instruction will point at +the start of a function containing the code immediately following an +`await`). + +The next `count + 1` bytes following the instruction are an address +value. If `a` is set, the computed address is equal to the address +value. If `a` is not set, the computed address is equal to the +preceding computed address *plus* the address value. + +Address values are sign-extended to the machine word width before +processing. Thus a single address byte with value `0xff` on a 32-bit +backtrace represents the address value `0xffffffff`. + +### `omit` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────┬───┬───────────────────┐ + │ 0 1 │ x │ count │ omit + └───────┴───┴───────────────────┘ +~~~ + +#### Meaning + +Indicates that a number of frames were skipped when capturing the +backtrace. This is used to allow a backtrace to include both the top +and bottom of the stack, without carrying every intervening frame, and +is useful to prevent the data from exploding where recursion has taken +place. + +If `x` is `1`, the instruction is followed by `count + 1` bytes (up to the +machine word length) that are zero-extended to machine word length and +that represent a count of the number of frames that were omitted. + +If `x` is `0`, `count + 1` is the number of frames that were omitted. diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index 90511ae6ed7..ba741287a16 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -651,22 +651,6 @@ static void recordShadowedDeclsAfterTypeMatch( } } - // Next, prefer any other module over the _Backtracing module. - if (auto spModule = ctx.getLoadedModule(ctx.Id_Backtracing)) { - if ((firstModule == spModule) != (secondModule == spModule)) { - // If second module is _StringProcessing, then it is shadowed by - // first. - if (secondModule == spModule) { - shadowed.insert(secondDecl); - continue; - } - - // Otherwise, the first declaration is shadowed by the second. - shadowed.insert(firstDecl); - break; - } - } - // Next, prefer any other module over the Observation module. if (auto obsModule = ctx.getLoadedModule(ctx.Id_Observation)) { if ((firstModule == obsModule) != (secondModule == obsModule)) { diff --git a/lib/DriverTool/autolink_extract_main.cpp b/lib/DriverTool/autolink_extract_main.cpp index 79d63e6ea5d..7bcc1251d82 100644 --- a/lib/DriverTool/autolink_extract_main.cpp +++ b/lib/DriverTool/autolink_extract_main.cpp @@ -230,9 +230,9 @@ int autolink_extract_main(ArrayRef Args, const char *Argv0, "-lswift_StringProcessing", "-lswiftRegexBuilder", "-lswift_RegexParser", - "-lswift_Backtracing", "-lswift_Builtin_float", "-lswift_math", + "-lswiftRuntime", "-lswiftSynchronization", "-lswiftGlibc", "-lswiftAndroid", diff --git a/stdlib/public/Backtracing/ArrayImageSource.swift b/stdlib/public/Backtracing/ArrayImageSource.swift deleted file mode 100644 index e9266806cb2..00000000000 --- a/stdlib/public/Backtracing/ArrayImageSource.swift +++ /dev/null @@ -1,48 +0,0 @@ -//===--- ArrayImageSource.swift - An image source backed by an Array -------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2023 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 -// -//===----------------------------------------------------------------------===// -// -// Defines ArrayImageSource, an image source that is backed by a Swift Array. -// -//===----------------------------------------------------------------------===// - -import Swift - -enum ArrayImageSourceError: Error { - case outOfBoundsRead(UInt64, UInt64) -} - -struct ArrayImageSource: ImageSource { - private var array: Array - - public init(array: Array) { - self.array = array - } - - public var isMappedImage: Bool { return false } - public var path: String? { return nil } - public var bounds: Bounds? { - return Bounds(base: 0, size: Size(array.count * MemoryLayout.stride)) - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - try array.withUnsafeBytes{ - let size = Size($0.count) - let requested = Size(buffer.count) - if addr > size || requested > size - addr { - throw ArrayImageSourceError.outOfBoundsRead(addr, requested) - } - - buffer.copyBytes(from: $0[Int(addr)..= buffer.count else { - throw FileImageSourceError.outOfRangeRead - } - buffer.copyBytes(from: slice[start.. { - var base: Address - var size: Size - var end: Address { - return base + Address(size) - } - - func adjusted(by offset: some FixedWidthInteger) -> Self { - return Self(base: base + Address(offset), size: size - Size(offset)) - } -} - -protocol ImageSource: MemoryReader { - typealias Bounds = ImageBounds - - /// Says whether we are looking at a loaded image in memory or not. - /// The layout in memory may differ from the on-disk layout; in particular, - /// some information may not be available when the image is mapped into - /// memory (an example is ELF section headers). - var isMappedImage: Bool { get } - - /// If this ImageSource knows its path, this will be non-nil. - var path: String? { get } - - /// If this ImageSource knows its bounds, this will be non-nil. - var bounds: Bounds? { get } -} - -struct ImageSourceCursor { - typealias Address = UInt64 - typealias Size = UInt64 - typealias Bounds = ImageBounds - - var source: any ImageSource - var pos: Address - - init(source: any ImageSource, offset: Address = 0) { - self.source = source - self.pos = offset - } - - public mutating func read(into buffer: UnsafeMutableRawBufferPointer) throws { - try source.fetch(from: pos, into: buffer) - pos += UInt64(buffer.count) - } - - public mutating func read(into buffer: UnsafeMutableBufferPointer) throws { - try source.fetch(from: pos, into: buffer) - pos += UInt64(MemoryLayout.stride * buffer.count) - } - - public mutating func read(into pointer: UnsafeMutablePointer) throws { - try source.fetch(from: pos, into: pointer) - pos += UInt64(MemoryLayout.stride) - } - - public mutating func read(as type: T.Type) throws -> T { - let stride = MemoryLayout.stride - let result = try source.fetch(from: pos, as: type) - pos += UInt64(stride) - return result - } - - public mutating func read(count: Int, as type: T.Type) throws -> [T] { - let stride = MemoryLayout.stride - let result = try source.fetch(from: pos, count: count, as: type) - pos += UInt64(stride * count) - return result - } - - public mutating func readString() throws -> String? { - var bytes: [UInt8] = [] - while true { - let ch = try read(as: UInt8.self) - if ch == 0 { - break - } - bytes.append(ch) - } - - return String(decoding: bytes, as: UTF8.self) - } - -} - -extension ImageSource { - /// Fetch all the data from this image source (which must be bounded) - func fetchAllBytes() -> [UInt8]? { - guard let bounds = self.bounds else { - return nil - } - if let data = try? fetch(from: bounds.base, - count: Int(bounds.size), - as: UInt8.self) { - return data - } - return nil - } -} - -enum SubImageSourceError: Error { - case outOfRangeFetch(UInt64, Int) -} - -struct SubImageSource: ImageSource { - var parent: S - var baseAddress: Address - var length: Size - var path: String? { return parent.path } - - var bounds: Bounds? { - return Bounds(base: 0, size: length) - } - - public init(parent: S, baseAddress: Address, length: Size) { - self.parent = parent - self.baseAddress = baseAddress - self.length = length - } - - public var isMappedImage: Bool { - return parent.isMappedImage - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - let toFetch = buffer.count - if addr < 0 || addr > length { - throw SubImageSourceError.outOfRangeFetch(UInt64(addr), toFetch) - } - if Address(length) - addr < toFetch { - throw SubImageSourceError.outOfRangeFetch(UInt64(addr), toFetch) - } - - return try parent.fetch(from: baseAddress + addr, into: buffer) - } -} diff --git a/stdlib/public/Backtracing/MemoryImageSource.swift b/stdlib/public/Backtracing/MemoryImageSource.swift deleted file mode 100644 index c22bb27307e..00000000000 --- a/stdlib/public/Backtracing/MemoryImageSource.swift +++ /dev/null @@ -1,35 +0,0 @@ -//===--- MemoryImageSource.swift - An image source that reads from a file ---===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2023 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 -// -//===----------------------------------------------------------------------===// -// -// Defines MemoryImageSource, an image source that reads data using a -// MemoryReader. -// -//===----------------------------------------------------------------------===// - -import Swift - -class MemoryImageSource: ImageSource { - private var reader: M - - public var isMappedImage: Bool { return true } - public var path: String? { return nil } - public var bounds: Bounds? { return nil } - - public init(with reader: M) { - self.reader = reader - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - try reader.fetch(from: addr, into: buffer) - } -} diff --git a/stdlib/public/CMakeLists.txt b/stdlib/public/CMakeLists.txt index a33c675ae22..618de91625f 100644 --- a/stdlib/public/CMakeLists.txt +++ b/stdlib/public/CMakeLists.txt @@ -301,8 +301,8 @@ if(SWIFT_BUILD_STDLIB AND NOT SWIFT_STDLIB_BUILD_ONLY_CORE_MODULES) add_subdirectory(Observation) endif() - if(SWIFT_ENABLE_BACKTRACING) - add_subdirectory(Backtracing) + if(SWIFT_ENABLE_RUNTIME_MODULE) + add_subdirectory(RuntimeModule) endif() if(SWIFT_ENABLE_SYNCHRONIZATION) diff --git a/stdlib/public/CompatibilityOverride/CompatibilityOverride.h b/stdlib/public/CompatibilityOverride/CompatibilityOverride.h index 76509b9a698..e44b52a6abe 100644 --- a/stdlib/public/CompatibilityOverride/CompatibilityOverride.h +++ b/stdlib/public/CompatibilityOverride/CompatibilityOverride.h @@ -123,7 +123,7 @@ namespace swift { // Include path computation. Code that includes this file can write `#include // "..CompatibilityOverride/CompatibilityOverrideIncludePath.h"` to include the // appropriate .def file for the current library. -#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swiftRuntime \ +#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swiftRuntimeCore \ "../CompatibilityOverride/CompatibilityOverrideRuntime.def" #define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swift_Concurrency \ "../CompatibilityOverride/CompatibilityOverrideConcurrency.def" @@ -155,7 +155,7 @@ namespace swift { // resolve to string literal containing the appropriate section name for the // current library. // Turns into '__swift_hooks' -#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntime "__swift" \ +#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntimeCore "__swift" \ SWIFT_VERSION_MAJOR \ SWIFT_VERSION_MINOR \ "_hooks" diff --git a/stdlib/public/RuntimeModule/Address.swift b/stdlib/public/RuntimeModule/Address.swift new file mode 100644 index 00000000000..6e6296a47a6 --- /dev/null +++ b/stdlib/public/RuntimeModule/Address.swift @@ -0,0 +1,263 @@ +//===--- Address.swift ----------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the `Backtrace.Address` struct that represents addresses in a +// captured backtrace. This type is *not* used for storage; rather, it's +// used as an interface type. +// +//===----------------------------------------------------------------------===// + +import Swift + +// .. Comparable ............................................................. + +extension Backtrace.Address { + fileprivate var widestRepresentation: UInt64 { + switch representation { + case .null: + return 0 + case let .sixteenBit(addr): + return UInt64(addr) + case let .thirtyTwoBit(addr): + return UInt64(addr) + case let .sixtyFourBit(addr): + return addr + } + } +} + +extension Backtrace.Address: Comparable { + /// Return true if `lhs` is lower than `rhs` + public static func < (lhs: Backtrace.Address, rhs: Backtrace.Address) -> Bool { + return lhs.widestRepresentation < rhs.widestRepresentation + } + /// Return true if `lhs` is equal to `rhs` + public static func == (lhs: Backtrace.Address, rhs: Backtrace.Address) -> Bool { + return lhs.widestRepresentation == rhs.widestRepresentation + } +} + +// .. LosslessStringConvertible .............................................. + +extension Backtrace.Address: LosslessStringConvertible { + /// Create an Backtrace.Address from its string representation + public init?(_ s: String) { + self.init(s[...]) + } + + public init?(_ s: Substring) { + let unprefixed: Substring + + // Explicit support for null + if s == "null" { + self.representation = .null + return + } + + // Drop the prefix, if any + if s.hasPrefix("0x") { + unprefixed = s[s.index(s.startIndex, offsetBy: 2)...] + } else { + unprefixed = Substring(s) + } + + // Work out whether it's 64-bit or 32-bit and parse it + if unprefixed.count > 8 && unprefixed.count <= 16 { + guard let addr = UInt64(unprefixed, radix: 16) else { + return nil + } + if addr == 0 { + self.representation = .null + } else { + self.representation = .sixtyFourBit(addr) + } + } else if unprefixed.count <= 8 { + guard let addr = UInt32(unprefixed, radix: 16) else { + return nil + } + if addr == 0 { + self.representation = .null + } else { + self.representation = .thirtyTwoBit(addr) + } + } else { + return nil + } + } + + /// A textual representation of this address + public var description: String { + switch representation { + case .null: + return "null" + case let .sixteenBit(addr): + if addr == 0 { + return "null" + } + return hex(addr) + case let .thirtyTwoBit(addr): + if addr == 0 { + return "null" + } + return hex(addr) + case let .sixtyFourBit(addr): + if addr == 0 { + return "null" + } + return hex(addr) + } + } +} + +// .. ExpressibleByIntegerLiteral ............................................ + +extension Backtrace.Address: ExpressibleByIntegerLiteral { + public typealias IntegerLiteralType = UInt64 + + /// Convert from an integer literal. + public init(integerLiteral: Self.IntegerLiteralType) { + if integerLiteral == 0 { + self.representation = .null + } else if integerLiteral < 0x10000 { + self.representation = .sixteenBit(UInt16(truncatingIfNeeded: integerLiteral)) + } else if integerLiteral < 0x100000000 { + self.representation = .thirtyTwoBit(UInt32(truncatingIfNeeded: integerLiteral)) + } else { + self.representation = .sixtyFourBit(integerLiteral) + } + } +} + +// .. FixedWidthInteger conversions .......................................... + +extension Backtrace.Address { + fileprivate func toFixedWidth( + type: T.Type = T.self + ) -> T? { + switch representation { + case .null: + return T(0) + case let .sixteenBit(addr): + guard T.bitWidth >= 16 else { return nil } + return T(addr) + case let .thirtyTwoBit(addr): + guard T.bitWidth >= 32 else { return nil } + return T(addr) + case let .sixtyFourBit(addr): + guard T.bitWidth >= 64 else { return nil } + return T(addr) + } + } +} + +extension FixedWidthInteger { + /// Convert from an Backtrace.Address. + /// + /// This initializer will return nil if the address width is larger than the + /// type you are attempting to convert into. + init?(_ address: Backtrace.Address) { + guard let result = address.toFixedWidth(type: Self.self) else { + return nil + } + self = result + } +} + +extension Backtrace.Address { + /// Convert from a UInt16. + init(_ value: UInt16) { + if value == 0 { + self.representation = .null + return + } + self.representation = .sixteenBit(value) + } + + /// Convert from a UInt32. + init(_ value: UInt32) { + if value == 0 { + self.representation = .null + return + } + self.representation = .thirtyTwoBit(value) + } + + /// Convert from a UInt64. + init(_ value: UInt64) { + if value == 0 { + self.representation = .null + return + } + self.representation = .sixtyFourBit(value) + } + + /// Convert from a FixedWidthInteger + init(_ value: T) { + if value == 0 { + self.representation = .null + return + } + + switch T.bitWidth { + case 16: + self.representation = .sixteenBit(UInt16(value)) + case 32: + self.representation = .thirtyTwoBit(UInt32(value)) + case 64: + self.representation = .sixtyFourBit(UInt64(value)) + default: + fatalError("Unsupported address width") + } + } +} + +// -- Arithmetic ------------------------------------------------------------- + +extension Backtrace.Address { + static func - (lhs: Backtrace.Address, rhs: Backtrace.Address) -> Int64 { + let ulhs = UInt64(lhs)! + let urhs = UInt64(rhs)! + return Int64(bitPattern: ulhs - urhs) + } + + static func - (lhs: Backtrace.Address, rhs: Int64) -> Backtrace.Address { + switch lhs.representation { + case .null: + return Backtrace.Address(0) + case let .sixteenBit(addr): + let newAddr = addr &- UInt16(bitPattern: Int16(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .thirtyTwoBit(addr): + let newAddr = addr &- UInt32(bitPattern: Int32(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .sixtyFourBit(addr): + let newAddr = addr &- UInt64(bitPattern: rhs) + return Backtrace.Address(newAddr) + } + } + + static func + (lhs: Backtrace.Address, rhs: Int64) -> Backtrace.Address { + switch lhs.representation { + case .null: + return Backtrace.Address(0) + case let .sixteenBit(addr): + let newAddr = addr &+ UInt16(bitPattern: Int16(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .thirtyTwoBit(addr): + let newAddr = addr &+ UInt32(bitPattern: Int32(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .sixtyFourBit(addr): + let newAddr = addr &+ UInt64(bitPattern: rhs) + return Backtrace.Address(newAddr) + } + } +} diff --git a/stdlib/public/Backtracing/Backtrace.swift b/stdlib/public/RuntimeModule/Backtrace.swift similarity index 51% rename from stdlib/public/Backtracing/Backtrace.swift rename to stdlib/public/RuntimeModule/Backtrace.swift index 50bc6c36b00..4b03f438102 100644 --- a/stdlib/public/Backtracing/Backtrace.swift +++ b/stdlib/public/RuntimeModule/Backtrace.swift @@ -38,10 +38,51 @@ internal import BacktracingImpl.ImageFormats.Elf public struct Backtrace: CustomStringConvertible, Sendable { /// The type of an address. /// + /// This is used as an opaque type; if you have some Address, you + /// can ask if it's NULL, and you can attempt to convert it to a + /// FixedWidthInteger. + /// /// This is intentionally _not_ a pointer, because you shouldn't be /// dereferencing them; they may refer to some other process, for /// example. - public typealias Address = UInt64 + public struct Address: Hashable, Codable, Sendable { + enum Representation: Hashable, Codable, Sendable { + case null + case sixteenBit(UInt16) + case thirtyTwoBit(UInt32) + case sixtyFourBit(UInt64) + } + + var representation: Representation + + /// The width of this address, in bits + public var bitWidth: Int { + switch representation { + case .null: + return 0 + case .sixteenBit(_): + return 16 + case .thirtyTwoBit(_): + return 32 + case .sixtyFourBit(_): + return 64 + } + } + + /// True if this address is a NULL pointer + public var isNull: Bool { + switch representation { + case .null: + return true + case let .sixteenBit(addr): + return addr == 0 + case let .thirtyTwoBit(addr): + return addr == 0 + case let .sixtyFourBit(addr): + return addr == 0 + } + } + } /// The unwind algorithm to use. public enum UnwindAlgorithm { @@ -108,6 +149,16 @@ public struct Backtrace: CustomStringConvertible, Sendable { case omittedFrames(Int) /// Indicates a discontinuity of unknown length. + /// + /// This can only be present at the end of a backtrace; in other cases + /// we will know how many frames we have omitted. For instance, + /// + /// 0: frame 100 <----- bottom of call stack + /// 1: frame 99 + /// 2: frame 98 + /// 3: frame 97 + /// 4: frame 96 + /// 5: ... <----- truncated case truncated /// The program counter, without any adjustment. @@ -139,66 +190,63 @@ public struct Backtrace: CustomStringConvertible, Sendable { } /// A textual description of this frame. - public func description(width: Int) -> String { + public var description: String { switch self { case let .programCounter(addr): - return "\(hex(addr, width: width))" + return "\(addr)" case let .returnAddress(addr): - return "\(hex(addr, width: width)) [ra]" + return "\(addr) [ra]" case let .asyncResumePoint(addr): - return "\(hex(addr, width: width)) [async]" + return "\(addr) [async]" case .omittedFrames(_), .truncated: return "..." } } - - /// A textual description of this frame. - public var description: String { - return description(width: MemoryLayout
.size * 2) - } } /// Represents an image loaded in the process's address space public struct Image: CustomStringConvertible, Sendable { /// The name of the image (e.g. libswiftCore.dylib). - public var name: String + private(set) public var name: String? /// The full path to the image (e.g. /usr/lib/swift/libswiftCore.dylib). - public var path: String + private(set) public var path: String? - /// The build ID of the image, as a byte array (note that the exact number - /// of bytes may vary, and that some images may not have a build ID). - public var buildID: [UInt8]? + /// The unique ID of the image, as a byte array (note that the exact number + /// of bytes may vary, and that some images may not have a unique ID). + /// + /// On Darwin systems, this is the LC_UUID value; on Linux this is the + /// build ID, which may take one of a number of forms or may not even + /// be present. + private(set) public var uniqueID: [UInt8]? /// The base address of the image. - public var baseAddress: Backtrace.Address + private(set) public var baseAddress: Backtrace.Address /// The end of the text segment in this image. - public var endOfText: Backtrace.Address + private(set) public var endOfText: Backtrace.Address /// Provide a textual description of an Image. - public func description(width: Int) -> String { - if let buildID = self.buildID { - return "\(hex(baseAddress, width: width))-\(hex(endOfText, width: width)) \(hex(buildID)) \(name) \(path)" - } else { - return "\(hex(baseAddress, width: width))-\(hex(endOfText, width: width)) \(name) \(path)" - } - } - - /// A textual description of an Image. public var description: String { - return description(width: MemoryLayout
.size * 2) + if let uniqueID = self.uniqueID { + return "\(baseAddress)-\(endOfText) \(hex(uniqueID)) \(name ?? "") \(path ?? "")" + } else { + return "\(baseAddress)-\(endOfText) \(name ?? "") \(path ?? "")" + } } } /// The architecture of the system that captured this backtrace. public var architecture: String - /// The width of an address in this backtrace, in bits. - public var addressWidth: Int + /// The actual backtrace data, stored in Compact Backtrace Format. + private var representation: [UInt8] /// A list of captured frame information. - public var frames: [Frame] + @available(macOS 10.15, *) + public var frames: some Sequence { + return CompactBacktraceFormat.Decoder(representation) + } /// A list of captured images. /// @@ -208,36 +256,6 @@ public struct Backtrace: CustomStringConvertible, Sendable { /// separately yourself using `captureImages()`. public var images: [Image]? - /// Holds information about the shared cache. - public struct SharedCacheInfo: Sendable { - /// The UUID from the shared cache. - public var uuid: [UInt8] - - /// The base address of the shared cache. - public var baseAddress: Backtrace.Address - - /// Says whether there is in fact a shared cache. - public var noCache: Bool - } - - /// Information about the shared cache. - /// - /// Holds information about the shared cache. On Darwin only, this is - /// required for symbolication. On non-Darwin platforms it will always - /// be `nil`. - public var sharedCacheInfo: SharedCacheInfo? - - /// Format an address according to the addressWidth. - /// - /// @param address The address to format. - /// @param prefix Whether to include a "0x" prefix. - /// - /// @returns A String containing the formatted Address. - public func formatAddress(_ address: Address, - prefix: Bool = true) -> String { - return hex(address, prefix: prefix, width: (addressWidth + 3) / 4) - } - /// Capture a backtrace from the current program location. /// /// The `capture()` method itself will not be included in the backtrace; @@ -245,28 +263,33 @@ public struct Backtrace: CustomStringConvertible, Sendable { /// and its programCounter value will be the return address for the /// `capture()` method call. /// - /// @param algorithm Specifies which unwind mechanism to use. If this - /// is set to `.auto`, we will use the platform default. - /// @param limit The backtrace will include at most this number of - /// frames; you can set this to `nil` to remove the - /// limit completely if required. - /// @param offset Says how many frames to skip; this makes it easy to - /// wrap this API without having to inline things and - /// without including unnecessary frames in the backtrace. - /// @param top Sets the minimum number of frames to capture at the - /// top of the stack. + /// Parameters: /// - /// @returns A new `Backtrace` struct. + /// - algorithm: Specifies which unwind mechanism to use. If this + /// is set to `.auto`, we will use the platform default. + /// - limit: The backtrace will include at most this number of + /// frames; you can set this to `nil` to remove the + /// limit completely if required. + /// - offset: Says how many frames to skip; this makes it easy to + /// wrap this API without having to inline things and + /// without including unnecessary frames in the backtrace. + /// - top: Sets the minimum number of frames to capture at the + /// top of the stack. + /// - images: (Optional) A list of captured images. This allows you + /// to capture images once, and then generate multiple + /// backtraces using a single set of captured images. @inline(never) @_semantics("use_frame_pointer") public static func capture(algorithm: UnwindAlgorithm = .auto, limit: Int? = 64, offset: Int = 0, - top: Int = 16) throws -> Backtrace { + top: Int = 16, + images: [Image]? = nil) throws -> Backtrace { #if os(Linux) - let images = captureImages() + // On Linux, we need the captured images to resolve async functions + let theImages = images ?? captureImages() #else - let images: [Image]? = nil + let theImages = images #endif // N.B. We use offset+1 here to skip this frame, rather than inlining @@ -274,7 +297,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { return try HostContext.withCurrentContext { ctx in try capture(from: ctx, using: UnsafeLocalMemoryReader(), - images: images, + images: theImages, algorithm: algorithm, limit: limit, offset: offset + 1, @@ -282,108 +305,6 @@ public struct Backtrace: CustomStringConvertible, Sendable { } } - @_spi(Internal) - public static func capture( - from context: Ctx, - using memoryReader: Rdr, - images: [Image]?, - algorithm: UnwindAlgorithm = .auto, - limit: Int? = 64, - offset: Int = 0, - top: Int = 16 - ) throws -> Backtrace { - let addressWidth = 8 * MemoryLayout.size - - switch algorithm { - // All of them, for now, use the frame pointer unwinder. In the long - // run, we should be using DWARF EH frame data for .precise. - case .auto, .fast, .precise: - let unwinder = - FramePointerUnwinder(context: context, - images: images, - memoryReader: memoryReader) - .dropFirst(offset) - - if let limit = limit { - if limit <= 0 { - return Backtrace(architecture: context.architecture, - addressWidth: addressWidth, - frames: [.truncated]) - } - - let realTop = top < limit ? top : limit - 1 - var iterator = unwinder.makeIterator() - var frames: [Frame] = [] - - // Capture frames normally until we hit limit - while let frame = iterator.next() { - if frames.count < limit { - frames.append(frame) - if frames.count == limit { - break - } - } - } - - if realTop == 0 { - if let _ = iterator.next() { - // More frames than we were asked for; replace the last - // one with a discontinuity - frames[limit - 1] = .truncated - } - - return Backtrace(architecture: context.architecture, - addressWidth: addressWidth, - frames: frames) - } else { - - // If we still have frames at this point, start tracking the - // last `realTop` frames in a circular buffer. - if let frame = iterator.next() { - let topSection = limit - realTop - var topFrames: [Frame] = [] - var topNdx = 0 - var omittedFrames = 0 - - topFrames.reserveCapacity(realTop) - topFrames.insert(contentsOf: frames.suffix(realTop - 1), at: 0) - topFrames.append(frame) - - while let frame = iterator.next() { - topFrames[topNdx] = frame - topNdx += 1 - omittedFrames += 1 - if topNdx >= realTop { - topNdx = 0 - } - } - - // Fix the backtrace to include a discontinuity followed by - // the contents of the circular buffer. - let firstPart = realTop - topNdx - let secondPart = topNdx - frames[topSection - 1] = .omittedFrames(omittedFrames) - - frames.replaceSubrange(topSection..<(topSection+firstPart), - with: topFrames.suffix(firstPart)) - frames.replaceSubrange((topSection+firstPart).. SymbolicatedBacktrace? { + return SymbolicatedBacktrace.symbolicate( + backtrace: self, + images: images, + options: options + ) + } + + /// Provide a textual version of the backtrace. + public var description: String { + var lines: [String] = [] + + var n = 0 + for frame in frames { + lines.append("\(n)\t\(frame.description)") + switch frame { + case let .omittedFrames(count): + n += count + default: + n += 1 + } + } + + if let images = images { + lines.append("") + lines.append("Images:") + lines.append("") + for (n, image) in images.enumerated() { + lines.append("\(n)\t\(image.description)") + } + } + + return lines.joined(separator: "\n") + } + + /// Initialise a Backtrace from a sequence of `RichFrame`s + init(architecture: String, + frames: some Sequence>, + images: [Image]?) { + self.architecture = architecture + self.representation = Array(CompactBacktraceFormat.Encoder(frames)) + self.images = images + } +} + +// -- Capture Implementation ------------------------------------------------- + +extension Backtrace { + @_spi(Internal) + @_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader) + #endif + public static func capture( + from context: Ctx, + using memoryReader: Rdr, + images: [Image]?, + algorithm: UnwindAlgorithm, + limit: Int?, + offset: Int, + top: Int + ) throws -> Backtrace { + switch algorithm { + // All of them, for now, use the frame pointer unwinder. In the long + // run, we should be using DWARF EH frame data for .precise. + case .auto, .fast, .precise: + let unwinder = + FramePointerUnwinder(context: context, + images: images, + memoryReader: memoryReader) + + if let limit = limit { + let limited = LimitSequence(unwinder, + limit: limit, + offset: offset, + top: top) + + return Backtrace(architecture: context.architecture, + frames: limited, + images: images) + } + + return Backtrace(architecture: context.architecture, + frames: unwinder, + images: images) + } + } +} + +// -- Darwin ----------------------------------------------------------------- + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +extension Backtrace { + private static func withDyldProcessInfo(for task: task_t, fn: (OpaquePointer?) throws -> T) rethrows -> T { @@ -413,9 +466,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { return try fn(dyldInfo) } - #endif - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @_spi(Internal) public static func captureImages(for process: Any) -> [Image] { var images: [Image] = [] @@ -450,7 +501,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { images.append(Image(name: name, path: pathString, - buildID: theUUID, + uniqueID: theUUID, baseAddress: Address(machHeaderAddress), endOfText: Address(endOfText))) } @@ -459,18 +510,28 @@ public struct Backtrace: CustomStringConvertible, Sendable { return images.sorted(by: { $0.baseAddress < $1.baseAddress }) } - #else // !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) +} +#endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +// -- Linux ------------------------------------------------------------------ + +#if os(Linux) +extension Backtrace { private struct AddressRange { var low: Address = 0 var high: Address = 0 } @_spi(Internal) - public static func captureImages(using reader: M, - forProcess pid: Int? = nil) -> [Image] { + @_specialize(exported: true, kind: full, where M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where M == RemoteMemoryReader) + @_specialize(exported: true, kind: full, where M == LocalMemoryReader) + public static func captureImages( + using reader: M, + forProcess pid: Int? = nil + ) -> [Image] { var images: [Image] = [] - #if os(Linux) let path: String if let pid = pid { path = "/proc/\(pid)/maps" @@ -489,8 +550,8 @@ public struct Backtrace: CustomStringConvertible, Sendable { if match.inode == "0" || path == "" { continue } - guard let start = Address(match.start, radix: 16), - let end = Address(match.end, radix: 16) else { + guard let start = Address(match.start), + let end = Address(match.end) else { continue } @@ -503,25 +564,8 @@ public struct Backtrace: CustomStringConvertible, Sendable { } } - // Look for ELF headers in the process' memory - typealias Source = MemoryImageSource - let source = Source(with: reader) - for match in ProcMapsScanner(procMaps) { - let path = stripWhitespace(match.pathname) - if match.inode == "0" || path == "" { - continue - } - - guard let start = Address(match.start, radix: 16), - let end = Address(match.end, radix: 16), - let offset = Address(match.offset, radix: 16) else { - continue - } - - if offset != 0 || end - start < EI_NIDENT { - continue - } - + // Look at each mapped file to see if it's an ELF image + for (path, range) in mappedFiles { // Extract the filename from path let name: Substring if let slashIndex = path.lastIndex(of: "/") { @@ -531,155 +575,24 @@ public struct Backtrace: CustomStringConvertible, Sendable { } // Inspect the image and extract the UUID and end of text - let range = mappedFiles[path]! - let subSource = SubImageSource(parent: source, - baseAddress: Source.Address(range.low), - length: Source.Size(range.high - - range.low)) - var theUUID: [UInt8]? = nil - var endOfText: Address = range.low - - if let image = try? Elf32Image(source: subSource) { - theUUID = image.uuid - - for hdr in image.programHeaders { - if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { - endOfText = max(endOfText, range.low + Address(hdr.p_vaddr - + hdr.p_memsz)) - } - } - } else if let image = try? Elf64Image(source: subSource) { - theUUID = image.uuid - - for hdr in image.programHeaders { - if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { - endOfText = max(endOfText, range.low + Address(hdr.p_vaddr - + hdr.p_memsz)) - } - } - } else { - // Not a valid ELF image + guard let (endOfText, uuid) = getElfImageInfo(at: M.Address(range.low)!, + using: reader) else { + // Not an ELF iamge continue } let image = Image(name: String(name), path: String(path), - buildID: theUUID, + uniqueID: uuid, baseAddress: range.low, - endOfText: endOfText) + endOfText: Address(endOfText)) images.append(image) } - #endif return images.sorted(by: { $0.baseAddress < $1.baseAddress }) } - #endif - - /// Capture shared cache information. - /// - /// @returns A `SharedCacheInfo`. - public static func captureSharedCacheInfo() -> SharedCacheInfo? { - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - return captureSharedCacheInfo(for: mach_task_self()) - #else - return nil - #endif - } - - @_spi(Internal) - public static func captureSharedCacheInfo(for t: Any) -> SharedCacheInfo? { - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - let task = t as! task_t - return withDyldProcessInfo(for: task) { dyldInfo in - var cacheInfo = dyld_process_cache_info() - _dyld_process_info_get_cache(dyldInfo, &cacheInfo) - let theUUID = withUnsafePointer(to: cacheInfo.cacheUUID) { - Array(UnsafeRawBufferPointer(start: $0, - count: MemoryLayout.size)) - } - return SharedCacheInfo(uuid: theUUID, - baseAddress: Address(cacheInfo.cacheBaseAddress), - noCache: cacheInfo.noCache) - } - #else // !os(Darwin) - return nil - #endif - } - - /// Return a symbolicated version of the backtrace. - /// - /// @param images Specifies the set of images to use for symbolication. - /// If `nil`, the function will look to see if the `Backtrace` - /// has already captured images. If it has, those will be - /// used; otherwise we will capture images at this point. - /// - /// @param sharedCacheInfo Provides information about the location and - /// identity of the shared cache, if applicable. - /// - /// @param showInlineFrames If `true` and we know how on the platform we're - /// running on, add virtual frames to show inline - /// function calls. - /// - /// @param showSourceLocation If `true`, look up the source location for - /// each address. - /// - /// @param useSymbolCache If the system we are on has a symbol cache, - /// says whether or not to use it. - /// - /// @returns A new `SymbolicatedBacktrace`. - public func symbolicated(with images: [Image]? = nil, - sharedCacheInfo: SharedCacheInfo? = nil, - showInlineFrames: Bool = true, - showSourceLocations: Bool = true, - useSymbolCache: Bool = true) - -> SymbolicatedBacktrace? { - return SymbolicatedBacktrace.symbolicate( - backtrace: self, - images: images, - sharedCacheInfo: sharedCacheInfo, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: useSymbolCache - ) - } - - /// Provide a textual version of the backtrace. - public var description: String { - var lines: [String] = [] - let addressChars = (addressWidth + 3) / 4 - - var n = 0 - for frame in frames { - lines.append("\(n)\t\(frame.description(width: addressChars))") - switch frame { - case let .omittedFrames(count): - n += count - default: - n += 1 - } - } - - if let images = images { - lines.append("") - lines.append("Images:") - lines.append("") - for (n, image) in images.enumerated() { - lines.append("\(n)\t\(image.description(width: addressChars))") - } - } - - #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - if let sharedCacheInfo = sharedCacheInfo { - lines.append("") - lines.append("Shared Cache:") - lines.append("") - lines.append(" UUID: \(hex(sharedCacheInfo.uuid))") - lines.append(" Base: \(hex(sharedCacheInfo.baseAddress, width: addressChars))") - lines.append(" Active: \(!sharedCacheInfo.noCache)") - } - #endif - - return lines.joined(separator: "\n") - } } +#endif // os(Linux) + + diff --git a/stdlib/public/Backtracing/BacktraceFormatter.swift b/stdlib/public/RuntimeModule/BacktraceFormatter.swift similarity index 91% rename from stdlib/public/Backtracing/BacktraceFormatter.swift rename to stdlib/public/RuntimeModule/BacktraceFormatter.swift index cfa6cd886fb..71a6fa87195 100644 --- a/stdlib/public/Backtracing/BacktraceFormatter.swift +++ b/stdlib/public/RuntimeModule/BacktraceFormatter.swift @@ -527,24 +527,22 @@ public struct BacktraceFormatter { /// Format an individual frame into a list of columns. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result An array of strings, one per column. public func formatColumns(frame: Backtrace.Frame, - addressWidth: Int, index: Int? = nil) -> [String] { let pc: String var attrs: [String] = [] switch frame { case let .programCounter(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" case let .returnAddress(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("ra") case let .asyncResumePoint(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("async") case .omittedFrames(_), .truncated: pc = "..." @@ -567,30 +565,24 @@ public struct BacktraceFormatter { /// Format a frame into a list of rows. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result An array of table rows. public func formatRows(frame: Backtrace.Frame, - addressWidth: Int, index: Int? = nil) -> [TableRow] { return [.columns(formatColumns(frame: frame, - addressWidth: addressWidth, index: index))] } /// Format just one frame. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result A `String` containing the formatted data. public func format(frame: Backtrace.Frame, - addressWidth: Int, index: Int? = nil) -> String { let rows = formatRows(frame: frame, - addressWidth: addressWidth, index: index) return BacktraceFormatter.formatTable(rows, alignments: [.right]) } @@ -598,16 +590,14 @@ public struct BacktraceFormatter { /// Format the frame list from a backtrace. /// /// @param frames The frames to format. - /// @param addressWidth The width, in characters, of an address. /// /// @result A `String` containing the formatted data. - public func format(frames: some Sequence, - addressWidth: Int) -> String { + public func format(frames: some Sequence) -> String { var rows: [TableRow] = [] var n = 0 for frame in frames { - rows += formatRows(frame: frame, addressWidth: addressWidth, index: n) + rows += formatRows(frame: frame, index: n) if case let .omittedFrames(count) = frame { n += count @@ -625,8 +615,7 @@ public struct BacktraceFormatter { /// /// @result A `String` containing the formatted data. public func format(backtrace: Backtrace) -> String { - return format(frames: backtrace.frames, - addressWidth: (backtrace.addressWidth + 3) / 4) + return format(frames: backtrace.frames) } /// Grab source lines for a symbolicated backtrace. @@ -743,19 +732,18 @@ public struct BacktraceFormatter { /// /// @result An array of strings, one per column. public func formatColumns(frame: SymbolicatedBacktrace.Frame, - addressWidth: Int, index: Int? = nil) -> [String] { let pc: String var attrs: [String] = [] switch frame.captured { case let .programCounter(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" case let .returnAddress(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("ra") case let .asyncResumePoint(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("async") case .omittedFrames(_), .truncated: pc = "" @@ -855,16 +843,13 @@ public struct BacktraceFormatter { /// Format a frame into a list of rows. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result An array of table rows. public func formatRows(frame: SymbolicatedBacktrace.Frame, - addressWidth: Int, index: Int? = nil, showSource: Bool = true) -> [TableRow] { let columns = formatColumns(frame: frame, - addressWidth: addressWidth, index: index) var rows: [TableRow] = [.columns(columns)] @@ -884,16 +869,13 @@ public struct BacktraceFormatter { /// Format just one frame. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result A `String` containing the formatted data. public func format(frame: SymbolicatedBacktrace.Frame, - addressWidth: Int, index: Int? = nil, showSource: Bool = true) -> String { - let rows = formatRows(frame: frame, addressWidth: addressWidth, - index: index, showSource: showSource) + let rows = formatRows(frame: frame, index: index, showSource: showSource) return BacktraceFormatter.formatTable(rows, alignments: [.right]) } @@ -907,11 +889,11 @@ public struct BacktraceFormatter { /// Format the frame list from a symbolicated backtrace. /// /// @param frames The frames to format. - /// @param addressWidth The width, in characters, of an address. /// /// @result A `String` containing the formatted data. - public func format(frames: some Sequence, - addressWidth: Int) -> String { + public func format( + frames: some Sequence + ) -> String { var rows: [TableRow] = [] var sourceLocationsShown = Set() @@ -931,8 +913,7 @@ public struct BacktraceFormatter { } } - rows += formatRows(frame: frame, addressWidth: addressWidth, - index: n, showSource: showSource) + rows += formatRows(frame: frame, index: n, showSource: showSource) if case let .omittedFrames(count) = frame.captured { n += count @@ -950,16 +931,14 @@ public struct BacktraceFormatter { /// /// @result A `String` containing the formatted data. public func format(backtrace: SymbolicatedBacktrace) -> String { - let addressChars = (backtrace.addressWidth + 3) / 4 - var result = format(frames: backtrace.frames, addressWidth: addressChars) + var result = format(frames: backtrace.frames) switch options._showImages { case .none: break case .all: result += "\n\nImages:\n" - result += format(images: backtrace.images, - addressWidth: addressChars) + result += format(images: backtrace.images) case .mentioned: var mentionedImages = Set() for frame in backtrace.frames { @@ -978,7 +957,7 @@ public struct BacktraceFormatter { } else { result += "\n\nImages (only mentioned):\n" } - result += format(images: images, addressWidth: addressChars) + result += format(images: images) } return result @@ -987,28 +966,31 @@ public struct BacktraceFormatter { /// Format a `Backtrace.Image` into a list of columns. /// /// @param image The `Image` object to format. - /// @param addressWidth The width of an address, in characters. /// /// @result An array of strings, one per column. - public func formatColumns(image: Backtrace.Image, - addressWidth: Int) -> [String] { - let addressRange = "\(hex(image.baseAddress, width: addressWidth))–\(hex(image.endOfText, width: addressWidth))" + public func formatColumns(image: Backtrace.Image) -> [String] { + let addressRange = "\(image.baseAddress)–\(image.endOfText)" let buildID: String - if let bytes = image.buildID { + if let bytes = image.uniqueID { buildID = hex(bytes) } else { buildID = "" } let imagePath: String - if options._sanitizePaths { - imagePath = sanitizePath(image.path) + if let path = image.path { + if options._sanitizePaths { + imagePath = sanitizePath(path) + } else { + imagePath = path + } } else { - imagePath = image.path + imagePath = "" } + let imageName = image.name ?? "" return [ options._theme.imageAddressRange(addressRange), options._theme.imageBuildID(buildID), - options._theme.imageName(image.name), + options._theme.imageName(imageName), options._theme.imagePath(imagePath) ] } @@ -1016,15 +998,12 @@ public struct BacktraceFormatter { /// Format an array of `Backtrace.Image`s. /// /// @param images The array of `Image` objects to format. - /// @param addressWidth The width of an address, in characters. /// /// @result A string containing the formatted data. - public func format(images: some Sequence, - addressWidth: Int) -> String { + public func format(images: some Sequence) -> String { let rows = images.map{ TableRow.columns( - formatColumns(image: $0, - addressWidth: addressWidth) + formatColumns(image: $0) ) } diff --git a/stdlib/public/Backtracing/ByteSwapping.swift b/stdlib/public/RuntimeModule/ByteSwapping.swift similarity index 100% rename from stdlib/public/Backtracing/ByteSwapping.swift rename to stdlib/public/RuntimeModule/ByteSwapping.swift diff --git a/stdlib/public/Backtracing/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt similarity index 73% rename from stdlib/public/Backtracing/CMakeLists.txt rename to stdlib/public/RuntimeModule/CMakeLists.txt index 2a61525fb01..2f67f35faf6 100644 --- a/stdlib/public/Backtracing/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -1,4 +1,4 @@ -#===--- CMakeLists.txt - Backtracing support library -----------------------===# +#===--- CMakeLists.txt - Runtime module ------------------------------------===# # # This source file is part of the Swift.org open source project # @@ -9,8 +9,10 @@ # See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors # #===------------------------------------------------------------------------===# - -set(swift_backtracing_link_libraries +# +# The Runtime module isn't the runtime itself; that lives in libswiftCore; +# rather, it's a high level Swift interface to things +set(swift_runtime_link_libraries swiftCore swift_Concurrency ) @@ -20,27 +22,30 @@ if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_EXPERIMENTAL_CONCURRENCY) set(concurrency _Concurrency) endif() -set(BACKTRACING_SOURCES - ArrayImageSource.swift +set(RUNTIME_SOURCES + Address.swift Backtrace.swift BacktraceFormatter.swift ByteSwapping.swift CachingMemoryReader.swift - Context.swift + CompactBacktrace.swift Compression.swift + Context.swift CoreSymbolication.swift Dwarf.swift + EightByteBuffer.swift Elf.swift - FileImageSource.swift + ElfImageCache.swift FramePointerUnwinder.swift Image.swift ImageSource.swift - MemoryImageSource.swift Libc.swift + LimitSequence.swift MemoryReader.swift ProcMapsScanner.swift Registers.swift Runtime.swift + RichFrame.swift SymbolicatedBacktrace.swift Utils.swift Win32Extras.cpp @@ -48,24 +53,24 @@ set(BACKTRACING_SOURCES get-cpu-context.${SWIFT_ASM_EXT} ) -set(BACKTRACING_COMPILE_FLAGS +set(RUNTIME_COMPILE_FLAGS "-cxx-interoperability-mode=default" "-Xfrontend;-experimental-spi-only-imports" "-Xcc;-I${SWIFT_SOURCE_DIR}/include" "-Xcc;-I${CMAKE_BINARY_DIR}/include" - "-Xcc;-I${SWIFT_STDLIB_SOURCE_DIR}/public/Backtracing/modules" + "-Xcc;-I${SWIFT_STDLIB_SOURCE_DIR}/public/RuntimeModule/modules" "-disable-upcoming-feature;MemberImportVisibility") ###TODO: Add these when we add static linking support # -#list(APPEND BACKTRACING_COMPILE_FLAGS +#list(APPEND RUNTIME_COMPILE_FLAGS # "-Xcc;-I${SWIFT_PATH_TO_ZLIB_SOURCE}" # "-Xcc;-I${SWIFT_PATH_TO_ZSTD_SOURCE}/lib" # "-Xcc;-I${SWIFT_PATH_TO_LIBLZMA_SOURCE}/src/liblzma/api") if(SWIFT_ASM_AVAILABLE) - list(APPEND BACKTRACING_SOURCES get-cpu-context.${SWIFT_ASM_EXT}) - list(APPEND BACKTRACING_COMPILE_FLAGS "-DSWIFT_ASM_AVAILABLE") + list(APPEND RUNTIME_SOURCES get-cpu-context.${SWIFT_ASM_EXT}) + list(APPEND RUNTIME_COMPILE_FLAGS "-DSWIFT_ASM_AVAILABLE") else() message(warning "Assembly language not available on this platform; backtracing will fail.") endif() @@ -75,8 +80,8 @@ set(LLVM_OPTIONAL_SOURCES get-cpu-context.asm ) -add_swift_target_library(swift_Backtracing ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB - ${BACKTRACING_SOURCES} +add_swift_target_library(swiftRuntime ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB + ${RUNTIME_SOURCES} SWIFT_MODULE_DEPENDS ${concurrency} @@ -89,11 +94,11 @@ add_swift_target_library(swift_Backtracing ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I SWIFT_MODULE_DEPENDS_HAIKU Glibc SWIFT_MODULE_DEPENDS_WINDOWS CRT - PRIVATE_LINK_LIBRARIES ${swift_backtracing_link_libraries} + PRIVATE_LINK_LIBRARIES ${swift_runtime_link_libraries} SWIFT_COMPILE_FLAGS ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS} - ${BACKTRACING_COMPILE_FLAGS} + ${RUNTIME_COMPILE_FLAGS} -parse-stdlib LINK_FLAGS diff --git a/stdlib/public/Backtracing/CachingMemoryReader.swift b/stdlib/public/RuntimeModule/CachingMemoryReader.swift similarity index 64% rename from stdlib/public/Backtracing/CachingMemoryReader.swift rename to stdlib/public/RuntimeModule/CachingMemoryReader.swift index 967f3a5394b..33c8d10aeb8 100644 --- a/stdlib/public/Backtracing/CachingMemoryReader.swift +++ b/stdlib/public/RuntimeModule/CachingMemoryReader.swift @@ -18,18 +18,17 @@ import Swift // The size of the pages in the page cache (must be a power of 2) fileprivate let pageSize = 4096 - fileprivate let pageMask = pageSize - 1 // The largest chunk we will try to cache data for fileprivate let maxCachedSize = pageSize * 8 @_spi(MemoryReaders) -public class CachingMemoryReader: MemoryReader { - private var reader: T +public class CachingMemoryReader: MemoryReader { + private var reader: Reader private var cache: [Address:UnsafeRawBufferPointer] - public init(for reader: T) { + public init(for reader: Reader) { self.reader = reader self.cache = [:] } @@ -40,7 +39,7 @@ public class CachingMemoryReader: MemoryReader { } } - private func getPage(at address: Address) throws -> UnsafeRawBufferPointer { + func getPage(at address: Address) throws -> UnsafeRawBufferPointer { precondition((address & Address(pageMask)) == 0) if let page = cache[address] { @@ -84,3 +83,39 @@ public class CachingMemoryReader: MemoryReader { } } } + +#if os(Linux) +@_spi(MemoryReaders) +public typealias MemserverMemoryReader + = CachingMemoryReader + +extension CachingMemoryReader where Reader == UncachedMemserverMemoryReader { + convenience public init(fd: CInt) { + self.init(for: UncachedMemserverMemoryReader(fd: fd)) + } +} +#endif + +@_spi(MemoryReaders) +public typealias RemoteMemoryReader = CachingMemoryReader + +extension CachingMemoryReader where Reader == UncachedRemoteMemoryReader { + #if os(macOS) + convenience public init(task: Any) { + self.init(for: UncachedRemoteMemoryReader(task: task)) + } + #elseif os(Linux) + convenience public init(pid: Any) { + self.init(for: UncachedRemoteMemoryReader(pid: pid)) + } + #endif +} + +@_spi(MemoryReaders) +public typealias LocalMemoryReader = CachingMemoryReader + +extension CachingMemoryReader where Reader == UncachedLocalMemoryReader { + convenience public init() { + self.init(for: UncachedLocalMemoryReader()) + } +} diff --git a/stdlib/public/RuntimeModule/CompactBacktrace.swift b/stdlib/public/RuntimeModule/CompactBacktrace.swift new file mode 100644 index 00000000000..a4d4ed46fdd --- /dev/null +++ b/stdlib/public/RuntimeModule/CompactBacktrace.swift @@ -0,0 +1,439 @@ +//===--- CompactBacktrace.swift -------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Definitions for Compact Backtrace Format +// +//===----------------------------------------------------------------------===// + +import Swift + +enum CompactBacktraceFormat { + /// Tells us what size of machine words were used when generating the + /// backtrace. + enum WordSize: UInt8 { + case sixteenBit = 0 + case thirtyTwoBit = 1 + case sixtyFourBit = 2 + } + + // Instruction encodings + struct Instruction: RawRepresentable { + typealias RawValue = UInt8 + + private(set) var rawValue: UInt8 + + init?(rawValue: Self.RawValue) { + self.rawValue = rawValue + } + + static let end = Instruction(rawValue: 0b00000000)! + static let trunc = Instruction(rawValue: 0b00000001)! + + static let pc_first = Instruction(rawValue: 0b00010000)! + static let pc_last = Instruction(rawValue: 0b00011111)! + static let ra_first = Instruction(rawValue: 0b00100000)! + static let ra_last = Instruction(rawValue: 0b00101111)! + static let async_first = Instruction(rawValue: 0b00110000)! + static let async_last = Instruction(rawValue: 0b00111111)! + + static let omit_first = Instruction(rawValue: 0b01000000)! + static let omit_last = Instruction(rawValue: 0b01111111)! + + private static func addressInstr( + _ code: UInt8, _ absolute: Bool, _ count: Int + ) -> Instruction { + return Instruction(rawValue: code + | (absolute ? 0b00001000 : 0) + | UInt8(count - 1))! + } + + static func pc(absolute: Bool, count: Int) -> Instruction { + return addressInstr(0b00010000, absolute, count) + } + static func ra(absolute: Bool, count: Int) -> Instruction { + return addressInstr(0b00100000, absolute, count) + } + static func `async`(absolute: Bool, count: Int) -> Instruction { + return addressInstr(0b00110000, absolute, count) + } + + static func omit(external: Bool, count: Int) -> Instruction { + return Instruction(rawValue: 0b01000000 + | (external ? 0b00100000 : 0) + | UInt8(count - 1))! + } + } + + // Represents a decoded instruction + enum DecodedInstruction { + case end + case trunc + case pc(absolute: Bool, count: Int) + case ra(absolute: Bool, count: Int) + case `async`(absolute: Bool, count: Int) + case omit(external: Bool, count: Int) + } + + + /// Adapts a Sequence containing Compact Backtrace Format data into a + /// Sequence of `Backtrace.Frame`s. + struct Decoder>: Sequence { + typealias Frame = Backtrace.Frame + typealias Address = Backtrace.Address + typealias Storage = S + + private var storage: Storage + + init(_ storage: S) { + self.storage = storage + } + + public func makeIterator() -> Iterator { + var iterator = storage.makeIterator() + guard let infoByte = iterator.next() else { + return Iterator(nil, .sixtyFourBit) + } + let version = infoByte >> 2 + guard let size = WordSize(rawValue: infoByte & 0x3) else { + return Iterator(nil, .sixtyFourBit) + } + guard version == 0 else { + return Iterator(nil, .sixtyFourBit) + } + return Iterator(iterator, size) + } + + struct Iterator: IteratorProtocol { + var iterator: Storage.Iterator? + let wordSize: WordSize + let wordMask: UInt64 + var lastAddress: UInt64 + + init(_ iterator: Storage.Iterator?, _ size: WordSize) { + self.iterator = iterator + self.wordSize = size + + switch size { + case .sixteenBit: + self.wordMask = 0xff00 + case .thirtyTwoBit: + self.wordMask = 0xffffff00 + case .sixtyFourBit: + self.wordMask = 0xffffffffffffff00 + } + + self.lastAddress = 0 + } + + private mutating func decodeAddress( + _ absolute: Bool, _ count: Int + ) -> Address? { + var word: UInt64 + guard let firstByte = iterator!.next() else { + return nil + } + if (firstByte & 0x80) != 0 { + word = wordMask | UInt64(firstByte) + } else { + word = UInt64(firstByte) + } + for _ in 1.. Frame? { + if iterator == nil { + return nil + } + + guard let instr = iterator!.next() else { + finished() + return .truncated + } + + guard let decoded = Instruction(rawValue: instr)?.decoded() else { + fail() + return .truncated + } + + switch decoded { + case .end: + finished() + return nil + case .trunc: + finished() + return .truncated + case let .pc(absolute, count): + guard let addr = decodeAddress(absolute, count) else { + finished() + return .truncated + } + return .programCounter(addr) + case let .ra(absolute, count): + guard let addr = decodeAddress(absolute, count) else { + finished() + return .truncated + } + return .returnAddress(addr) + case let .async(absolute, count): + guard let addr = decodeAddress(absolute, count) else { + finished() + return .truncated + } + return .asyncResumePoint(addr) + case let .omit(external, count): + if !external { + return .omittedFrames(count) + } else { + var word: Int = 0 + for _ in 0..>>: Sequence { + typealias Element = UInt8 + typealias Frame = Backtrace.Frame + typealias Address = A + typealias Source = S + + private var source: Source + + init(_ source: Source) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source.makeIterator()) + } + + struct Iterator: IteratorProtocol { + var iterator: Source.Iterator + var lastAddress: Address = 0 + + enum State { + case start + case ready + case emittingBytes(Int) + case done + } + var bytes = EightByteBuffer() + var state: State = .start + + init(_ iterator: Source.Iterator) { + self.iterator = iterator + } + + /// Set up to emit the bytes of `address`, returning the number of bytes + /// we will need to emit + private mutating func emitAddressNext( + _ address: Address + ) -> (absolute: Bool, count: Int) { + let delta = address &- lastAddress + + let absCount: Int + if address & (1 << (Address.bitWidth - 1)) != 0 { + let ones = ((~address).leadingZeroBitCount - 1) >> 3 + absCount = (Address.bitWidth >> 3) - ones + } else { + let zeroes = (address.leadingZeroBitCount - 1) >> 3 + absCount = (Address.bitWidth >> 3) - zeroes + } + + let deltaCount: Int + if delta & (1 << (Address.bitWidth - 1)) != 0 { + let ones = ((~delta).leadingZeroBitCount - 1) >> 3 + deltaCount = (Address.bitWidth >> 3) - ones + } else { + let zeroes = (delta.leadingZeroBitCount - 1) >> 3 + deltaCount = (Address.bitWidth >> 3) - zeroes + } + + lastAddress = address + + if absCount < deltaCount { + bytes = EightByteBuffer(address) + state = .emittingBytes(8 - absCount) + return (absolute: true, count: absCount) + } else { + bytes = EightByteBuffer(delta) + state = .emittingBytes(8 - deltaCount) + return (absolute: false, count: deltaCount) + } + } + + /// Set up to emit the bytes of `count`, returning the number of bytes + /// we will need to emit + private mutating func emitExternalCountNext( + _ count: Int + ) -> Int { + let ucount = UInt64(count) + let zeroes = ucount.leadingZeroBitCount >> 3 + let byteCount = 8 - zeroes + bytes = EightByteBuffer(ucount) + state = .emittingBytes(zeroes) + return byteCount + } + + public mutating func next() -> UInt8? { + switch state { + case .done: + return nil + + case .start: + // The first thing we emit is the info byte + let size: WordSize + switch Address.bitWidth { + case 16: + size = .sixteenBit + case 32: + size = .thirtyTwoBit + case 64: + size = .sixtyFourBit + default: + state = .done + return nil + } + + state = .ready + + let version: UInt8 = 0 + let infoByte = (version << 2) | size.rawValue + return infoByte + + case let .emittingBytes(ndx): + + let byte = bytes[ndx] + if ndx + 1 == 8 { + state = .ready + } else { + state = .emittingBytes(ndx + 1) + } + return byte + + case .ready: + + // Grab a rich frame and encode it + guard let frame = iterator.next() else { + state = .done + return nil + } + + switch frame { + case let .programCounter(addr): + let (absolute, count) = emitAddressNext(addr) + return Instruction.pc(absolute: absolute, + count: count).rawValue + case let .returnAddress(addr): + let (absolute, count) = emitAddressNext(addr) + return Instruction.ra(absolute: absolute, + count: count).rawValue + case let .asyncResumePoint(addr): + let (absolute, count) = emitAddressNext(addr) + return Instruction.async(absolute: absolute, + count: count).rawValue + case let .omittedFrames(count): + if count <= 0x1f { + return Instruction.omit(external: false, + count: count).rawValue + } + let countCount = emitExternalCountNext(count) + return Instruction.omit(external: true, + count: countCount).rawValue + case .truncated: + self.state = .done + return Instruction.trunc.rawValue + } + } + } + } + } +} + +extension CompactBacktraceFormat.Instruction: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } + public static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue == rhs.rawValue + } +} + +extension CompactBacktraceFormat.Instruction { + func decoded() -> CompactBacktraceFormat.DecodedInstruction? { + switch self { + case .end: + return .end + case .trunc: + return .trunc + case .pc_first ... .pc_last: + let count = Int((self.rawValue & 0x7) + 1) + let absolute = (self.rawValue & 0x8) != 0 + return .pc(absolute: absolute, count: count) + case .ra_first ... .ra_last: + let count = Int((self.rawValue & 0x7) + 1) + let absolute = (self.rawValue & 0x8) != 0 + return .ra(absolute: absolute, count: count) + case .async_first ... .async_last: + let count = Int((self.rawValue & 0x7) + 1) + let absolute = (self.rawValue & 0x8) != 0 + return .async(absolute: absolute, count: count) + case .omit_first ... .omit_last: + let count = Int((self.rawValue & 0x1f) + 1) + let external = (self.rawValue & 0x20) != 0 + return .omit(external: external, count: count) + default: + return nil + } + } +} diff --git a/stdlib/public/Backtracing/Compression.swift b/stdlib/public/RuntimeModule/Compression.swift similarity index 57% rename from stdlib/public/Backtracing/Compression.swift rename to stdlib/public/RuntimeModule/Compression.swift index 0b0f0c0e7e2..575e7b7a58f 100644 --- a/stdlib/public/Backtracing/Compression.swift +++ b/stdlib/public/RuntimeModule/Compression.swift @@ -25,8 +25,6 @@ // //===----------------------------------------------------------------------===// -#if os(Linux) - import Swift #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) @@ -56,18 +54,26 @@ let lzma_stream_init = swift.runtime.lzma_stream_init // .. CompressedStream ......................................................... protocol CompressedStream { - typealias InputSource = () throws -> UnsafeBufferPointer + typealias InputSource = () throws -> UnsafeRawBufferPointer typealias OutputSink = (_ used: UInt, _ done: Bool) throws - -> UnsafeMutableBufferPointer? + -> UnsafeMutableRawBufferPointer? func decompress(input: InputSource, output: OutputSink) throws -> UInt } // .. Compression library bindings ............................................. +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +private var lzmaHandle = dlopen("liblzma.dylib", RTLD_LAZY) +private var zlibHandle = dlopen("libz.dylib", RTLD_LAZY) +private var zstdHandle = dlopen("libzstd.dylib", RTLD_LAZY) +#elseif os(Linux) private var lzmaHandle = dlopen("liblzma.so.5", RTLD_LAZY) private var zlibHandle = dlopen("libz.so.1", RTLD_LAZY) private var zstdHandle = dlopen("libzstd.so.1", RTLD_LAZY) +#elseif os(Windows) +// ###TODO +#endif private func symbol(_ handle: UnsafeMutableRawPointer?, _ name: String) -> T? { guard let handle = handle, let result = dlsym(handle, name) else { @@ -152,7 +158,9 @@ struct ZLibStream: CompressedStream { let buffer = try input() // Not really mutable; this is just an issue with z_const - stream.next_in = UnsafeMutablePointer(mutating: buffer.baseAddress) + stream.next_in = UnsafeMutablePointer( + mutating: buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) + ) stream.avail_in = CUnsignedInt(buffer.count) } @@ -161,7 +169,7 @@ struct ZLibStream: CompressedStream { throw CompressedImageSourceError.outputOverrun } - stream.next_out = buffer.baseAddress + stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) stream.avail_out = CUnsignedInt(buffer.count) outputBufferSize = UInt(buffer.count) } @@ -211,7 +219,7 @@ struct ZStdStream: CompressedStream { if inBuffer.size == inBuffer.pos { let buffer = try input() - inBuffer.src = UnsafeRawPointer(buffer.baseAddress) + inBuffer.src = buffer.baseAddress inBuffer.size = buffer.count inBuffer.pos = 0 } @@ -225,7 +233,7 @@ struct ZStdStream: CompressedStream { throw CompressedImageSourceError.outputOverrun } - outBuffer.dst = UnsafeMutableRawPointer(buffer.baseAddress) + outBuffer.dst = buffer.baseAddress outBuffer.size = buffer.count outBuffer.pos = 0 } @@ -280,7 +288,7 @@ struct LZMAStream: CompressedStream { while true { if stream.avail_in == 0 { let buffer = try input() - stream.next_in = buffer.baseAddress + stream.next_in = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) stream.avail_in = buffer.count } @@ -289,7 +297,7 @@ struct LZMAStream: CompressedStream { throw CompressedImageSourceError.outputOverrun } - stream.next_out = buffer.baseAddress + stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) stream.avail_out = buffer.count outputBufferSize = UInt(buffer.count) } @@ -310,230 +318,130 @@ struct LZMAStream: CompressedStream { // .. Image Sources ............................................................ -fileprivate func decompress( - stream: S, source: I, dataBounds: I.Bounds, uncompressedSize: UInt? = nil) - throws -> [UInt8] { +fileprivate func decompress( + stream: S, + source: ImageSource, + offset: Int, + output: inout ImageSource +) throws { + let totalBytes = try stream.decompress( + input: { + () throws -> UnsafeRawBufferPointer in - var pos = dataBounds.base - var remaining = dataBounds.size + return UnsafeRawBufferPointer(rebasing: source.bytes[offset...]) + }, + output: { + (used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in - let bufSize = 65536 - - if let uncompressedSize = uncompressedSize { - // If we know the uncompressed size, we can decompress directly into the - // array. - - let inputBuffer - = UnsafeMutableBufferPointer.allocate(capacity: bufSize) - defer { - inputBuffer.deallocate() - } - - return try [UInt8].init(unsafeUninitializedCapacity: Int(uncompressedSize)) { - (outputBuffer: inout UnsafeMutableBufferPointer, - count: inout Int) in - - count = Int(try stream.decompress( - input: { () throws -> UnsafeBufferPointer in - - let chunkSize = min(Int(remaining), inputBuffer.count) - let slice = inputBuffer[0.. UnsafeMutableBufferPointer? in - - if used == 0 { - return outputBuffer - } else { - return nil - } - } - )) - } - } else { - // Otherwise, we decompress in chunks and append them to the array. - - let buffer - = UnsafeMutableBufferPointer.allocate(capacity: 2 * bufSize) - defer { - buffer.deallocate() - } - - let inputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[0.. UnsafeBufferPointer in - - let chunkSize = min(Int(remaining), inputBuffer.count) - let slice = inputBuffer[0.. UnsafeMutableBufferPointer? in - - data.append(contentsOf: outputBuffer[..: ImageSource { +fileprivate func decompressChunked( + stream: S, + source: ImageSource, + offset: Int, + output: inout ImageSource +) throws { + let bufSize = 65536 + let outputBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bufSize, + alignment: 16) + defer { + outputBuffer.deallocate() + } - private var data: [UInt8] + let _ = try stream.decompress( + input: { + () throws -> UnsafeRawBufferPointer in - var isMappedImage: Bool { return false } - var path: String? { return nil } - var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) } + return UnsafeRawBufferPointer(rebasing: source.bytes[offset...]) + }, + output: { + (used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in - init(source: some ImageSource) throws { - guard let bounds = source.bounds else { - throw CompressedImageSourceError.unboundedImageSource + output.append( + bytes: UnsafeRawBufferPointer(rebasing: outputBuffer[...size { +extension ImageSource { + @_specialize(kind: full, where Traits == Elf32Traits) + @_specialize(kind: full, where Traits == Elf64Traits) + init(elfCompressedImageSource source: ImageSource, + traits: Traits.Type) throws { + if source.bytes.count < MemoryLayout.size { throw CompressedImageSourceError.badCompressedData } - let chdr = try source.fetch(from: bounds.base, - as: Traits.Chdr.self) - let dataBounds = bounds.adjusted(by: MemoryLayout.stride) + let rawChdr = try source.fetch(from: 0, as: Traits.Chdr.self) + let chdr: Traits.Chdr + switch rawChdr.ch_type { + case .ELFCOMPRESS_ZLIB.byteSwapped, .ELFCOMPRESS_ZSTD.byteSwapped: + chdr = rawChdr.byteSwapped + default: + chdr = rawChdr + } + let uncompressedSize = UInt(chdr.ch_size) + self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil) + switch chdr.ch_type { case .ELFCOMPRESS_ZLIB: - data = try decompress(stream: ZLibStream(), - source: source, dataBounds: dataBounds, - uncompressedSize: uncompressedSize) + try decompress(stream: ZLibStream(), + source: source, offset: MemoryLayout.stride, + output: &self) case .ELFCOMPRESS_ZSTD: - data = try decompress(stream: ZStdStream(), - source: source, dataBounds: dataBounds, - uncompressedSize: uncompressedSize) + try decompress(stream: ZStdStream(), + source: source, offset: MemoryLayout.stride, + output: &self) default: throw CompressedImageSourceError.unsupportedFormat } } - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - let toFetch = buffer.count - if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch { - throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch) - } - - buffer.withMemoryRebound(to: UInt8.self) { outBuf in - for n in 0.. data.count || data.count - Int(addr) < toFetch { - throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch) - } + init(lzmaCompressedImageSource source: ImageSource) throws { + self.init(isMappedImage: false, path: nil) - buffer.withMemoryRebound(to: UInt8.self) { outBuf in - for n in 0.. data.count || data.count - Int(addr) < toFetch { - throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch) - } - - buffer.withMemoryRebound(to: UInt8.self) { outBuf in - for n in 0.. (any ImageSource)? + func getDwarfSection(_ section: DwarfSection) -> ImageSource? } @@ -523,14 +521,14 @@ struct DwarfReader { var attributes: [(Dwarf_Attribute, Dwarf_Form, Int64?)] } - var infoSection: any ImageSource - var abbrevSection: any ImageSource - var lineSection: (any ImageSource)? - var addrSection: (any ImageSource)? - var strSection: (any ImageSource)? - var lineStrSection: (any ImageSource)? - var strOffsetsSection: (any ImageSource)? - var rangesSection: (any ImageSource)? + var infoSection: ImageSource + var abbrevSection: ImageSource + var lineSection: ImageSource? + var addrSection: ImageSource? + var strSection: ImageSource? + var lineStrSection: ImageSource? + var strOffsetsSection: ImageSource? + var rangesSection: ImageSource? var shouldSwap: Bool typealias DwarfAbbrev = UInt64 @@ -557,263 +555,9 @@ struct DwarfReader { var attributes: [Dwarf_Attribute:DwarfValue] = [:] } - struct FileInfo { - var path: String - var directoryIndex: Int? - var timestamp: Int? - var size: UInt64? - var md5sum: [UInt8]? - } - - struct LineNumberState: CustomStringConvertible { - var address: Address - var opIndex: UInt - var file: Int - var path: String - var line: Int - var column: Int - var isStmt: Bool - var basicBlock: Bool - var endSequence: Bool - var prologueEnd: Bool - var epilogueBegin: Bool - var isa: UInt - var discriminator: UInt - - var description: String { - var flags: [String] = [] - if isStmt { - flags.append("is_stmt") - } - if basicBlock { - flags.append("basic_block") - } - if endSequence { - flags.append("end_sequence") - } - if prologueEnd { - flags.append("prologue_end") - } - if epilogueBegin { - flags.append("epilogue_begin") - } - - let flagsString = flags.joined(separator:" ") - - return """ - \(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \ - \(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString) - """ - } - } - - struct LineNumberInfo { - var baseOffset: Address - var version: Int - var addressSize: Int? - var selectorSize: Int? - var headerLength: UInt64 - var minimumInstructionLength: UInt - var maximumOpsPerInstruction: UInt - var defaultIsStmt: Bool - var lineBase: Int8 - var lineRange: UInt8 - var opcodeBase: UInt8 - var standardOpcodeLengths: [UInt64] - var directories: [String] = [] - var files: [FileInfo] = [] - var program: [UInt8] = [] - var shouldSwap: Bool - - /// Compute the full path for a file, given its index in the file table. - func fullPathForFile(index: Int) -> String { - if index >= files.count { - return "" - } - - let info = files[index] - if info.path.hasPrefix("/") { - return info.path - } - - let dirName: String - if let dirIndex = info.directoryIndex, - dirIndex < directories.count { - dirName = directories[dirIndex] - } else { - dirName = "" - } - - return "\(dirName)/\(info.path)" - } - - /// Execute the line number program, calling a closure for every line - /// table entry. - mutating func executeProgram( - line: (LineNumberState, inout Bool) -> () - ) throws { - let source = ArrayImageSource(array: program) - let bounds = source.bounds! - var cursor = ImageSourceCursor(source: source) - - func maybeSwap(_ x: T) -> T { - if shouldSwap { - return x.byteSwapped - } - return x - } - - // Table 6.4: Line number program initial state - let initialState = LineNumberState( - address: 0, - opIndex: 0, - file: 1, - path: fullPathForFile(index: 1), - line: 1, - column: 0, - isStmt: defaultIsStmt, - basicBlock: false, - endSequence: false, - prologueEnd: false, - epilogueBegin: false, - isa: 0, - discriminator: 0 - ) - - var state = initialState - - // Flag to allow fast exit - var done = false - - while !done && cursor.pos < bounds.end { - let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self) - - if opcode.rawValue >= opcodeBase { - // Special opcode - let adjustedOpcode = UInt(opcode.rawValue - opcodeBase) - let advance = adjustedOpcode / UInt(lineRange) - let lineAdvance = adjustedOpcode % UInt(lineRange) - let instrAdvance - = (state.opIndex + advance) / maximumOpsPerInstruction - let newOp = (state.opIndex + advance) % maximumOpsPerInstruction - state.address += Address(instrAdvance) - state.opIndex = newOp - state.line += Int(lineBase) + Int(lineAdvance) - - line(state, &done) - - state.discriminator = 0 - state.basicBlock = false - state.prologueEnd = false - state.epilogueBegin = false - } else if opcode == .DW_LNS_extended { - // Extended opcode - let length = try cursor.readULEB128() - let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self) - - switch opcode { - case .DW_LNE_end_sequence: - state.endSequence = true - line(state, &done) - state = initialState - case .DW_LNE_set_address: - let address: UInt64 - guard let addressSize = addressSize else { - throw DwarfError.unspecifiedAddressSize - } - switch addressSize { - case 4: - address = UInt64(maybeSwap(try cursor.read(as: UInt32.self))) - case 8: - address = maybeSwap(try cursor.read(as: UInt64.self)) - default: - throw DwarfError.badAddressSize(addressSize) - } - state.address = Address(address) - case .DW_LNE_define_file: - guard let path = try cursor.readString() else { - throw DwarfError.badString - } - let directoryIndex = try cursor.readULEB128() - let timestamp = try cursor.readULEB128() - let size = try cursor.readULEB128() - files.append(FileInfo( - path: path, - directoryIndex: Int(directoryIndex), - timestamp: timestamp != 0 ? Int(timestamp) : nil, - size: size != 0 ? size : nil, - md5sum: nil - )) - case .DW_LNE_set_discriminator: - let discriminator = try cursor.readULEB128() - state.discriminator = UInt(discriminator) - default: - cursor.pos += length - 1 - } - } else { - // Standard opcode - switch opcode { - case .DW_LNS_copy: - line(state, &done) - state.discriminator = 0 - state.basicBlock = false - state.prologueEnd = false - state.epilogueBegin = false - case .DW_LNS_advance_pc: - let advance = UInt(try cursor.readULEB128()) - let instrAdvance - = (state.opIndex + advance) / maximumOpsPerInstruction - let newOp = (state.opIndex + advance) % maximumOpsPerInstruction - state.address += Address(instrAdvance) - state.opIndex = newOp - case .DW_LNS_advance_line: - let advance = try cursor.readSLEB128() - state.line += Int(advance) - case .DW_LNS_set_file: - let file = Int(try cursor.readULEB128()) - state.file = file - state.path = fullPathForFile(index: state.file) - case .DW_LNS_set_column: - let column = Int(try cursor.readULEB128()) - state.column = column - case .DW_LNS_negate_stmt: - state.isStmt = !state.isStmt - case .DW_LNS_set_basic_block: - state.basicBlock = true - case .DW_LNS_const_add_pc: - let adjustedOpcode = UInt(255 - opcodeBase) - let advance = adjustedOpcode / UInt(lineRange) - let instrAdvance - = (state.opIndex + advance) / maximumOpsPerInstruction - let newOp = (state.opIndex + advance) % maximumOpsPerInstruction - state.address += Address(instrAdvance) - state.opIndex = newOp - case .DW_LNS_fixed_advance_pc: - let advance = try cursor.read(as: Dwarf_Half.self) - state.address += Address(advance) - state.opIndex = 0 - case .DW_LNS_set_prologue_end: - state.prologueEnd = true - case .DW_LNS_set_epilogue_begin: - state.epilogueBegin = true - case .DW_LNS_set_isa: - let isa = UInt(try cursor.readULEB128()) - state.isa = isa - default: - // Skip this unknown opcode - let length = standardOpcodeLengths[Int(opcode.rawValue)] - for _ in 0.. { var rangeListInfo: RangeListInfo? + @_specialize(kind: full, where S == Elf32Image) + @_specialize(kind: full, where S == Elf64Image) init(source: Source, shouldSwap: Bool = false) throws { // ###TODO: This should be optional, because we can have just line number // information. We should test that, too. @@ -873,7 +619,7 @@ struct DwarfReader { } lineNumberInfo[n].directories[0] = dirname - lineNumberInfo[n].files[0] = FileInfo( + lineNumberInfo[n].files[0] = DwarfFileInfo( path: filename, directoryIndex: 0, timestamp: nil, @@ -896,14 +642,11 @@ struct DwarfReader { } private func readUnits() throws -> [Unit] { - guard let bounds = infoSection.bounds else { - return [] - } - + let end = infoSection.bytes.count var units: [Unit] = [] var cursor = ImageSourceCursor(source: infoSection) - while cursor.pos < bounds.end { + while cursor.pos < end { // See 7.5.1.1 Full and Partial Compilation Unit Headers let base = cursor.pos @@ -1053,16 +796,16 @@ struct DwarfReader { return units } - private func readLineNumberInfo() throws -> [LineNumberInfo] { - guard let lineSection = lineSection, - let bounds = lineSection.bounds else { + private func readLineNumberInfo() throws -> [DwarfLineNumberInfo] { + guard let lineSection = lineSection else { return [] } - var result: [LineNumberInfo] = [] + let end = lineSection.bytes.count + var result: [DwarfLineNumberInfo] = [] var cursor = ImageSourceCursor(source: lineSection, offset: 0) - while cursor.pos < bounds.end { + while cursor.pos < end { // 6.2.4 The Line Number Program Header // .1 unit_length @@ -1127,7 +870,7 @@ struct DwarfReader { } var dirNames: [String] = [] - var fileInfo: [FileInfo] = [] + var fileInfo: [DwarfFileInfo] = [] if version == 3 || version == 4 { // .11 include_directories @@ -1152,7 +895,7 @@ struct DwarfReader { // Prior to version 5, the compilation unit's filename is not included; // put a placeholder here for now, which we'll fix up later. - fileInfo.append(FileInfo( + fileInfo.append(DwarfFileInfo( path: "", directoryIndex: 0, timestamp: nil, @@ -1172,7 +915,7 @@ struct DwarfReader { let timestamp = try cursor.readULEB128() let size = try cursor.readULEB128() - fileInfo.append(FileInfo( + fileInfo.append(DwarfFileInfo( path: path, directoryIndex: Int(dirIndex), timestamp: timestamp != 0 ? Int(timestamp) : nil, @@ -1275,7 +1018,7 @@ struct DwarfReader { md5sum = nil } - fileInfo.append(FileInfo( + fileInfo.append(DwarfFileInfo( path: path, directoryIndex: dirIndex, timestamp: timestamp, @@ -1285,12 +1028,10 @@ struct DwarfReader { } // The actual program comes next - let program = try cursor.read(count: Int(nextOffset - cursor.pos), - as: UInt8.self) - + let program = cursor.source[cursor.pos.. { if tag != .DW_TAG_subprogram { return } - + refAttrs = try readDieAttributes( at: &cursor, unit: unit, @@ -2005,11 +1746,268 @@ struct DwarfReader { } +struct DwarfFileInfo { + var path: String + var directoryIndex: Int? + var timestamp: Int? + var size: UInt64? + var md5sum: [UInt8]? +} + +struct DwarfLineNumberState: CustomStringConvertible { + typealias Address = UInt64 + + var address: Address + var opIndex: UInt + var file: Int + var path: String + var line: Int + var column: Int + var isStmt: Bool + var basicBlock: Bool + var endSequence: Bool + var prologueEnd: Bool + var epilogueBegin: Bool + var isa: UInt + var discriminator: UInt + + var description: String { + var flags: [String] = [] + if isStmt { + flags.append("is_stmt") + } + if basicBlock { + flags.append("basic_block") + } + if endSequence { + flags.append("end_sequence") + } + if prologueEnd { + flags.append("prologue_end") + } + if epilogueBegin { + flags.append("epilogue_begin") + } + + let flagsString = flags.joined(separator:" ") + + return """ + \(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \ + \(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString) + """ + } +} + +struct DwarfLineNumberInfo { + typealias Address = UInt64 + + var baseOffset: Address + var version: Int + var addressSize: Int? + var selectorSize: Int? + var headerLength: UInt64 + var minimumInstructionLength: UInt + var maximumOpsPerInstruction: UInt + var defaultIsStmt: Bool + var lineBase: Int8 + var lineRange: UInt8 + var opcodeBase: UInt8 + var standardOpcodeLengths: [UInt64] + var directories: [String] = [] + var files: [DwarfFileInfo] = [] + var program: ImageSource + var shouldSwap: Bool + + /// Compute the full path for a file, given its index in the file table. + func fullPathForFile(index: Int) -> String { + if index >= files.count { + return "" + } + + let info = files[index] + if info.path.hasPrefix("/") { + return info.path + } + + let dirName: String + if let dirIndex = info.directoryIndex, + dirIndex < directories.count { + dirName = directories[dirIndex] + } else { + dirName = "" + } + + return "\(dirName)/\(info.path)" + } + + /// Execute the line number program, calling a closure for every line + /// table entry. + mutating func executeProgram( + line: (DwarfLineNumberState, inout Bool) -> () + ) throws { + let end = program.bytes.count + var cursor = ImageSourceCursor(source: program) + + func maybeSwap(_ x: T) -> T { + if shouldSwap { + return x.byteSwapped + } + return x + } + + // Table 6.4: Line number program initial state + let initialState = DwarfLineNumberState( + address: 0, + opIndex: 0, + file: 1, + path: fullPathForFile(index: 1), + line: 1, + column: 0, + isStmt: defaultIsStmt, + basicBlock: false, + endSequence: false, + prologueEnd: false, + epilogueBegin: false, + isa: 0, + discriminator: 0 + ) + + var state = initialState + + // Flag to allow fast exit + var done = false + + while !done && cursor.pos < end { + let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self) + + if opcode.rawValue >= opcodeBase { + // Special opcode + let adjustedOpcode = UInt(opcode.rawValue - opcodeBase) + let advance = adjustedOpcode / UInt(lineRange) + let lineAdvance = adjustedOpcode % UInt(lineRange) + let instrAdvance + = (state.opIndex + advance) / maximumOpsPerInstruction + let newOp = (state.opIndex + advance) % maximumOpsPerInstruction + state.address += Address(instrAdvance) + state.opIndex = newOp + state.line += Int(lineBase) + Int(lineAdvance) + + line(state, &done) + + state.discriminator = 0 + state.basicBlock = false + state.prologueEnd = false + state.epilogueBegin = false + } else if opcode == .DW_LNS_extended { + // Extended opcode + let length = try cursor.readULEB128() + let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self) + + switch opcode { + case .DW_LNE_end_sequence: + state.endSequence = true + line(state, &done) + state = initialState + case .DW_LNE_set_address: + let address: UInt64 + guard let addressSize = addressSize else { + throw DwarfError.unspecifiedAddressSize + } + switch addressSize { + case 4: + address = UInt64(maybeSwap(try cursor.read(as: UInt32.self))) + case 8: + address = maybeSwap(try cursor.read(as: UInt64.self)) + default: + throw DwarfError.badAddressSize(addressSize) + } + state.address = Address(address) + case .DW_LNE_define_file: + guard let path = try cursor.readString() else { + throw DwarfError.badString + } + let directoryIndex = try cursor.readULEB128() + let timestamp = try cursor.readULEB128() + let size = try cursor.readULEB128() + files.append(DwarfFileInfo( + path: path, + directoryIndex: Int(directoryIndex), + timestamp: timestamp != 0 ? Int(timestamp) : nil, + size: size != 0 ? size : nil, + md5sum: nil + )) + case .DW_LNE_set_discriminator: + let discriminator = try cursor.readULEB128() + state.discriminator = UInt(discriminator) + default: + cursor.pos += length - 1 + } + } else { + // Standard opcode + switch opcode { + case .DW_LNS_copy: + line(state, &done) + state.discriminator = 0 + state.basicBlock = false + state.prologueEnd = false + state.epilogueBegin = false + case .DW_LNS_advance_pc: + let advance = UInt(try cursor.readULEB128()) + let instrAdvance + = (state.opIndex + advance) / maximumOpsPerInstruction + let newOp = (state.opIndex + advance) % maximumOpsPerInstruction + state.address += Address(instrAdvance) + state.opIndex = newOp + case .DW_LNS_advance_line: + let advance = try cursor.readSLEB128() + state.line += Int(advance) + case .DW_LNS_set_file: + let file = Int(try cursor.readULEB128()) + state.file = file + state.path = fullPathForFile(index: state.file) + case .DW_LNS_set_column: + let column = Int(try cursor.readULEB128()) + state.column = column + case .DW_LNS_negate_stmt: + state.isStmt = !state.isStmt + case .DW_LNS_set_basic_block: + state.basicBlock = true + case .DW_LNS_const_add_pc: + let adjustedOpcode = UInt(255 - opcodeBase) + let advance = adjustedOpcode / UInt(lineRange) + let instrAdvance + = (state.opIndex + advance) / maximumOpsPerInstruction + let newOp = (state.opIndex + advance) % maximumOpsPerInstruction + state.address += Address(instrAdvance) + state.opIndex = newOp + case .DW_LNS_fixed_advance_pc: + let advance = try cursor.read(as: Dwarf_Half.self) + state.address += Address(advance) + state.opIndex = 0 + case .DW_LNS_set_prologue_end: + state.prologueEnd = true + case .DW_LNS_set_epilogue_begin: + state.epilogueBegin = true + case .DW_LNS_set_isa: + let isa = UInt(try cursor.readULEB128()) + state.isa = isa + default: + // Skip this unknown opcode + let length = standardOpcodeLengths[Int(opcode.rawValue)] + for _ in 0.. Bool { - guard let source = try? FileImageSource(path: path) else { + guard let source = try? ImageSource(path: path) else { print("\(path) was not accessible") return false } @@ -2017,7 +2015,7 @@ public func testDwarfReaderFor(path: String) -> Bool { if let elfImage = try? Elf32Image(source: source) { print("\(path) is a 32-bit ELF image") - var reader: DwarfReader> + var reader: DwarfReader do { reader = try DwarfReader(source: elfImage) } catch { @@ -2034,7 +2032,7 @@ public func testDwarfReaderFor(path: String) -> Bool { } else if let elfImage = try? Elf64Image(source: source) { print("\(path) is a 64-bit ELF image") - var reader: DwarfReader> + var reader: DwarfReader do { reader = try DwarfReader(source: elfImage) } catch { @@ -2060,5 +2058,3 @@ public func testDwarfReaderFor(path: String) -> Bool { return false } } - -#endif // os(Linux) diff --git a/stdlib/public/RuntimeModule/EightByteBuffer.swift b/stdlib/public/RuntimeModule/EightByteBuffer.swift new file mode 100644 index 00000000000..e1cfcc8ba42 --- /dev/null +++ b/stdlib/public/RuntimeModule/EightByteBuffer.swift @@ -0,0 +1,60 @@ +//===--- EightByteBuffer.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// A statically allocated buffer for holding a small number of bytes. +// +//===----------------------------------------------------------------------===// + +import Swift + +struct EightByteBuffer { + var word: UInt64 + + init() { + word = 0 + } + + init(_ qword: UInt64) { + word = qword.bigEndian + } + + init(_ qword: Int64) { + self.init(UInt64(bitPattern: qword)) + } + + init(_ value: T) where T: SignedInteger { + self.init(Int64(value)) + } + + init(_ value: T) { + self.init(UInt64(value)) + } + + subscript(ndx: Int) -> UInt8 { + get { + if ndx < 0 || ndx >= 8 { + fatalError("Index out of range") + } + return withUnsafeBytes(of: word) { buffer in + return buffer[ndx] + } + } + set(newValue) { + if ndx < 0 || ndx >= 8 { + fatalError("Index out of range") + } + withUnsafeMutableBytes(of: &word) { buffer in + buffer[ndx] = newValue + } + } + } +} diff --git a/stdlib/public/Backtracing/Elf.swift b/stdlib/public/RuntimeModule/Elf.swift similarity index 71% rename from stdlib/public/Backtracing/Elf.swift rename to stdlib/public/RuntimeModule/Elf.swift index 3c37ad47c0d..c603d3d92a6 100644 --- a/stdlib/public/Backtracing/Elf.swift +++ b/stdlib/public/RuntimeModule/Elf.swift @@ -17,8 +17,6 @@ // ###FIXME: We shouldn't really use String for paths. -#if os(Linux) - import Swift #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) @@ -75,7 +73,6 @@ let EI_VERSION = swift.runtime.EI_VERSION let EI_OSABI = swift.runtime.EI_OSABI let EI_ABIVERSION = swift.runtime.EI_ABIVERSION let EI_PAD = swift.runtime.EI_PAD -let EI_NIDENT = BacktracingImpl.EI_NIDENT let ELFMAG0 = swift.runtime.ELFMAG0 let ELFMAG1 = swift.runtime.ELFMAG1 @@ -83,109 +80,105 @@ let ELFMAG2 = swift.runtime.ELFMAG2 let ELFMAG3 = swift.runtime.ELFMAG3 typealias Elf_Ehdr_Class = swift.runtime.Elf_Ehdr_Class -typealias Elf_Ehdr_Data = swift.runtime.Elf_Ehdr_Data +typealias Elf_Ehdr_Data = swift.runtime.Elf_Ehdr_Data typealias Elf_Ehdr_OsAbi = swift.runtime.Elf_Ehdr_OsAbi -let SHN_UNDEF = swift.runtime.SHN_UNDEF +let SHN_UNDEF = swift.runtime.SHN_UNDEF let SHN_LORESERVE = swift.runtime.SHN_LORESERVE -let SHN_LOPROC = swift.runtime.SHN_LOPROC -let SHN_HIPROC = swift.runtime.SHN_HIPROC -let SHN_LOOS = swift.runtime.SHN_LOOS -let SHN_HIOS = swift.runtime.SHN_HIOS -let SHN_ABS = swift.runtime.SHN_ABS -let SHN_COMMON = swift.runtime.SHN_COMMON -let SHN_XINDEX = swift.runtime.SHN_XINDEX +let SHN_LOPROC = swift.runtime.SHN_LOPROC +let SHN_HIPROC = swift.runtime.SHN_HIPROC +let SHN_LOOS = swift.runtime.SHN_LOOS +let SHN_HIOS = swift.runtime.SHN_HIOS +let SHN_ABS = swift.runtime.SHN_ABS +let SHN_COMMON = swift.runtime.SHN_COMMON +let SHN_XINDEX = swift.runtime.SHN_XINDEX let SHN_HIRESERVE = swift.runtime.SHN_HIRESERVE typealias Elf_Shdr_Type = swift.runtime.Elf_Shdr_Type -let SHF_WRITE = swift.runtime.SHF_WRITE -let SHF_ALLOC = swift.runtime.SHF_ALLOC -let SHF_EXECINSTR = swift.runtime.SHF_EXECINSTR -let SHF_MERGE = swift.runtime.SHF_MERGE -let SHF_STRINGS = swift.runtime.SHF_STRINGS -let SHF_INFO_LINK = swift.runtime.SHF_INFO_LINK -let SHF_LINK_ORDER = swift.runtime.SHF_LINK_ORDER +let SHF_WRITE = swift.runtime.SHF_WRITE +let SHF_ALLOC = swift.runtime.SHF_ALLOC +let SHF_EXECINSTR = swift.runtime.SHF_EXECINSTR +let SHF_MERGE = swift.runtime.SHF_MERGE +let SHF_STRINGS = swift.runtime.SHF_STRINGS +let SHF_INFO_LINK = swift.runtime.SHF_INFO_LINK +let SHF_LINK_ORDER = swift.runtime.SHF_LINK_ORDER let SHF_OS_NONCONFORMING = swift.runtime.SHF_OS_NONCONFORMING -let SHF_GROUP = swift.runtime.SHF_GROUP -let SHF_TLS = swift.runtime.SHF_TLS -let SHF_COMPRESSED = swift.runtime.SHF_COMPRESSED -let SHF_MASKOS = swift.runtime.SHF_MASKOS -let SHF_MASKPROC = swift.runtime.SHF_MASKPROC +let SHF_GROUP = swift.runtime.SHF_GROUP +let SHF_TLS = swift.runtime.SHF_TLS +let SHF_COMPRESSED = swift.runtime.SHF_COMPRESSED +let SHF_MASKOS = swift.runtime.SHF_MASKOS +let SHF_MASKPROC = swift.runtime.SHF_MASKPROC -let GRP_COMDAT = swift.runtime.GRP_COMDAT -let GRP_MASKOS = swift.runtime.GRP_MASKOS +let GRP_COMDAT = swift.runtime.GRP_COMDAT +let GRP_MASKOS = swift.runtime.GRP_MASKOS let GRP_MASKPROC = swift.runtime.GRP_MASKPROC typealias Elf_Chdr_Type = swift.runtime.Elf_Chdr_Type -typealias Elf_Sym_Binding = swift.runtime.Elf_Sym_Binding -typealias Elf_Sym_Type = swift.runtime.Elf_Sym_Type +typealias Elf_Sym_Binding = swift.runtime.Elf_Sym_Binding +typealias Elf_Sym_Type = swift.runtime.Elf_Sym_Type typealias Elf_Sym_Visibility = swift.runtime.Elf_Sym_Visibility -typealias Elf_Phdr_Type = swift.runtime.Elf_Phdr_Type +typealias Elf_Phdr_Type = swift.runtime.Elf_Phdr_Type typealias Elf_Phdr_Flags = swift.runtime.Elf_Phdr_Flags let PF_X = swift.runtime.PF_X let PF_W = swift.runtime.PF_W let PF_R = swift.runtime.PF_R -let PF_MASKOS = swift.runtime.PF_MASKOS +let PF_MASKOS = swift.runtime.PF_MASKOS let PF_MASKPROC = swift.runtime.PF_MASKPROC -let DT_NULL = swift.runtime.DT_NULL -let DT_NEEDED = swift.runtime.DT_NEEDED -let DT_PLTRELSZ = swift.runtime.DT_PLTRELSZ -let DT_PLTGOT = swift.runtime.DT_PLTGOT -let DT_HASH = swift.runtime.DT_HASH -let DT_STRTAB = swift.runtime.DT_STRTAB -let DT_SYMTAB = swift.runtime.DT_SYMTAB -let DT_RELA = swift.runtime.DT_RELA -let DT_RELASZ = swift.runtime.DT_RELASZ -let DT_RELAENT = swift.runtime.DT_RELAENT -let DT_STRSZ = swift.runtime.DT_STRSZ -let DT_SYMENT = swift.runtime.DT_SYMENT -let DT_INIT = swift.runtime.DT_INIT -let DT_FINI = swift.runtime.DT_FINI -let DT_SONAME = swift.runtime.DT_SONAME -let DT_RPATH = swift.runtime.DT_RPATH -let DT_SYMBOLIC = swift.runtime.DT_SYMBOLIC -let DT_REL = swift.runtime.DT_REL -let DT_RELSZ = swift.runtime.DT_RELSZ -let DT_RELENT = swift.runtime.DT_RELENT -let DT_PLTREL = swift.runtime.DT_PLTREL -let DT_DEBUG = swift.runtime.DT_DEBUG -let DT_TEXTREL = swift.runtime.DT_TEXTREL -let DT_JMPREL = swift.runtime.DT_JMPREL -let DT_BIND_NOW = swift.runtime.DT_BIND_NOW -let DT_INIT_ARRAY = swift.runtime.DT_INIT_ARRAY -let DT_FINI_ARRAY = swift.runtime.DT_FINI_ARRAY -let DT_INIT_ARRAYSZ = swift.runtime.DT_INIT_ARRAYSZ -let DT_FINI_ARRAYSZ = swift.runtime.DT_FINI_ARRAYSZ -let DT_RUNPATH = swift.runtime.DT_RUNPATH -let DT_FLAGS = swift.runtime.DT_FLAGS - -let DT_ENCODING = swift.runtime.DT_ENCODING - -let DT_PREINIT_ARRAY = swift.runtime.DT_PREINIT_ARRAY +let DT_NULL = swift.runtime.DT_NULL +let DT_NEEDED = swift.runtime.DT_NEEDED +let DT_PLTRELSZ = swift.runtime.DT_PLTRELSZ +let DT_PLTGOT = swift.runtime.DT_PLTGOT +let DT_HASH = swift.runtime.DT_HASH +let DT_STRTAB = swift.runtime.DT_STRTAB +let DT_SYMTAB = swift.runtime.DT_SYMTAB +let DT_RELA = swift.runtime.DT_RELA +let DT_RELASZ = swift.runtime.DT_RELASZ +let DT_RELAENT = swift.runtime.DT_RELAENT +let DT_STRSZ = swift.runtime.DT_STRSZ +let DT_SYMENT = swift.runtime.DT_SYMENT +let DT_INIT = swift.runtime.DT_INIT +let DT_FINI = swift.runtime.DT_FINI +let DT_SONAME = swift.runtime.DT_SONAME +let DT_RPATH = swift.runtime.DT_RPATH +let DT_SYMBOLIC = swift.runtime.DT_SYMBOLIC +let DT_REL = swift.runtime.DT_REL +let DT_RELSZ = swift.runtime.DT_RELSZ +let DT_RELENT = swift.runtime.DT_RELENT +let DT_PLTREL = swift.runtime.DT_PLTREL +let DT_DEBUG = swift.runtime.DT_DEBUG +let DT_TEXTREL = swift.runtime.DT_TEXTREL +let DT_JMPREL = swift.runtime.DT_JMPREL +let DT_BIND_NOW = swift.runtime.DT_BIND_NOW +let DT_INIT_ARRAY = swift.runtime.DT_INIT_ARRAY +let DT_FINI_ARRAY = swift.runtime.DT_FINI_ARRAY +let DT_INIT_ARRAYSZ = swift.runtime.DT_INIT_ARRAYSZ +let DT_FINI_ARRAYSZ = swift.runtime.DT_FINI_ARRAYSZ +let DT_RUNPATH = swift.runtime.DT_RUNPATH +let DT_FLAGS = swift.runtime.DT_FLAGS +let DT_ENCODING = swift.runtime.DT_ENCODING +let DT_PREINIT_ARRAY = swift.runtime.DT_PREINIT_ARRAY let DT_PREINIT_ARRAYSZ = swift.runtime.DT_PREINIT_ARRAYSZ +let DT_LOOS = swift.runtime.DT_LOOS +let DT_HIOS = swift.runtime.DT_HIOS +let DT_LOPROC = swift.runtime.DT_LOPROC +let DT_HIPROC = swift.runtime.DT_HIPROC -let DT_LOOS = swift.runtime.DT_LOOS -let DT_HIOS = swift.runtime.DT_HIOS - -let DT_LOPROC = swift.runtime.DT_LOPROC -let DT_HIPROC = swift.runtime.DT_HIPROC - -let DF_ORIGIN = swift.runtime.DF_ORIGIN -let DF_SYMBOLIC = swift.runtime.DF_SYMBOLIC -let DF_TEXTREL = swift.runtime.DF_TEXTREL -let DF_BIND_NOW = swift.runtime.DF_BIND_NOW +let DF_ORIGIN = swift.runtime.DF_ORIGIN +let DF_SYMBOLIC = swift.runtime.DF_SYMBOLIC +let DF_TEXTREL = swift.runtime.DF_TEXTREL +let DF_BIND_NOW = swift.runtime.DF_BIND_NOW let DF_STATIC_TLS = swift.runtime.DF_STATIC_TLS -let NT_GNU_ABI_TAG = swift.runtime.NT_GNU_ABI_TAG -let NT_GNU_HWCAP = swift.runtime.NT_GNU_HWCAP -let NT_GNU_BUILD_ID = swift.runtime.NT_GNU_BUILD_ID -let NT_GNU_GOLD_VERSION = swift.runtime.NT_GNU_GOLD_VERSION +let NT_GNU_ABI_TAG = swift.runtime.NT_GNU_ABI_TAG +let NT_GNU_HWCAP = swift.runtime.NT_GNU_HWCAP +let NT_GNU_BUILD_ID = swift.runtime.NT_GNU_BUILD_ID +let NT_GNU_GOLD_VERSION = swift.runtime.NT_GNU_GOLD_VERSION let NT_GNU_PROPERTY_TYPE_0 = swift.runtime.NT_GNU_PROPERTY_TYPE_0 typealias Elf32_Ehdr = swift.runtime.Elf32_Ehdr @@ -200,32 +193,35 @@ typealias Elf64_Chdr = swift.runtime.Elf64_Chdr typealias Elf32_Sym = swift.runtime.Elf32_Sym typealias Elf64_Sym = swift.runtime.Elf64_Sym -let ELF32_ST_BIND = swift.runtime.ELF32_ST_BIND -let ELF32_ST_TYPE = swift.runtime.ELF32_ST_TYPE -let ELF32_ST_INFO = swift.runtime.ELF32_ST_INFO +let ELF32_ST_BIND = swift.runtime.ELF32_ST_BIND +let ELF32_ST_TYPE = swift.runtime.ELF32_ST_TYPE +let ELF32_ST_INFO = swift.runtime.ELF32_ST_INFO let ELF32_ST_VISIBILITY = swift.runtime.ELF32_ST_VISIBILITY -let ELF64_ST_BIND = swift.runtime.ELF64_ST_BIND -let ELF64_ST_TYPE = swift.runtime.ELF64_ST_TYPE -let ELF64_ST_INFO = swift.runtime.ELF64_ST_INFO +let ELF64_ST_BIND = swift.runtime.ELF64_ST_BIND +let ELF64_ST_TYPE = swift.runtime.ELF64_ST_TYPE +let ELF64_ST_INFO = swift.runtime.ELF64_ST_INFO let ELF64_ST_VISIBILITY = swift.runtime.ELF64_ST_VISIBILITY -typealias Elf32_Rel = swift.runtime.Elf32_Rel +typealias Elf32_Rel = swift.runtime.Elf32_Rel typealias Elf32_Rela = swift.runtime.Elf32_Rela -typealias Elf64_Rel = swift.runtime.Elf64_Rel +typealias Elf64_Rel = swift.runtime.Elf64_Rel typealias Elf64_Rela = swift.runtime.Elf64_Rela -let ELF32_R_SYM = swift.runtime.ELF32_R_SYM +let ELF32_R_SYM = swift.runtime.ELF32_R_SYM let ELF32_R_TYPE = swift.runtime.ELF32_R_TYPE let ELF32_R_INFO = swift.runtime.ELF32_R_INFO -let ELF64_R_SYM = swift.runtime.ELF64_R_SYM +let ELF64_R_SYM = swift.runtime.ELF64_R_SYM let ELF64_R_TYPE = swift.runtime.ELF64_R_TYPE let ELF64_R_INFO = swift.runtime.ELF64_R_INFO typealias Elf32_Phdr = swift.runtime.Elf32_Phdr typealias Elf64_Phdr = swift.runtime.Elf64_Phdr +typealias Elf32_Nhdr = swift.runtime.Elf32_Nhdr +typealias Elf64_Nhdr = swift.runtime.Elf64_Nhdr + typealias Elf32_Dyn = swift.runtime.Elf32_Dyn typealias Elf64_Dyn = swift.runtime.Elf64_Dyn @@ -302,7 +298,7 @@ private let crc32Table: [UInt32] = [ ] private func updateCrc(_ crc: UInt32, - _ bytes: UnsafeBufferPointer) -> UInt32 { + _ bytes: UnsafeRawBufferPointer) -> UInt32 { var theCrc = ~crc for byte in bytes { theCrc = crc32Table[Int(UInt8(truncatingIfNeeded: theCrc) @@ -420,6 +416,12 @@ extension Elf64_Chdr: Elf_Chdr { } } +extension Elf_Chdr_Type: ByteSwappable { + var byteSwapped: Self { + return Elf_Chdr_Type(rawValue: rawValue.byteSwapped)! + } +} + extension Elf32_Sym: ByteSwappable { var byteSwapped: Self { return Elf32_Sym( @@ -970,19 +972,17 @@ struct Elf64Traits: ElfTraits { // .. ElfStringSection ......................................................... struct ElfStringSection { - let bytes: [UInt8] + let source: ImageSource func getStringAt(index: Int) -> String? { - if index < 0 || index >= bytes.count { + if index < 0 || index >= source.bytes.count { return nil } - let slice = bytes[index...] + let slice = UnsafeRawBufferPointer(rebasing: source.bytes[index...]) var len: Int = 0 - slice.withUnsafeBufferPointer{ ptr in - len = strnlen(ptr.baseAddress!, ptr.count) - } - return String(decoding: bytes[index.. (any ImageSource)? - func getSection(_ name: String) -> (any ImageSource)? -} - -extension ElfGetSectionProtocol { - func getSection(_ name: String) -> (any ImageSource)? { - return getSection(name, debug: false) - } -} - protocol ElfSymbolProtocol: Equatable { associatedtype Address: FixedWidthInteger associatedtype Size: FixedWidthInteger @@ -1027,22 +1016,14 @@ protocol ElfSymbolTableProtocol { func lookupSymbol(address: Traits.Address) -> Symbol? } -protocol ElfImageProtocol: Image, ElfGetSectionProtocol, DwarfSource { +protocol ElfSymbolLookupProtocol { associatedtype Traits: ElfTraits - associatedtype SymbolTable: ElfSymbolTableProtocol - where SymbolTable.Traits == Traits + typealias CallSiteInfo = DwarfReader>.CallSiteInfo + typealias SourceLocation = SymbolicatedBacktrace.SourceLocation - var header: Traits.Ehdr { get } - var programHeaders: [Traits.Phdr] { get } - var sectionHeaders: [Traits.Shdr]? { get } - - var imageName: String { get } - var debugImage: (any ElfImageProtocol)? { get } - var debugLinkCRC: UInt32? { get } - - var symbolTable: SymbolTable { get } - - func _getSymbolTable(debug: Bool) -> SymbolTable + func lookupSymbol(address: Traits.Address) -> ImageSymbol? + func inlineCallSites(at address: Traits.Address) -> ArraySlice + func sourceLocation(for address: Traits.Address) throws -> SourceLocation? } struct ElfSymbolTable: ElfSymbolTableProtocol { @@ -1065,43 +1046,41 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { init() {} - init?(image: ElfImage) { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + init?(image: ElfImage) { guard let strtab = image.getSection(".strtab", debug: false), - let symtab = image.getSection(".symtab", debug: false), - let strings = strtab.fetchAllBytes(), - let symdata = symtab.fetchAllBytes() else { + let symtab = image.getSection(".symtab", debug: false) else { return nil } - let stringSect = ElfStringSection(bytes: strings) + let stringSect = ElfStringSection(source: strtab) // Extract all the data - symdata.withUnsafeBufferPointer{ - $0.withMemoryRebound(to: Traits.Sym.self) { symbols in - for symbol in symbols { - // Ignore things that are not functions - if symbol.st_type != .STT_FUNC { - continue - } - - // Ignore anything undefined - if symbol.st_shndx == SHN_UNDEF { - continue - } - - _symbols.append( - Symbol( - name: (stringSect.getStringAt(index: Int(symbol.st_name)) - ?? ""), - value: symbol.st_value, - size: symbol.st_size, - sectionIndex: Int(symbol.st_shndx), - binding: symbol.st_binding, - type: symbol.st_type, - visibility: symbol.st_visibility - ) - ) + symtab.bytes.withMemoryRebound(to: Traits.Sym.self) { symbols in + for symbol in symbols { + // Ignore things that are not functions + if symbol.st_type != .STT_FUNC { + continue } + + // Ignore anything undefined + if symbol.st_shndx == SHN_UNDEF { + continue + } + + _symbols.append( + Symbol( + name: (stringSect.getStringAt(index: Int(symbol.st_name)) + ?? ""), + value: symbol.st_value, + size: symbol.st_size, + sectionIndex: Int(symbol.st_shndx), + binding: symbol.st_binding, + type: symbol.st_type, + visibility: symbol.st_visibility + ) + ) } } @@ -1117,6 +1096,8 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { _symbols = sortedSymbols } + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) public func merged(with other: ElfSymbolTable) -> ElfSymbolTable { var merged: [Symbol] = [] @@ -1159,6 +1140,8 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { return ElfSymbolTable(sortedSymbols: merged) } + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) public func lookupSymbol(address: Traits.Address) -> Symbol? { var min = 0 var max = _symbols.count @@ -1173,7 +1156,7 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { nextValue = _symbols[mid + 1].value } - if symbol.value <= address && nextValue > address { + if symbol.value <= address && nextValue >= address { var ndx = mid while ndx > 0 && _symbols[ndx - 1].value == address { ndx -= 1 @@ -1190,27 +1173,28 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { } } -class ElfImage: ElfImageProtocol { +final class ElfImage + : DwarfSource, ElfSymbolLookupProtocol { typealias Traits = SomeElfTraits - typealias Source = SomeImageSource typealias SymbolTable = ElfSymbolTable // This is arbitrary and it isn't in the spec let maxNoteNameLength = 256 - var baseAddress: Source.Address - var endAddress: Source.Address + var baseAddress: ImageSource.Address + var endAddress: ImageSource.Address - var source: SomeImageSource + var source: ImageSource var header: Traits.Ehdr var programHeaders: [Traits.Phdr] var sectionHeaders: [Traits.Shdr]? var shouldByteSwap: Bool { return header.shouldByteSwap } - required init(source: SomeImageSource, - baseAddress: Source.Address = 0, - endAddress: Source.Address = 0) throws { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + required init(source: ImageSource, + baseAddress: ImageSource.Address = 0, + endAddress: ImageSource.Address = 0) throws { self.source = source self.baseAddress = baseAddress self.endAddress = endAddress @@ -1237,11 +1221,11 @@ class ElfImage + var image: ElfImage struct NoteIterator: IteratorProtocol { - var image: ElfImage + var image: ElfImage var hdrNdx = -1 - var noteAddr = Source.Address() - var noteEnd = Source.Address() + var noteAddr = ImageSource.Address() + var noteEnd = ImageSource.Address() - init(image: ElfImage) { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + init(image: ElfImage) { self.image = image } @@ -1287,15 +1273,25 @@ class ElfImage Note? { + let byteSwap = image.shouldByteSwap + func maybeSwap(_ x: T) -> T { + if byteSwap { + return x.byteSwapped + } + return x + } + if hdrNdx >= image.programHeaders.count { return nil } @@ -1311,9 +1307,10 @@ class ElfImage.size) + noteAddr += ImageSource.Address(MemoryLayout.size) if noteEnd - noteAddr < nhdr.n_namesz { // The segment is probably corrupted @@ -1322,12 +1319,15 @@ class ElfImage 0 ? nhdr.n_namesz - 1 : 0 - let nameBytes = try image.fetch(from: noteAddr, - count: Int(nameLen), - as: UInt8.self) - let name = String(decoding: nameBytes, as: UTF8.self) + guard let name = try image.source.fetchString(from: noteAddr, + length: Int(nameLen)) + else { + // Bad note name + noteAddr = noteEnd + continue + } - noteAddr += Source.Address(nhdr.n_namesz) + noteAddr += ImageSource.Address(nhdr.n_namesz) if (noteAddr & 3) != 0 { noteAddr += 4 - (noteAddr & 3) } @@ -1338,11 +1338,11 @@ class ElfImage.allocate(capacity: bufSize) - defer { - buffer.deallocate() - } - - var pos = bounds.base - var remaining = bounds.size - var crc: UInt32 = 0 - do { - while remaining > 0 { - let todo = min(bufSize, Int(remaining)) - let slice = buffer[..(rebasing: slice) - - try fetch(from: pos, into: chunk) - - crc = updateCrc(crc, UnsafeBufferPointer(chunk)) - - remaining -= Source.Size(todo) - pos += Source.Address(todo) - } - } catch { - return nil - } - + let crc = updateCrc(0, source.bytes) + _debugLinkCRC = crc return crc } struct Range { - var base: Source.Address - var size: Source.Size + var base: ImageSource.Address + var size: ImageSource.Size } struct EHFrameInfo { @@ -1442,19 +1414,21 @@ class ElfImage.size) { continue } - guard let ehdr = try? fetch(from: Source.Address(ehFrameHdrRange.base), - as: EHFrameHdr.self) else { + guard let ehdr = try? source.fetch( + from: ImageSource.Address(ehFrameHdrRange.base), + as: EHFrameHdr.self + ) else { continue } @@ -1462,11 +1436,11 @@ class ElfImage.size) + let pc = ehFrameHdrRange.base + ImageSource.Address(MemoryLayout.size) guard let (_, eh_frame_ptr) = - try? source.fetchEHValue(from: Source.Address(pc), + try? source.fetchEHValue(from: ImageSource.Address(pc), with: ehdr.eh_frame_ptr_enc, - pc: Source.Address(pc)) else { + pc: ImageSource.Address(pc)) else { continue } @@ -1476,30 +1450,27 @@ class ElfImage? + var debugImage: ElfImage? { if let checked = _checkedDebugImage, checked { return _debugImage } - let tryPath = { [self] (_ path: String) -> (any ElfImageProtocol)? in + let tryPath = { [self] (_ path: String) -> ElfImage? in do { - let fileSource = try FileImageSource(path: path) - let image = try ElfImage(source: fileSource) + let fileSource = try ImageSource(path: path) + let image = try ElfImage(source: fileSource) _debugImage = image return image } catch { @@ -1562,7 +1533,7 @@ class ElfImage (any ElfImageProtocol)? in + let tryLink = { (_ link: String) -> ElfImage? in if let image = tryPath("\(imageDir)/\(link)") { return image } @@ -1592,9 +1563,8 @@ class ElfImage(source: source) + let source = try ImageSource(lzmaCompressedImageSource: debugData) + _debugImage = try ElfImage(source: source) _checkedDebugImage = true return _debugImage } catch let CompressedImageSourceError.libraryNotFound(library) { @@ -1617,15 +1587,17 @@ class ElfImage (any ImageSource)? { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + func getSection(_ name: String, debug: Bool = false) -> ImageSource? { if let sectionHeaders = sectionHeaders { let zname = ".z" + name.dropFirst() let stringShdr = sectionHeaders[Int(header.e_shstrndx)] do { - let bytes = try source.fetch(from: Source.Address(stringShdr.sh_offset), - count: Int(stringShdr.sh_size), - as: UInt8.self) - let stringSect = ElfStringSection(bytes: bytes) + let base = ImageSource.Address(stringShdr.sh_offset) + let end = base + ImageSource.Size(stringShdr.sh_size) + let stringSource = source[base..(source: subSource) + return try ImageSource(elfCompressedImageSource: subSource, + traits: Traits.self) } else { return subSource } } if zname == sname { - let subSource = SubImageSource(parent: source, - baseAddress: Source.Address(shdr.sh_offset), - length: Source.Size(shdr.sh_size)) - return try ElfGNUCompressedImageSource(source: subSource) + let base = ImageSource.Address(shdr.sh_offset) + let end = base + ImageSource.Size(shdr.sh_size) + let subSource = source[base.. ImageSymbol? { - let relativeAddress = Traits.Address(address - baseAddress) + public func lookupSymbol(address: Traits.Address) -> ImageSymbol? { + let relativeAddress = address - Traits.Address(baseAddress) guard let symbol = symbolTable.lookupSymbol(address: relativeAddress) else { return nil } @@ -1800,7 +1766,7 @@ class ElfImage (any ImageSource)? { + func getDwarfSection(_ section: DwarfSection) -> ImageSource? { switch section { case .debugAbbrev: return getSection(".debug_abbrev") case .debugAddr: return getSection(".debug_addr") @@ -1832,7 +1798,9 @@ class ElfImage.CallSiteInfo - func inlineCallSites(at address: Address) -> ArraySlice { + func inlineCallSites( + at address: Traits.Address + ) -> ArraySlice { guard let callSiteInfo = dwarfReader?.inlineCallSites else { return [][0..<0] } @@ -1870,9 +1838,11 @@ class ElfImage SourceLocation? { + func sourceLocation( + for address: Traits.Address + ) throws -> SourceLocation? { var result: SourceLocation? = nil - var prevState: DwarfReader.LineNumberState? = nil + var prevState: DwarfLineNumberState? = nil guard let dwarfReader = dwarfReader else { return nil } @@ -1901,14 +1871,157 @@ class ElfImage = ElfImage -typealias Elf64Image = ElfImage +typealias Elf32Image = ElfImage +typealias Elf64Image = ElfImage + +// .. Checking for ELF images .................................................. + +/// Test if there is a valid ELF image at the specified address; if there is, +/// extract the address range for the text segment and the UUID, if any. +@_specialize(kind: full, where R == UnsafeLocalMemoryReader) +@_specialize(kind: full, where R == RemoteMemoryReader) +#if os(Linux) +@_specialize(kind: full, where R == MemserverMemoryReader) +#endif +func getElfImageInfo(at address: R.Address, + using reader: R) + -> (endOfText: R.Address, uuid: [UInt8]?)? +{ + do { + // Check the magic number first + let magic = try reader.fetch(from: address, as: Elf_Magic.self) + + if magic != ElfMagic { + return nil + } + + // Read the class from the next byte + let elfClass = Elf_Ehdr_Class(rawValue: try reader.fetch(from: address + 4, + as: UInt8.self)) + + if elfClass == .ELFCLASS32 { + return try getElfImageInfo(at: address, using: reader, + traits: Elf32Traits.self) + } else if elfClass == .ELFCLASS64 { + return try getElfImageInfo(at: address, using: reader, + traits: Elf64Traits.self) + } else { + return nil + } + } catch { + return nil + } +} + +@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf32Traits) +@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf64Traits) +@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf32Traits) +@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf64Traits) +#if os(Linux) +@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf32Traits) +@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf64Traits) +#endif +func getElfImageInfo( + at address: R.Address, + using reader: R, + traits: Traits.Type +) throws -> (endOfText: R.Address, uuid: [UInt8]?)? { + // Grab the whole 32-bit header + let unswappedHeader = try reader.fetch(from: address, as: Traits.Ehdr.self) + + let header: Traits.Ehdr + if unswappedHeader.shouldByteSwap { + header = unswappedHeader.byteSwapped + } else { + header = unswappedHeader + } + + let byteSwap = header.shouldByteSwap + func maybeSwap(_ x: T) -> T { + if byteSwap { + return x.byteSwapped + } + return x + } + + var endOfText = address + var uuid: [UInt8]? = nil + + // Find the last loadable executable segment, and scan for PT_NOTE + // segments that contain the UUID + var phAddr = ImageSource.Address(address) + ImageSource.Size(header.e_phoff) + for _ in 0...size) + + if noteEnd - noteAddr < nhdr.n_namesz { + // This segment is probably corrupted, so skip it + noteAddr = noteEnd + continue + } + + var isBuildId = false + let nameLen = nhdr.n_namesz > 0 ? nhdr.n_namesz - 1 : 0 + + // Test if this is a "GNU" NT_GNU_BUILD_ID note + if nameLen == 3 { + let byte0 = try reader.fetch(from: noteAddr, as: UInt8.self) + let byte1 = try reader.fetch(from: noteAddr + 1, as: UInt8.self) + let byte2 = try reader.fetch(from: noteAddr + 2, as: UInt8.self) + + if byte0 == 0x47 && byte1 == 0x4e && byte2 == 0x55 && + UInt32(nhdr.n_type) == NT_GNU_BUILD_ID { + isBuildId = true + } + } + + noteAddr += ImageSource.Size(nhdr.n_namesz) + if (noteAddr & 3) != 0 { + noteAddr += 4 - (noteAddr & 3) + } + + if noteEnd - noteAddr < nhdr.n_descsz { + // Corrupted segment, skip + noteAddr = noteEnd + continue + } + + if isBuildId { + uuid = try reader.fetch(from: noteAddr, + count: Int(nhdr.n_descsz), + as: UInt8.self) + } + + noteAddr += ImageSource.Size(nhdr.n_descsz) + if (noteAddr & 3) != 0 { + noteAddr += 4 - (noteAddr & 3) + } + } + } + + phAddr += ImageSource.Address(header.e_phentsize) + } + + return (endOfText: endOfText, uuid: uuid) +} // .. Testing .................................................................. @_spi(ElfTest) public func testElfImageAt(path: String) -> Bool { - guard let source = try? FileImageSource(path: path) else { + guard let source = try? ImageSource(path: path) else { print("\(path) was not accessible") return false } @@ -1978,5 +2091,3 @@ public func testElfImageAt(path: String) -> Bool { return false } } - -#endif // os(Linux) diff --git a/stdlib/public/RuntimeModule/ElfImageCache.swift b/stdlib/public/RuntimeModule/ElfImageCache.swift new file mode 100644 index 00000000000..cf8ae0664a3 --- /dev/null +++ b/stdlib/public/RuntimeModule/ElfImageCache.swift @@ -0,0 +1,95 @@ +//===--- ElfImageCache.swift - ELF support for Swift ----------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Provides a per-thread Elf image cache that improves efficiency when +// taking multiple backtraces by avoiding loading ELF images multiple times. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +internal import Darwin +#elseif os(Windows) +internal import ucrt +#elseif canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif + +/// Provides a per-thread image cache for ELF image processing. This means +/// if you take multiple backtraces from a thread, you won't load the same +/// image multiple times. +final class ElfImageCache { + var elf32: [String: Elf32Image] = [:] + var elf64: [String: Elf64Image] = [:] + + func purge() { + elf32 = [:] + elf64 = [:] + } + + enum Result { + case elf32Image(Elf32Image) + case elf64Image(Elf64Image) + } + func lookup(path: String?) -> Result? { + guard let path = path else { + return nil + } + if let image = elf32[path] { + return .elf32Image(image) + } + if let image = elf64[path] { + return .elf64Image(image) + } + if let source = try? ImageSource(path: path) { + if let elfImage = try? Elf32Image(source: source) { + elf32[path] = elfImage + return .elf32Image(elfImage) + } + if let elfImage = try? Elf64Image(source: source) { + elf64[path] = elfImage + return .elf64Image(elfImage) + } + } + return nil + } + + private static var key: pthread_key_t? = { + var theKey = pthread_key_t() + let err = pthread_key_create( + &theKey, + { rawPtr in + let ptr = Unmanaged.fromOpaque( + notMutable(notOptional(rawPtr)) + ) + ptr.release() + } + ) + if err != 0 { + return nil + } + return theKey + }() + + static var threadLocal: ElfImageCache { + guard let rawPtr = pthread_getspecific(key!) else { + let cache = Unmanaged.passRetained(ElfImageCache()) + pthread_setspecific(key!, cache.toOpaque()) + return cache.takeUnretainedValue() + } + let cache = Unmanaged.fromOpaque(rawPtr) + return cache.takeUnretainedValue() + } +} diff --git a/stdlib/public/Backtracing/FramePointerUnwinder.swift b/stdlib/public/RuntimeModule/FramePointerUnwinder.swift similarity index 51% rename from stdlib/public/Backtracing/FramePointerUnwinder.swift rename to stdlib/public/RuntimeModule/FramePointerUnwinder.swift index 9d5b502f35c..0e66b20fabe 100644 --- a/stdlib/public/Backtracing/FramePointerUnwinder.swift +++ b/stdlib/public/RuntimeModule/FramePointerUnwinder.swift @@ -20,7 +20,7 @@ import Swift public struct FramePointerUnwinder: Sequence, IteratorProtocol { public typealias Context = C public typealias MemoryReader = M - public typealias Address = MemoryReader.Address + public typealias Address = Context.Address var pc: Address var fp: Address @@ -30,13 +30,16 @@ public struct FramePointerUnwinder: Sequence, Itera var done: Bool #if os(Linux) - var elf32Cache: [Int:Elf32Image] = [:] - var elf64Cache: [Int:Elf64Image] = [:] var images: [Backtrace.Image]? #endif var reader: MemoryReader + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif public init(context: Context, images: [Backtrace.Image]?, memoryReader: MemoryReader) { @@ -73,42 +76,51 @@ public struct FramePointerUnwinder: Sequence, Itera return false } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private mutating func isAsyncPC(_ pc: Address) -> Bool { // On Linux, we need to examine the PC to see if this is an async frame #if os(Linux) - let address = FileImageSource.Address(pc) + let address = MemoryReader.Address(pc) if let images = images, let imageNdx = images.firstIndex( - where: { address >= $0.baseAddress && address < $0.endOfText } + where: { address >= MemoryReader.Address($0.baseAddress)! + && address < MemoryReader.Address($0.endOfText)! } ) { - let relativeAddress = address - FileImageSource.Address(images[imageNdx].baseAddress) - var elf32Image = elf32Cache[imageNdx] - var elf64Image = elf64Cache[imageNdx] + let base = MemoryReader.Address(images[imageNdx].baseAddress)! + let relativeAddress = address - base + let cache = ElfImageCache.threadLocal - if elf32Image == nil && elf64Image == nil { - if let source = try? FileImageSource(path: images[imageNdx].path) { - if let elfImage = try? Elf32Image(source: source) { - elf32Image = elfImage - elf32Cache[imageNdx] = elfImage - } else if let elfImage = try? Elf64Image(source: source) { - elf64Image = elfImage - elf64Cache[imageNdx] = elfImage - } + if let hit = cache.lookup(path: images[imageNdx].path) { + switch hit { + case let .elf32Image(image): + if let theSymbol = image.lookupSymbol( + address: Elf32Image.Traits.Address(relativeAddress) + ) { + return isAsyncSymbol(theSymbol.name) + } + case let .elf64Image(image): + if let theSymbol = image.lookupSymbol( + address: Elf64Image.Traits.Address(relativeAddress)) { + return isAsyncSymbol(theSymbol.name) + } } } - - if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) { - return isAsyncSymbol(theSymbol.name) - } else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) { - return isAsyncSymbol(theSymbol.name) - } } #endif return false } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private func isAsyncFrame(_ storedFp: Address) -> Bool { #if (os(macOS) || os(iOS) || os(watchOS)) && (arch(arm64) || arch(arm64_32) || arch(x86_64)) // On Darwin, we borrow a bit of the frame pointer to indicate async @@ -119,15 +131,25 @@ public struct FramePointerUnwinder: Sequence, Itera #endif } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private func stripPtrAuth(_ address: Address) -> Address { - return Address(Context.stripPtrAuth(address: Context.Address(address))) + return Context.stripPtrAuth(address: address) } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private mutating func fetchAsyncContext() -> Bool { let strippedFp = stripPtrAuth(fp) do { - asyncContext = try reader.fetch(from: Address(strippedFp - 8), + asyncContext = try reader.fetch(from: MemoryReader.Address(strippedFp - 8), as: Address.self) return true } catch { @@ -135,7 +157,12 @@ public struct FramePointerUnwinder: Sequence, Itera } } - public mutating func next() -> Backtrace.Frame? { + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif + public mutating func next() -> RichFrame
? { if done { return nil } @@ -143,7 +170,7 @@ public struct FramePointerUnwinder: Sequence, Itera if first { first = false pc = stripPtrAuth(pc) - return .programCounter(Backtrace.Address(pc)) + return .programCounter(pc) } if !isAsync { @@ -153,17 +180,20 @@ public struct FramePointerUnwinder: Sequence, Itera let strippedFp = stripPtrAuth(fp) if strippedFp == 0 - || !Context.isAlignedForStack(framePointer: - Context.Address(strippedFp)) { + || !Context.isAlignedForStack(framePointer:strippedFp) { done = true return nil } do { - pc = stripPtrAuth(try reader.fetch(from: - strippedFp + Address(MemoryLayout
.size), - as: Address.self)) - next = try reader.fetch(from: Address(strippedFp), as: Address.self) + pc = stripPtrAuth(try reader.fetch( + from:MemoryReader.Address( + strippedFp + + Address(MemoryLayout
.size) + ), + as: Address.self)) + next = try reader.fetch(from: MemoryReader.Address(strippedFp), + as: Address.self) } catch { done = true return nil @@ -176,7 +206,7 @@ public struct FramePointerUnwinder: Sequence, Itera if !isAsyncFrame(next) { fp = next - return .returnAddress(Backtrace.Address(pc)) + return .returnAddress(pc) } } @@ -202,8 +232,10 @@ public struct FramePointerUnwinder: Sequence, Itera // On arm64_32, the two pointers at the start of the context are 32-bit, // although the stack layout is identical to vanilla arm64 do { - var next32 = try reader.fetch(from: strippedCtx, as: UInt32.self) - var pc32 = try reader.fetch(from: strippedCtx + 4, as: UInt32.self) + var next32 = try reader.fetch(from: MemoryReader.Address(strippedCtx), + as: UInt32.self) + var pc32 = try reader.fetch(from: MemoryReader.Address(strippedCtx + 4), + as: UInt32.self) next = Address(next32) pc = stripPtrAuth(Address(pc32)) @@ -215,8 +247,10 @@ public struct FramePointerUnwinder: Sequence, Itera // Otherwise it's two 64-bit words do { - next = try reader.fetch(from: strippedCtx, as: Address.self) - pc = stripPtrAuth(try reader.fetch(from: strippedCtx + 8, as: Address.self)) + next = try reader.fetch(from: MemoryReader.Address(strippedCtx), + as: Address.self) + pc = stripPtrAuth(try reader.fetch(from: MemoryReader.Address(strippedCtx + 8), + as: Address.self)) } catch { done = true return nil @@ -226,6 +260,6 @@ public struct FramePointerUnwinder: Sequence, Itera asyncContext = next - return .asyncResumePoint(Backtrace.Address(pc)) + return .asyncResumePoint(pc) } } diff --git a/stdlib/public/Backtracing/Image.swift b/stdlib/public/RuntimeModule/Image.swift similarity index 96% rename from stdlib/public/Backtracing/Image.swift rename to stdlib/public/RuntimeModule/Image.swift index 7f3d1ef6eba..e43b8f16263 100644 --- a/stdlib/public/Backtracing/Image.swift +++ b/stdlib/public/RuntimeModule/Image.swift @@ -23,17 +23,15 @@ struct ImageSymbol { } internal protocol Image { - associatedtype Source: ImageSource - typealias UUID = [UInt8] - typealias Address = Source.Address + typealias Address = ImageSource.Address - init(source: Source, baseAddress: Address, endAddress: Address) throws + init(source: ImageSource, baseAddress: Address, endAddress: Address) throws var baseAddress: Address { get set } var endAddress: Address { get set } - var source: Source { get } + var source: ImageSource { get } var uuid: UUID? { get } var shouldByteSwap: Bool { get } diff --git a/stdlib/public/RuntimeModule/ImageSource.swift b/stdlib/public/RuntimeModule/ImageSource.swift new file mode 100644 index 00000000000..9cd4da43496 --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageSource.swift @@ -0,0 +1,436 @@ +//===--- ImageSource.swift - A place from which to read image data --------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Defines ImageSource, which tells us where to look for image data. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +internal import Darwin +#elseif os(Windows) +internal import ucrt +#elseif canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif + +enum ImageSourceError: Error { + case outOfBoundsRead + case posixError(Int32) +} + +struct ImageSource { + + private class Storage { + /// Says how we allocated the buffer. + private enum MemoryBufferKind { + /// Currently empty + case empty + + /// Allocated with UnsafeRawBufferPointer.allocate() + case allocated(Int) + + /// Allocated by mapping memory with mmap() or similar + case mapped + + /// A reference to a subordinate storage + case substorage(Storage) + + /// Not allocated (probably points to a loaded image) + case unowned + } + + private var kind: MemoryBufferKind + + /// The pointer to the actual memory + private(set) var bytes: UnsafeRawBufferPointer! + + /// Gets a mutable pointer to the actual memory + var mutableBytes: UnsafeMutableRawBufferPointer { + guard case let .allocated(count) = kind else { + fatalError("attempted to get mutable reference to immutable ImageSource") + } + return UnsafeMutableRawBufferPointer( + mutating: UnsafeRawBufferPointer(rebasing: bytes[0..= count { + fatalError("ImageSource access out of range") + } + } + + init() { + self.kind = .empty + self.bytes = nil + } + + init(unowned buffer: UnsafeRawBufferPointer) { + self.kind = .unowned + self.bytes = buffer + } + + init(mapped buffer: UnsafeRawBufferPointer) { + self.kind = .mapped + self.bytes = buffer + } + + init(allocated buffer: UnsafeMutableRawBufferPointer, count: Int? = nil) { + self.kind = .allocated(count ?? buffer.count) + self.bytes = UnsafeRawBufferPointer(buffer) + } + + convenience init(capacity: Int, alignment: Int = 0x4000) { + self.init(allocated: UnsafeMutableRawBufferPointer.allocate( + byteCount: capacity, + alignment: 0x1000 + ), + count: 0) + } + + init(parent: Storage, range: Range) { + let chunk = UnsafeRawBufferPointer(rebasing: parent.bytes[range]) + + self.kind = .substorage(parent) + self.bytes = chunk + } + + convenience init(path: String) throws { + let fd = open(path, O_RDONLY, 0) + if fd < 0 { + throw ImageSourceError.posixError(errno) + } + defer { close(fd) } + let size = lseek(fd, 0, SEEK_END) + if size < 0 { + throw ImageSourceError.posixError(errno) + } + let base = mmap(nil, Int(size), PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0) + if base == nil || base! == UnsafeRawPointer(bitPattern: -1)! { + throw ImageSourceError.posixError(errno) + } + + self.init(mapped: UnsafeRawBufferPointer( + start: base, count: Int(size))) + } + + deinit { + switch kind { + case .allocated: + mutableBytes.deallocate() + case .mapped: + munmap(UnsafeMutableRawPointer(mutating: bytes.baseAddress), + bytes.count) + case .substorage, .unowned, .empty: + break + } + } + + /// Subscripting (read-only, for subranges) + subscript(range: Range) -> Storage { + return Storage(parent: self, range: range) + } + + /// Resize the buffer; only supported for allocated or empty storage + func resize(newSize: Int) -> UnsafeMutableRawBufferPointer { + let newBuffer = UnsafeMutableRawBufferPointer.allocate( + byteCount: newSize, + alignment: 0x1000 + ) + switch kind { + case .empty: + kind = .allocated(0) + case let .allocated(count): + assert(newSize >= count) + + let oldPart = UnsafeMutableRawBufferPointer( + rebasing: newBuffer[0.. UnsafeMutableRawBufferPointer { + let capacity: Int + switch kind { + case .empty: + capacity = 0 + case .allocated: + capacity = bytes.count + default: + fatalError("Cannot resize immutable image source storage") + } + + if capacity >= byteCount { + return mutableBytes + } + + let extra = byteCount - capacity + + let increment: Int + if capacity < 1048576 { + let roundedExtra = (extra + 0xffff) & ~0xffff + increment = max(roundedExtra, capacity) + } else { + let roundedExtra = (extra + 0xfffff) & ~0xfffff + let topBit = capacity.bitWidth - capacity.leadingZeroBitCount + increment = max(roundedExtra, 1048576 * (topBit - 20)) + } + + return resize(newSize: capacity + increment) + } + + /// Mark a number of bytes in the mutable buffer as in use. This is + /// used when passing `unusedBytes` to some other code that fills in + /// part of the buffer. + func used(bytes: Int) { + guard bytes >= 0 else { + fatalError("Bytes should not be less than zero") + } + guard case let .allocated(count) = kind else { + fatalError("Cannot append to immutable image source storage") + } + guard mutableBytes.count - count <= bytes else { + fatalError("Buffer overrun detected") + } + kind = .allocated(count + bytes) + } + + /// Append bytes to the mutable buffer; this is only supported for + /// allocated or empty storage. + func append(bytes toAppend: UnsafeRawBufferPointer) { + // Short circuit, otherwise we get in a muddle in requireAtLeast() + if toAppend.count == 0 { + return + } + + let newCount = count + toAppend.count + + let mutableBytes = requireAtLeast(byteCount: newCount) + + guard case let .allocated(count) = kind else { + fatalError("Cannot append to immutable image source storage") + } + + let dest = UnsafeMutableRawBufferPointer( + rebasing: mutableBytes[count..) -> ImageSource { + let intRange = Int(range.lowerBound)..= buffer.count && + offset <= bytes.count - buffer.count else { + throw ImageSourceError.outOfBoundsRead + } + buffer.copyMemory(from: UnsafeRawBufferPointer( + rebasing: bytes[offset..(from address: Address, as type: T.Type) throws -> T { + let size = MemoryLayout.size + let offset = Int(address) + guard offset <= bytes.count - size else { + throw ImageSourceError.outOfBoundsRead + } + return bytes.loadUnaligned(fromByteOffset: offset, as: type) + } + + public func fetchString(from address: Address) throws -> String? { + let offset = Int(address) + let len = strnlen(bytes.baseAddress! + offset, bytes.count - offset) + let stringBytes = bytes[offset.. String? { + let offset = Int(address) + let stringBytes = bytes[offset..(into buffer: UnsafeMutableBufferPointer) throws { + try source.fetch(from: pos, into: buffer) + pos += Size(MemoryLayout.stride * buffer.count) + } + + mutating func read(into pointer: UnsafeMutablePointer) throws { + try source.fetch(from: pos, into: pointer) + pos += Size(MemoryLayout.stride) + } + + mutating func read(as type: T.Type) throws -> T { + let result = try source.fetch(from: pos, as: type) + pos += Size(MemoryLayout.stride) + return result + } + + mutating func read(count: Int, as type: T.Type) throws -> [T] { + let result = try source.fetch(from: pos, count: count, as: type) + pos += Size(MemoryLayout.stride * count) + return result + } + + mutating func readString() throws -> String? { + guard let result = try source.fetchString(from: pos) else { + return nil + } + pos += Size(result.utf8.count + 1) // +1 for the NUL + return result + } + + mutating func readString(length: Int) throws -> String? { + guard let result = try source.fetchString(from: pos, length: length) else { + return nil + } + pos += Size(length) + return result + } +} diff --git a/stdlib/public/Backtracing/Libc.swift b/stdlib/public/RuntimeModule/Libc.swift similarity index 100% rename from stdlib/public/Backtracing/Libc.swift rename to stdlib/public/RuntimeModule/Libc.swift diff --git a/stdlib/public/RuntimeModule/LimitSequence.swift b/stdlib/public/RuntimeModule/LimitSequence.swift new file mode 100644 index 00000000000..8cbdda46a77 --- /dev/null +++ b/stdlib/public/RuntimeModule/LimitSequence.swift @@ -0,0 +1,231 @@ +//===--- LimitSequence.swift ----------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Defines a sequence adapter that implements the ability to limit the +// number of items in its output in various ways. +// +//===----------------------------------------------------------------------===// + +import Swift + +/// Sequences you wish to use with `LimitSequence` must use an element type +/// that implements this protocol, so that `LimitSequence` can indicate when +/// it omits or truncates the sequence. +protocol LimitableElement { + static func omitted(_: Int) -> Self + static var truncated: Self { get } +} + +/// A `Sequence` that adds the ability to limit the output of another sequence. +struct LimitSequence: Sequence + where S.Element == T +{ + /// The element type, which must conform to `LimitableElement` + typealias Element = T + + /// The source sequence + typealias Source = S + + var source: Source + + /// The maximum number of items that we want in the output of this sequence. + /// This includes `.omitted()` and `.truncated` items. + var limit: Int + + /// The number of items to drop from the head of the sequence. + var offset: Int + + /// The minimum number of items to capture at the tail end of the input + /// sequence. This can be _at most_ `limit - 1`. + var top: Int + + /// Initialise the `LimitSequence` + /// + /// - source: The sequence to draw items from. + /// - limit: The maximum number of items of output we desire. + /// - offset: The number of items to drop from the head of the input sequence. + /// - top: The minimum number of items to capture at the tail end of the + /// input sequence. + /// + /// A `LimitSequence` will read from `source` and emit at most `limit` items, + /// after discarding the first `offset` items from `source`, including a + /// minimum of `top` items. + /// + /// When `LimitSequence` omits items or truncates the sequence, it will + /// insert `.omitted(count)` or `.truncated` items into its output. + init(_ source: Source, limit: Int, offset: Int = 0, top: Int = 0) { + self.source = source + self.limit = limit + self.offset = offset + self.top = top + } + + /// Create an iterator for this sequence. + public func makeIterator() -> Iterator { + return Iterator(source.makeIterator(), limit: limit, offset: offset, top: top) + } + + /// The `LimitSequence` Iterator implementation. + /// + /// This works by buffering an element ahead of where we are in the input + /// sequence, so that it can tell whether or not there is more input to + /// follow at any given point. + struct Iterator: IteratorProtocol { + /// The iterator for the input sequence. + var iterator: Source.Iterator + + /// We read one element ahead in the input sequence; that element is + /// stored here. + var readAhead: Element? + + /// Tracks the number of items emitted before getting to `top`. + var count = 0 + + /// The maximum number of items to emit, including the `.truncated` + /// or `.omitted()` markers. + var limit: Int + + /// The minimum number of items to capture from the tail of the input + /// sequence. Must be strictly less than `limit`. + var top: Int + + /// A ring buffer that we use to capture the tail. + var topBuffer: [Element] + + /// Points at the first item in `topBuffer`. + var topBase: Int + + /// The index in `topBuffer` that we should output from the next + /// call to `next()`. + var topNdx: Int + + /// Tracks the iterator state. + var state: State + + enum State { + case normal + case outputTop + case done + } + + /// Fill `readAhead` with the next element from the input sequence. + private mutating func readNext() { + if let elt = self.iterator.next() { + readAhead = elt + } else { + readAhead = nil + } + } + + /// Initialise the iterator, and fill in the first read ahead element. + init(_ iterator: Source.Iterator, limit: Int, offset: Int, top: Int) { + self.iterator = iterator + + for _ in 0.. Element? { + switch state { + case .done: + return nil + case .outputTop: + let result = topBuffer[topNdx] + topNdx += 1 + if topNdx == top { + topNdx = 0 + } + if topNdx == topBase { + state = .done + } + return result + case .normal: + break + } + + guard let element = readAhead else { + state = .done + return nil + } + + readNext() + + // Capture the easy part + if count < limit - top - 1 { + count += 1 + return element + } + + if top == 0 && readAhead != nil { + state = .done + return .truncated + } + + let beforeTop = element + + // Fill the top buffer + while let elt = readAhead, topBuffer.count < top{ + topBuffer.append(elt) + + readNext() + } + + if readAhead == nil { + // No elements means we just output beforeTop and we're done + if topBuffer.count == 0 { + state = .done + return beforeTop + } + + // Otherwise, output beforeTop and then the top buffer + topNdx = 0 + if topBuffer.count < top { + topBase = topBuffer.count + } + state = .outputTop + return beforeTop + } + + // Use the top buffer as a circular buffer + var omitted = 1 + while let elt = readAhead { + topBuffer[topBase] = elt + topBase += 1 + omitted += 1 + if topBase == top { + topBase = 0 + } + + readNext() + } + + topNdx = topBase + state = .outputTop + return .omitted(omitted) + } + } +} diff --git a/stdlib/public/Backtracing/MemoryReader.swift b/stdlib/public/RuntimeModule/MemoryReader.swift similarity index 87% rename from stdlib/public/Backtracing/MemoryReader.swift rename to stdlib/public/RuntimeModule/MemoryReader.swift index 6df5073fa93..3dcbec58e50 100644 --- a/stdlib/public/Backtracing/MemoryReader.swift +++ b/stdlib/public/RuntimeModule/MemoryReader.swift @@ -57,6 +57,9 @@ internal import BacktracingImpl.OS.Darwin /// Fetch a NUL terminated string from the specified location in the source func fetchString(from addr: Address) throws -> String? + + /// Fetch a fixed-length string from the specified location in the source + func fetchString(from addr: Address, length: Int) throws -> String? } extension MemoryReader { @@ -106,6 +109,10 @@ extension MemoryReader { return String(decoding: bytes, as: UTF8.self) } + public func fetchString(from addr: Address, length: Int) throws -> String? { + let bytes = try fetch(from: addr, count: length, as: UInt8.self) + return String(decoding: bytes, as: UTF8.self) + } } @_spi(MemoryReaders) public struct UnsafeLocalMemoryReader: MemoryReader { @@ -118,6 +125,16 @@ extension MemoryReader { byteCount: buffer.count ) } + + public func fetch(from address: Address, as type: T.Type) throws -> T { + let ptr = UnsafeRawPointer(bitPattern: UInt(address))! + return ptr.loadUnaligned(fromByteOffset: 0, as: type) + } + + public func fetchString(from address: Address) throws -> String? { + let ptr = UnsafeRawPointer(bitPattern: UInt(address))! + return String(validatingUTF8: ptr.assumingMemoryBound(to: CChar.self)) + } } #if os(macOS) @@ -125,7 +142,8 @@ extension MemoryReader { var result: kern_return_t } -@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedRemoteMemoryReader: MemoryReader { private var task: task_t // Sadly we can't expose the type of this argument @@ -151,13 +169,14 @@ extension MemoryReader { } } -@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedLocalMemoryReader: MemoryReader { public typealias Address = UInt64 public typealias Size = UInt64 public func fetch(from address: Address, into buffer: UnsafeMutableRawBufferPointer) throws { - let reader = RemoteMemoryReader(task: mach_task_self()) + let reader = UncachedRemoteMemoryReader(task: mach_task_self()) return try reader.fetch(from: address, into: buffer) } } @@ -172,7 +191,8 @@ extension MemoryReader { var message: String } -@_spi(MemoryReaders) public struct MemserverMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedMemserverMemoryReader: MemoryReader { private var fd: CInt public init(fd: CInt) { @@ -267,7 +287,8 @@ extension MemoryReader { } } -@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedRemoteMemoryReader: MemoryReader { private var pid: pid_t public init(pid: Any) { @@ -288,7 +309,8 @@ extension MemoryReader { } } -@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedLocalMemoryReader: MemoryReader { private var reader: RemoteMemoryReader init() { diff --git a/stdlib/public/Backtracing/ProcMapsScanner.swift b/stdlib/public/RuntimeModule/ProcMapsScanner.swift similarity index 100% rename from stdlib/public/Backtracing/ProcMapsScanner.swift rename to stdlib/public/RuntimeModule/ProcMapsScanner.swift diff --git a/stdlib/public/Backtracing/Registers.swift b/stdlib/public/RuntimeModule/Registers.swift similarity index 100% rename from stdlib/public/Backtracing/Registers.swift rename to stdlib/public/RuntimeModule/Registers.swift diff --git a/stdlib/public/RuntimeModule/RichFrame.swift b/stdlib/public/RuntimeModule/RichFrame.swift new file mode 100644 index 00000000000..ac7bbbb5c02 --- /dev/null +++ b/stdlib/public/RuntimeModule/RichFrame.swift @@ -0,0 +1,146 @@ +//===--- RichFrame.swift --------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the default rich frame storage type used by `Backtrace` +// +//===----------------------------------------------------------------------===// + +import Swift + +@_spi(Internal) +public enum RichFrame: CustomStringConvertible { + public typealias Address = T + + /// A program counter value. + /// + /// This might come from a signal handler, or an exception or some + /// other situation in which we have captured the actual program counter. + /// + /// These can be directly symbolicated, as-is, with no adjustment. + case programCounter(Address) + + /// A return address. + /// + /// Corresponds to a normal function call. + /// + /// Requires adjustment when symbolicating for a backtrace, because it + /// points at the address after the one that triggered the child frame. + case returnAddress(Address) + + /// An async resume point. + /// + /// Corresponds to an `await` in an async task. + /// + /// Can be directly symbolicated, as-is. + case asyncResumePoint(Address) + + /// Indicates a discontinuity in the backtrace. + /// + /// This occurs when you set a limit and a minimum number of frames at + /// the top. For example, if you set a limit of 10 frames and a minimum + /// of 4 top frames, but the backtrace generated 100 frames, you will see + /// + /// 0: frame 100 <----- bottom of call stack + /// 1: frame 99 + /// 2: frame 98 + /// 3: frame 97 + /// 4: frame 96 + /// 5: ... <----- omittedFrames(92) + /// 6: frame 3 + /// 7: frame 2 + /// 8: frame 1 + /// 9: frame 0 <----- top of call stack + /// + /// Note that the limit *includes* the discontinuity. + /// + /// This is good for handling cases involving deep recursion. + case omittedFrames(Int) + + /// Indicates a discontinuity of unknown length. + /// + /// This can only be present at the end of a backtrace; in other cases + /// we will know how many frames we have omitted. For instance, + /// + /// 0: frame 100 <----- bottom of call stack + /// 1: frame 99 + /// 2: frame 98 + /// 3: frame 97 + /// 4: frame 96 + /// 5: ... <----- truncated + case truncated + + /// The program counter, without any adjustment. + public var originalProgramCounter: Address { + switch self { + case let .returnAddress(addr): + return addr + case let .programCounter(addr): + return addr + case let .asyncResumePoint(addr): + return addr + case .omittedFrames, .truncated: + return 0 + } + } + + /// The adjusted program counter to use for symbolication. + public var adjustedProgramCounter: Address { + switch self { + case let .returnAddress(addr): + return addr - 1 + case let .programCounter(addr): + return addr + case let .asyncResumePoint(addr): + return addr + case .omittedFrames, .truncated: + return 0 + } + } + + /// A textual description of this frame. + public var description: String { + switch self { + case let .programCounter(addr): + return "\(hex(addr))" + case let .returnAddress(addr): + return "\(hex(addr)) [ra]" + case let .asyncResumePoint(addr): + return "\(hex(addr)) [async]" + case .omittedFrames, .truncated: + return "..." + } + } +} + +extension RichFrame: LimitableElement { + // LimitableElement wants to call this "omitted" + public static func omitted(_ count: Int) -> Self { + return .omittedFrames(count) + } +} + +extension Backtrace.Frame { + init(_ frame: RichFrame) { + switch frame { + case let .returnAddress(addr): + self = .returnAddress(Backtrace.Address(addr)) + case let .programCounter(addr): + self = .programCounter(Backtrace.Address(addr)) + case let .asyncResumePoint(addr): + self = .asyncResumePoint(Backtrace.Address(addr)) + case let .omittedFrames(count): + self = .omittedFrames(count) + case .truncated: + self = .truncated + } + } +} diff --git a/stdlib/public/Backtracing/Runtime.swift b/stdlib/public/RuntimeModule/Runtime.swift similarity index 100% rename from stdlib/public/Backtracing/Runtime.swift rename to stdlib/public/RuntimeModule/Runtime.swift diff --git a/stdlib/public/Backtracing/SymbolicatedBacktrace.swift b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift similarity index 74% rename from stdlib/public/Backtracing/SymbolicatedBacktrace.swift rename to stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift index 0e194bc9498..71662c334fc 100644 --- a/stdlib/public/Backtracing/SymbolicatedBacktrace.swift +++ b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift @@ -92,20 +92,15 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } /// A textual description of this frame. - public func description(width: Int) -> String { + public var description: String { if let symbol = symbol { let isInlined = inlined ? " [inlined]" : "" let isThunk = isSwiftThunk ? " [thunk]" : "" - return "\(captured.description(width: width))\(isInlined)\(isThunk) \(symbol)" + return "\(captured.description)\(isInlined)\(isThunk) \(symbol)" } else { - return captured.description(width: width) + return captured.description } } - - /// A textual description of this frame. - public var description: String { - return description(width: MemoryLayout.size * 2) - } } /// Represents a symbol we've located @@ -250,20 +245,12 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } } - /// The width, in bits, of an address in this backtrace. - public var addressWidth: Int { - return backtrace.addressWidth - } - /// A list of captured frame information. public var frames: [Frame] /// A list of images found in the process. public var images: [Backtrace.Image] - /// Shared cache information. - public var sharedCacheInfo: Backtrace.SharedCacheInfo? - /// True if this backtrace is a Swift runtime failure. public var isSwiftRuntimeFailure: Bool { guard let frame = frames.first else { return false } @@ -284,11 +271,9 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { /// Construct a SymbolicatedBacktrace from a backtrace and a list of images. private init(backtrace: Backtrace, images: [Backtrace.Image], - sharedCacheInfo: Backtrace.SharedCacheInfo?, frames: [Frame]) { self.backtrace = backtrace self.images = images - self.sharedCacheInfo = sharedCacheInfo self.frames = frames } @@ -307,20 +292,19 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { /// Create a symbolicator. private static func withSymbolicator(images: [Backtrace.Image], - sharedCacheInfo: Backtrace.SharedCacheInfo?, useSymbolCache: Bool, fn: (CSSymbolicatorRef) throws -> T) rethrows -> T { let binaryImageList = images.map{ image in BinaryImageInformation( - base: vm_address_t(image.baseAddress), - extent: vm_address_t(image.endOfText), - uuid: uuidBytesFromBuildID(image.buildID!), + base: vm_address_t(image.baseAddress)!, + extent: vm_address_t(image.endOfText)!, + uuid: uuidBytesFromBuildID(image.uniqueID!), arch: HostContext.coreSymbolicationArchitecture, - path: image.path, + path: image.path ?? "", relocations: [ BinaryRelocationInformation( - base: vm_address_t(image.baseAddress), - extent: vm_address_t(image.endOfText), + base: vm_address_t(image.baseAddress)!, + extent: vm_address_t(image.endOfText)!, name: "__TEXT" ) ], @@ -375,9 +359,9 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { var imageIndex = -1 var imageName = "" for (ndx, image) in images.enumerated() { - if image.baseAddress == imageBase { + if vm_address_t(image.baseAddress) == imageBase { imageIndex = ndx - imageName = image.name + imageName = image.name ?? "" break } } @@ -385,7 +369,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { let theSymbol = Symbol(imageIndex: imageIndex, imageName: imageName, rawName: rawName, - offset: Int(address - UInt64(range.location)), + offset: Int(UInt64(address)! - UInt64(range.location)), sourceLocation: location) theSymbol.name = name @@ -396,10 +380,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { /// Actually symbolicate. internal static func symbolicate(backtrace: Backtrace, images: [Backtrace.Image]?, - sharedCacheInfo: Backtrace.SharedCacheInfo?, - showInlineFrames: Bool, - showSourceLocations: Bool, - useSymbolCache: Bool) + options: Backtrace.SymbolicationOptions) -> SymbolicatedBacktrace? { let theImages: [Backtrace.Image] @@ -411,27 +392,18 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { theImages = Backtrace.captureImages() } - let theCacheInfo: Backtrace.SharedCacheInfo? - if let sharedCacheInfo = sharedCacheInfo { - theCacheInfo = sharedCacheInfo - } else if let sharedCacheInfo = backtrace.sharedCacheInfo { - theCacheInfo = sharedCacheInfo - } else { - theCacheInfo = Backtrace.captureSharedCacheInfo() - } - var frames: [Frame] = [] #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) withSymbolicator(images: theImages, - sharedCacheInfo: theCacheInfo, - useSymbolCache: useSymbolCache) { symbolicator in + useSymbolCache: options.contains(.useSymbolCache)) { + symbolicator in for frame in backtrace.frames { switch frame { case .omittedFrames(_), .truncated: frames.append(Frame(captured: frame, symbol: nil)) default: - let address = vm_address_t(frame.adjustedProgramCounter) + let address = vm_address_t(frame.adjustedProgramCounter)! let owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator, address, @@ -439,7 +411,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { if CSIsNull(owner) { frames.append(Frame(captured: frame, symbol: nil)) - } else if showInlineFrames { + } else if options.contains(.showInlineFrames) { // These present in *reverse* order (i.e. the real one first, // then the inlined frames from callee to caller). let pos = frames.count @@ -458,7 +430,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { first = false } - } else if showSourceLocations { + } else if options.contains(.showSourceLocations) { let symbol = CSSymbolOwnerGetSymbolWithAddress(owner, address) let sourceInfo = CSSymbolOwnerGetSourceInfoWithAddress(owner, address) @@ -483,108 +455,92 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } } #elseif os(Linux) - var elf32Cache: [Int:Elf32Image] = [:] - var elf64Cache: [Int:Elf64Image] = [:] + let cache = ElfImageCache.threadLocal // This could be more efficient; at the moment we execute the line // number programs once per frame, whereas we could just run them once // for all the addresses we're interested in. for frame in backtrace.frames { - let address = FileImageSource.Address(frame.adjustedProgramCounter) + let address = frame.adjustedProgramCounter if let imageNdx = theImages.firstIndex( - where: { address >= $0.baseAddress - && address < $0.endOfText } + where: { address >= $0.baseAddress && address < $0.endOfText } ) { - let relativeAddress = address - FileImageSource.Address(theImages[imageNdx].baseAddress) + let relativeAddress = ImageSource.Address( + address - theImages[imageNdx].baseAddress + ) + let name = theImages[imageNdx].name ?? "" var symbol: Symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, + imageName: name, rawName: "", offset: 0, sourceLocation: nil) - var elf32Image = elf32Cache[imageNdx] - var elf64Image = elf64Cache[imageNdx] - if elf32Image == nil && elf64Image == nil { - if let source = try? FileImageSource(path: theImages[imageNdx].path) { - if let elfImage = try? Elf32Image(source: source) { - elf32Image = elfImage - elf32Cache[imageNdx] = elfImage - } else if let elfImage = try? Elf64Image(source: source) { - elf64Image = elfImage - elf64Cache[imageNdx] = elfImage + func lookupSymbol( + image: ElfImage?, + at imageNdx: Int, + named name: String, + address imageAddr: ImageSource.Address + ) -> Symbol? { + let address = ElfImage.Traits.Address(imageAddr) + + guard let image = image else { + return nil + } + guard let theSymbol = image.lookupSymbol(address: address) else { + return nil + } + + var location: SourceLocation? + + if options.contains(.showSourceLocations) + || options.contains(.showInlineFrames) { + location = try? image.sourceLocation(for: address) + } else { + location = nil + } + + if options.contains(.showInlineFrames) { + for inline in image.inlineCallSites(at: address) { + let fakeSymbol = Symbol(imageIndex: imageNdx, + imageName: name, + rawName: inline.rawName ?? "", + offset: 0, + sourceLocation: location) + frames.append(Frame(captured: frame, + symbol: fakeSymbol, + inlined: true)) + + location = SourceLocation(path: inline.filename, + line: inline.line, + column: inline.column) } } + + return Symbol(imageIndex: imageNdx, + imageName: name, + rawName: theSymbol.name, + offset: theSymbol.offset, + sourceLocation: location) } - if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) { - var location: SourceLocation? - - if showSourceLocations || showInlineFrames { - location = try? elf32Image!.sourceLocation(for: relativeAddress) - } else { - location = nil + if let hit = cache.lookup(path: theImages[imageNdx].path) { + switch hit { + case let .elf32Image(image): + if let theSymbol = lookupSymbol(image: image, + at: imageNdx, + named: name, + address: relativeAddress) { + symbol = theSymbol + } + case let .elf64Image(image): + if let theSymbol = lookupSymbol(image: image, + at: imageNdx, + named: name, + address: relativeAddress) { + symbol = theSymbol + } } - - if showInlineFrames { - for inline in elf32Image!.inlineCallSites(at: relativeAddress) { - let fakeSymbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: inline.rawName ?? "", - offset: 0, - sourceLocation: location) - frames.append(Frame(captured: frame, - symbol: fakeSymbol, - inlined: true)) - - location = SourceLocation(path: inline.filename, - line: inline.line, - column: inline.column) - } - } - - symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: theSymbol.name, - offset: theSymbol.offset, - sourceLocation: location) - } else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) { - var location: SourceLocation? - - if showSourceLocations || showInlineFrames { - location = try? elf64Image!.sourceLocation(for: relativeAddress) - } else { - location = nil - } - - if showInlineFrames { - for inline in elf64Image!.inlineCallSites(at: relativeAddress) { - let fakeSymbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: inline.rawName ?? "", - offset: 0, - sourceLocation: location) - frames.append(Frame(captured: frame, - symbol: fakeSymbol, - inlined: true)) - - location = SourceLocation(path: inline.filename, - line: inline.line, - column: inline.column) - } - } - - symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: theSymbol.name, - offset: theSymbol.offset, - sourceLocation: location) - } else { - symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: "", - offset: 0, - sourceLocation: nil) } frames.append(Frame(captured: frame, symbol: symbol)) @@ -599,18 +555,16 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { return SymbolicatedBacktrace(backtrace: backtrace, images: theImages, - sharedCacheInfo: theCacheInfo, frames: frames) } /// Provide a textual version of the backtrace. public var description: String { var lines: [String] = [] - let addressChars = (backtrace.addressWidth + 3) / 4 var n = 0 for frame in frames { - lines.append("\(n)\t\(frame.description(width: addressChars))") + lines.append("\(n)\t\(frame.description)") switch frame.captured { case let .omittedFrames(count): n += count @@ -623,16 +577,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { lines.append("Images:") lines.append("") for (n, image) in images.enumerated() { - lines.append("\(n)\t\(image.description(width: addressChars))") - } - - if let sharedCacheInfo = sharedCacheInfo { - lines.append("") - lines.append("Shared Cache:") - lines.append("") - lines.append(" UUID: \(hex(sharedCacheInfo.uuid))") - lines.append(" Base: \(hex(sharedCacheInfo.baseAddress, width: addressChars))") - lines.append(" Active: \(!sharedCacheInfo.noCache)") + lines.append("\(n)\t\(image.description)") } return lines.joined(separator: "\n") diff --git a/stdlib/public/Backtracing/Utils.swift b/stdlib/public/RuntimeModule/Utils.swift similarity index 76% rename from stdlib/public/Backtracing/Utils.swift rename to stdlib/public/RuntimeModule/Utils.swift index 50a2a571d9c..e986f2a3882 100644 --- a/stdlib/public/Backtracing/Utils.swift +++ b/stdlib/public/RuntimeModule/Utils.swift @@ -95,3 +95,32 @@ public func stripWhitespace(_ s: S) let lastNonWhitespace = s.lastIndex(where: { !$0.isWhitespace })! return s[firstNonWhitespace...lastNonWhitespace] } + +/// Strip any Optional from a value. +/// +/// This is useful when interfacing with the system C library, because some +/// C libraries have nullability annotations while others do not. +func notOptional(_ optional: T?) -> T { + return optional! +} + +func notOptional(_ value: T) -> T { + return value +} + +/// Convert mutable pointers to non-mutable +/// +/// This is useful when interfacing with the system C library, because some +/// C libraries have const annotations in places others do not. +func notMutable(_ mutable: UnsafeMutablePointer) -> UnsafePointer { + return UnsafePointer(mutable) +} +func notMutable(_ immutable: UnsafePointer) -> UnsafePointer { + return immutable +} +func notMutable(_ mutable: UnsafeMutableRawPointer) -> UnsafeRawPointer { + return UnsafeRawPointer(mutable) +} +func notMutable(_ immutable: UnsafeRawPointer) -> UnsafeRawPointer { + return immutable +} diff --git a/stdlib/public/Backtracing/Win32Extras.cpp b/stdlib/public/RuntimeModule/Win32Extras.cpp similarity index 100% rename from stdlib/public/Backtracing/Win32Extras.cpp rename to stdlib/public/RuntimeModule/Win32Extras.cpp diff --git a/stdlib/public/Backtracing/get-cpu-context.S b/stdlib/public/RuntimeModule/get-cpu-context.S similarity index 100% rename from stdlib/public/Backtracing/get-cpu-context.S rename to stdlib/public/RuntimeModule/get-cpu-context.S diff --git a/stdlib/public/Backtracing/get-cpu-context.asm b/stdlib/public/RuntimeModule/get-cpu-context.asm similarity index 100% rename from stdlib/public/Backtracing/get-cpu-context.asm rename to stdlib/public/RuntimeModule/get-cpu-context.asm diff --git a/stdlib/public/Backtracing/modules/Compression.h b/stdlib/public/RuntimeModule/modules/Compression.h similarity index 100% rename from stdlib/public/Backtracing/modules/Compression.h rename to stdlib/public/RuntimeModule/modules/Compression.h diff --git a/stdlib/public/Backtracing/modules/FixedLayout.h b/stdlib/public/RuntimeModule/modules/FixedLayout.h similarity index 100% rename from stdlib/public/Backtracing/modules/FixedLayout.h rename to stdlib/public/RuntimeModule/modules/FixedLayout.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/Dwarf/dwarf.h b/stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/dwarf.h similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/Dwarf/dwarf.h rename to stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/dwarf.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/Dwarf/eh_frame_hdr.h b/stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/eh_frame_hdr.h similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/Dwarf/eh_frame_hdr.h rename to stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/eh_frame_hdr.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/Elf/elf.h b/stdlib/public/RuntimeModule/modules/ImageFormats/Elf/elf.h similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/Elf/elf.h rename to stdlib/public/RuntimeModule/modules/ImageFormats/Elf/elf.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/ImageFormats.modulemap b/stdlib/public/RuntimeModule/modules/ImageFormats/ImageFormats.modulemap similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/ImageFormats.modulemap rename to stdlib/public/RuntimeModule/modules/ImageFormats/ImageFormats.modulemap diff --git a/stdlib/public/Backtracing/modules/OS/Darwin.h b/stdlib/public/RuntimeModule/modules/OS/Darwin.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Darwin.h rename to stdlib/public/RuntimeModule/modules/OS/Darwin.h diff --git a/stdlib/public/Backtracing/modules/OS/Libc.h b/stdlib/public/RuntimeModule/modules/OS/Libc.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Libc.h rename to stdlib/public/RuntimeModule/modules/OS/Libc.h diff --git a/stdlib/public/Backtracing/modules/OS/Linux.h b/stdlib/public/RuntimeModule/modules/OS/Linux.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Linux.h rename to stdlib/public/RuntimeModule/modules/OS/Linux.h diff --git a/stdlib/public/Backtracing/modules/OS/OS.modulemap b/stdlib/public/RuntimeModule/modules/OS/OS.modulemap similarity index 100% rename from stdlib/public/Backtracing/modules/OS/OS.modulemap rename to stdlib/public/RuntimeModule/modules/OS/OS.modulemap diff --git a/stdlib/public/Backtracing/modules/OS/Windows.h b/stdlib/public/RuntimeModule/modules/OS/Windows.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Windows.h rename to stdlib/public/RuntimeModule/modules/OS/Windows.h diff --git a/stdlib/public/Backtracing/modules/Runtime/Runtime.h b/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h similarity index 70% rename from stdlib/public/Backtracing/modules/Runtime/Runtime.h rename to stdlib/public/RuntimeModule/modules/Runtime/Runtime.h index c6e830d7ee7..0cda2acc684 100644 --- a/stdlib/public/Backtracing/modules/Runtime/Runtime.h +++ b/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h @@ -23,21 +23,23 @@ #include "swift/Runtime/CrashInfo.h" #ifdef __cplusplus -#define EXTERN_C extern "C" -#else -#define EXTERN_C +extern "C" { #endif // Can't import swift/Runtime/Debug.h because it assumes C++ -EXTERN_C void swift_reportWarning(uint32_t flags, const char *message); +void swift_reportWarning(uint32_t flags, const char *message); // Returns true if the given function is a thunk function -EXTERN_C bool _swift_backtrace_isThunkFunction(const char *rawName); +bool _swift_backtrace_isThunkFunction(const char *rawName); // Demangle the given raw name (supports Swift and C++) -EXTERN_C char *_swift_backtrace_demangle(const char *rawName, - size_t rawNameLength, - char *outputBuffer, - size_t *outputBufferSize); +char *_swift_backtrace_demangle(const char *rawName, + size_t rawNameLength, + char *outputBuffer, + size_t *outputBufferSize); + +#ifdef __cplusplus +} +#endif #endif // SWIFT_BACKTRACING_RUNTIME_H diff --git a/stdlib/public/Backtracing/modules/module.modulemap b/stdlib/public/RuntimeModule/modules/module.modulemap similarity index 100% rename from stdlib/public/Backtracing/modules/module.modulemap rename to stdlib/public/RuntimeModule/modules/module.modulemap diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index eb9547e7491..c706fe53f6a 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -335,7 +335,7 @@ endif() list(APPEND swift_stdlib_compile_flags "-plugin-path" "${swift_lib_dir}/swift/host/plugins") set(swift_core_incorporate_object_libraries) -list(APPEND swift_core_incorporate_object_libraries swiftRuntime) +list(APPEND swift_core_incorporate_object_libraries swiftRuntimeCore) list(APPEND swift_core_incorporate_object_libraries swiftLLVMSupport) list(APPEND swift_core_incorporate_object_libraries swiftDemangling) list(APPEND swift_core_incorporate_object_libraries swiftStdlibStubs) diff --git a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt index 7a354cabe3c..8e828f3d7c7 100644 --- a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt +++ b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt @@ -12,11 +12,11 @@ if(SWIFT_BUILD_SDK_OVERLAY) set(musl Musl) endif() -# Similarly, we only want the _Backtracing dependency if we're building +# Similarly, we only want the Runtime dependency if we're building # with the stdlib. -set(backtracing) -if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_BACKTRACING) - set(backtracing _Backtracing) +set(runtime) +if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_RUNTIME_MODULE) + set(runtime Runtime) endif() if(NOT SWIFT_BUILD_STDLIB) @@ -25,7 +25,7 @@ endif() set(BACKTRACING_COMPILE_FLAGS "-cxx-interoperability-mode=default" - "-I${SWIFT_STDLIB_SOURCE_DIR}/public/Backtracing/modules" + "-I${SWIFT_STDLIB_SOURCE_DIR}/public/RuntimeModule/modules" "-Xcc;-I${SWIFT_SOURCE_DIR}/include" "-Xcc;-I${CMAKE_BINARY_DIR}/include" "-disable-upcoming-feature;MemberImportVisibility") @@ -51,7 +51,7 @@ endif() add_swift_target_executable(swift-backtrace BUILD_WITH_LIBEXEC ${BACKTRACING_SOURCES} - SWIFT_MODULE_DEPENDS ${backtracing} + SWIFT_MODULE_DEPENDS ${runtime} SWIFT_MODULE_DEPENDS_OSX ${darwin} SWIFT_MODULE_DEPENDS_WINDOWS ${wincrt_sdk} diff --git a/stdlib/public/libexec/swift-backtrace/TargetLinux.swift b/stdlib/public/libexec/swift-backtrace/TargetLinux.swift index d681b724de8..9f9d0cf095b 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetLinux.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetLinux.swift @@ -23,11 +23,11 @@ import Glibc import Musl #endif -import _Backtracing -@_spi(Internal) import _Backtracing -@_spi(Contexts) import _Backtracing -@_spi(MemoryReaders) import _Backtracing -@_spi(Utils) import _Backtracing +import Runtime +@_spi(Internal) import Runtime +@_spi(Contexts) import Runtime +@_spi(MemoryReaders) import Runtime +@_spi(Utils) import Runtime enum SomeBacktrace { case raw(Backtrace) @@ -84,7 +84,7 @@ class Target { } } - var reader: CachingMemoryReader + var reader: MemserverMemoryReader // Get the name of a process private static func getProcessName(pid: pid_t) -> String { @@ -118,7 +118,7 @@ class Target { let memserverFd: CInt = 4 pid = getppid() - reader = CachingMemoryReader(for: MemserverMemoryReader(fd: memserverFd)) + reader = MemserverMemoryReader(fd: memserverFd) name = Self.getProcessName(pid: pid) let crashInfo: CrashInfo @@ -172,34 +172,34 @@ class Target { let backtrace = try Backtrace.capture(from: context, using: reader, images: images, + algorithm: .auto, limit: limit, + offset: 0, top: top) let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { - guard let symbolicated - = backtrace.symbolicated(with: images, - sharedCacheInfo: nil, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + guard let symbolicated = backtrace.symbolicated( + with: images, + options: options + ) else { print("unable to symbolicate backtrace for thread \(t.tid)") exit(1) } @@ -243,37 +243,37 @@ class Target { guard let backtrace = try? Backtrace.capture(from: context, using: reader, images: images, + algorithm: .auto, limit: limit, + offset: 0, top: top) else { print("unable to capture backtrace from context for thread \(ndx)") continue } let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { guard let symbolicated = backtrace.symbolicated( with: images, - sharedCacheInfo: nil, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + options: options + ) else { print("unable to symbolicate backtrace from context for thread \(ndx)") continue } diff --git a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift index fba4f76300c..89dea203e80 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift @@ -20,10 +20,10 @@ import Darwin import Darwin.Mach -import _Backtracing -@_spi(Internal) import _Backtracing -@_spi(Contexts) import _Backtracing -@_spi(MemoryReaders) import _Backtracing +import Runtime +@_spi(Internal) import Runtime +@_spi(Contexts) import Runtime +@_spi(MemoryReaders) import Runtime internal import BacktracingImpl.OS.Darwin @@ -70,7 +70,6 @@ class Target { var task: task_t var images: [Backtrace.Image] = [] - var sharedCacheInfo: Backtrace.SharedCacheInfo? var threads: [TargetThread] = [] var crashingThreadNdx: Int = -1 @@ -102,7 +101,7 @@ class Target { } } - var reader: CachingMemoryReader + var reader: RemoteMemoryReader var mcontext: MContext @@ -170,7 +169,7 @@ class Target { task = parentTask - reader = CachingMemoryReader(for: RemoteMemoryReader(task: task_t(task))) + reader = RemoteMemoryReader(task: task_t(task)) name = Self.getProcessName(pid: pid) @@ -195,7 +194,6 @@ class Target { mcontext = mctx images = Backtrace.captureImages(for: task) - sharedCacheInfo = Backtrace.captureSharedCacheInfo(for: task) fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate) } @@ -269,7 +267,9 @@ class Target { guard let backtrace = try? Backtrace.capture(from: ctx, using: reader, images: nil, + algorithm: .auto, limit: limit, + offset: 0, top: top) else { print("swift-backtrace: unable to capture backtrace from context for thread \(ndx)", to: &standardError) @@ -277,30 +277,28 @@ class Target { } let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { guard let symbolicated = backtrace.symbolicated( with: images, - sharedCacheInfo: sharedCacheInfo, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + options: options + ) else { print("unable to symbolicate backtrace from context for thread \(ndx)", to: &standardError) exit(1) @@ -334,7 +332,9 @@ class Target { guard let backtrace = try? Backtrace.capture(from: context, using: reader, images: nil, + algorithm: .auto, limit: limit, + offset: 0, top: top) else { print("swift-backtrace: unable to capture backtrace from context for thread \(ndx)", to: &standardError) @@ -342,30 +342,28 @@ class Target { } let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { guard let symbolicated = backtrace.symbolicated( with: images, - sharedCacheInfo: sharedCacheInfo, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + options: options + ) else { print("swift-backtrace: unable to symbolicate backtrace from context for thread \(ndx)", to: &standardError) continue diff --git a/stdlib/public/libexec/swift-backtrace/Themes.swift b/stdlib/public/libexec/swift-backtrace/Themes.swift index f9addbc9028..347bd679bef 100644 --- a/stdlib/public/libexec/swift-backtrace/Themes.swift +++ b/stdlib/public/libexec/swift-backtrace/Themes.swift @@ -14,7 +14,7 @@ // //===----------------------------------------------------------------------===// -@_spi(Formatting) import _Backtracing +@_spi(Formatting) import Runtime protocol ErrorAndWarningTheme { func crashReason(_ s: String) -> String diff --git a/stdlib/public/libexec/swift-backtrace/Utils.swift b/stdlib/public/libexec/swift-backtrace/Utils.swift index 2fa492bbb09..b12b0159d7d 100644 --- a/stdlib/public/libexec/swift-backtrace/Utils.swift +++ b/stdlib/public/libexec/swift-backtrace/Utils.swift @@ -151,6 +151,18 @@ internal func spawn(_ path: String, args: [String]) throws { #endif // os(macOS) +extension Sequence { + /// Return the first element in a Sequence. + /// + /// This is not, in general, a safe thing to do, because the sequence might + /// not be restartable. For the cases where we're using it here, it's OK + /// though. + public var unsafeFirst: Element? { + var iterator = makeIterator() + return iterator.next() + } +} + struct CFileStream: TextOutputStream { var fp: UnsafeMutablePointer diff --git a/stdlib/public/libexec/swift-backtrace/main.swift b/stdlib/public/libexec/swift-backtrace/main.swift index 5c5db180060..3fc10013173 100644 --- a/stdlib/public/libexec/swift-backtrace/main.swift +++ b/stdlib/public/libexec/swift-backtrace/main.swift @@ -22,10 +22,10 @@ import Musl import CRT #endif -@_spi(Formatting) import _Backtracing -@_spi(Contexts) import _Backtracing -@_spi(Registers) import _Backtracing -@_spi(MemoryReaders) import _Backtracing +@_spi(Formatting) import Runtime +@_spi(Contexts) import Runtime +@_spi(Registers) import Runtime +@_spi(MemoryReaders) import Runtime @main internal struct SwiftBacktrace { @@ -144,8 +144,8 @@ internal struct SwiftBacktrace { } static func measureDuration(_ body: () -> ()) -> timespec { - var startTime = timespec() - var endTime = timespec() + var startTime = timespec(tv_sec: 0, tv_nsec: 0) + var endTime = timespec(tv_sec: 0, tv_nsec: 0) clock_gettime(CLOCK_MONOTONIC, &startTime) body() @@ -786,13 +786,6 @@ Generate a backtrace for the parent process. } } - let addressWidthInChars: Int - switch crashingThread.backtrace { - case let .raw(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - case let .symbolicated(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - } switch args.showImages! { case .none: break @@ -804,12 +797,10 @@ Generate a backtrace for the parent process. } else { writeln("\n\nImages:\n") } - writeln(formatter.format(images: images, - addressWidth: addressWidthInChars)) + writeln(formatter.format(images: images)) case .all: writeln("\n\nImages:\n") - writeln(formatter.format(images: target.images, - addressWidth: addressWidthInChars)) + writeln(formatter.format(images: target.images)) } } @@ -891,20 +882,15 @@ Generate a backtrace for the parent process. let formatter = backtraceFormatter() switch thread.backtrace { case let .raw(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - if let frame = backtrace.frames.first { - let formatted = formatter.format(frame: frame, - addressWidth: addressWidthInChars) + if let frame = backtrace.frames.unsafeFirst { + let formatted = formatter.format(frame: frame) writeln("\(formatted)") } case let .symbolicated(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - if let frame = backtrace.frames.drop(while: { $0.isSwiftRuntimeFailure - }).first { - let formatted = formatter.format(frame: frame, - addressWidth: addressWidthInChars) + }).unsafeFirst { + let formatted = formatter.format(frame: frame) writeln("\(formatted)") } } @@ -975,12 +961,10 @@ Generate a backtrace for the parent process. switch thread.backtrace { case let .raw(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - - if let frame = backtrace.frames.first { + if let frame = backtrace.frames.unsafeFirst { rows += formatter.formatRows( - frame: frame, - addressWidth: addressWidthInChars).map{ row in + frame: frame + ).map{ row in switch row { case let .columns(columns): @@ -991,14 +975,12 @@ Generate a backtrace for the parent process. } } case let .symbolicated(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - if let frame = backtrace.frames.drop(while: { $0.isSwiftRuntimeFailure - }).first { + }).unsafeFirst { rows += formatter.formatRows( - frame: frame, - addressWidth: addressWidthInChars).map{ row in + frame: frame + ).map{ row in switch row { case let .columns(columns): @@ -1020,15 +1002,7 @@ Generate a backtrace for the parent process. case "images": let formatter = backtraceFormatter() let images = target.images - let addressWidthInChars: Int - switch target.threads[currentThread].backtrace { - case let .raw(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - case let .symbolicated(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - } - let output = formatter.format(images: images, - addressWidth: addressWidthInChars) + let output = formatter.format(images: images) writeln(output) case "set": diff --git a/stdlib/public/runtime/CMakeLists.txt b/stdlib/public/runtime/CMakeLists.txt index be5bb9e352b..7c9a8532913 100644 --- a/stdlib/public/runtime/CMakeLists.txt +++ b/stdlib/public/runtime/CMakeLists.txt @@ -144,7 +144,7 @@ foreach(sdk ${SWIFT_SDKS}) endif() endforeach() -add_swift_target_library(swiftRuntime OBJECT_LIBRARY +add_swift_target_library(swiftRuntimeCore OBJECT_LIBRARY ${swift_runtime_sources} ${swift_runtime_objc_sources} ${swift_runtime_leaks_sources} diff --git a/test/Backtracing/BacktraceWithLimit.swift b/test/Backtracing/BacktraceWithLimit.swift index c91cd29d60c..8c4963eb013 100644 --- a/test/Backtracing/BacktraceWithLimit.swift +++ b/test/Backtracing/BacktraceWithLimit.swift @@ -9,7 +9,7 @@ // REQUIRES: backtracing // REQUIRES: OS=macosx || OS=linux-gnu -import _Backtracing +import Runtime func doFrames(_ count: Int) { if count <= 0 { diff --git a/test/Backtracing/BacktraceWithLimitAndTop.swift b/test/Backtracing/BacktraceWithLimitAndTop.swift index ccd067d1346..618e24606e3 100644 --- a/test/Backtracing/BacktraceWithLimitAndTop.swift +++ b/test/Backtracing/BacktraceWithLimitAndTop.swift @@ -9,7 +9,7 @@ // REQUIRES: backtracing // REQUIRES: OS=macosx || OS=linux-gnu -import _Backtracing +import Runtime func doFrames(_ count: Int, limit: Int, top: Int) { if count <= 0 { diff --git a/test/Backtracing/DwarfReader.swift b/test/Backtracing/DwarfReader.swift index 53ac43b45d8..6c1d71fc3f0 100644 --- a/test/Backtracing/DwarfReader.swift +++ b/test/Backtracing/DwarfReader.swift @@ -6,7 +6,7 @@ // REQUIRES: OS=linux-gnu // REQUIRES: backtracing -@_spi(DwarfTest) import _Backtracing +@_spi(DwarfTest) import Runtime #if canImport(Darwin) import Darwin #elseif canImport(SwiftWASILibc) diff --git a/test/Backtracing/ElfReader.swift b/test/Backtracing/ElfReader.swift index dbd8b5b68c6..0d011617bd9 100644 --- a/test/Backtracing/ElfReader.swift +++ b/test/Backtracing/ElfReader.swift @@ -15,7 +15,7 @@ // REQUIRES: OS=linux-gnu // REQUIRES: backtracing -@_spi(ElfTest) import _Backtracing +@_spi(ElfTest) import Runtime #if canImport(Darwin) import Darwin #elseif canImport(SwiftWASILibc) diff --git a/test/Backtracing/SimpleAsyncBacktrace.swift b/test/Backtracing/SimpleAsyncBacktrace.swift index 592a080fba9..941466aae17 100644 --- a/test/Backtracing/SimpleAsyncBacktrace.swift +++ b/test/Backtracing/SimpleAsyncBacktrace.swift @@ -12,7 +12,7 @@ // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime -import _Backtracing +import Runtime @available(SwiftStdlib 5.1, *) func level1() async { diff --git a/test/Backtracing/SimpleBacktrace.swift b/test/Backtracing/SimpleBacktrace.swift index da37be4f6dd..acccd40c75a 100644 --- a/test/Backtracing/SimpleBacktrace.swift +++ b/test/Backtracing/SimpleBacktrace.swift @@ -10,7 +10,7 @@ // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime -import _Backtracing +import Runtime func level1() { level2() diff --git a/test/Backtracing/SymbolicatedBacktrace.swift b/test/Backtracing/SymbolicatedBacktrace.swift index f1251b82df6..0d11b30ff5d 100644 --- a/test/Backtracing/SymbolicatedBacktrace.swift +++ b/test/Backtracing/SymbolicatedBacktrace.swift @@ -9,7 +9,7 @@ // REQUIRES: backtracing // REQUIRES: OS=macosx || OS=linux-gnu -import _Backtracing +import Runtime func kablam() { kerpow() @@ -32,7 +32,9 @@ func splat() { } func pow() { - let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)! + let backtrace = try! Backtrace.capture().symbolicated( + options: [ .showInlineFrames, .showSourceLocations ] + )! // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace pow() // CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace splat() diff --git a/test/Backtracing/SymbolicatedBacktraceInline.swift b/test/Backtracing/SymbolicatedBacktraceInline.swift index 3f7065dd0e7..2990bb6f029 100644 --- a/test/Backtracing/SymbolicatedBacktraceInline.swift +++ b/test/Backtracing/SymbolicatedBacktraceInline.swift @@ -13,7 +13,7 @@ // which presumably doesn't have a frame pointer. When we add the Dwarf EH // unwinder, we should be able to turn this test on. -import _Backtracing +import Runtime func kablam() { kerpow() @@ -36,7 +36,9 @@ func splat() { } func pow() { - let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)! + let backtrace = try! Backtrace.capture().symbolicated( + options: [ .showInlineFrames, .showSourceLocations ] + )! // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktraceInline pow() // CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline splat() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 90d828db69f..ec3ef53ede6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -82,6 +82,11 @@ function(get_test_dependencies SDK result_var_name) set(deps_binaries) + if (SWIFT_ENABLE_BACKTRACING) + list(APPEND deps_binaries + swift-backtrace) + endif() + if (SWIFT_INCLUDE_TOOLS) list(APPEND deps_binaries libMockPlugin @@ -251,6 +256,7 @@ normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_UNICODE_DATA) normalize_boolean_spelling(SWIFT_ENABLE_DISPATCH) normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_OBJC_INTEROP) normalize_boolean_spelling(SWIFT_ENABLE_BACKTRACING) +normalize_boolean_spelling(SWIFT_ENABLE_RUNTIME_MODULE) normalize_boolean_spelling(SWIFT_BUILD_SWIFT_SYNTAX) normalize_boolean_spelling(SWIFT_ENABLE_SYNCHRONIZATION) normalize_boolean_spelling(SWIFT_ENABLE_VOLATILE) @@ -473,6 +479,10 @@ foreach(SDK ${SWIFT_SDKS}) list(APPEND LIT_ARGS "--param" "volatile") endif() + if(SWIFT_ENABLE_RUNTIME_MODULE) + list(APPEND LIT_ARGS "--param" "runtime_module") + endif() + if(SWIFT_BUILD_REMOTE_MIRROR) list(APPEND LIT_ARGS "--param" "remote_mirror") endif() diff --git a/test/lit.cfg b/test/lit.cfg index dcb96fded8c..cd6931963bc 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -1326,7 +1326,7 @@ if run_vendor == 'apple': "swiftDarwin", "swiftSwiftPrivateThreadExtras", "swiftSwiftOnoneSupport", "swift_Concurrency"] if backtracing is not None: - libraries.append('swift_Backtracing') + libraries.append('swiftRuntime') for library in libraries: swift_execution_tests_extra_flags += ' -Xlinker -l%s'% library @@ -2208,12 +2208,12 @@ config.substitutions.append(('%concurrency_module', concurrency_module)) config.substitutions.append(('%/concurrency_module', '/'.join(os.path.normpath(concurrency_module).split(os.sep)))) -# Add 'backtracing_module' as the path to the _Backtracing .swiftmodule file -backtracing_module = os.path.join(stdlib_dir, "_Backtracing.swiftmodule", +# Add 'runtime_module' as the path to the Runtime .swiftmodule file +runtime_module = os.path.join(stdlib_dir, "Runtime.swiftmodule", target_specific_module_triple + ".swiftmodule") -config.substitutions.append(('%backtracing_module', backtracing_module)) -config.substitutions.append(('%/backtracing_module', - '/'.join(os.path.normpath(backtracing_module).split(os.sep)))) +config.substitutions.append(('%runtime_module', runtime_module)) +config.substitutions.append(('%/runtime_module', + '/'.join(os.path.normpath(runtime_module).split(os.sep)))) # Add 'distributed_module' as the path to the Distributed .swiftmodule file distributed_module = os.path.join(stdlib_dir, "Distributed.swiftmodule", diff --git a/unittests/runtime/CMakeLists.txt b/unittests/runtime/CMakeLists.txt index 4a9d32e5e24..920452c17fe 100644 --- a/unittests/runtime/CMakeLists.txt +++ b/unittests/runtime/CMakeLists.txt @@ -116,7 +116,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND # The runtime tests link to internal runtime symbols, which aren't exported # from the swiftCore dylib, so we need to link to both the runtime archive # and the stdlib. - $ + $ $ $ ) diff --git a/unittests/runtime/LongTests/CMakeLists.txt b/unittests/runtime/LongTests/CMakeLists.txt index e16bc0d540b..74709e914cd 100644 --- a/unittests/runtime/LongTests/CMakeLists.txt +++ b/unittests/runtime/LongTests/CMakeLists.txt @@ -42,7 +42,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND # The runtime tests link to internal runtime symbols, which aren't exported # from the swiftCore dylib, so we need to link to both the runtime archive # and the stdlib. - $ + $ $ $ ) diff --git a/utils/build_swift/build_swift/driver_arguments.py b/utils/build_swift/build_swift/driver_arguments.py index e53dccb61bb..71042e53dc1 100644 --- a/utils/build_swift/build_swift/driver_arguments.py +++ b/utils/build_swift/build_swift/driver_arguments.py @@ -1493,6 +1493,10 @@ def create_argument_parser(): default=True, help='Enable Volatile module.') + option('--enable-runtime-module', toggle_true, + default=True, + help='Enable Runtime module.') + option('--enable-experimental-parser-validation', toggle_true, default=True, help='Enable experimental Swift Parser validation by default.') diff --git a/utils/build_swift/tests/expected_options.py b/utils/build_swift/tests/expected_options.py index 5c37377c30b..0133fafeb8d 100644 --- a/utils/build_swift/tests/expected_options.py +++ b/utils/build_swift/tests/expected_options.py @@ -188,6 +188,7 @@ EXPECTED_DEFAULTS = { 'swift_enable_backtracing': True, 'enable_synchronization': True, 'enable_volatile': True, + 'enable_runtime_module': True, 'enable_lsan': False, 'enable_sanitize_coverage': False, 'disable_guaranteed_normal_arguments': False, diff --git a/utils/swift_build_support/swift_build_support/products/swift.py b/utils/swift_build_support/swift_build_support/products/swift.py index ba36cf4edaa..d649c8d6bf4 100644 --- a/utils/swift_build_support/swift_build_support/products/swift.py +++ b/utils/swift_build_support/swift_build_support/products/swift.py @@ -74,6 +74,9 @@ class Swift(product.Product): # Add volatile flag. self.cmake_options.extend(self._enable_volatile) + # Add runtime module flag. + self.cmake_options.extend(self._enable_runtime_module) + # Add static vprintf flag self.cmake_options.extend(self._enable_stdlib_static_vprintf) @@ -235,6 +238,11 @@ updated without updating swift.py?") return [('SWIFT_ENABLE_VOLATILE:BOOL', self.args.enable_volatile)] + @property + def _enable_runtime_module(self): + return [('SWIFT_ENABLE_RUNTIME_MODULE:BOOL', + self.args.enable_runtime_module)] + @property def _enable_stdlib_static_vprintf(self): return [('SWIFT_STDLIB_STATIC_PRINT', diff --git a/utils/swift_build_support/tests/products/test_swift.py b/utils/swift_build_support/tests/products/test_swift.py index 2d690aaa631..22f82238848 100644 --- a/utils/swift_build_support/tests/products/test_swift.py +++ b/utils/swift_build_support/tests/products/test_swift.py @@ -63,6 +63,7 @@ class SwiftTestCase(unittest.TestCase): swift_enable_backtracing=False, enable_synchronization=False, enable_volatile=False, + enable_runtime_module=False, build_early_swiftsyntax=False, build_swift_stdlib_static_print=False, build_swift_stdlib_unicode_data=True, @@ -112,6 +113,7 @@ class SwiftTestCase(unittest.TestCase): '-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE', '-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE', '-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE', + '-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=FALSE', '-DSWIFT_STDLIB_STATIC_PRINT=FALSE', '-DSWIFT_FREESTANDING_IS_DARWIN:BOOL=FALSE', '-DSWIFT_STDLIB_BUILD_PRIVATE:BOOL=TRUE', @@ -146,6 +148,7 @@ class SwiftTestCase(unittest.TestCase): '-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE', '-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE', '-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE', + '-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=FALSE', '-DSWIFT_STDLIB_STATIC_PRINT=FALSE', '-DSWIFT_FREESTANDING_IS_DARWIN:BOOL=FALSE', '-DSWIFT_STDLIB_BUILD_PRIVATE:BOOL=TRUE', @@ -470,6 +473,19 @@ class SwiftTestCase(unittest.TestCase): [x for x in swift.cmake_options if 'DSWIFT_ENABLE_VOLATILE' in x]) + def test_runtime_module_flags(self): + self.args.enable_runtime_module = True + swift = Swift( + args=self.args, + toolchain=self.toolchain, + source_dir='/path/to/src', + build_dir='/path/to/build') + self.assertEqual( + ['-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=' + 'TRUE'], + [x for x in swift.cmake_options + if 'DSWIFT_ENABLE_RUNTIME_MODULE in x]) + def test_freestanding_is_darwin_flags(self): self.args.swift_freestanding_is_darwin = True swift = Swift( diff --git a/validation-test/SIL/verify_all_overlays.py b/validation-test/SIL/verify_all_overlays.py index 5f9a244f780..c2b3441f250 100755 --- a/validation-test/SIL/verify_all_overlays.py +++ b/validation-test/SIL/verify_all_overlays.py @@ -39,9 +39,9 @@ for module_file in os.listdir(sdk_overlay_dir): if module_name == "DifferentiationUnittest": continue # Backtracing needs its own additional modules in the module path - if module_name == "_Backtracing": + if module_name == "Runtime": extra_args = ["-I", os.path.join(source_dir, "stdlib", - "public", "Backtracing", "modules"), + "public", "RuntimeModule", "modules"), "-I", os.path.join(source_dir, "include")] # _Concurrency needs its own additional modules in the module path if module_name == "_Concurrency": From 0e3e9efcd3d84d1f0c49b18b7ab6e3adb5de7238 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 15 Nov 2024 12:36:38 +0000 Subject: [PATCH 03/17] [Backtracing] Add ImageMap instead of just using an Array. We want to be able to efficiently serialise lists of images, and to do so it makes most sense to create a separate `ImageMap` type. This also provides a useful place to put methods to e.g. find an image by address or by build ID. rdar://124913332 --- docs/Backtracing.rst | 3 + docs/CompactBacktraceFormat.md | 2 +- docs/CompactImageMapFormat.md | 226 ++++++ stdlib/public/RuntimeModule/Backtrace.swift | 237 +----- stdlib/public/RuntimeModule/CMakeLists.txt | 4 + .../RuntimeModule/CompactImageMap.swift | 730 ++++++++++++++++++ .../RuntimeModule/FramePointerUnwinder.swift | 10 +- .../RuntimeModule/ImageMap+Darwin.swift | 91 +++ .../public/RuntimeModule/ImageMap+Linux.swift | 121 +++ stdlib/public/RuntimeModule/ImageMap.swift | 174 +++++ .../public/RuntimeModule/LimitSequence.swift | 6 + .../RuntimeModule/SymbolicatedBacktrace.swift | 19 +- .../libexec/swift-backtrace/TargetLinux.swift | 5 +- .../libexec/swift-backtrace/TargetMacOS.swift | 4 +- test/Backtracing/CompactImageMap.swift | 66 ++ test/Backtracing/ImageMap.swift | 27 + 16 files changed, 1501 insertions(+), 224 deletions(-) create mode 100644 docs/CompactImageMapFormat.md create mode 100644 stdlib/public/RuntimeModule/CompactImageMap.swift create mode 100644 stdlib/public/RuntimeModule/ImageMap+Darwin.swift create mode 100644 stdlib/public/RuntimeModule/ImageMap+Linux.swift create mode 100644 stdlib/public/RuntimeModule/ImageMap.swift create mode 100644 test/Backtracing/CompactImageMap.swift create mode 100644 test/Backtracing/ImageMap.swift diff --git a/docs/Backtracing.rst b/docs/Backtracing.rst index b2ee7a628e6..7543ceadbaf 100644 --- a/docs/Backtracing.rst +++ b/docs/Backtracing.rst @@ -327,3 +327,6 @@ Backtraces are stored internally in a format called :download:`Compact Backtrace Format `. This provides us with a way to store a large number of frames in a much smaller space than would otherwise be possible. +Similarly, where we need to store address to image mappings, we +use :download:`Compact ImageMap Format ` to minimise +storage requirements. diff --git a/docs/CompactBacktraceFormat.md b/docs/CompactBacktraceFormat.md index c1746e7c593..7b1441b5328 100644 --- a/docs/CompactBacktraceFormat.md +++ b/docs/CompactBacktraceFormat.md @@ -25,7 +25,7 @@ information byte: ~~~ The `version` field identifies the version of CBF that is in use; this -document describes version `0`. The `size` field is encoded as +document describes version `0`. The `size` field is encqoded as follows: | `size` | Machine word size | diff --git a/docs/CompactImageMapFormat.md b/docs/CompactImageMapFormat.md new file mode 100644 index 00000000000..5c5bb2442bf --- /dev/null +++ b/docs/CompactImageMapFormat.md @@ -0,0 +1,226 @@ +Compact ImageMap Format +======================= + +A process' address space contains (among other things) the set of +dynamically loaded images that have been mapped into that address +space. When generating crash logs or symbolicating backtraces, we +need to be able to capture and potentially store the list of images +that has been loaded, as well as some of the attributes of those +images, including each image's + +- Path +- Build ID (aka UUID) +- Base address +- End-of-text address + +Compact ImageMap Format (CIF) is a binary format for holding this +information. + +### General Format + +Compact ImageMap Format data is byte aligned and starts with an +information byte: + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────┬───────┐ + │ version │ size │ + └───────────────────────┴───────┘ +~~~ + +The `version` field identifies the version of CIF that is in use; this +document describes version `0`. The `size` field is encoded as +follows: + +| `size` | Machine word size | +| :----: | :---------------- | +| 00 | 16-bit | +| 01 | 32-bit | +| 10 | 64-bit | +| 11 | Reserved | + +This is followed immediately by a field encoding the number of images +in the image map; this field is encoded as a sequence of bytes, each +holding seven bits of data, with the top bit clear for the final byte. +The most significant byte is the first. e.g. + +| `count` | Encoding | +| ------: | :---------- | +| 0 | 00 | +| 1 | 01 | +| 127 | 7f | +| 128 | 81 00 | +| 129 | 81 01 | +| 700 | 85 3c | +| 1234 | 89 52 | +| 16384 | 81 80 00 | +| 65535 | 83 ff 7f | +| 2097152 | 81 80 80 00 | + +This in turn is followed by the list of images, stored in order of +increasing base address. For each image, we start with a header byte: + +~~~ + 7 6 5 4 3 2 1 0 + ┌───┬───┬───────────┬───────────┐ + │ r │ 0 │ acount │ ecount │ + └───┴───┴───────────┴───────────┘ +~~~ + +If `r` is set, then the base address is understood to be relative to +the previously computed base address. + +This byte is followed by `acount + 1` bytes of base address, then +`ecount + 1` bytes of offset to the end of text. + +Following this is an encoded count of bytes in the build ID, +encoded using the 7-bit scheme we used to encode the image count, and +then after that come the build ID bytes themselves. + +Finally, we encode the path string using the scheme below. + +### String Encoding + +Image paths contain a good deal of redundancy; paths are therefore +encoded using a prefix compression scheme. The basic idea here is +that while generating or reading the data, we maintain a mapping from +small integers to path prefix segments. + +The mapping is initialised with the following fixed list that never +need to be stored in CIF data: + +| code | Path prefix | +| :--: | :---------------------------------- | +| 0 | `/lib` | +| 1 | `/usr/lib` | +| 2 | `/usr/local/lib` | +| 3 | `/opt/lib` | +| 4 | `/System/Library/Frameworks` | +| 5 | `/System/Library/PrivateFrameworks` | +| 6 | `/System/iOSSupport` | +| 7 | `/Library/Frameworks` | +| 8 | `/System/Applications` | +| 9 | `/Applications` | +| 10 | `C:\Windows\System32` | +| 11 | `C:\Program Files\` | + +Codes below 32 are reserved for future expansion of the fixed list. + +Strings are encoded as a sequence of bytes, as follows: + +| `opcode` | Mnemonic | Meaning | +| :--------: | :-------- | :---------------------------------------- | +| `00000000` | `end` | Marks the end of the string | +| `00xxxxxx` | `str` | Raw string data | +| `01xxxxxx` | `framewk` | Names a framework | +| `1exxxxxx` | `expand` | Identifies a prefix in the table | + +#### `end` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────────────┐ + │ 0 0 0 0 0 0 0 0 │ end + └───────────────────────────────┘ +~~~ + +#### Meaning + +Marks the end of the string + +#### `str` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────┬───────────────────────┐ + │ 0 0 │ count │ str + └───────┴───────────────────────┘ +~~~ + +##### Meaning + +The next `count` bytes are included in the string verbatim. +Additionally, all path prefixes of this string data will be added to +the current prefix table. For instance, if the string data is +`/swift/linux/x86_64/libfoo.so`, then the prefix `/swift` will be +assigned the next available code, `/swift/linux` the code after that, +and `/swift/linux/x86_64` the code following that one. + +#### `framewk` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────┬───────────────────────┐ + │ 0 1 │ count │ framewk + └───────┴───────────────────────┘ +~~~ + +##### Meaning + +The next byte is a version character (normally `A`, but some +frameworks use higher characters), after which there are `count + 1` +bytes of name. + +This is expanded using the pattern +`/.framework/Versions//`. This also marks the +end of the string. + +#### `expand` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───┬───┬───────────────────────┐ + │ 1 │ e │ code │ expand + └───┴───┴───────────────────────┘ +~~~ + +##### Meaning + +If `e` is `0`, `code` is the index into the prefix table for the +prefix that should be appended to the string at this point. + +If `e` is `1`, this opcode is followed by `code + 1` bytes that give +a value `v` such that `v + 64` is the index into the prefix table for +the prefix that should be appended to the string at this point. + +#### Example + +Let's say we wish to encode the following strings: + + /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit + /System/Library/Frameworks/Photos.framework/Versions/A/Photos + /usr/lib/libobjc.A.dylib + /usr/lib/libz.1.dylib + /usr/lib/swift/libswiftCore.dylib + /usr/lib/libSystem.B.dylib + /usr/lib/libc++.1.dylib + +We would encode + + <84> <45> CAppKit <00> + +We then follow with + + <84> <45> APhotos <00> + +Next we have + + <81> <10> /libobjc.A.dylib <00> + <81> <0d> /libz.1.dylib <00> + <81> <19> /swift/libswiftCore.dylib <00> + +assigning code 32 to `/swift`, then + + <81> <12> /libSystem.B.dylib <00> + <81> <0f> /libc++.1.dylib <00> + +In total the original data would have taken up 256 bytes. Instead, we +have used 122 bytes, a saving of over 50%. diff --git a/stdlib/public/RuntimeModule/Backtrace.swift b/stdlib/public/RuntimeModule/Backtrace.swift index 4b03f438102..c4806979c91 100644 --- a/stdlib/public/RuntimeModule/Backtrace.swift +++ b/stdlib/public/RuntimeModule/Backtrace.swift @@ -16,23 +16,15 @@ import Swift -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) -internal import Darwin -#elseif os(Windows) -internal import ucrt -#elseif canImport(Glibc) -internal import Glibc -#elseif canImport(Musl) -internal import Musl -#endif - -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -internal import BacktracingImpl.OS.Darwin -#endif - -#if os(Linux) -internal import BacktracingImpl.ImageFormats.Elf -#endif +// #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +// internal import Darwin +// #elseif os(Windows) +// internal import ucrt +// #elseif canImport(Glibc) +// internal import Glibc +// #elseif canImport(Musl) +// internal import Musl +// #endif /// Holds a backtrace. public struct Backtrace: CustomStringConvertible, Sendable { @@ -45,8 +37,8 @@ public struct Backtrace: CustomStringConvertible, Sendable { /// This is intentionally _not_ a pointer, because you shouldn't be /// dereferencing them; they may refer to some other process, for /// example. - public struct Address: Hashable, Codable, Sendable { - enum Representation: Hashable, Codable, Sendable { + public struct Address: Hashable, Sendable { + enum Representation: Hashable, Sendable { case null case sixteenBit(UInt16) case thirtyTwoBit(UInt32) @@ -253,8 +245,8 @@ public struct Backtrace: CustomStringConvertible, Sendable { /// Some backtracing algorithms may require this information, in which case /// it will be filled in by the `capture()` method. Other algorithms may /// not, in which case it will be `nil` and you can capture an image list - /// separately yourself using `captureImages()`. - public var images: [Image]? + /// separately yourself using `ImageMap.capture()`. + public var images: ImageMap? /// Capture a backtrace from the current program location. /// @@ -284,10 +276,10 @@ public struct Backtrace: CustomStringConvertible, Sendable { limit: Int? = 64, offset: Int = 0, top: Int = 16, - images: [Image]? = nil) throws -> Backtrace { + images: ImageMap? = nil) throws -> Backtrace { #if os(Linux) // On Linux, we need the captured images to resolve async functions - let theImages = images ?? captureImages() + let theImages = images ?? ImageMap.capture() #else let theImages = images #endif @@ -305,18 +297,6 @@ public struct Backtrace: CustomStringConvertible, Sendable { } } - /// Capture a list of the images currently mapped into the calling - /// process. - /// - /// @returns A list of `Image`s. - public static func captureImages() -> [Image] { - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - return captureImages(for: mach_task_self()) - #else - return captureImages(using: UnsafeLocalMemoryReader()) - #endif - } - /// Specifies options for the `symbolicated` method. public struct SymbolicationOptions: OptionSet { public let rawValue: Int @@ -353,7 +333,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { /// used; otherwise we will capture images at this point. /// /// - options: Symbolication options; see `SymbolicationOptions`. - public func symbolicated(with images: [Image]? = nil, + public func symbolicated(with images: ImageMap? = nil, options: SymbolicationOptions = .default) -> SymbolicatedBacktrace? { return SymbolicatedBacktrace.symbolicate( @@ -391,9 +371,10 @@ public struct Backtrace: CustomStringConvertible, Sendable { } /// Initialise a Backtrace from a sequence of `RichFrame`s - init(architecture: String, + @_spi(Internal) + public init(architecture: String, frames: some Sequence>, - images: [Image]?) { + images: ImageMap?) { self.architecture = architecture self.representation = Array(CompactBacktraceFormat.Encoder(frames)) self.images = images @@ -403,20 +384,26 @@ public struct Backtrace: CustomStringConvertible, Sendable { // -- Capture Implementation ------------------------------------------------- extension Backtrace { + + // ###FIXME: There is a problem with @_specialize here that results in the + // arguments not lining up properly when this gets used from + // swift-backtrace. + @_spi(Internal) - @_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader) - @_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader) - #if os(Linux) - @_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader) - #endif + //@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader) + //@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader) + //#if os(Linux) + //@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader) + //#endif + @inlinable public static func capture( from context: Ctx, using memoryReader: Rdr, - images: [Image]?, + images: ImageMap?, algorithm: UnwindAlgorithm, - limit: Int?, - offset: Int, - top: Int + limit: Int? = 64, + offset: Int = 0, + top: Int = 16 ) throws -> Backtrace { switch algorithm { // All of them, for now, use the frame pointer unwinder. In the long @@ -428,6 +415,8 @@ extension Backtrace { memoryReader: memoryReader) if let limit = limit { + print("limit = \(limit), offset = \(offset), top = \(top)") + let limited = LimitSequence(unwinder, limit: limit, offset: offset, @@ -439,160 +428,8 @@ extension Backtrace { } return Backtrace(architecture: context.architecture, - frames: unwinder, + frames: unwinder.dropFirst(offset), images: images) } } } - -// -- Darwin ----------------------------------------------------------------- - -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -extension Backtrace { - - private static func withDyldProcessInfo(for task: task_t, - fn: (OpaquePointer?) throws -> T) - rethrows -> T { - var kret = kern_return_t(KERN_SUCCESS) - let dyldInfo = _dyld_process_info_create(task, 0, &kret) - - if kret != KERN_SUCCESS { - fatalError("error: cannot create dyld process info") - } - - defer { - _dyld_process_info_release(dyldInfo) - } - - return try fn(dyldInfo) - } - - @_spi(Internal) - public static func captureImages(for process: Any) -> [Image] { - var images: [Image] = [] - let task = process as! task_t - - withDyldProcessInfo(for: task) { dyldInfo in - _dyld_process_info_for_each_image(dyldInfo) { - (machHeaderAddress, uuid, path) in - - if let path = path, let uuid = uuid { - let pathString = String(cString: path) - let theUUID = Array(UnsafeBufferPointer(start: uuid, - count: MemoryLayout.size)) - let name: String - if let slashIndex = pathString.lastIndex(of: "/") { - name = String(pathString.suffix(from: - pathString.index(after:slashIndex))) - } else { - name = pathString - } - - // Find the end of the __TEXT segment - var endOfText = machHeaderAddress + 4096 - - _dyld_process_info_for_each_segment(dyldInfo, machHeaderAddress) { - address, size, name in - - if let name = String(validatingCString: name!), name == "__TEXT" { - endOfText = address + size - } - } - - images.append(Image(name: name, - path: pathString, - uniqueID: theUUID, - baseAddress: Address(machHeaderAddress), - endOfText: Address(endOfText))) - } - } - } - - return images.sorted(by: { $0.baseAddress < $1.baseAddress }) - } -} -#endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - -// -- Linux ------------------------------------------------------------------ - -#if os(Linux) -extension Backtrace { - private struct AddressRange { - var low: Address = 0 - var high: Address = 0 - } - - @_spi(Internal) - @_specialize(exported: true, kind: full, where M == UnsafeLocalMemoryReader) - @_specialize(exported: true, kind: full, where M == RemoteMemoryReader) - @_specialize(exported: true, kind: full, where M == LocalMemoryReader) - public static func captureImages( - using reader: M, - forProcess pid: Int? = nil - ) -> [Image] { - var images: [Image] = [] - - let path: String - if let pid = pid { - path = "/proc/\(pid)/maps" - } else { - path = "/proc/self/maps" - } - - guard let procMaps = readString(from: path) else { - return [] - } - - // Find all the mapped files and get high/low ranges - var mappedFiles: [Substring:AddressRange] = [:] - for match in ProcMapsScanner(procMaps) { - let path = stripWhitespace(match.pathname) - if match.inode == "0" || path == "" { - continue - } - guard let start = Address(match.start), - let end = Address(match.end) else { - continue - } - - if let range = mappedFiles[path] { - mappedFiles[path] = AddressRange(low: min(start, range.low), - high: max(end, range.high)) - } else { - mappedFiles[path] = AddressRange(low: start, - high: end) - } - } - - // Look at each mapped file to see if it's an ELF image - for (path, range) in mappedFiles { - // Extract the filename from path - let name: Substring - if let slashIndex = path.lastIndex(of: "/") { - name = path.suffix(from: path.index(after: slashIndex)) - } else { - name = path - } - - // Inspect the image and extract the UUID and end of text - guard let (endOfText, uuid) = getElfImageInfo(at: M.Address(range.low)!, - using: reader) else { - // Not an ELF iamge - continue - } - - let image = Image(name: String(name), - path: String(path), - uniqueID: uuid, - baseAddress: range.low, - endOfText: Address(endOfText)) - - images.append(image) - } - - return images.sorted(by: { $0.baseAddress < $1.baseAddress }) - } -} -#endif // os(Linux) - - diff --git a/stdlib/public/RuntimeModule/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt index 2f67f35faf6..6d89e52a41f 100644 --- a/stdlib/public/RuntimeModule/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -29,6 +29,7 @@ set(RUNTIME_SOURCES ByteSwapping.swift CachingMemoryReader.swift CompactBacktrace.swift + CompactImageMap.swift Compression.swift Context.swift CoreSymbolication.swift @@ -38,6 +39,9 @@ set(RUNTIME_SOURCES ElfImageCache.swift FramePointerUnwinder.swift Image.swift + ImageMap.swift + ImageMap+Darwin.swift + ImageMap+Linux.swift ImageSource.swift Libc.swift LimitSequence.swift diff --git a/stdlib/public/RuntimeModule/CompactImageMap.swift b/stdlib/public/RuntimeModule/CompactImageMap.swift new file mode 100644 index 00000000000..3bd18ddb822 --- /dev/null +++ b/stdlib/public/RuntimeModule/CompactImageMap.swift @@ -0,0 +1,730 @@ +//===--- CompactImageMap.swift -------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Definitions for Compact ImageMap Format +// +//===----------------------------------------------------------------------===// + +import Swift + +@_spi(Internal) +public enum CompactImageMapFormat { + + /// The list of fixed prefixes used to encode paths. + static let fixedPathPrefixes = [ + // Traditional UNIX + (0, "/lib"), + (1, "/usr/lib"), + (2, "/usr/local/lib"), + (3, "/opt/lib"), + + // NeXT/Darwin + (4, "/System/Library/Frameworks"), + (5, "/System/Library/PrivateFrameworks"), + (6, "/System/iOSSupport"), + (7, "/Library/Frameworks"), + (8, "/System/Applications"), + (9, "/Applications"), + + // Windows + (10, "C:\\Windows\\System32"), + (11, "C:\\Program Files") + ] + + /// Tells us what size of machine words were used when generating the + /// image map. + enum WordSize: UInt8 { + case sixteenBit = 0 + case thirtyTwoBit = 1 + case sixtyFourBit = 2 + } + + /// Run a closure for each prefix of the specified string + static func forEachPrefix(of str: String.UTF8View.SubSequence, + body: (String) -> ()) { + let base = str.startIndex + let end = str.endIndex + var pos = base + + while pos < end { + let ch = str[pos] + + if pos > base && (ch == 0x2f || ch == 0x5c) { + let range = base..> { + var sequence: S + var iterator: S.Iterator + var imageCount: Int = 0 + var wordSize: WordSize = .sixtyFourBit + var wordMask: UInt64 = 0 + var pathPrefixes = Dictionary(uniqueKeysWithValues: fixedPathPrefixes) + var nextCode = 32 + + public init(_ sequence: S) { + self.sequence = sequence + self.iterator = sequence.makeIterator() + } + + mutating func decodeCount() -> Int? { + var value: Int = 0 + while true { + guard let byte = iterator.next() else { + return nil + } + + value = (value << 7) | Int(byte & 0x7f) + + if (byte & 0x80) == 0 { + break + } + } + return value + } + + mutating func decodeAddress(_ count: Int) -> UInt64? { + var word: UInt64 + guard let firstByte = iterator.next() else { + return nil + } + + // Sign extend + if (firstByte & 0x80) != 0 { + word = wordMask | UInt64(firstByte) + } else { + word = UInt64(firstByte) + } + + for _ in 1.. String? { + var byte: UInt8 + + guard let b = iterator.next() else { + return nil + } + + byte = b + + // `end` here means no string at all + if byte == 0x00 { + return nil + } + + var resultBytes: [UInt8] = [] + var stringBase: Int? = nil + + while true { + if byte == 0x00 { + // `end` + #if DEBUG_COMPACT_IMAGE_MAP + print("end") + #endif + return String(decoding: resultBytes, as: UTF8.self) + } else if byte < 0x40 { + // `str` + let count = Int(byte) + resultBytes.reserveCapacity(resultBytes.count + count) + let base = resultBytes.count + if stringBase == nil { + stringBase = base + } + for n in 0.. 0 && char == 0x2f { + let prefix = String(decoding: resultBytes[stringBase!.. ImageMap? { + // Check the version and decode the size + guard let infoByte = iterator.next() else { + return nil + } + let version = infoByte >> 2 + guard let size = WordSize(rawValue: infoByte & 0x3) else { + return nil + } + wordSize = size + guard version == 0 else { + return nil + } + + // Set up the word mask + switch wordSize { + case .sixteenBit: + wordMask = 0xff00 + case .thirtyTwoBit: + wordMask = 0xffffff00 + case .sixtyFourBit: + wordMask = 0xffffffffffffff00 + } + + // Next is the image count + guard let count = decodeCount() else { + return nil + } + + imageCount = count + + // Now decode all of the images + var images: [ImageMap.Image] = [] + var lastAddress: UInt64 = 0 + + images.reserveCapacity(count) + + for _ in 0..> 3) & 0x7) + 1) + let ecount = Int((header & 0x7) + 1) + + // Now the base and end of text addresses + guard let address = decodeAddress(acount) else { + return nil + } + let baseAddress: UInt64 + if relative { + baseAddress = lastAddress &+ address + } else { + baseAddress = address + } + + lastAddress = baseAddress + + guard let eotOffset = decodeAddress(ecount) else { + return nil + } + let endOfText = baseAddress &+ eotOffset + + // Next, get the build ID byte count + guard let buildIdBytes = decodeCount() else { + return nil + } + + // Read the build ID + var buildId: [UInt8]? = nil + + if buildIdBytes > 0 { + buildId = [] + buildId!.reserveCapacity(buildIdBytes) + + for _ in 0.. + @_spi(Internal) + public struct Encoder: Sequence { + public typealias Element = UInt8 + + private var source: ImageMap + + public init(_ source: ImageMap) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source) + } + + public struct Iterator: IteratorProtocol { + enum State { + case start + case count(Int) + case image + case baseAddress(Int) + case endOfText(Int) + case uniqueID(Int) + case uniqueIDBytes(Int) + case path + case pathCode(Int) + case pathString + case pathStringChunk(Int) + case version + case framework + case done + } + + var abytes = EightByteBuffer() + var ebytes = EightByteBuffer() + var acount: Int = 0 + var ecount: Int = 0 + var version: UInt8 = 0 + var lastAddress: UInt64 = 0 + var ndx: Int = 0 + var state: State = .start + var source: ImageMap + var pathPrefixes = fixedPathPrefixes + var nextCode = 32 + var remainingPath: String.UTF8View.SubSequence? + + func signExtend(_ value: UInt64) -> UInt64 { + let mask: UInt64 + let topBit: UInt64 + switch source.wordSize { + case .sixteenBit: + topBit = 0x8000 + mask = 0xffffffffffff0000 + case .thirtyTwoBit: + topBit = 0x80000000 + mask = 0xffffffff00000000 + case .sixtyFourBit: + return value + } + + if (value & topBit) != 0 { + return value | mask + } + return value + } + + init(_ source: ImageMap) { + self.source = source + } + + public mutating func next() -> UInt8? { + switch state { + case .done: + return nil + + case .start: + // The first thing we emit is the info byte + let size: WordSize + switch source.wordSize { + case .sixteenBit: + size = .sixteenBit + case .thirtyTwoBit: + size = .thirtyTwoBit + case .sixtyFourBit: + size = .sixtyFourBit + } + + let count = source.images.count + let bits = Int.bitWidth - count.leadingZeroBitCount + state = .count(7 * (bits / 7)) + + let version: UInt8 = 0 + let infoByte = (version << 2) | size.rawValue + return infoByte + + case let .count(ndx): + let count = source.images.count + let byte = UInt8(truncatingIfNeeded:(count >> ndx) & 0x7f) + if ndx == 0 { + state = .image + return byte + } else { + state = .count(ndx - 7) + return 0x80 | byte + } + + case .image: + if ndx == source.images.count { + state = .done + return nil + } + + let baseAddress = signExtend(source.images[ndx].baseAddress) + let delta = baseAddress &- lastAddress + + let endOfText = signExtend(source.images[ndx].endOfText) + let endOfTextOffset = endOfText - baseAddress + + let eotCount: Int + if endOfTextOffset & (1 << 63) != 0 { + let ones = ((~endOfTextOffset).leadingZeroBitCount - 1) >> 3 + eotCount = 8 - ones + } else { + let zeroes = (endOfTextOffset.leadingZeroBitCount - 1) >> 3 + eotCount = 8 - zeroes + } + + ebytes = EightByteBuffer(endOfTextOffset) + ecount = eotCount + + let absCount: Int + if baseAddress & (1 << 63) != 0 { + let ones = ((~baseAddress).leadingZeroBitCount - 1) >> 3 + absCount = 8 - ones + } else { + let zeroes = (baseAddress.leadingZeroBitCount - 1) >> 3 + absCount = 8 - zeroes + } + + let deltaCount: Int + if delta & (1 << 63) != 0 { + let ones = ((~delta).leadingZeroBitCount - 1) >> 3 + deltaCount = 8 - ones + } else { + let zeroes = (delta.leadingZeroBitCount - 1) >> 3 + deltaCount = 8 - zeroes + } + + lastAddress = baseAddress + + let relativeFlag: UInt8 + if absCount <= deltaCount { + abytes = EightByteBuffer(baseAddress) + acount = absCount + relativeFlag = 0 + } else { + abytes = EightByteBuffer(delta) + acount = deltaCount + relativeFlag = 0x80 + } + + state = .baseAddress(8 - acount) + return relativeFlag + | UInt8(truncatingIfNeeded: (acount - 1) << 3) + | UInt8(truncatingIfNeeded: ecount - 1) + + case let .baseAddress(ndx): + let byte = abytes[ndx] + if ndx + 1 == 8 { + state = .endOfText(8 - ecount) + } else { + state = .baseAddress(ndx + 1) + } + return byte + + case let .endOfText(ndx): + let byte = ebytes[ndx] + if ndx + 1 == 8 { + let count = source.images[self.ndx].uniqueID?.count ?? 0 + let bits = Int.bitWidth - count.leadingZeroBitCount + state = .uniqueID(7 * (bits / 7)) + } else { + state = .endOfText(ndx + 1) + } + return byte + + case let .uniqueID(cndx): + guard let count = source.images[self.ndx].uniqueID?.count else { + state = .path + if let path = source.images[self.ndx].path { + remainingPath = path.utf8[...] + } else { + remainingPath = nil + } + return 0 + } + let byte = UInt8(truncatingIfNeeded: (count >> cndx) & 0x7f) + if cndx == 0 { + state = .uniqueIDBytes(0) + return byte + } else { + state = .uniqueID(cndx - 7) + return 0x80 | byte + } + + case let .uniqueIDBytes(byteNdx): + let uniqueID = source.images[self.ndx].uniqueID! + let byte = uniqueID[byteNdx] + if byteNdx + 1 == uniqueID.count { + state = .path + if let path = source.images[self.ndx].path { + remainingPath = path.utf8[...] + } else { + remainingPath = nil + } + } else { + state = .uniqueIDBytes(byteNdx + 1) + } + return byte + + case .path: + guard let remainingPath = remainingPath, + remainingPath.count > 0 else { + ndx += 1 + state = .image + return 0x00 + } + + // Find the longest prefix match + var longestMatchLen = 0 + var matchedPrefix: Int? = nil + for (ndx, (_, prefix)) in pathPrefixes.enumerated() { + let prefixUTF8 = prefix.utf8 + if prefixUTF8.count > remainingPath.count { + continue + } + if prefixUTF8.count > longestMatchLen + && remainingPath.starts(with: prefixUTF8) { + longestMatchLen = prefixUTF8.count + matchedPrefix = ndx + } + } + + if let ndx = matchedPrefix { + let (code, prefix) = pathPrefixes[ndx] + self.remainingPath = remainingPath.dropFirst(prefix.utf8.count) + if code <= 0x3f { + return 0x80 | UInt8(exactly: code)! + } + + let theCode = UInt64(exactly: code - 0x40)! + abytes = EightByteBuffer(theCode) + + let codeBytes = Swift.max( + (64 - theCode.leadingZeroBitCount) >> 3, 1 + ) + + state = .pathCode(8 - codeBytes) + + return 0xc0 | UInt8(exactly: codeBytes - 1)! + } + + // Check for /.framework/Versions// + if let name = source.images[ndx].name, !name.isEmpty { + let nameCount = name.utf8.count + let expectedLen = 1 // '/' + + nameCount // + + 20 // .framework/Versions/ + + 1 // + + 1 // '/' + + nameCount // + if remainingPath.count == expectedLen { + let framework = "/\(name).framework/Versions/" + if remainingPath.starts(with: framework.utf8) { + var verNdx = remainingPath.startIndex + remainingPath.formIndex(&verNdx, offsetBy: framework.utf8.count) + + version = remainingPath[verNdx] + + let slashNdx = remainingPath.index(after: verNdx) + if remainingPath[slashNdx] == 0x2f { + let nameNdx = remainingPath.index(after: slashNdx) + if remainingPath[nameNdx...].elementsEqual(name.utf8) { + self.remainingPath = remainingPath[nameNdx...] + + state = .version + return 0x40 | UInt8(exactly: nameCount - 1)! + } + } + } + } + } + + // Add any new prefixes + forEachPrefix(of: remainingPath) { prefix in + pathPrefixes.append((nextCode, prefix)) + nextCode += 1 + } + + fallthrough + + case .pathString: + if remainingPath!.count == 0 { + ndx += 1 + state = .image + return 0x00 + } + + let chunkLength = Swift.min(remainingPath!.count, 0x3f) + state = .pathStringChunk(chunkLength) + return UInt8(truncatingIfNeeded: chunkLength) + + case let .pathStringChunk(length): + let byte = remainingPath!.first! + remainingPath = remainingPath!.dropFirst() + if length == 1 { + state = .pathString + } else { + state = .pathStringChunk(length - 1) + } + return byte + + case .version: + state = .framework + return version + + case .framework: + let byte = remainingPath!.first! + remainingPath = remainingPath!.dropFirst() + if remainingPath!.count == 0 { + ndx += 1 + state = .image + } + return byte + + case let .pathCode(ndx): + let byte = abytes[ndx] + if ndx + 1 == 8 { + state = .path + } else { + state = .pathCode(ndx + 1) + } + return byte + } + } + } + } + +} diff --git a/stdlib/public/RuntimeModule/FramePointerUnwinder.swift b/stdlib/public/RuntimeModule/FramePointerUnwinder.swift index 0e66b20fabe..3b874bb9425 100644 --- a/stdlib/public/RuntimeModule/FramePointerUnwinder.swift +++ b/stdlib/public/RuntimeModule/FramePointerUnwinder.swift @@ -30,7 +30,7 @@ public struct FramePointerUnwinder: Sequence, Itera var done: Bool #if os(Linux) - var images: [Backtrace.Image]? + var images: ImageMap? #endif var reader: MemoryReader @@ -41,7 +41,7 @@ public struct FramePointerUnwinder: Sequence, Itera @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) #endif public init(context: Context, - images: [Backtrace.Image]?, + images: ImageMap?, memoryReader: MemoryReader) { pc = Address(context.programCounter) @@ -87,10 +87,7 @@ public struct FramePointerUnwinder: Sequence, Itera let address = MemoryReader.Address(pc) if let images = images, - let imageNdx = images.firstIndex( - where: { address >= MemoryReader.Address($0.baseAddress)! - && address < MemoryReader.Address($0.endOfText)! } - ) { + let imageNdx = images.indexOfImage(at: Backtrace.Address(address)) { let base = MemoryReader.Address(images[imageNdx].baseAddress)! let relativeAddress = address - base let cache = ElfImageCache.threadLocal @@ -259,7 +256,6 @@ public struct FramePointerUnwinder: Sequence, Itera #endif asyncContext = next - return .asyncResumePoint(pc) } } diff --git a/stdlib/public/RuntimeModule/ImageMap+Darwin.swift b/stdlib/public/RuntimeModule/ImageMap+Darwin.swift new file mode 100644 index 00000000000..b72d2dbda6b --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageMap+Darwin.swift @@ -0,0 +1,91 @@ +//===--- ImageMap+Darwin.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Darwin specifics for ImageMap capture. +// +//===----------------------------------------------------------------------===// + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +import Swift + +internal import Darwin +internal import BacktracingImpl.OS.Darwin + +extension ImageMap { + + private static func withDyldProcessInfo(for task: task_t, + fn: (OpaquePointer?) throws -> T) + rethrows -> T { + var kret = kern_return_t(KERN_SUCCESS) + let dyldInfo = _dyld_process_info_create(task, 0, &kret) + + if kret != KERN_SUCCESS { + fatalError("error: cannot create dyld process info") + } + + defer { + _dyld_process_info_release(dyldInfo) + } + + return try fn(dyldInfo) + } + + @_spi(Internal) + public static func capture(for process: Any) -> ImageMap { + var images: [Image] = [] + let task = process as! task_t + + withDyldProcessInfo(for: task) { dyldInfo in + _dyld_process_info_for_each_image(dyldInfo) { + (machHeaderAddress, uuid, path) in + + if let path = path, let uuid = uuid { + let pathString = String(cString: path) + let theUUID = Array(UnsafeBufferPointer(start: uuid, + count: MemoryLayout.size)) + let name: String + if let slashIndex = pathString.lastIndex(of: "/") { + name = String(pathString.suffix(from: + pathString.index(after:slashIndex))) + } else { + name = pathString + } + + // Find the end of the __TEXT segment + var endOfText = machHeaderAddress + 4096 + + _dyld_process_info_for_each_segment(dyldInfo, machHeaderAddress) { + address, size, name in + + if let name = String(validatingCString: name!), name == "__TEXT" { + endOfText = address + size + } + } + + images.append(Image(name: name, + path: pathString, + uniqueID: theUUID, + baseAddress: machHeaderAddress, + endOfText: endOfText)) + } + } + } + + images.sort(by: { $0.baseAddress < $1.baseAddress }) + + return ImageMap(images: images, wordSize: .sixtyFourBit) + } + +} + +#endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS) diff --git a/stdlib/public/RuntimeModule/ImageMap+Linux.swift b/stdlib/public/RuntimeModule/ImageMap+Linux.swift new file mode 100644 index 00000000000..1608f72e8b4 --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageMap+Linux.swift @@ -0,0 +1,121 @@ +//===--- ImageMap+Linux.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Linux specifics for ImageMap capture. +// +//===----------------------------------------------------------------------===// + +#if os(Linux) + +import Swift + +#if canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif + +internal import BacktracingImpl.ImageFormats.Elf + +extension ImageMap { + + private struct AddressRange { + var low: Address = 0 + var high: Address = 0 + } + + @_specialize(exported: true, kind: full, where M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where M == RemoteMemoryReader) + @_specialize(exported: true, kind: full, where M == LocalMemoryReader) + @_spi(Internal) + public static func capture( + using reader: M, + forProcess pid: Int? = nil + ) -> ImageMap { + var images: [Image] = [] + + let wordSize: WordSize + + #if arch(x86_64) || arch(arm64) || arch(arm64_32) + wordSize = .sixtyFourBit + #elseif arch(i386) || arch(arm) + wordSize = .thirtyTwoBit + #endif + + let path: String + if let pid = pid { + path = "/proc/\(pid)/maps" + } else { + path = "/proc/self/maps" + } + + guard let procMaps = readString(from: path) else { + return ImageMap(images: [], wordSize: wordSize) + } + + // Find all the mapped files and get high/low ranges + var mappedFiles: [Substring:AddressRange] = [:] + for match in ProcMapsScanner(procMaps) { + let path = stripWhitespace(match.pathname) + if match.inode == "0" || path == "" { + continue + } + guard let start = Address(match.start, radix: 16), + let end = Address(match.end, radix: 16) else { + continue + } + + if let range = mappedFiles[path] { + mappedFiles[path] = AddressRange(low: Swift.min(start, range.low), + high: Swift.max(end, range.high)) + } else { + mappedFiles[path] = AddressRange(low: start, + high: end) + } + } + + // Look at each mapped file to see if it's an ELF image + for (path, range) in mappedFiles { + // Extract the filename from path + let name: Substring + if let slashIndex = path.lastIndex(of: "/") { + name = path.suffix(from: path.index(after: slashIndex)) + } else { + name = path + } + + // Inspect the image and extract the UUID and end of text + guard let (endOfText, uuid) = getElfImageInfo( + at: M.Address(exactly: range.low)!, + using: reader + ) else { + // Not an ELF image + continue + } + + let image = Image(name: String(name), + path: String(path), + uniqueID: uuid, + baseAddress: range.low, + endOfText: Address(endOfText)) + + images.append(image) + } + + images.sort(by: { $0.baseAddress < $1.baseAddress }) + + return ImageMap(images: images, wordSize: wordSize) + } + +} + +#endif // os(Linux) diff --git a/stdlib/public/RuntimeModule/ImageMap.swift b/stdlib/public/RuntimeModule/ImageMap.swift new file mode 100644 index 00000000000..7d50a5a7edd --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageMap.swift @@ -0,0 +1,174 @@ +//===--- ImageMap.swift ----------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the `ImageMap` struct that represents a captured list of loaded +// images. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +internal import Darwin +internal import BacktracingImpl.OS.Darwin +#endif + +/// Holds a map of the process's address space. +public struct ImageMap: Collection, Sendable, Hashable { + + /// A type representing the sequence's elements. + public typealias Element = Backtrace.Image + + /// A type that represents a position in the collection. + public typealias Index = Int + + /// Tells us what size of machine words were used when capturing the + /// image map. + enum WordSize { + case sixteenBit + case thirtyTwoBit + case sixtyFourBit + } + + /// We use UInt64s for addresses here. + typealias Address = UInt64 + + /// The internal representation of an image. + struct Image: Sendable, Hashable { + var name: String? + var path: String? + var uniqueID: [UInt8]? + var baseAddress: Address + var endOfText: Address + } + + /// The actual image storage. + var images: [Image] + + /// The size of words used when capturing. + var wordSize: WordSize + + /// The position of the first element in a non-empty collection. + public var startIndex: Self.Index { + return 0 + } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + public var endIndex: Self.Index { + return images.count + } + + /// Accesses the element at the specified position. + public subscript(_ ndx: Self.Index) -> Self.Element { + return Backtrace.Image(images[ndx], wordSize: wordSize) + } + + /// Look-up an image by address. + public func indexOfImage(at address: Backtrace.Address) -> Int? { + let addr = UInt64(address)! + var lo = 0, hi = images.count + while lo < hi { + let mid = (lo + hi) / 2 + if images[mid].baseAddress > addr { + hi = mid + } else if images[mid].endOfText <= addr { + lo = mid + 1 + } else { + return mid + } + } + + return nil + } + + /// Returns the position immediately after the given index. + public func index(after ndx: Self.Index) -> Self.Index { + return ndx + 1 + } + + /// Capture the image map for the current process. + public static func capture() -> ImageMap { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return capture(for: mach_task_self()) + #else + return capture(using: UnsafeLocalMemoryReader()) + #endif + } +} + +extension ImageMap: CustomStringConvertible { + /// Generate a description of an ImageMap + public var description: String { + var lines: [String] = [] + let addressWidth: Int + switch wordSize { + case .sixteenBit: addressWidth = 4 + case .thirtyTwoBit: addressWidth = 8 + case .sixtyFourBit: addressWidth = 16 + } + + for image in images { + let hexBase = hex(image.baseAddress, width: addressWidth) + let hexEnd = hex(image.endOfText, width: addressWidth) + let buildId: String + if let bytes = image.uniqueID { + buildId = hex(bytes) + } else { + buildId = "" + } + let path = image.path ?? "" + let name = image.name ?? "" + + lines.append("\(hexBase)-\(hexEnd) \(buildId) \(name) \(path)") + } + + return lines.joined(separator: "\n") + } +} + +extension Backtrace.Image { + /// Convert an ImageMap.Image to a Backtrace.Image. + /// + /// Backtrace.Image is the public, user-visible type; ImageMap.Image + /// is an in-memory representation. + init(_ image: ImageMap.Image, wordSize: ImageMap.WordSize) { + let baseAddress: Backtrace.Address + let endOfText: Backtrace.Address + + switch wordSize { + case .sixteenBit: + baseAddress = Backtrace.Address( + UInt16(truncatingIfNeeded: image.baseAddress) + ) + endOfText = Backtrace.Address( + UInt16(truncatingIfNeeded: image.endOfText) + ) + case .thirtyTwoBit: + baseAddress = Backtrace.Address( + UInt32(truncatingIfNeeded: image.baseAddress) + ) + endOfText = Backtrace.Address( + UInt32(truncatingIfNeeded: image.endOfText) + ) + case .sixtyFourBit: + baseAddress = Backtrace.Address(image.baseAddress) + endOfText = Backtrace.Address(image.endOfText) + } + + self.init(name: image.name, + path: image.path, + uniqueID: image.uniqueID, + baseAddress: baseAddress, + endOfText: endOfText) + } +} diff --git a/stdlib/public/RuntimeModule/LimitSequence.swift b/stdlib/public/RuntimeModule/LimitSequence.swift index 8cbdda46a77..0b608dc320f 100644 --- a/stdlib/public/RuntimeModule/LimitSequence.swift +++ b/stdlib/public/RuntimeModule/LimitSequence.swift @@ -20,19 +20,23 @@ import Swift /// Sequences you wish to use with `LimitSequence` must use an element type /// that implements this protocol, so that `LimitSequence` can indicate when /// it omits or truncates the sequence. +@usableFromInline protocol LimitableElement { static func omitted(_: Int) -> Self static var truncated: Self { get } } /// A `Sequence` that adds the ability to limit the output of another sequence. +@usableFromInline struct LimitSequence: Sequence where S.Element == T { /// The element type, which must conform to `LimitableElement` + @usableFromInline typealias Element = T /// The source sequence + @usableFromInline typealias Source = S var source: Source @@ -62,6 +66,7 @@ struct LimitSequence: Sequence /// /// When `LimitSequence` omits items or truncates the sequence, it will /// insert `.omitted(count)` or `.truncated` items into its output. + @usableFromInline init(_ source: Source, limit: Int, offset: Int = 0, top: Int = 0) { self.source = source self.limit = limit @@ -79,6 +84,7 @@ struct LimitSequence: Sequence /// This works by buffering an element ahead of where we are in the input /// sequence, so that it can tell whether or not there is more input to /// follow at any given point. + @usableFromInline struct Iterator: IteratorProtocol { /// The iterator for the input sequence. var iterator: Source.Iterator diff --git a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift index 71662c334fc..096c8563086 100644 --- a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift +++ b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift @@ -249,7 +249,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { public var frames: [Frame] /// A list of images found in the process. - public var images: [Backtrace.Image] + public var images: ImageMap /// True if this backtrace is a Swift runtime failure. public var isSwiftRuntimeFailure: Bool { @@ -270,8 +270,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } /// Construct a SymbolicatedBacktrace from a backtrace and a list of images. - private init(backtrace: Backtrace, images: [Backtrace.Image], - frames: [Frame]) { + private init(backtrace: Backtrace, images: ImageMap, frames: [Frame]) { self.backtrace = backtrace self.images = images self.frames = frames @@ -291,7 +290,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } /// Create a symbolicator. - private static func withSymbolicator(images: [Backtrace.Image], + private static func withSymbolicator(images: ImageMap, useSymbolCache: Bool, fn: (CSSymbolicatorRef) throws -> T) rethrows -> T { let binaryImageList = images.map{ image in @@ -329,7 +328,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { isInline: Bool, symbol: CSSymbolRef, sourceInfo: CSSourceInfoRef?, - images: [Backtrace.Image]) -> Frame { + images: ImageMap) -> Frame { if CSIsNull(symbol) { return Frame(captured: capturedFrame, symbol: nil) } @@ -379,17 +378,17 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { /// Actually symbolicate. internal static func symbolicate(backtrace: Backtrace, - images: [Backtrace.Image]?, + images: ImageMap?, options: Backtrace.SymbolicationOptions) -> SymbolicatedBacktrace? { - let theImages: [Backtrace.Image] + let theImages: ImageMap if let images = images { theImages = images } else if let images = backtrace.images { theImages = images } else { - theImages = Backtrace.captureImages() + theImages = ImageMap.capture() } var frames: [Frame] = [] @@ -463,9 +462,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { for frame in backtrace.frames { let address = frame.adjustedProgramCounter - if let imageNdx = theImages.firstIndex( - where: { address >= $0.baseAddress && address < $0.endOfText } - ) { + if let imageNdx = theImages.indexOfImage(at: address) { let relativeAddress = ImageSource.Address( address - theImages[imageNdx].baseAddress ) diff --git a/stdlib/public/libexec/swift-backtrace/TargetLinux.swift b/stdlib/public/libexec/swift-backtrace/TargetLinux.swift index 9f9d0cf095b..5ef68a49484 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetLinux.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetLinux.swift @@ -52,7 +52,7 @@ class Target { var faultAddress: Address var crashingThread: TargetThread.ThreadID - var images: [Backtrace.Image] = [] + var images: ImageMap var threads: [TargetThread] = [] var crashingThreadNdx: Int = -1 @@ -133,8 +133,7 @@ class Target { signal = crashInfo.signal faultAddress = crashInfo.fault_address - images = Backtrace.captureImages(using: reader, - forProcess: Int(pid)) + images = ImageMap.capture(using: reader, forProcess: Int(pid)) do { try fetchThreads(threadListHead: Address(crashInfo.thread_list), diff --git a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift index 89dea203e80..2b1237e8891 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift @@ -69,7 +69,7 @@ class Target { var crashingThread: TargetThread.ThreadID var task: task_t - var images: [Backtrace.Image] = [] + var images: ImageMap var threads: [TargetThread] = [] var crashingThreadNdx: Int = -1 @@ -193,7 +193,7 @@ class Target { mcontext = mctx - images = Backtrace.captureImages(for: task) + images = ImageMap.capture(for: task) fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate) } diff --git a/test/Backtracing/CompactImageMap.swift b/test/Backtracing/CompactImageMap.swift new file mode 100644 index 00000000000..83e9b813b1b --- /dev/null +++ b/test/Backtracing/CompactImageMap.swift @@ -0,0 +1,66 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap +// RUN: %target-codesign %t/ImageMap +// RUN: %target-run %t/ImageMap | %FileCheck %s + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: executable_test +// REQUIRES: backtracing +// REQUIRES: OS=macosx || OS=linux-gnu + +import Runtime +@_spi(Internal) import Runtime + +@main +struct ImageMapTest { + static func main() { + let map = ImageMap.capture() + let encoder = CompactImageMapFormat.Encoder(map) + let encoded = Array(encoder) + + print(map) + + print("Encoded \(map.count) images in \(encoded.count) bytes") + + for (ndx, byte) in encoded.enumerated() { + let separator: String + if ((ndx + 1) & 0xf) == 0 { + separator = "\n" + } else { + separator = " " + } + + var hex = String(byte, radix: 16) + if hex.count < 2 { + hex = "0" + hex + } + print(hex, terminator: separator) + } + print("") + + var decoder = CompactImageMapFormat.Decoder(encoded) + guard let decodedMap = decoder.decode() else { + print("Unable to decode") + return + } + + print("Decoded \(decodedMap.count) images") + + print(decodedMap) + + if map.description != decodedMap.description { + print("Maps do not match") + } else { + print("Maps match") + } + + // CHECK: Encoded [[COUNT:[0-9]+]] images in [[BYTES:[0-9]+]] bytes + // CHECK-NOT: Unable to decode + // CHECK: Decoded [[COUNT]] images + // CHECK-NOT: Maps do not match + // CHECK: Maps match + } +} + + diff --git a/test/Backtracing/ImageMap.swift b/test/Backtracing/ImageMap.swift new file mode 100644 index 00000000000..d8e0c9e98c4 --- /dev/null +++ b/test/Backtracing/ImageMap.swift @@ -0,0 +1,27 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap +// RUN: %target-codesign %t/ImageMap +// RUN: %target-run %t/ImageMap | %FileCheck %s + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: executable_test +// REQUIRES: backtracing +// REQUIRES: OS=macosx || OS=linux-gnu + +import Runtime + +@main +struct ImageMapTest { + static func main() { + let map = ImageMap.capture() + + // We expect ImageMap, followed by one or more additional lines + + // CHECK: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(|[0-9a-f]*)}} ImageMap {{.*}}/ImageMap + // CHECK-NEXT: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(|[0-9a-f]*)}} [[NAME:[^ ]*]] {{.*}}/[[NAME]] + print(map) + } +} + + From 348a325d2ae91faea4885fa82b5110d3101ef1af Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 14 Nov 2024 14:43:24 +0000 Subject: [PATCH 04/17] [Backtracing] Also try `CFStringGetCharactersPtr` when converting `CFString`s. We already try using `CFStringGetCStringPtr` to see if we can get ASCII; we should also try `CFStringGetCharactersPtr` as the native encoding of `CFNSString` is very often UTF-16. While we're at it, try asking explicitly for UTF-8 as well. That won't work for `CFNSString` because AFAIK it's currently never encoded that way, but it might work if something hands us a Swift-backed string or if some future implementation of `CFString` happens to support UTF-8 encoded data. rdar://124913332 --- .../RuntimeModule/CoreSymbolication.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/stdlib/public/RuntimeModule/CoreSymbolication.swift b/stdlib/public/RuntimeModule/CoreSymbolication.swift index a9ec307f7b9..94634b6fedb 100644 --- a/stdlib/public/RuntimeModule/CoreSymbolication.swift +++ b/stdlib/public/RuntimeModule/CoreSymbolication.swift @@ -46,6 +46,9 @@ private func symbol(_ handle: UnsafeMutableRawPointer, _ name: String) -> T { return unsafeBitCast(result, to: T.self) } +// Define UniChar +typealias UniChar = UInt16 + private enum Sym { // CRCopySanitizedPath static let CRCopySanitizedPath: @convention(c) (CFString, CFIndex) -> CFString = @@ -130,6 +133,9 @@ private enum Sym { UnsafeMutableRawPointer?, CFIndex, UnsafeMutablePointer?) -> CFIndex = symbol(coreFoundationHandle, "CFStringGetBytes") + static let CFStringGetCharactersPtr: + @convention(c) (CFString) -> UnsafePointer? = + symbol(coreFoundationHandle, "CFStringGetCharactersPtr") } // .. Core Foundation miscellany ............................................... @@ -161,6 +167,11 @@ internal func CFStringGetCStringPtr(_ s: CFString, return Sym.CFStringGetCStringPtr(s, encoding) } +internal func CFStringGetCharactersPtr(_ s: CFString) + -> UnsafePointer? { + return Sym.CFStringGetCharactersPtr(s); +} + internal func CFStringGetBytes(_ s: CFString, _ range: CFRange, _ encoding: CFStringEncoding, @@ -199,8 +210,15 @@ private func fromCFString(_ cf: CFString) -> String { if let ptr = CFStringGetCStringPtr(cf, CFStringBuiltInEncodings.ASCII.rawValue) { - return String(decoding: UnsafeRawBufferPointer(start: ptr, count: length), - as: UTF8.self) + let buffer = UnsafeRawBufferPointer(start: ptr, count: length) + return String(decoding: buffer, as: UTF8.self) + } else if let ptr = CFStringGetCharactersPtr(cf) { + let buffer = UnsafeBufferPointer(start: ptr, count: length) + return String(decoding: buffer, as: UTF16.self) + } else if let ptr = CFStringGetCStringPtr(cf, + CFStringBuiltInEncodings.UTF8.rawValue) { + let buffer = UnsafeRawBufferPointer(start: ptr, count: length) + return String(decoding: buffer, as: UTF8.self) } else { var byteLen = CFIndex(0) From ffda416d364d03aa3a6d850aaf49008455e84053 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 1 Jan 2025 16:43:19 +0000 Subject: [PATCH 05/17] [Backtracing] Add support for repeats to Compact Backtrace Format. This lets us repeat a frame over and over; it only works for a single frame, but that's the common case for infinite recursions. rdar://124913332 --- docs/CompactBacktraceFormat.md | 22 ++ .../RuntimeModule/CompactBacktrace.swift | 317 +++++++++++++++--- stdlib/public/RuntimeModule/RichFrame.swift | 2 +- 3 files changed, 292 insertions(+), 49 deletions(-) diff --git a/docs/CompactBacktraceFormat.md b/docs/CompactBacktraceFormat.md index 7b1441b5328..c527cfbb266 100644 --- a/docs/CompactBacktraceFormat.md +++ b/docs/CompactBacktraceFormat.md @@ -54,6 +54,7 @@ The following instructions are currently defined | `0010axxx` | `ra` | A return address value follows | | `0011axxx` | `async` | An async resume point follows | | `01xxxxxx` | `omit` | Indicates frames have been omitted | +| `1000xxxx` | `rep` | Repeat the previous frame | | `1xxxxxxx` | reserved | Reserved for future expansion | ### `end`/`trunc` @@ -139,3 +140,24 @@ machine word length) that are zero-extended to machine word length and that represent a count of the number of frames that were omitted. If `x` is `0`, `count + 1` is the number of frames that were omitted. + +### `rep` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌────────────────┬───┬──────────┐ + │ 1 0 0 0 │ x │ count │ repeat + └────────────────┴───┴──────────┘ +~~~ + +#### Meaning + +Repeat the previous frame. + +If `x` is `1`, the instruction is followed by `count + 1` bytes that are zero +extended to machine word length and that represent a count of the number of +times to repeat the preceding frame. + +If `x` is `0`, the previous frame should be repeated `count + 1` times. diff --git a/stdlib/public/RuntimeModule/CompactBacktrace.swift b/stdlib/public/RuntimeModule/CompactBacktrace.swift index a4d4ed46fdd..7a8fc3559a6 100644 --- a/stdlib/public/RuntimeModule/CompactBacktrace.swift +++ b/stdlib/public/RuntimeModule/CompactBacktrace.swift @@ -48,6 +48,9 @@ enum CompactBacktraceFormat { static let omit_first = Instruction(rawValue: 0b01000000)! static let omit_last = Instruction(rawValue: 0b01111111)! + static let rep_first = Instruction(rawValue: 0b10000000)! + static let rep_last = Instruction(rawValue: 0b10001111)! + private static func addressInstr( _ code: UInt8, _ absolute: Bool, _ count: Int ) -> Instruction { @@ -71,6 +74,12 @@ enum CompactBacktraceFormat { | (external ? 0b00100000 : 0) | UInt8(count - 1))! } + + static func rep(external: Bool, count: Int) -> Instruction { + return Instruction(rawValue: 0b10000000 + | (external ? 0b00001000 : 0) + | UInt8(count - 1))! + } } // Represents a decoded instruction @@ -81,9 +90,119 @@ enum CompactBacktraceFormat { case ra(absolute: Bool, count: Int) case `async`(absolute: Bool, count: Int) case omit(external: Bool, count: Int) + case rep(external: Bool, count: Int) } + /// Accumulates bytes until the end of a Compact Backtrace Format + /// sequence is detected. + public struct Accumulator>: Sequence { + public typealias Element = UInt8 + typealias Source = S + + private var source: S + + public init(_ source: S) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source.makeIterator()) + } + + public struct Iterator: IteratorProtocol { + var iterator: Source.Iterator? + + enum State { + case infoByte + case instruction + case argumentData(Int) + } + + var state: State + + init(_ iterator: Source.Iterator?) { + self.iterator = iterator + self.state = .infoByte + } + + private mutating func finished() { + iterator = nil + } + + private mutating func fail() { + iterator = nil + } + + public mutating func next() -> UInt8? { + if iterator == nil { + return nil + } + + switch state { + case .infoByte: + guard let infoByte = iterator!.next() else { + fail() + return nil + } + let version = infoByte >> 2 + guard let _ = WordSize(rawValue: infoByte & 0x3) else { + fail() + return nil + } + guard version == 0 else { + fail() + return nil + } + + state = .instruction + + return infoByte + + case .instruction: + guard let instr = iterator!.next() else { + finished() + return nil + } + + guard let decoded = Instruction(rawValue: instr)?.decoded() else { + fail() + return nil + } + + switch decoded { + case .end, .trunc: + finished() + return instr + case let .pc(_, count), let .ra(_, count), let .async(_, count): + state = .argumentData(count) + return instr + case let .omit(external, count), let .rep(external, count): + if external { + state = .argumentData(count) + } + return instr + } + + case let .argumentData(count): + guard let byte = iterator!.next() else { + fail() + return nil + } + + let newCount = count - 1 + if newCount == 0 { + state = .instruction + } else { + state = .argumentData(newCount) + } + + return byte + } + } + } + } + /// Adapts a Sequence containing Compact Backtrace Format data into a /// Sequence of `Backtrace.Frame`s. struct Decoder>: Sequence { @@ -117,6 +236,8 @@ enum CompactBacktraceFormat { let wordSize: WordSize let wordMask: UInt64 var lastAddress: UInt64 + var lastFrame: Backtrace.Frame? + var repeatCount: Int = 0 init(_ iterator: Storage.Iterator?, _ size: WordSize) { self.iterator = iterator @@ -169,6 +290,19 @@ enum CompactBacktraceFormat { } } + private mutating func decodeWord( + _ count: Int + ) -> Int? { + var word: Int = 0 + for _ in 0.. Backtrace.Frame? { + if repeatCount > 0 { + repeatCount -= 1 + return lastFrame + } - public mutating func next() -> Frame? { if iterator == nil { return nil } @@ -194,6 +333,7 @@ enum CompactBacktraceFormat { return .truncated } + let result: Backtrace.Frame switch decoded { case .end: finished() @@ -206,34 +346,49 @@ enum CompactBacktraceFormat { finished() return .truncated } - return .programCounter(addr) + result = .programCounter(addr) case let .ra(absolute, count): guard let addr = decodeAddress(absolute, count) else { finished() return .truncated } - return .returnAddress(addr) + result = .returnAddress(addr) case let .async(absolute, count): guard let addr = decodeAddress(absolute, count) else { finished() return .truncated } - return .asyncResumePoint(addr) + result = .asyncResumePoint(addr) case let .omit(external, count): if !external { - return .omittedFrames(count) + result = .omittedFrames(count) } else { - var word: Int = 0 - for _ in 0..>>: Sequence { typealias Element = UInt8 typealias Frame = Backtrace.Frame + typealias SourceFrame = RichFrame typealias Address = A typealias Source = S @@ -264,11 +420,13 @@ enum CompactBacktraceFormat { enum State { case start case ready - case emittingBytes(Int) + case emittingBytes(Int, SourceFrame?) + case stashedFrame(SourceFrame) case done } var bytes = EightByteBuffer() var state: State = .start + var lastFrame: SourceFrame? = nil init(_ iterator: Source.Iterator) { self.iterator = iterator @@ -276,8 +434,8 @@ enum CompactBacktraceFormat { /// Set up to emit the bytes of `address`, returning the number of bytes /// we will need to emit - private mutating func emitAddressNext( - _ address: Address + private mutating func emitNext( + address: Address ) -> (absolute: Bool, count: Int) { let delta = address &- lastAddress @@ -303,28 +461,77 @@ enum CompactBacktraceFormat { if absCount < deltaCount { bytes = EightByteBuffer(address) - state = .emittingBytes(8 - absCount) + state = .emittingBytes(8 - absCount, nil) return (absolute: true, count: absCount) } else { bytes = EightByteBuffer(delta) - state = .emittingBytes(8 - deltaCount) + state = .emittingBytes(8 - deltaCount, nil) return (absolute: false, count: deltaCount) } } /// Set up to emit the bytes of `count`, returning the number of bytes /// we will need to emit - private mutating func emitExternalCountNext( - _ count: Int + private mutating func emitNext( + externalCount count: Int ) -> Int { let ucount = UInt64(count) let zeroes = ucount.leadingZeroBitCount >> 3 let byteCount = 8 - zeroes bytes = EightByteBuffer(ucount) - state = .emittingBytes(zeroes) + state = .emittingBytes(zeroes, nil) return byteCount } + private mutating func emitNext( + frame: SourceFrame?, + externalCount count: Int? = nil + ) -> Int { + if let count { + let ucount = UInt64(count) + let zeroes = ucount.leadingZeroBitCount >> 3 + let byteCount = 8 - zeroes + bytes = EightByteBuffer(ucount) + state = .emittingBytes(zeroes, frame) + return byteCount + } else if let frame { + state = .stashedFrame(frame) + } else { + state = .ready + } + return 0 + } + + private mutating func emit(frame: SourceFrame) -> UInt8 { + lastFrame = frame + + switch frame { + case let .programCounter(addr): + let (absolute, count) = emitNext(address: addr) + return Instruction.pc(absolute: absolute, + count: count).rawValue + case let .returnAddress(addr): + let (absolute, count) = emitNext(address: addr) + return Instruction.ra(absolute: absolute, + count: count).rawValue + case let .asyncResumePoint(addr): + let (absolute, count) = emitNext(address: addr) + return Instruction.async(absolute: absolute, + count: count).rawValue + case let .omittedFrames(count): + if count <= 0x1f { + return Instruction.omit(external: false, + count: count).rawValue + } + let countCount = emitNext(externalCount: count) + return Instruction.omit(external: true, + count: countCount).rawValue + case .truncated: + self.state = .done + return Instruction.trunc.rawValue + } + } + public mutating func next() -> UInt8? { switch state { case .done: @@ -351,13 +558,17 @@ enum CompactBacktraceFormat { let infoByte = (version << 2) | size.rawValue return infoByte - case let .emittingBytes(ndx): + case let .emittingBytes(ndx, frame): let byte = bytes[ndx] if ndx + 1 == 8 { - state = .ready + if let frame { + state = .stashedFrame(frame) + } else { + state = .ready + } } else { - state = .emittingBytes(ndx + 1) + state = .emittingBytes(ndx + 1, frame) } return byte @@ -369,31 +580,37 @@ enum CompactBacktraceFormat { return nil } - switch frame { - case let .programCounter(addr): - let (absolute, count) = emitAddressNext(addr) - return Instruction.pc(absolute: absolute, - count: count).rawValue - case let .returnAddress(addr): - let (absolute, count) = emitAddressNext(addr) - return Instruction.ra(absolute: absolute, - count: count).rawValue - case let .asyncResumePoint(addr): - let (absolute, count) = emitAddressNext(addr) - return Instruction.async(absolute: absolute, - count: count).rawValue - case let .omittedFrames(count): - if count <= 0x1f { - return Instruction.omit(external: false, - count: count).rawValue + if let lastFrame, lastFrame == frame { + var count = 1 + var nextFrame: SourceFrame? = nil + while let frame = iterator.next() { + if frame != lastFrame { + nextFrame = frame + break + } else { + count += 1 } - let countCount = emitExternalCountNext(count) - return Instruction.omit(external: true, - count: countCount).rawValue - case .truncated: - self.state = .done - return Instruction.trunc.rawValue + } + + if count <= 8 { + _ = emitNext(frame: nextFrame) + return Instruction.rep(external: false, + count: count).rawValue + } else { + let countCount = emitNext(frame: nextFrame, + externalCount: count) + return Instruction.rep(external: true, + count: countCount).rawValue + } } + + return emit(frame: frame) + + case let .stashedFrame(frame): + + state = .ready + + return emit(frame: frame) } } } @@ -432,6 +649,10 @@ extension CompactBacktraceFormat.Instruction { let count = Int((self.rawValue & 0x1f) + 1) let external = (self.rawValue & 0x20) != 0 return .omit(external: external, count: count) + case .rep_first ... .rep_last: + let count = Int((self.rawValue & 0x7) + 1) + let external = (self.rawValue & 0x8) != 0 + return .rep(external: external, count: count) default: return nil } diff --git a/stdlib/public/RuntimeModule/RichFrame.swift b/stdlib/public/RuntimeModule/RichFrame.swift index ac7bbbb5c02..c7521d7bbbf 100644 --- a/stdlib/public/RuntimeModule/RichFrame.swift +++ b/stdlib/public/RuntimeModule/RichFrame.swift @@ -17,7 +17,7 @@ import Swift @_spi(Internal) -public enum RichFrame: CustomStringConvertible { +public enum RichFrame: CustomStringConvertible, Equatable { public typealias Address = T /// A program counter value. From 4826d60bc2504bae4331e992c998594cca9be805 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 1 Jan 2025 16:40:38 +0000 Subject: [PATCH 06/17] [Backtracing] Implement Codable for Backtrace. Add support for Codable to Bactkrace. rdar://124913332 --- .../RuntimeModule/Backtrace+Codable.swift | 69 ++++ stdlib/public/RuntimeModule/Backtrace.swift | 2 +- stdlib/public/RuntimeModule/Base64.swift | 333 ++++++++++++++++++ stdlib/public/RuntimeModule/CMakeLists.txt | 3 + .../RuntimeModule/CompactImageMap.swift | 4 +- stdlib/public/RuntimeModule/ImageMap.swift | 16 + .../RuntimeModule/SymbolicatedBacktrace.swift | 4 +- .../libexec/swift-backtrace/TargetMacOS.swift | 1 + .../libexec/swift-backtrace/Utils.swift | 2 +- test/Backtracing/CodableBacktrace.swift | 67 ++++ test/Backtracing/CompactImageMap.swift | 5 +- 11 files changed, 496 insertions(+), 10 deletions(-) create mode 100644 stdlib/public/RuntimeModule/Backtrace+Codable.swift create mode 100644 stdlib/public/RuntimeModule/Base64.swift create mode 100644 test/Backtracing/CodableBacktrace.swift diff --git a/stdlib/public/RuntimeModule/Backtrace+Codable.swift b/stdlib/public/RuntimeModule/Backtrace+Codable.swift new file mode 100644 index 00000000000..6a7df75e710 --- /dev/null +++ b/stdlib/public/RuntimeModule/Backtrace+Codable.swift @@ -0,0 +1,69 @@ +//===--- Backtrace+Codable.swift ------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the Codable conformance for Backtrace. +// +//===----------------------------------------------------------------------===// + +import Swift + +func stringFrom(sequence: some Sequence) -> String? { + if #available(macOS 15.0, *) { + return String(validating: sequence, as: UTF8.self) + } else { + let bytes = Array(sequence) + return String(decoding: bytes, as: UTF8.self) + } +} + +@available(macOS 15.0, *) +extension Backtrace: Codable { + + enum CodingKeys: CodingKey { + case architecture + case backtrace + case imageMap + } + + public init(from decoder: any Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + self.architecture = try values.decode(String.self, forKey: .architecture) + + let backtraceB64 = try values.decode(String.self, forKey: .backtrace) + self.representation = Array(Base64Decoder(source: backtraceB64.utf8)) + + if let imageMapB64 = try values.decodeIfPresent(String.self, + forKey: .imageMap) { + self.images = ImageMap(compactImageMapData: + Base64Decoder(source: imageMapB64.utf8)) + } else { + self.images = nil + } + } + + public func encode(to encoder: any Encoder) throws { + var values = encoder.container(keyedBy: CodingKeys.self) + try values.encode(architecture, forKey: .architecture) + + let backtraceB64 = stringFrom(sequence: + Base64Encoder(source: self.representation)) + try values.encode(backtraceB64, forKey: .backtrace) + + if let imageMap = self.images { + let encoder = CompactImageMapFormat.Encoder(imageMap) + let imageMapB64 = stringFrom(sequence: + Base64Encoder(source: encoder)) + try values.encode(imageMapB64, forKey: .imageMap) + } + } + +} diff --git a/stdlib/public/RuntimeModule/Backtrace.swift b/stdlib/public/RuntimeModule/Backtrace.swift index c4806979c91..7a26e693b40 100644 --- a/stdlib/public/RuntimeModule/Backtrace.swift +++ b/stdlib/public/RuntimeModule/Backtrace.swift @@ -232,7 +232,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { public var architecture: String /// The actual backtrace data, stored in Compact Backtrace Format. - private var representation: [UInt8] + var representation: [UInt8] /// A list of captured frame information. @available(macOS 10.15, *) diff --git a/stdlib/public/RuntimeModule/Base64.swift b/stdlib/public/RuntimeModule/Base64.swift new file mode 100644 index 00000000000..608ce60eb99 --- /dev/null +++ b/stdlib/public/RuntimeModule/Base64.swift @@ -0,0 +1,333 @@ +//===--- Base64.swift -----------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Encode and decode sequences of bytes as base64. +// +//===----------------------------------------------------------------------===// + +import Swift + +// Forward mapping (we encode normal base64) +fileprivate let forwardMapping: ( + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit +) = ( + // A B C D E F G H + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + // I J K L M N O P + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + // Q R S T U V W X + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + // Y Z a b c d e f + 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + // g h i j k l m n + 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + // o p q r s t u v + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + // w x y z 0 1 2 3 + 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, + // 4 5 6 7 8 9 + / + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f +) + +fileprivate func forward(at ndx: Int) -> UTF8.CodeUnit { + precondition(ndx >= 0 && ndx < 64) + return withUnsafePointer(to: forwardMapping) { + $0.withMemoryRebound(to: UTF8.CodeUnit.self, + capacity: 64) { table in + return table[ndx] + } + } +} + +// Reverse (we support URL-safe and normal base64) +fileprivate let reverseMapping: ( + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8 +) = ( +// + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +// + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +// + - / + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, +// 0 1 2 3 4 5 6 7 8 9 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, +// A B C D E F G H I J K L M N O + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +// P Q R S T U V W X Y Z _ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, +// a b c d e f g h i j k l m n o + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +// p q r s t u v w x y z + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +) + +fileprivate func reverse(at char: UTF8.CodeUnit) -> UInt8? { + if char >= 128 { + return nil + } + return withUnsafePointer(to: reverseMapping) { + $0.withMemoryRebound(to: Int8.self, + capacity: 128) { + (table: UnsafePointer) -> UInt8? in + + let value = table[Int(char)] + guard value >= 0 else { + return nil + } + return UInt8(truncatingIfNeeded: value) + } + } +} + +@_spi(Base64) +public struct Base64Encoder: Sequence + where S.Element == UInt8 +{ + public typealias Element = UTF8.CodeUnit + + var source: S + + public init(source: S) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source: source) + } + + public struct Iterator: IteratorProtocol { + public typealias Element = UTF8.CodeUnit + + var sourceIterator: S.Iterator + var output: (UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit) + var ndx: Int + var buffer: UInt32 + var count: Int + + init(source: S) { + sourceIterator = source.makeIterator() + output = (0, 0, 0) + buffer = 0 + count = 0 + ndx = 3 + } + + public mutating func next() -> UTF8.CodeUnit? { + // If we have bytes, output those first + switch ndx { + case 0: + ndx += 1 + return output.0 + case 1: + ndx += 1 + return output.1 + case 2: + ndx += 1 + return output.2 + default: + break + } + + // Now try to refill the buffer with up to three bytes + buffer = 0 + count = 0 + while count < 3 { + if let byte = sourceIterator.next() { + buffer = (buffer << 8) | UInt32(truncatingIfNeeded: byte) + } else { + break + } + count += 1 + } + + switch count { + case 0: + return nil + case 1: + let first = Int(buffer >> 2) + let second = Int((buffer << 4) & 0x3f) + output.2 = forward(at: second) + ndx = 2 + return forward(at: first) + case 2: + let first = Int(buffer >> 10) + let second = Int((buffer >> 4) & 0x3f) + let third = Int((buffer << 2) & 0x3f) + output.1 = forward(at: second) + output.2 = forward(at: third) + ndx = 1 + return forward(at: first) + case 3: + let first = Int(buffer >> 18) + let second = Int((buffer >> 12) & 0x3f) + let third = Int((buffer >> 6) & 0x3f) + let fourth = Int(buffer & 0x3f) + output.0 = forward(at: second) + output.1 = forward(at: third) + output.2 = forward(at: fourth) + ndx = 0 + return forward(at: first) + default: + fatalError("count has an impossible value") + } + } + } +} + +@_spi(Base64) +public struct Base64Decoder: Sequence + where S.Element == UTF8.CodeUnit +{ + public typealias Element = UInt8 + + var source: S + + public init(source: S) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source: source) + } + + public struct Iterator: IteratorProtocol { + public typealias Element = UInt8 + + var sourceIterator: S.Iterator + var output: (UInt8, UInt8) + var ndx: Int + var buffer: UInt32 + var count: Int + var bad: Bool + var done: Bool + + init(source: S) { + sourceIterator = source.makeIterator() + output = (0, 0) + ndx = 2 + buffer = 0 + count = 0 + bad = false + done = false + } + + public mutating func next() -> UInt8? { + if bad { + return nil + } + + // If we have bytes, output those first + switch ndx { + case 0: + ndx += 1 + return output.0 + case 1: + ndx += 1 + return output.1 + default: + break + } + + // If we've finished, stop + if done { + return nil + } + + // Now try to refill the buffer + count = 0 + while count < 4 { + if let encoded = sourceIterator.next() { + if encoded >= 128 { + bad = true + return nil + } + + // '=' + if encoded == 0x3d { + break + } + + guard let value = reverse(at: encoded) else { + bad = true + return nil + } + + buffer = (buffer << 6) | UInt32(truncatingIfNeeded: value) + count += 1 + } else { + break + } + } + + switch count { + case 0: + return nil + case 1: + bad = true + return nil + case 2: + // 12 bits + done = true + return UInt8(truncatingIfNeeded: buffer >> 4) + case 3: + // 18 bits + done = true + let first = UInt8(truncatingIfNeeded: buffer >> 10) + let second = UInt8(truncatingIfNeeded: buffer >> 2) + output.1 = second + ndx = 1 + return first + case 4: + // 24 bits + let first = UInt8(truncatingIfNeeded: buffer >> 16) + let second = UInt8(truncatingIfNeeded: buffer >> 8) + let third = UInt8(truncatingIfNeeded: buffer) + output.0 = second + output.1 = third + ndx = 0 + return first + default: + fatalError("count has an impossible value") + } + } + } +} diff --git a/stdlib/public/RuntimeModule/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt index 6d89e52a41f..0c01739e8b0 100644 --- a/stdlib/public/RuntimeModule/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -14,6 +14,7 @@ # rather, it's a high level Swift interface to things set(swift_runtime_link_libraries swiftCore + swiftCxxStdlib swift_Concurrency ) @@ -25,7 +26,9 @@ endif() set(RUNTIME_SOURCES Address.swift Backtrace.swift + Backtrace+Codable.swift BacktraceFormatter.swift + Base64.swift ByteSwapping.swift CachingMemoryReader.swift CompactBacktrace.swift diff --git a/stdlib/public/RuntimeModule/CompactImageMap.swift b/stdlib/public/RuntimeModule/CompactImageMap.swift index 3bd18ddb822..e4c9c467e12 100644 --- a/stdlib/public/RuntimeModule/CompactImageMap.swift +++ b/stdlib/public/RuntimeModule/CompactImageMap.swift @@ -250,7 +250,7 @@ public enum CompactImageMapFormat { } } - public mutating func decode() -> ImageMap? { + mutating func decode() -> ([ImageMap.Image], ImageMap.WordSize)? { // Check the version and decode the size guard let infoByte = iterator.next() else { return nil @@ -375,7 +375,7 @@ public enum CompactImageMapFormat { wsMap = .sixtyFourBit } - return ImageMap(images: images, wordSize: wsMap) + return (images, wsMap) } } diff --git a/stdlib/public/RuntimeModule/ImageMap.swift b/stdlib/public/RuntimeModule/ImageMap.swift index 7d50a5a7edd..acc7a5edf9e 100644 --- a/stdlib/public/RuntimeModule/ImageMap.swift +++ b/stdlib/public/RuntimeModule/ImageMap.swift @@ -57,6 +57,22 @@ public struct ImageMap: Collection, Sendable, Hashable { /// The size of words used when capturing. var wordSize: WordSize + /// Construct an ImageMap. + init(images: [Image], wordSize: WordSize) { + self.images = images + self.wordSize = wordSize + } + + /// Construct an ImageMap from CompactImageMap data { + @_spi(Internal) + public init?(compactImageMapData: some Sequence) { + var decoder = CompactImageMapFormat.Decoder(compactImageMapData) + guard let (images, wordSize) = decoder.decode() else { + return nil + } + self.init(images: images, wordSize: wordSize) + } + /// The position of the first element in a non-empty collection. public var startIndex: Self.Index { return 0 diff --git a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift index 096c8563086..72d2ce25431 100644 --- a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift +++ b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift @@ -213,8 +213,8 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { if stringLen > 0 { return demangled.withMemoryRebound(to: UInt8.self, capacity: stringLen) { - let demangledBytes = UnsafeBufferPointer(start: $0, - count: stringLen) + let demangledBytes = UnsafeBufferPointer(start: $0, + count: stringLen) return String(decoding: demangledBytes, as: UTF8.self) } } diff --git a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift index 2b1237e8891..fe7f445a0d8 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift @@ -26,6 +26,7 @@ import Runtime @_spi(MemoryReaders) import Runtime internal import BacktracingImpl.OS.Darwin +internal import BacktracingImpl.Runtime #if arch(x86_64) typealias MContext = darwin_x86_64_mcontext diff --git a/stdlib/public/libexec/swift-backtrace/Utils.swift b/stdlib/public/libexec/swift-backtrace/Utils.swift index b12b0159d7d..20e164d5b08 100644 --- a/stdlib/public/libexec/swift-backtrace/Utils.swift +++ b/stdlib/public/libexec/swift-backtrace/Utils.swift @@ -26,7 +26,7 @@ import CRT import Swift -import BacktracingImpl.Runtime +internal import BacktracingImpl.Runtime typealias CrashInfo = swift.runtime.backtrace.CrashInfo diff --git a/test/Backtracing/CodableBacktrace.swift b/test/Backtracing/CodableBacktrace.swift new file mode 100644 index 00000000000..bb9ece5f785 --- /dev/null +++ b/test/Backtracing/CodableBacktrace.swift @@ -0,0 +1,67 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -target %target-cpu-macos15.0 -Xfrontend -parse-as-library -Onone -o %t/CodableBacktrace +// RUN: %target-codesign %t/CodableBacktrace +// RUN: %target-run %t/CodableBacktrace | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: backtracing +// REQUIRES: OS=macosx || OS=linux-gnu + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime + +import Runtime +import Foundation + +func level1() { + level2() +} + +func level2() { + level3() +} + +func level3() { + level4() +} + +func level4() { + level5() +} + +func level5() { + let backtrace = try! Backtrace.capture() + + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + let data = try! encoder.encode(backtrace) + let json = String(data: data, encoding: .utf8)! + + print(json) + + // CHECK: { + // CHECK-DAG: "architecture" : "{{.*}}" + // CHECK-DAG: "backtrace" : "{{[A-Za-z0-9+/]*}}" + // CHECK: } + + let decoder = JSONDecoder() + + let bt2 = try! decoder.decode(Backtrace.self, from: data) + + print(bt2) + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 5{{[ \t]+}}0x{{[0-9a-f]+}} [ra] +} + +@main +struct CodableBacktrace { + static func main() { + level1() + } +} diff --git a/test/Backtracing/CompactImageMap.swift b/test/Backtracing/CompactImageMap.swift index 83e9b813b1b..09cce742f11 100644 --- a/test/Backtracing/CompactImageMap.swift +++ b/test/Backtracing/CompactImageMap.swift @@ -39,8 +39,7 @@ struct ImageMapTest { } print("") - var decoder = CompactImageMapFormat.Decoder(encoded) - guard let decodedMap = decoder.decode() else { + guard let decodedMap = ImageMap(compactImageMapData: encoded) else { print("Unable to decode") return } @@ -62,5 +61,3 @@ struct ImageMapTest { // CHECK: Maps match } } - - From 6c0749d3693b4620606a8934e5d19dda8126186f Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 9 Jan 2025 16:24:49 +0000 Subject: [PATCH 07/17] [Backtracing][Tests] Fix the name of the swift-backtrace dependency. Because it's a target executable, the name of the dependency is suffixed with the SDK. rdar://124913332 --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ec3ef53ede6..2ae8cce6b69 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -82,9 +82,9 @@ function(get_test_dependencies SDK result_var_name) set(deps_binaries) - if (SWIFT_ENABLE_BACKTRACING) + if (SWIFT_BUILD_LIBEXEC AND SWIFT_ENABLE_BACKTRACING) list(APPEND deps_binaries - swift-backtrace) + "swift-backtrace-${SDK}") endif() if (SWIFT_INCLUDE_TOOLS) From c7bb91d8fe6970b52e19a9252ea14babfb7a65e4 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 16 Jan 2025 18:08:25 +0000 Subject: [PATCH 08/17] [Backtracing] Tweak tests slightly. `JSONEncoder` by default will escape slashes, which results in a string that isn't technically valid Base64. Since that behaviour is optional, turn it off, and at the same time tell it to output in lexical key order, which makes the test slightly simpler (no `CHECK-DAG` needed). Also fixed a typo in `test_swift.py` rdar://124913332 --- test/Backtracing/CodableBacktrace.swift | 6 +++--- utils/swift_build_support/tests/products/test_swift.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Backtracing/CodableBacktrace.swift b/test/Backtracing/CodableBacktrace.swift index bb9ece5f785..daef88a1c51 100644 --- a/test/Backtracing/CodableBacktrace.swift +++ b/test/Backtracing/CodableBacktrace.swift @@ -33,7 +33,7 @@ func level5() { let backtrace = try! Backtrace.capture() let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted + encoder.outputFormatting = [.prettyPrinted,.sortedKeys,.withoutEscapingSlashes] let data = try! encoder.encode(backtrace) let json = String(data: data, encoding: .utf8)! @@ -41,8 +41,8 @@ func level5() { print(json) // CHECK: { - // CHECK-DAG: "architecture" : "{{.*}}" - // CHECK-DAG: "backtrace" : "{{[A-Za-z0-9+/]*}}" + // CHECK: "architecture" : "{{.*}}", + // CHECK: "backtrace" : "{{[A-Za-z0-9+/]*}}" // CHECK: } let decoder = JSONDecoder() diff --git a/utils/swift_build_support/tests/products/test_swift.py b/utils/swift_build_support/tests/products/test_swift.py index 22f82238848..6a7ea32f241 100644 --- a/utils/swift_build_support/tests/products/test_swift.py +++ b/utils/swift_build_support/tests/products/test_swift.py @@ -484,7 +484,7 @@ class SwiftTestCase(unittest.TestCase): ['-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=' 'TRUE'], [x for x in swift.cmake_options - if 'DSWIFT_ENABLE_RUNTIME_MODULE in x]) + if 'DSWIFT_ENABLE_RUNTIME_MODULE' in x]) def test_freestanding_is_darwin_flags(self): self.args.swift_freestanding_is_darwin = True From 4a7ca682d581afe47403d0816629e48719a0b6f7 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 17 Jan 2025 10:08:12 +0000 Subject: [PATCH 09/17] [Backtracing] Fix dependency for static swift-backtrace. We'd missed renaming a CMake variable, which led to link errors. rdar://124913332 --- stdlib/public/libexec/swift-backtrace/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt index 8e828f3d7c7..ed4bb13dc0f 100644 --- a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt +++ b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt @@ -80,7 +80,7 @@ if(static_target_sdks) ${BACKTRACING_SOURCES} - SWIFT_MODULE_DEPENDS ${backtracing} + SWIFT_MODULE_DEPENDS ${runtime} SWIFT_MODULE_DEPENDS_OSX ${darwin} SWIFT_MODULE_DEPENDS_WINDOWS ${wincrt_sdk} From dd84241d366780b83b8e8fd2d28bfe121d0cea03 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 17 Jan 2025 14:31:16 +0000 Subject: [PATCH 10/17] [Backtracing] CMake fixes. Remove a `-disable-implicit-backtracing-module-import` that was added in `Runtimes/Core/CMakeLists.txt`, and also remove the reference to `swiftCxxStdlib` from `RuntimeModule` as that causes the build to fail when attempting to build the static version of `swift-backtrace` on Linux. rdar://124913332 --- Runtimes/Core/CMakeLists.txt | 1 - stdlib/public/RuntimeModule/CMakeLists.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/Runtimes/Core/CMakeLists.txt b/Runtimes/Core/CMakeLists.txt index 0dac62a7253..24d817ef51e 100644 --- a/Runtimes/Core/CMakeLists.txt +++ b/Runtimes/Core/CMakeLists.txt @@ -157,7 +157,6 @@ add_compile_options( "$<$:SHELL:-Xfrontend -enable-experimental-concise-pound-file>" "$<$:SHELL:-Xfrontend -enable-lexical-lifetimes=false>" "$<$:SHELL:-Xfrontend -disable-implicit-concurrency-module-import>" - "$<$:SHELL:-Xfrontend -disable-implicit-backtracing-module-import>" "$<$:SHELL:-Xfrontend -disable-implicit-string-processing-module-import>" "$<$:SHELL:-Xfrontend -enforce-exclusivity=unchecked>" "$<$:SHELL:-Xfrontend -enable-ossa-modules>" diff --git a/stdlib/public/RuntimeModule/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt index 0c01739e8b0..080fae461d5 100644 --- a/stdlib/public/RuntimeModule/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -14,7 +14,6 @@ # rather, it's a high level Swift interface to things set(swift_runtime_link_libraries swiftCore - swiftCxxStdlib swift_Concurrency ) From ca233af1ea84b0877d945bbdeabefee7376b44dd Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 21 Jan 2025 19:02:47 +0000 Subject: [PATCH 11/17] [Backtracing] Fix Python test failure. We needed to add `--enable-runtime-module` in the Python test scripts. rdar://124913332 --- utils/build_swift/tests/expected_options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/build_swift/tests/expected_options.py b/utils/build_swift/tests/expected_options.py index 0133fafeb8d..1b906115dc0 100644 --- a/utils/build_swift/tests/expected_options.py +++ b/utils/build_swift/tests/expected_options.py @@ -630,6 +630,7 @@ EXPECTED_OPTIONS = [ EnableOption('--enable-experimental-observation'), EnableOption('--enable-experimental-parser-validation'), EnableOption('--enable-lsan'), + EnableOption('--enable-runtime-module'), EnableOption('--enable-sanitize-coverage'), EnableOption('--enable-tsan'), EnableOption('--enable-tsan-runtime'), From 2300577b5640efb84e51255e14566784cb68fa32 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 21 Jan 2025 19:03:51 +0000 Subject: [PATCH 12/17] [Backtracing] Fix symbol lookup on Linux. Also removed a spurious `print()` that got left in by accident, and disabled the `CodableBacktrace.swift` test on Linux since we need Foundation and we don't have it there. rdar://124913332 --- stdlib/public/RuntimeModule/Backtrace.swift | 2 -- stdlib/public/RuntimeModule/Elf.swift | 4 ++-- test/Backtracing/CodableBacktrace.swift | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/stdlib/public/RuntimeModule/Backtrace.swift b/stdlib/public/RuntimeModule/Backtrace.swift index 7a26e693b40..b79dc4bc871 100644 --- a/stdlib/public/RuntimeModule/Backtrace.swift +++ b/stdlib/public/RuntimeModule/Backtrace.swift @@ -415,8 +415,6 @@ extension Backtrace { memoryReader: memoryReader) if let limit = limit { - print("limit = \(limit), offset = \(offset), top = \(top)") - let limited = LimitSequence(unwinder, limit: limit, offset: offset, diff --git a/stdlib/public/RuntimeModule/Elf.swift b/stdlib/public/RuntimeModule/Elf.swift index c603d3d92a6..d2501b33d25 100644 --- a/stdlib/public/RuntimeModule/Elf.swift +++ b/stdlib/public/RuntimeModule/Elf.swift @@ -1156,13 +1156,13 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { nextValue = _symbols[mid + 1].value } - if symbol.value <= address && nextValue >= address { + if symbol.value <= address && nextValue > address { var ndx = mid while ndx > 0 && _symbols[ndx - 1].value == address { ndx -= 1 } return _symbols[ndx] - } else if symbol.value < address { + } else if symbol.value <= address { min = mid + 1 } else if symbol.value > address { max = mid diff --git a/test/Backtracing/CodableBacktrace.swift b/test/Backtracing/CodableBacktrace.swift index daef88a1c51..47e4a4ba790 100644 --- a/test/Backtracing/CodableBacktrace.swift +++ b/test/Backtracing/CodableBacktrace.swift @@ -5,7 +5,7 @@ // REQUIRES: executable_test // REQUIRES: backtracing -// REQUIRES: OS=macosx || OS=linux-gnu +// REQUIRES: OS=macosx // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime From ca8683613385509f7bead5bd395e764731e7d412 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 22 Jan 2025 10:39:44 +0000 Subject: [PATCH 13/17] [Backtracing][Tests] Improve debugability of CompactImageMap test. If this test fails in CI, we could do with being able to see the output from the test program in full. rdar://124913332 --- test/Backtracing/CompactImageMap.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Backtracing/CompactImageMap.swift b/test/Backtracing/CompactImageMap.swift index 09cce742f11..226a1c48d19 100644 --- a/test/Backtracing/CompactImageMap.swift +++ b/test/Backtracing/CompactImageMap.swift @@ -1,7 +1,8 @@ // RUN: %empty-directory(%t) // RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap // RUN: %target-codesign %t/ImageMap -// RUN: %target-run %t/ImageMap | %FileCheck %s +// RUN: %target-run %t/ImageMap | tee %t/ImageMap.out +// RUN: cat %t/ImageMap.out | %FileCheck %s // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime From 12aee901c24bb62215d81b0a99f82736251806c7 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 22 Jan 2025 16:35:44 +0000 Subject: [PATCH 14/17] [Backtracing] Fix compact image map decoding bug. If we ended up with a `/` at the beginning of a string segment, we were erroneously not adding to the expansion dictionary when we should have been. rdar://124913332 --- .../RuntimeModule/CompactImageMap.swift | 22 ++++++++++++++++++- stdlib/public/RuntimeModule/ImageMap.swift | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/stdlib/public/RuntimeModule/CompactImageMap.swift b/stdlib/public/RuntimeModule/CompactImageMap.swift index e4c9c467e12..0e963ef91f1 100644 --- a/stdlib/public/RuntimeModule/CompactImageMap.swift +++ b/stdlib/public/RuntimeModule/CompactImageMap.swift @@ -160,7 +160,7 @@ public enum CompactImageMapFormat { guard let char = iterator.next() else { return nil } - if n > 0 && char == 0x2f { + if base + n > stringBase! && (char == 0x2f || char == 0x5c) { let prefix = String(decoding: resultBytes[stringBase!..> 3) & 0x7) + 1) let ecount = Int((header & 0x7) + 1) + #if DEBUG_COMPACT_IMAGE_MAP + print("r = \(relative), acount = \(acount), ecount = \(ecount)") + #endif + // Now the base and end of text addresses guard let address = decodeAddress(acount) else { return nil @@ -315,11 +319,20 @@ public enum CompactImageMapFormat { } let endOfText = baseAddress &+ eotOffset + #if DEBUG_COMPACT_IMAGE_MAP + print("address = \(hex(address)), eotOffset = \(hex(eotOffset))") + print("baseAddress = \(hex(baseAddress)), endOfText = \(hex(endOfText))") + #endif + // Next, get the build ID byte count guard let buildIdBytes = decodeCount() else { return nil } + #if DEBUG_COMPACT_IMAGE_MAP + print("buildIdBytes = \(buildIdBytes)") + #endif + // Read the build ID var buildId: [UInt8]? = nil @@ -335,6 +348,10 @@ public enum CompactImageMapFormat { } } + #if DEBUG_COMPACT_IMAGE_MAP + print("buildId = \(buildId)") + #endif + // Decode the path let path = decodePath() let name: String? @@ -674,6 +691,9 @@ public enum CompactImageMapFormat { // Add any new prefixes forEachPrefix(of: remainingPath) { prefix in + #if DEBUG_COMPACT_IMAGE_MAP + print("defining \(nextCode) as \"\(prefix)\"") + #endif pathPrefixes.append((nextCode, prefix)) nextCode += 1 } diff --git a/stdlib/public/RuntimeModule/ImageMap.swift b/stdlib/public/RuntimeModule/ImageMap.swift index acc7a5edf9e..4b68c5c83bb 100644 --- a/stdlib/public/RuntimeModule/ImageMap.swift +++ b/stdlib/public/RuntimeModule/ImageMap.swift @@ -33,7 +33,7 @@ public struct ImageMap: Collection, Sendable, Hashable { /// Tells us what size of machine words were used when capturing the /// image map. - enum WordSize { + enum WordSize: Sendable { case sixteenBit case thirtyTwoBit case sixtyFourBit From 432c138ccf6553776cb8c8f7c1a7c154bf4b86ab Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 23 Jan 2025 16:32:41 +0000 Subject: [PATCH 15/17] [Backtracing][Tests] Tweak swift-backtrace dependency. We only want to add the `swift-backtrace` binary dependency when the SDK we're looking at actually has `swift-backtrace` enabled. rdar://124913332 --- test/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2ae8cce6b69..cdb83b1db1c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,8 +83,11 @@ function(get_test_dependencies SDK result_var_name) set(deps_binaries) if (SWIFT_BUILD_LIBEXEC AND SWIFT_ENABLE_BACKTRACING) - list(APPEND deps_binaries - "swift-backtrace-${SDK}") + # Only add if `swift-backtrace` builds for this SDK + if (TARGET "swift-backtrace-${SDK}") + list(APPEND deps_binaries + "swift-backtrace-${SDK}") + endif() endif() if (SWIFT_INCLUDE_TOOLS) From c9c5dc0de132865a0b2f44472f22b115fb624073 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 27 Jan 2025 15:44:28 +0000 Subject: [PATCH 16/17] [Backtracing] Add platform and architecture information. It's useful to capture the platform and platform version with the image map. Also, display both the platform and architecture information when generating a crash log. rdar://124913332 --- docs/CompactImageMapFormat.md | 12 +- stdlib/public/RuntimeModule/Address.swift | 23 +-- stdlib/public/RuntimeModule/Backtrace.swift | 4 +- stdlib/public/RuntimeModule/CMakeLists.txt | 1 + .../RuntimeModule/CompactImageMap.swift | 60 +++++- .../RuntimeModule/ImageMap+Darwin.swift | 42 +++- .../public/RuntimeModule/ImageMap+Linux.swift | 52 ++++- stdlib/public/RuntimeModule/ImageMap.swift | 29 ++- .../RuntimeModule/OSReleaseScanner.swift | 186 ++++++++++++++++++ stdlib/public/RuntimeModule/RichFrame.swift | 6 +- .../RuntimeModule/SymbolicatedBacktrace.swift | 7 +- .../libexec/swift-backtrace/Themes.swift | 19 +- .../public/libexec/swift-backtrace/main.swift | 11 ++ 13 files changed, 414 insertions(+), 38 deletions(-) create mode 100644 stdlib/public/RuntimeModule/OSReleaseScanner.swift diff --git a/docs/CompactImageMapFormat.md b/docs/CompactImageMapFormat.md index 5c5bb2442bf..a7a92bff85d 100644 --- a/docs/CompactImageMapFormat.md +++ b/docs/CompactImageMapFormat.md @@ -39,10 +39,14 @@ follows: | 10 | 64-bit | | 11 | Reserved | -This is followed immediately by a field encoding the number of images -in the image map; this field is encoded as a sequence of bytes, each -holding seven bits of data, with the top bit clear for the final byte. -The most significant byte is the first. e.g. +This is followed immediately by a field containing the name of the platform +that generated this image map. This field consists of a single byte length +followed by a UTF-8 string of that length. + +After that is a field encoding the number of images in the image map; +this field is encoded as a sequence of bytes, each holding seven bits +of data, with the top bit clear for the final byte. The most +significant byte is the first. e.g. | `count` | Encoding | | ------: | :---------- | diff --git a/stdlib/public/RuntimeModule/Address.swift b/stdlib/public/RuntimeModule/Address.swift index 6e6296a47a6..5f2f22c8531 100644 --- a/stdlib/public/RuntimeModule/Address.swift +++ b/stdlib/public/RuntimeModule/Address.swift @@ -164,7 +164,7 @@ extension FixedWidthInteger { /// /// This initializer will return nil if the address width is larger than the /// type you are attempting to convert into. - init?(_ address: Backtrace.Address) { + public init?(_ address: Backtrace.Address) { guard let result = address.toFixedWidth(type: Self.self) else { return nil } @@ -174,7 +174,7 @@ extension FixedWidthInteger { extension Backtrace.Address { /// Convert from a UInt16. - init(_ value: UInt16) { + public init(_ value: UInt16) { if value == 0 { self.representation = .null return @@ -183,7 +183,7 @@ extension Backtrace.Address { } /// Convert from a UInt32. - init(_ value: UInt32) { + public init(_ value: UInt32) { if value == 0 { self.representation = .null return @@ -192,7 +192,7 @@ extension Backtrace.Address { } /// Convert from a UInt64. - init(_ value: UInt64) { + public init(_ value: UInt64) { if value == 0 { self.representation = .null return @@ -201,21 +201,16 @@ extension Backtrace.Address { } /// Convert from a FixedWidthInteger - init(_ value: T) { - if value == 0 { - self.representation = .null - return - } - + public init?(_ value: T) { switch T.bitWidth { case 16: - self.representation = .sixteenBit(UInt16(value)) + self.init(UInt16(value)) case 32: - self.representation = .thirtyTwoBit(UInt32(value)) + self.init(UInt32(value)) case 64: - self.representation = .sixtyFourBit(UInt64(value)) + self.init(UInt64(value)) default: - fatalError("Unsupported address width") + return nil } } } diff --git a/stdlib/public/RuntimeModule/Backtrace.swift b/stdlib/public/RuntimeModule/Backtrace.swift index b79dc4bc871..a6e682e576d 100644 --- a/stdlib/public/RuntimeModule/Backtrace.swift +++ b/stdlib/public/RuntimeModule/Backtrace.swift @@ -229,7 +229,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { } /// The architecture of the system that captured this backtrace. - public var architecture: String + public internal(set) var architecture: String /// The actual backtrace data, stored in Compact Backtrace Format. var representation: [UInt8] @@ -345,7 +345,7 @@ public struct Backtrace: CustomStringConvertible, Sendable { /// Provide a textual version of the backtrace. public var description: String { - var lines: [String] = [] + var lines: [String] = ["Architecture: \(architecture)", ""] var n = 0 for frame in frames { diff --git a/stdlib/public/RuntimeModule/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt index 080fae461d5..dc72ae13c8e 100644 --- a/stdlib/public/RuntimeModule/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -48,6 +48,7 @@ set(RUNTIME_SOURCES Libc.swift LimitSequence.swift MemoryReader.swift + OSReleaseScanner.swift ProcMapsScanner.swift Registers.swift Runtime.swift diff --git a/stdlib/public/RuntimeModule/CompactImageMap.swift b/stdlib/public/RuntimeModule/CompactImageMap.swift index 0e963ef91f1..74a50aab817 100644 --- a/stdlib/public/RuntimeModule/CompactImageMap.swift +++ b/stdlib/public/RuntimeModule/CompactImageMap.swift @@ -101,6 +101,25 @@ public enum CompactImageMapFormat { return value } + mutating func decodeString() -> String? { + guard let utf8Length = iterator.next() else { + return nil + } + + var bytes: [UInt8] = [] + bytes.reserveCapacity(Int(utf8Length)) + + for _ in 0.. UInt64? { var word: UInt64 guard let firstByte = iterator.next() else { @@ -250,7 +269,7 @@ public enum CompactImageMapFormat { } } - mutating func decode() -> ([ImageMap.Image], ImageMap.WordSize)? { + mutating func decode() -> (String, [ImageMap.Image], ImageMap.WordSize)? { // Check the version and decode the size guard let infoByte = iterator.next() else { return nil @@ -274,6 +293,11 @@ public enum CompactImageMapFormat { wordMask = 0xffffffffffffff00 } + // Now decode the platform + guard let platform = decodeString() else { + return nil + } + // Next is the image count guard let count = decodeCount() else { return nil @@ -392,7 +416,7 @@ public enum CompactImageMapFormat { wsMap = .sixtyFourBit } - return (images, wsMap) + return (platform, images, wsMap) } } @@ -414,6 +438,7 @@ public enum CompactImageMapFormat { public struct Iterator: IteratorProtocol { enum State { case start + case platform(Int) case count(Int) case image case baseAddress(Int) @@ -483,14 +508,39 @@ public enum CompactImageMapFormat { size = .sixtyFourBit } - let count = source.images.count - let bits = Int.bitWidth - count.leadingZeroBitCount - state = .count(7 * (bits / 7)) + state = .platform(-1) let version: UInt8 = 0 let infoByte = (version << 2) | size.rawValue return infoByte + case let .platform(ndx): + let length = UInt8(source.platform.utf8.count) + let byte: UInt8 + + if ndx == -1 { + // The length byte comes first + byte = length + } else { + byte = source.platform.utf8[ + source.platform.utf8.index( + source.platform.utf8.startIndex, + offsetBy: ndx + ) + ] + } + + // If we're done, move to the .count state + if ndx + 1 == length { + let count = source.images.count + let bits = Int.bitWidth - count.leadingZeroBitCount + state = .count(7 * (bits / 7)) + } else { + state = .platform(ndx + 1) + } + + return byte + case let .count(ndx): let count = source.images.count let byte = UInt8(truncatingIfNeeded:(count >> ndx) & 0x7f) diff --git a/stdlib/public/RuntimeModule/ImageMap+Darwin.swift b/stdlib/public/RuntimeModule/ImageMap+Darwin.swift index b72d2dbda6b..ee0415f7378 100644 --- a/stdlib/public/RuntimeModule/ImageMap+Darwin.swift +++ b/stdlib/public/RuntimeModule/ImageMap+Darwin.swift @@ -21,8 +21,44 @@ import Swift internal import Darwin internal import BacktracingImpl.OS.Darwin +fileprivate func getSysCtlString(_ name: String) -> String? { + return withUnsafeTemporaryAllocation(byteCount: 256, alignment: 16) { + (buffer: UnsafeMutableRawBufferPointer) -> String? in + + var len = buffer.count + let ret = sysctlbyname(name, + buffer.baseAddress, &len, + nil, 0) + if ret != 0 { + return nil + } + + return String(validatingUTF8: + buffer.baseAddress!.assumingMemoryBound(to: CChar.self)) + } +} + extension ImageMap { + private static let platform = { + #if os(macOS) + var platform = "macOS" + #elseif os(iOS) + var platform = "iOS" + #elseif os(watchOS) + var platform = "watchOS" + #elseif os(tvOS) + var platform = "tvOS" + #elseif os(visionOS) + var platform = "visionOS" + #endif + + let osVersion = getSysCtlString("kern.osversion") ?? "" + let osProductVersion = getSysCtlString("kern.osproductversion") ?? "" + + return "\(platform) \(osProductVersion) (\(osVersion))" + }() + private static func withDyldProcessInfo(for task: task_t, fn: (OpaquePointer?) throws -> T) rethrows -> T { @@ -83,7 +119,11 @@ extension ImageMap { images.sort(by: { $0.baseAddress < $1.baseAddress }) - return ImageMap(images: images, wordSize: .sixtyFourBit) + return ImageMap( + platform: ImageMap.platform, + images: images, + wordSize: .sixtyFourBit + ) } } diff --git a/stdlib/public/RuntimeModule/ImageMap+Linux.swift b/stdlib/public/RuntimeModule/ImageMap+Linux.swift index 1608f72e8b4..6a565ddad95 100644 --- a/stdlib/public/RuntimeModule/ImageMap+Linux.swift +++ b/stdlib/public/RuntimeModule/ImageMap+Linux.swift @@ -26,8 +26,52 @@ internal import Musl internal import BacktracingImpl.ImageFormats.Elf +fileprivate func readOSRelease(fd: CInt) -> [String:String]? { + let len = lseek(fd, 0, SEEK_END) + guard len >= 0 else { + return nil + } + return withUnsafeTemporaryAllocation(byteCount: len, alignment: 16) { + (buffer: UnsafeMutableRawBufferPointer) -> [String:String]? in + + _ = lseek(fd, 0, SEEK_SET) + let bytesRead = read(fd, buffer.baseAddress, buffer.count) + guard bytesRead == buffer.count else { + return nil + } + + let asString = String(decoding: buffer, as: UTF8.self) + return Dictionary(OSReleaseScanner(asString), + uniquingKeysWith: { $1 }) + } +} + +fileprivate func readOSRelease() -> [String:String]? { + var fd = open("/etc/os-release", O_RDONLY) + if fd == -1 { + fd = open("/usr/lib/os-release", O_RDONLY) + } + if fd == -1 { + return nil + } + defer { + close(fd) + } + + return readOSRelease(fd: fd) +} + extension ImageMap { + private static var platform = { + guard let info = readOSRelease(), + let pretty = info["PRETTY_NAME"] else { + return "Linux (unknown)" + } + + return "Linux (\(pretty))" + }() + private struct AddressRange { var low: Address = 0 var high: Address = 0 @@ -59,7 +103,7 @@ extension ImageMap { } guard let procMaps = readString(from: path) else { - return ImageMap(images: [], wordSize: wordSize) + return ImageMap(platform: ImageMap.platform, images: [], wordSize: wordSize) } // Find all the mapped files and get high/low ranges @@ -113,7 +157,11 @@ extension ImageMap { images.sort(by: { $0.baseAddress < $1.baseAddress }) - return ImageMap(images: images, wordSize: wordSize) + return ImageMap( + platform: ImageMap.platform, + images: images, + wordSize: wordSize + ) } } diff --git a/stdlib/public/RuntimeModule/ImageMap.swift b/stdlib/public/RuntimeModule/ImageMap.swift index 4b68c5c83bb..662267280ad 100644 --- a/stdlib/public/RuntimeModule/ImageMap.swift +++ b/stdlib/public/RuntimeModule/ImageMap.swift @@ -51,6 +51,9 @@ public struct ImageMap: Collection, Sendable, Hashable { var endOfText: Address } + /// The name of the platform that captured this image map. + public private(set) var platform: String + /// The actual image storage. var images: [Image] @@ -58,7 +61,8 @@ public struct ImageMap: Collection, Sendable, Hashable { var wordSize: WordSize /// Construct an ImageMap. - init(images: [Image], wordSize: WordSize) { + init(platform: String, images: [Image], wordSize: WordSize) { + self.platform = platform self.images = images self.wordSize = wordSize } @@ -67,10 +71,10 @@ public struct ImageMap: Collection, Sendable, Hashable { @_spi(Internal) public init?(compactImageMapData: some Sequence) { var decoder = CompactImageMapFormat.Decoder(compactImageMapData) - guard let (images, wordSize) = decoder.decode() else { + guard let (platform, images, wordSize) = decoder.decode() else { return nil } - self.init(images: images, wordSize: wordSize) + self.init(platform: platform, images: images, wordSize: wordSize) } /// The position of the first element in a non-empty collection. @@ -125,7 +129,7 @@ public struct ImageMap: Collection, Sendable, Hashable { extension ImageMap: CustomStringConvertible { /// Generate a description of an ImageMap public var description: String { - var lines: [String] = [] + var lines: [String] = ["Platform: \(platform)", ""] let addressWidth: Int switch wordSize { case .sixteenBit: addressWidth = 4 @@ -188,3 +192,20 @@ extension Backtrace.Image { endOfText: endOfText) } } + +extension ImageMap: Codable { + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + let cimfEncoder = CompactImageMapFormat.Encoder(self) + let base64 = stringFrom(sequence: Base64Encoder(source: cimfEncoder)) + try container.encode(base64) + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + let base64 = try container.decode(String.self) + self.init(compactImageMapData: Base64Decoder(source: base64.utf8))! + } + +} diff --git a/stdlib/public/RuntimeModule/OSReleaseScanner.swift b/stdlib/public/RuntimeModule/OSReleaseScanner.swift new file mode 100644 index 00000000000..e5d1d1a76f3 --- /dev/null +++ b/stdlib/public/RuntimeModule/OSReleaseScanner.swift @@ -0,0 +1,186 @@ +//===--- OSReleaseScanner.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// +// +// Defines OSReleaseScanner, which is for scanning the /etc/os-release +// file on Linux. +// +//===----------------------------------------------------------------------===// + +#if os(Linux) + +import Swift + +// Lines in /etc/os-release consist of KEY=VALUE pairs. +// +// The VALUE may be quoted with single quotes, in which case its contents +// are left alone. +// +// It may also be quoted with double quotes, in which case slash escapes +// are processed. +// +// If it is unquoted, whitespace will be stripped. + +struct OSReleaseScanner: Sequence, IteratorProtocol { + typealias SS = S.SubSequence + + private enum State { + case normal + case badLine + case comment + case key + case beforeEquals + case beforeValue + case value + case valueWhitespace + case singleQuote + case doubleQuote + case escape + case awaitingNewline + } + + private var asString: S + private var asUTF8: S.UTF8View + private var pos: S.UTF8View.Index + private var state: State + + init(_ string: S) { + asString = string + asUTF8 = string.utf8 + pos = asUTF8.startIndex + state = .normal + } + + mutating func next() -> (String, String)? { + var chunkStart = pos + var whitespaceStart = pos + var key: String = "" + var quotedValue: String = "" + + while pos < asUTF8.endIndex { + let ch = asUTF8[pos] + switch state { + case .normal: + if ch == 32 || ch == 9 || ch == 13 || ch == 10 { + break + } + if ch == UInt8(ascii: "#") { + state = .comment + break + } + chunkStart = pos + state = .key + case .badLine, .comment, .awaitingNewline: + if ch == 13 || ch == 10 { + state = .normal + } + case .key: + if ch == 32 || ch == 9 { + key = String(asString[chunkStart..(_ frame: RichFrame) { switch frame { case let .returnAddress(addr): - self = .returnAddress(Backtrace.Address(addr)) + self = .returnAddress(Backtrace.Address(addr)!) case let .programCounter(addr): - self = .programCounter(Backtrace.Address(addr)) + self = .programCounter(Backtrace.Address(addr)!) case let .asyncResumePoint(addr): - self = .asyncResumePoint(Backtrace.Address(addr)) + self = .asyncResumePoint(Backtrace.Address(addr)!) case let .omittedFrames(count): self = .omittedFrames(count) case .truncated: diff --git a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift index 72d2ce25431..214231cdc28 100644 --- a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift +++ b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift @@ -245,11 +245,14 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } } + /// The architecture on which this backtrace was captured. + public var architecture: String { return backtrace.architecture } + /// A list of captured frame information. - public var frames: [Frame] + public private(set) var frames: [Frame] /// A list of images found in the process. - public var images: ImageMap + public private(set) var images: ImageMap /// True if this backtrace is a Swift runtime failure. public var isSwiftRuntimeFailure: Bool { diff --git a/stdlib/public/libexec/swift-backtrace/Themes.swift b/stdlib/public/libexec/swift-backtrace/Themes.swift index 347bd679bef..d26f67ee070 100644 --- a/stdlib/public/libexec/swift-backtrace/Themes.swift +++ b/stdlib/public/libexec/swift-backtrace/Themes.swift @@ -38,6 +38,16 @@ extension PromptTheme { public func prompt(_ s: String) -> String { return s } } +protocol PlatformArchTheme { + func platform(_ s: String) -> String + func architecture(_ s: String) -> String +} + +extension PlatformArchTheme { + public func platform(_ s: String) -> String { return s } + public func architecture(_ s: String) -> String { return s } +} + protocol MemoryDumpTheme { func address(_ s: String) -> String func data(_ s: String) -> String @@ -67,7 +77,7 @@ extension RegisterDumpTheme { } typealias Theme = BacktraceFormattingTheme & ErrorAndWarningTheme & - PromptTheme & MemoryDumpTheme & RegisterDumpTheme + PromptTheme & MemoryDumpTheme & RegisterDumpTheme & PlatformArchTheme enum Themes { @@ -164,6 +174,13 @@ enum Themes { public func info(_ s: String) -> String { return "ℹ️ \(s)" } + + public func platform(_ s: String) -> String { + return "\(fg: .white)\(s)\(fg: .normal)" + } + public func architecture(_ s: String) -> String { + return "\(fg: .white)\(s)\(fg: .normal)" + } } static let plain = Plain() diff --git a/stdlib/public/libexec/swift-backtrace/main.swift b/stdlib/public/libexec/swift-backtrace/main.swift index 3fc10013173..e8c8000f61e 100644 --- a/stdlib/public/libexec/swift-backtrace/main.swift +++ b/stdlib/public/libexec/swift-backtrace/main.swift @@ -719,6 +719,17 @@ Generate a backtrace for the parent process. var mentionedImages = Set() let formatter = backtraceFormatter() + let platform = target.images.platform + let architecture: String + switch crashingThread.backtrace { + case let .raw(backtrace): + architecture = backtrace.architecture + case let .symbolicated(backtrace): + architecture = backtrace.architecture + } + + writeln("\nPlatform: \(theme.architecture(architecture)) \(theme.platform(target.images.platform))") + func dump(ndx: Int, thread: TargetThread) { let crashed = thread.id == target.crashingThread ? " crashed" : "" let name = !thread.name.isEmpty ? " \"\(thread.name)\"" : "" From 26d0e49764196ca34189184a97b8ab34d18eb442 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 27 Jan 2025 17:22:39 +0000 Subject: [PATCH 17/17] [Backtracing] Updates following review. Added some explanatory text to the Compact Backtrace Format documentation, and also to the `CMakeLists.txt` for the runtime. Tweaked the conversions for `Backtrace.Address` to truncate, which should result in reasonable behaviour for negative fixed-width integers. Use a constant for the ASCII values for `/` and `\` in the Compact Image Format encoder/decoder. Make the TSD key for `ElfImageCache` non-optional, and call `fatalError()` if we don't get one. rdar://124913332 --- docs/CompactBacktraceFormat.md | 4 ++++ stdlib/public/RuntimeModule/Address.swift | 12 ++++++------ .../public/RuntimeModule/CompactImageMap.swift | 16 ++++++++++------ stdlib/public/RuntimeModule/ElfImageCache.swift | 8 ++++---- stdlib/public/runtime/CMakeLists.txt | 5 ++++- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/docs/CompactBacktraceFormat.md b/docs/CompactBacktraceFormat.md index c527cfbb266..eef81c3a889 100644 --- a/docs/CompactBacktraceFormat.md +++ b/docs/CompactBacktraceFormat.md @@ -57,6 +57,10 @@ The following instructions are currently defined | `1000xxxx` | `rep` | Repeat the previous frame | | `1xxxxxxx` | reserved | Reserved for future expansion | +If the bit labelled `a` is set, it means that the address computation +is absolute rather than being relative to the previously computed +address. + ### `end`/`trunc` #### Encoding diff --git a/stdlib/public/RuntimeModule/Address.swift b/stdlib/public/RuntimeModule/Address.swift index 5f2f22c8531..24a8a0a7557 100644 --- a/stdlib/public/RuntimeModule/Address.swift +++ b/stdlib/public/RuntimeModule/Address.swift @@ -148,13 +148,13 @@ extension Backtrace.Address { return T(0) case let .sixteenBit(addr): guard T.bitWidth >= 16 else { return nil } - return T(addr) + return T(truncatingIfNeeded: addr) case let .thirtyTwoBit(addr): guard T.bitWidth >= 32 else { return nil } - return T(addr) + return T(truncatingIfNeeded: addr) case let .sixtyFourBit(addr): guard T.bitWidth >= 64 else { return nil } - return T(addr) + return T(truncatingIfNeeded: addr) } } } @@ -204,11 +204,11 @@ extension Backtrace.Address { public init?(_ value: T) { switch T.bitWidth { case 16: - self.init(UInt16(value)) + self.init(UInt16(truncatingIfNeeded: value)) case 32: - self.init(UInt32(value)) + self.init(UInt32(truncatingIfNeeded: value)) case 64: - self.init(UInt64(value)) + self.init(UInt64(truncatingIfNeeded: value)) default: return nil } diff --git a/stdlib/public/RuntimeModule/CompactImageMap.swift b/stdlib/public/RuntimeModule/CompactImageMap.swift index 74a50aab817..f76233ebbdd 100644 --- a/stdlib/public/RuntimeModule/CompactImageMap.swift +++ b/stdlib/public/RuntimeModule/CompactImageMap.swift @@ -16,6 +16,9 @@ import Swift +private let slash = UInt8(ascii: "/") +private let backslash = UInt8(ascii: "\\") + @_spi(Internal) public enum CompactImageMapFormat { @@ -58,7 +61,7 @@ public enum CompactImageMapFormat { while pos < end { let ch = str[pos] - if pos > base && (ch == 0x2f || ch == 0x5c) { + if pos > base && (ch == slash || ch == backslash) { let range = base.. stringBase! && (char == 0x2f || char == 0x5c) { + if base + n > stringBase! && (char == slash + || char == backslash) { let prefix = String(decoding: resultBytes[stringBase!...passRetained(ElfImageCache()) - pthread_setspecific(key!, cache.toOpaque()) + pthread_setspecific(key, cache.toOpaque()) return cache.takeUnretainedValue() } let cache = Unmanaged.fromOpaque(rawPtr) diff --git a/stdlib/public/runtime/CMakeLists.txt b/stdlib/public/runtime/CMakeLists.txt index 7c9a8532913..407382ff6b3 100644 --- a/stdlib/public/runtime/CMakeLists.txt +++ b/stdlib/public/runtime/CMakeLists.txt @@ -144,6 +144,10 @@ foreach(sdk ${SWIFT_SDKS}) endif() endforeach() +# In modern CMake, we would be able to use the previous name `swiftRuntime` +# without clashing with the `Runtime` module, but the current build system +# is architected in such a way that we had to rename this to `swiftRuntimeCore` +# in order to avoid a clash with the new Swift module. add_swift_target_library(swiftRuntimeCore OBJECT_LIBRARY ${swift_runtime_sources} ${swift_runtime_objc_sources} @@ -323,4 +327,3 @@ if(static_binary_lnk_file_list) add_dependencies(stdlib ${static_binary_lnk_file_list}) add_custom_target(static_binary_magic ALL DEPENDS ${static_binary_lnk_file_list}) endif() -