From 41c7b050ddd7e53abdfe0959231cb5bd3728fab9 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Thu, 16 Jan 2025 18:32:09 -0500 Subject: [PATCH 01/19] Add C++23 stacktrace --- libcxx/CMakeLists.txt | 6 + libcxx/docs/FeatureTestMacroTable.rst | 2 +- libcxx/docs/ReleaseNotes/21.rst | 2 + libcxx/docs/Status/Cxx23Issues.csv | 2 +- libcxx/docs/Status/Cxx23Papers.csv | 6 +- libcxx/docs/VendorDocumentation.rst | 8 + libcxx/include/CMakeLists.txt | 7 + libcxx/include/__config | 27 ++ libcxx/include/__config_site.in | 1 + libcxx/include/__stacktrace/base.h | 133 ++++++ libcxx/include/__stacktrace/basic.h | 247 +++++++++++ libcxx/include/__stacktrace/entry.h | 113 +++++ libcxx/include/__stacktrace/hash.h | 47 ++ libcxx/include/__stacktrace/nonmem.h | 55 +++ libcxx/include/__stacktrace/to_string.h | 53 +++ libcxx/include/module.modulemap.in | 37 +- libcxx/include/stacktrace | 191 +++++++++ libcxx/include/version | 2 +- ...bcxxabi.v1.stable.exceptions.nonew.abilist | 24 +- ...bcxxabi.v1.stable.exceptions.nonew.abilist | 47 +- libcxx/modules/std.compat.cppm.in | 3 - libcxx/modules/std.cppm.in | 4 +- libcxx/modules/std/stacktrace.inc | 6 +- libcxx/src/CMakeLists.txt | 9 + libcxx/src/stacktrace/README.md | 6 + libcxx/src/stacktrace/builder.cpp | 70 +++ libcxx/src/stacktrace/config.h | 26 ++ libcxx/src/stacktrace/linux/impl.cpp | 112 +++++ libcxx/src/stacktrace/linux/impl.h | 401 ++++++++++++++++++ libcxx/src/stacktrace/macos/impl.cpp | 99 +++++ libcxx/src/stacktrace/macos/impl.h | 40 ++ libcxx/src/stacktrace/to_string.cpp | 93 ++++ libcxx/src/stacktrace/tools/tools.cpp | 382 +++++++++++++++++ libcxx/src/stacktrace/tools/tools.h | 197 +++++++++ libcxx/src/stacktrace/unwind/impl.cpp | 61 +++ libcxx/src/stacktrace/unwind/impl.h | 32 ++ libcxx/src/stacktrace/utils/debug.h | 55 +++ libcxx/src/stacktrace/utils/failed.h | 29 ++ libcxx/src/stacktrace/utils/fd.h | 131 ++++++ libcxx/src/stacktrace/utils/image.h | 33 ++ libcxx/src/stacktrace/windows/dll.cpp | 247 +++++++++++ libcxx/src/stacktrace/windows/dll.h | 121 ++++++ libcxx/src/stacktrace/windows/impl.cpp | 200 +++++++++ libcxx/src/stacktrace/windows/impl.h | 50 +++ .../stacktrace/simple.o0.nodebug.pass.cpp | 32 ++ .../stacktrace/simple.o0.nosplit.pass.cpp | 29 ++ .../stacktrace/simple.o0.split.pass.cpp | 29 ++ .../stacktrace/simple.o3.nodebug.pass.cpp | 32 ++ .../stacktrace/simple.o3.nosplit.pass.cpp | 29 ++ .../stacktrace/simple.o3.split.pass.cpp | 29 ++ .../test/libcxx/transitive_includes/cxx23.csv | 21 + .../test/libcxx/transitive_includes/cxx26.csv | 21 + .../stacktrace/basic.cmp/equality.pass.cpp | 51 +++ .../basic.cmp/strong_ordering.pass.cpp | 81 ++++ .../basic.cons/copy_and_move.pass.cpp | 82 ++++ .../basic.cons/ctor_no_args.pass.cpp | 48 +++ .../basic.cons/ctor_with_alloc.pass.cpp | 79 ++++ .../basic.cons/current_no_args.pass.cpp | 67 +++ .../basic.cons/current_skip.pass.cpp | 45 ++ .../basic.cons/current_skip_depth.pass.cpp | 42 ++ .../basic.cons/only_uses_allocator.pass.cpp | 128 ++++++ .../stacktrace/basic.hash.pass.cpp | 37 ++ .../stacktrace/basic.mod/swap.pass.cpp | 41 ++ .../basic.nonmem/operator_left_shift.pass.cpp | 35 ++ .../stacktrace/basic.nonmem/swap.pass.cpp | 36 ++ .../basic.nonmem/to_string.pass.cpp | 33 ++ .../stacktrace/basic.obs/at.pass.cpp | 49 +++ .../stacktrace/basic.obs/begin_end.pass.cpp | 41 ++ .../stacktrace/basic.obs/cbegin_cend.pass.cpp | 36 ++ .../basic.obs/crbegin_crend.pass.cpp | 36 ++ .../stacktrace/basic.obs/empty.pass.cpp | 32 ++ .../basic.obs/get_allocator.pass.cpp | 25 ++ .../stacktrace/basic.obs/max_size.pass.cpp | 32 ++ .../basic.obs/operator_index.pass.cpp | 49 +++ .../stacktrace/basic.obs/rbegin_rend.pass.cpp | 37 ++ .../stacktrace/basic.obs/size.pass.cpp | 32 ++ .../std/diagnostics/stacktrace/basic.pass.cpp | 143 +++++++ .../stacktrace/entry.cmp/equality.pass.cpp | 42 ++ .../entry.cmp/strong_ordering.pass.cpp | 52 +++ .../entry.cons/copy_assign.pass.cpp | 34 ++ .../entry.cons/copy_construct.pass.cpp | 33 ++ .../stacktrace/entry.cons/default.pass.cpp | 35 ++ .../entry.obs/native_handle.pass.cpp | 28 ++ .../entry.obs/operator_bool.pass.cpp | 34 ++ .../std/diagnostics/stacktrace/entry.pass.cpp | 61 +++ .../entry.query/description.pass.cpp | 32 ++ .../entry.query/source_file.pass.cpp | 32 ++ .../entry.query/source_line.pass.cpp | 30 ++ .../formatter_basic_stacktrace.pass.cpp | 31 ++ .../formatter_stacktrace_entry.pass.cpp | 37 ++ .../stacktrace.version.compile.pass.cpp | 108 +++++ .../version.version.compile.pass.cpp | 32 +- .../generate_feature_test_macro_components.py | 1 - libcxx/utils/libcxx/header_information.py | 3 +- 94 files changed, 5537 insertions(+), 44 deletions(-) create mode 100644 libcxx/include/__stacktrace/base.h create mode 100644 libcxx/include/__stacktrace/basic.h create mode 100644 libcxx/include/__stacktrace/entry.h create mode 100644 libcxx/include/__stacktrace/hash.h create mode 100644 libcxx/include/__stacktrace/nonmem.h create mode 100644 libcxx/include/__stacktrace/to_string.h create mode 100644 libcxx/include/stacktrace create mode 100644 libcxx/src/stacktrace/README.md create mode 100644 libcxx/src/stacktrace/builder.cpp create mode 100644 libcxx/src/stacktrace/config.h create mode 100644 libcxx/src/stacktrace/linux/impl.cpp create mode 100644 libcxx/src/stacktrace/linux/impl.h create mode 100644 libcxx/src/stacktrace/macos/impl.cpp create mode 100644 libcxx/src/stacktrace/macos/impl.h create mode 100644 libcxx/src/stacktrace/to_string.cpp create mode 100644 libcxx/src/stacktrace/tools/tools.cpp create mode 100644 libcxx/src/stacktrace/tools/tools.h create mode 100644 libcxx/src/stacktrace/unwind/impl.cpp create mode 100644 libcxx/src/stacktrace/unwind/impl.h create mode 100644 libcxx/src/stacktrace/utils/debug.h create mode 100644 libcxx/src/stacktrace/utils/failed.h create mode 100644 libcxx/src/stacktrace/utils/fd.h create mode 100644 libcxx/src/stacktrace/utils/image.h create mode 100644 libcxx/src/stacktrace/windows/dll.cpp create mode 100644 libcxx/src/stacktrace/windows/dll.h create mode 100644 libcxx/src/stacktrace/windows/impl.cpp create mode 100644 libcxx/src/stacktrace/windows/impl.h create mode 100644 libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp create mode 100644 libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp create mode 100644 libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp create mode 100644 libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp create mode 100644 libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp create mode 100644 libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.pass.cpp create mode 100644 libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt index 85514cc7547a9..6e4624326100e 100644 --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -131,6 +131,11 @@ option(LIBCXX_ENABLE_VENDOR_AVAILABILITY_ANNOTATIONS the shared library they shipped should turn this on and see `include/__configuration/availability.h` for more details." OFF) +option(LIBCXX_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + "For C++23 : whether to allow invocation of `addr2line`, `llvm-addr2line`, or `atos` + at runtime (if it's available in `PATH`) to resolve call-chain addresses in the stacktrace + into source locations, if other methods are not available." ON) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(LIBCXX_DEFAULT_TEST_CONFIG "llvm-libc++-shared-gcc.cfg.in") elseif(MINGW) @@ -750,6 +755,7 @@ config_define(${LIBCXX_ENABLE_UNICODE} _LIBCPP_HAS_UNICODE) config_define(${LIBCXX_ENABLE_WIDE_CHARACTERS} _LIBCPP_HAS_WIDE_CHARACTERS) config_define(${LIBCXX_ENABLE_TIME_ZONE_DATABASE} _LIBCPP_HAS_TIME_ZONE_DATABASE) config_define(${LIBCXX_ENABLE_VENDOR_AVAILABILITY_ANNOTATIONS} _LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS) +config_define(${LIBCXX_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME} _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME) # TODO: Remove in LLVM 21. We're leaving an error to make this fail explicitly. if (LIBCXX_ENABLE_ASSERTIONS) diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst index a36848ebd24b4..072647bd3c740 100644 --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -396,7 +396,7 @@ Status ---------------------------------------------------------- ----------------- ``__cpp_lib_spanstream`` *unimplemented* ---------------------------------------------------------- ----------------- - ``__cpp_lib_stacktrace`` *unimplemented* + ``__cpp_lib_stacktrace`` ``202011L`` ---------------------------------------------------------- ----------------- ``__cpp_lib_stdatomic_h`` ``202011L`` ---------------------------------------------------------- ----------------- diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst index 91123ffa3e34b..5483c4e7c12cb 100644 --- a/libcxx/docs/ReleaseNotes/21.rst +++ b/libcxx/docs/ReleaseNotes/21.rst @@ -55,6 +55,8 @@ Implemented Papers - P2655R3: ``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type (`Github `__) - P2944R3: Comparisons for ``reference_wrapper`` (`Github `__) - P3379R0: Constrain ``std::expected equality`` operators (`Github `__) +- P0881R7: A Proposal to add stacktrace library (`Github `__) +- P2301R1: Add a `pmr` alias for `std::stacktrace` (`Github `__) Improvements and New Features ----------------------------- diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv index 189f8452e0678..50ba4275b1c7d 100644 --- a/libcxx/docs/Status/Cxx23Issues.csv +++ b/libcxx/docs/Status/Cxx23Issues.csv @@ -189,7 +189,7 @@ "`LWG3028 `__","Container requirements tables should distinguish ``const`` and non-``const`` variables","2022-11 (Kona)","","","" "`LWG3118 `__","``fpos`` equality comparison unspecified","2022-11 (Kona)","","","" "`LWG3177 `__","Limit permission to specialize variable templates to program-defined types","2022-11 (Kona)","|Nothing To Do|","","" -"`LWG3515 `__","§[stacktrace.basic.nonmem]: ``operator<<`` should be less templatized","2022-11 (Kona)","","","" +"`LWG3515 `__","§[stacktrace.basic.nonmem]: ``operator<<`` should be less templatized","2022-11 (Kona)","|Nothing To Do|","","" "`LWG3545 `__","``std::pointer_traits`` should be SFINAE-friendly","2022-11 (Kona)","|Complete|","18","" "`LWG3569 `__","``join_view`` fails to support ranges of ranges with non-default_initializable iterators","2022-11 (Kona)","","","" "`LWG3594 `__","``inout_ptr`` — inconsistent ``release()`` in destructor","2022-11 (Kona)","|Complete|","19","" diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index f1d8e9a2bd09c..9637e0cf65078 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -1,5 +1,5 @@ "Paper #","Paper Name","Meeting","Status","First released version","Notes" -"`P0881R7 `__","A Proposal to add stacktrace library","2020-11 (Virtual)","","","" +"`P0881R7 `__","A Proposal to add stacktrace library","2020-11 (Virtual)","|Complete|","21","" "`P0943R6 `__","Support C atomics in C++","2020-11 (Virtual)","|Complete|","15","" "`P1048R1 `__","A proposal for a type trait to detect scoped enumerations","2020-11 (Virtual)","|Complete|","12","" "`P1679R3 `__","string contains function","2020-11 (Virtual)","|Complete|","12","" @@ -32,7 +32,7 @@ "`P1675R2 `__","``rethrow_exception`` must be allowed to copy","2021-10 (Virtual)","|Nothing To Do|","","" "`P2077R3 `__","Heterogeneous erasure overloads for associative containers","2021-10 (Virtual)","","","" "`P2251R1 `__","Require ``span`` & ``basic_string_view`` to be Trivially Copyable","2021-10 (Virtual)","|Complete|","14","" -"`P2301R1 `__","Add a ``pmr`` alias for ``std::stacktrace``","2021-10 (Virtual)","","","" +"`P2301R1 `__","Add a ``pmr`` alias for ``std::stacktrace``","2021-10 (Virtual)","|Complete|","21","" "`P2321R2 `__","``zip``","2021-10 (Virtual)","|In Progress|","","" "`P2340R1 `__","Clarifying the status of the 'C headers'","2021-10 (Virtual)","|Nothing To Do|","","" "`P2393R1 `__","Cleaning up ``integer``-class types","2021-10 (Virtual)","","","" @@ -110,7 +110,7 @@ "`P2713R1 `__","Escaping improvements in ``std::format``","2023-02 (Issaquah)","|Complete|","19","" "`P2675R1 `__","``format``'s width estimation is too approximate and not forward compatible","2023-02 (Issaquah)","|Complete|","17","" "`P2572R1 `__","``std::format`` fill character allowances","2023-02 (Issaquah)","|Complete|","17","" -"`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","2023-02 (Issaquah)","|Partial|","","The formatter for ``stacktrace`` is not implemented, since ``stacktrace`` is not implemented yet" +"`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","2023-02 (Issaquah)","|Partial|","","The formatter for ``stacktrace`` is not implemented yet" "`P2679R2 `__","Fixing ``std::start_lifetime_as`` for arrays","2023-02 (Issaquah)","","","" "`P2674R1 `__","A trait for implicit lifetime types","2023-02 (Issaquah)","|Complete|","20","" "`P2655R3 `__","``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type","2023-02 (Issaquah)","|Complete|","21","The paper is implemented as a DR to C++20" diff --git a/libcxx/docs/VendorDocumentation.rst b/libcxx/docs/VendorDocumentation.rst index aede8f9a81dd2..971925bb8a7d0 100644 --- a/libcxx/docs/VendorDocumentation.rst +++ b/libcxx/docs/VendorDocumentation.rst @@ -185,6 +185,14 @@ General purpose options ship the IANA time zone database. When time zones are not supported, time zone support in will be disabled. +.. option:: LIBCXX_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME:BOOL + + **Default**: ``ON`` + + For C++23 : whether to allow invocation of ``addr2line``, ``llvm-addr2line``, or ``atos`` + at runtime (if it's available in ``PATH``) to resolve call-chain addresses in the stacktrace + into source locations, if other methods are not available. + .. option:: LIBCXX_INSTALL_LIBRARY_DIR:PATH **Default**: ``lib${LIBCXX_LIBDIR_SUFFIX}`` diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index c6b87a34a43e9..f5b1b90e500b7 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -740,6 +740,12 @@ set(files __ranges/zip_transform_view.h __ranges/zip_view.h __split_buffer + __stacktrace/base.h + __stacktrace/basic.h + __stacktrace/entry.h + __stacktrace/hash.h + __stacktrace/nonmem.h + __stacktrace/to_string.h __std_mbstate_t.h __stop_token/atomic_unique_lock.h __stop_token/intrusive_list_view.h @@ -1058,6 +1064,7 @@ set(files span sstream stack + stacktrace stdatomic.h stdbool.h stddef.h diff --git a/libcxx/include/__config b/libcxx/include/__config index 77a71b6cf1cae..bc407f41b3600 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -965,6 +965,33 @@ typedef __char32_t char32_t; # define _LIBCPP_NOINLINE # endif +// Some functions, e.g. std::stacktrace::current, need to avoid being +// tail-called by (and tail-calling other) functions, for proper enumeration of +// call-stack frames. +// clang-format off + +// Disables tail-call optimization for "outbound" calls +// performed in the function annotated with this attribute. +# if __has_cpp_attribute(_Clang::__disable_tail_calls__) +# define _LIBCPP_NO_TAIL_CALLS_OUT [[_Clang::__disable_tail_calls__]] +# elif __has_cpp_attribute(__gnu__::__optimize__) +# define _LIBCPP_NO_TAIL_CALLS_OUT [[__gnu__::__optimize__("no-optimize-sibling-calls")]] +# else +# define _LIBCPP_NO_TAIL_CALLS_OUT +# endif + +// Disables tail-call optimization for "inbound" calls -- that is, +// calls from some other function calling the one having this attribute. +# if __has_cpp_attribute(_Clang::__not_tail_called__) +# define _LIBCPP_NO_TAIL_CALLS_IN [[_Clang::__not_tail_called__]] +# else +# define _LIBCPP_NO_TAIL_CALLS_IN +# endif + +// Disable TCO for calls into, and out from, the annotated function. +# define _LIBCPP_NO_TAIL_CALLS _LIBCPP_NO_TAIL_CALLS_IN _LIBCPP_NO_TAIL_CALLS_OUT +// clang-format on + // We often repeat things just for handling wide characters in the library. // When wide characters are disabled, it can be useful to have a quick way of // disabling it without having to resort to #if-#endif, which has a larger diff --git a/libcxx/include/__config_site.in b/libcxx/include/__config_site.in index b68c0c8258366..d17fa45119439 100644 --- a/libcxx/include/__config_site.in +++ b/libcxx/include/__config_site.in @@ -32,6 +32,7 @@ #cmakedefine01 _LIBCPP_HAS_WIDE_CHARACTERS #cmakedefine01 _LIBCPP_HAS_TIME_ZONE_DATABASE #cmakedefine01 _LIBCPP_INSTRUMENTED_WITH_ASAN +#cmakedefine01 _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME // PSTL backends #cmakedefine _LIBCPP_PSTL_BACKEND_SERIAL diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h new file mode 100644 index 0000000000000..52e6d17e4e5be --- /dev/null +++ b/libcxx/include/__stacktrace/base.h @@ -0,0 +1,133 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_BUILDER +#define _LIBCPP_STACKTRACE_BUILDER + +#include <__config> +#include <__cstddef/byte.h> +#include <__cstddef/size_t.h> +#include <__functional/function.h> +#include <__fwd/format.h> +#include <__fwd/ostream.h> +#include <__memory/allocator.h> +#include <__memory/allocator_traits.h> +#include <__new/allocate.h> +#include <__vector/vector.h> +#include +#include +#include +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry; + +namespace __stacktrace { + +struct _LIBCPP_HIDE_FROM_ABI alloc final { + function __alloc_bytes_; + function __dealloc_bytes_; + + template + _LIBCPP_HIDE_FROM_ABI alloc(_Allocator __alloc) { + using _AT = allocator_traits<_Allocator>; + using _BA = typename _AT::template rebind_alloc; + auto __ba = _BA(__alloc); + __alloc_bytes_ = [__ba](size_t __sz) mutable { return __ba.allocate(__sz); }; + __dealloc_bytes_ = [__ba](void* __ptr, size_t __sz) mutable { __ba.deallocate((byte*)__ptr, __sz); }; + } + + template + struct _LIBCPP_HIDE_FROM_ABI Alloc { + function __alloc_bytes_; + function __dealloc_bytes_; + + Alloc(function __alloc_bytes, function __dealloc_bytes) + : __alloc_bytes_(__alloc_bytes), __dealloc_bytes_(__dealloc_bytes) {} + + template + Alloc(Alloc<_T2> const& __rhs) : Alloc(__rhs.__alloc_bytes_, __rhs.__dealloc_bytes_) {} + + using value_type = _Tp; + [[nodiscard]] _Tp* allocate(size_t __sz) { return (_Tp*)__alloc_bytes_(__sz * sizeof(_Tp)); } + void deallocate(_Tp* __ptr, size_t __sz) { __dealloc_bytes_((byte*)__ptr, __sz * sizeof(_Tp)); } + + template + bool operator==(_A2 const& __rhs) const { + return &__rhs == this; + } + }; + + template + Alloc<_Tp> _LIBCPP_HIDE_FROM_ABI make_alloc() { + return {__alloc_bytes_, __dealloc_bytes_}; + } + + using str = basic_string, Alloc>; + + template + str _LIBCPP_HIDE_FROM_ABI make_str(_Args... __args) { + return str(std::forward<_Args>(__args)..., make_alloc()); + } + + template + using vec = vector<_Tp, Alloc<_Tp>>; + + template + _LIBCPP_HIDE_FROM_ABI vec<_Tp> make_vec(_Args... __args) { + return vec(std::forward<_Args>(__args)..., make_alloc<_Tp>()); + } + + template + using list = ::std::list<_Tp, Alloc<_Tp>>; + + template + _LIBCPP_HIDE_FROM_ABI list<_Tp> make_list(_Args... __args) { + return list(std::forward<_Args>(__args)..., make_alloc<_Tp>()); + } +}; + +struct _LIBCPP_HIDE_FROM_ABI entry_base { + uintptr_t __addr_actual_{}; // this address, as observed in this current process + uintptr_t __addr_unslid_{}; // address adjusted for ASLR + optional<__stacktrace::alloc::str> __desc_{}; // uses wrapped _Allocator from caller + optional<__stacktrace::alloc::str> __file_{}; // uses wrapped _Allocator from caller + uint_least32_t __line_{}; + + _LIBCPP_HIDE_FROM_ABI stacktrace_entry to_stacktrace_entry() const; +}; + +struct _LIBCPP_EXPORTED_FROM_ABI builder final { + alloc __alloc_; // wraps the caller-provided allocator + alloc::vec __entries_; + alloc::str __main_prog_path_; + + template + explicit _LIBCPP_EXPORTED_FROM_ABI builder(_Allocator __alloc) + : __alloc_(__alloc), __entries_(__alloc_.make_vec()), __main_prog_path_(__alloc_.make_str()) {} + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void + build_stacktrace(size_t __skip, size_t __max_depth); +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_BUILDER diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h new file mode 100644 index 0000000000000..a73c8d56f6c21 --- /dev/null +++ b/libcxx/include/__stacktrace/basic.h @@ -0,0 +1,247 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_BASIC_STACKTRACE +#define _LIBCPP_BASIC_STACKTRACE + +#include <__config> +#include <__functional/hash.h> +#include <__fwd/format.h> +#include <__iterator/iterator.h> +#include <__iterator/reverse_iterator.h> +#include <__memory/allocator_traits.h> +#include <__memory_resource/polymorphic_allocator.h> +#include <__type_traits/is_nothrow_constructible.h> +#include <__vector/vector.h> +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/entry.h> +#include <__stacktrace/to_string.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// (19.6.4) +// Class template basic_stacktrace [stacktrace.basic] + +class stacktrace_entry; + +template +class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { + friend struct hash>; + friend struct __stacktrace::__to_string; + + using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; + constexpr static bool __kPropOnCopy = _ATraits::propagate_on_container_copy_assignment::value; + constexpr static bool __kPropOnMove = _ATraits::propagate_on_container_move_assignment::value; + constexpr static bool __kPropOnSwap = _ATraits::propagate_on_container_swap::value; + constexpr static bool __kAlwaysEqual = _ATraits::is_always_equal::value; + constexpr static bool __kNoThrowDflConstruct = is_nothrow_default_constructible_v<_Allocator>; + constexpr static bool __kNoThrowAlloc = + noexcept(noexcept(_Allocator().allocate(1)) && noexcept(_Allocator().allocate_at_least(1))); + + [[no_unique_address]] _Allocator __alloc_; + + using __entry_vec = vector; + __entry_vec __entries_; + +public: + // (19.6.4.1) + // Overview [stacktrace.basic.overview] + + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using difference_type = ptrdiff_t; + using size_type = size_t; + using allocator_type = _Allocator; + using const_iterator = __entry_vec::const_iterator; + using iterator = const_iterator; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // (19.6.4.2) + // Creation and assignment [stacktrace.basic.cons] + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace + current(const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + return current(1, /* no __max_depth */ ~0, __caller_alloc); + } + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace + current(size_type __skip, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + return current(__skip + 1, /* no __max_depth */ ~0, __caller_alloc); + } + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace + current(size_type __skip, + size_type __max_depth, + const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + __stacktrace::builder __builder(__caller_alloc); + __builder.build_stacktrace(__skip + 1, __max_depth); + basic_stacktrace<_Allocator> __ret{__caller_alloc}; + __ret.__entries_.reserve(__builder.__entries_.size()); + for (auto& __base : __builder.__entries_) { + __ret.__entries_.emplace_back(__base.to_stacktrace_entry()); + } + return __ret; + } + + _LIBCPP_EXPORTED_FROM_ABI constexpr ~basic_stacktrace() = default; + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace() noexcept(__kNoThrowDflConstruct) : basic_stacktrace(allocator_type()) {} + + _LIBCPP_EXPORTED_FROM_ABI explicit basic_stacktrace(const allocator_type& __alloc) noexcept + : __alloc_(__alloc), __entries_(__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) = default; + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept = default; + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other, allocator_type const& __alloc) + : __alloc_(__alloc), __entries_(__other.__entries_, __alloc) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other, allocator_type const& __alloc) + : __alloc_(__alloc) { + if (__kAlwaysEqual || __alloc_ == __other.__alloc_) { + __entries_ = std::move(__other.__entries_); + } else { + // "moving" from a container with a different allocator; we're forced to copy items instead + for (auto const& __entry : __other.__entries_) { + __entries_.push_back(__entry); + } + } + } + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& operator=(const basic_stacktrace& __other) { + if (this == std::addressof(__other)) { + return *this; + } + if (__kPropOnCopy) { + __alloc_ = __other.__alloc_; + } + __entries_ = {__other.__entries_, __alloc_}; + return *this; + } + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& + operator=(basic_stacktrace&& __other) noexcept(__kPropOnMove || __kAlwaysEqual) { + if (this == std::addressof(__other)) { + return *this; + } + if (__kPropOnMove) { + __alloc_ = __other.__alloc_; + __entries_ = std::move(__other.__entries_); + } else { + auto __allocs_eq = __kAlwaysEqual || __alloc_ == __other.__alloc_; + if (__allocs_eq) { + __entries_ = std::move(__other.__entries_); + } else { + // "moving" from a container with a different allocator; + // we're forced to copy items instead + for (auto const& __entry : __other.__entries_) { + __entries_.push_back(__entry); + } + } + } + return *this; + } + + // clang-format on + + // (19.6.4.3) + // [stacktrace.basic.obs], observers + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI allocator_type get_allocator() const noexcept { return __alloc_; } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator begin() const noexcept { return __entries_.begin(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator end() const noexcept { return __entries_.end(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator rbegin() const noexcept { return __entries_.rbegin(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator rend() const noexcept { return __entries_.rend(); } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator cbegin() const noexcept { return __entries_.cbegin(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator cend() const noexcept { return __entries_.cend(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator crbegin() const noexcept { + return __entries_.crbegin(); + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator crend() const noexcept { return __entries_.crend(); } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI bool empty() const noexcept { return __entries_.empty(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI size_type size() const noexcept { return __entries_.size(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI size_type max_size() const noexcept { return __entries_.max_size(); } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reference operator[](size_type __i) const { return __entries_[__i]; } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reference at(size_type __i) const { return __entries_.at(__i); } + + // (19.6.4.4) + // [stacktrace.basic.cmp], comparisons + + template + _LIBCPP_EXPORTED_FROM_ABI friend bool + operator==(const basic_stacktrace& __x, const basic_stacktrace<_Allocator2>& __y) noexcept { + if (__x.size() != __y.size()) { + return false; + } + auto __xi = __x.begin(); + auto __yi = __y.begin(); + auto __xe = __x.end(); + while (__xi != __xe) { + if (*__xi++ != *__yi++) { + return false; + } + } + return true; + } + + template + _LIBCPP_EXPORTED_FROM_ABI friend strong_ordering + operator<=>(const basic_stacktrace& __x, const basic_stacktrace<_Allocator2>& __y) noexcept { + auto __ret = __x.size() <=> __y.size(); + if (__ret != std::strong_ordering::equal) { + return __ret; + } + auto __xi = __x.begin(); + auto __yi = __y.begin(); + auto __xe = __x.end(); + while ((__ret == std::strong_ordering::equal) && __xi != __xe) { + __ret = *__xi++ <=> *__yi++; + } + return __ret; + } + + // (19.6.4.5) + // [stacktrace.basic.mod], modifiers + + _LIBCPP_EXPORTED_FROM_ABI void swap(basic_stacktrace<_Allocator>& __other) noexcept { + std::swap(__entries_, __other.__entries_); + if (__kPropOnSwap) { + std::swap(__alloc_, __other.__alloc_); + } + } +}; + +using stacktrace = basic_stacktrace>; + +namespace pmr { +using stacktrace = basic_stacktrace>; +} // namespace pmr + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_BASIC_STACKTRACE diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/entry.h new file mode 100644 index 0000000000000..6290ebea9bc85 --- /dev/null +++ b/libcxx/include/__stacktrace/entry.h @@ -0,0 +1,113 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_ENTRY +#define _LIBCPP_STACKTRACE_ENTRY + +#include <__config> +#include <__fwd/format.h> +#include <__fwd/ostream.h> +#include +#include +#include +#include + +#include <__stacktrace/base.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_base { + friend struct __stacktrace::entry_base; + _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry(entry_base const& __base) : entry_base(__base) {} + +public: + // (19.6.3.1) Overview [stacktrace.entry.overview] + using native_handle_type = uintptr_t; + + // (19.6.3.2) [stacktrace.entry.cons], constructors + _LIBCPP_EXPORTED_FROM_ABI ~stacktrace_entry() noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI constexpr stacktrace_entry() noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI constexpr stacktrace_entry(const stacktrace_entry&) noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI constexpr stacktrace_entry& operator=(const stacktrace_entry&) noexcept = default; + + // (19.6.3.3) [stacktrace.entry.obs], observers + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI constexpr native_handle_type native_handle() const noexcept { + return __addr_actual_; + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI constexpr explicit operator bool() const noexcept { + return native_handle() != 0; + } + + // (19.6.3.4) [stacktrace.entry.query], query + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string description() const { + if (__desc_->empty()) { + return ""; + } + return {__desc_->data(), __desc_->size()}; + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { + if (__desc_->empty()) { + return ""; + } + return {__file_->data(), __file_->size()}; + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI uint_least32_t source_line() const { return __line_; } + + // (19.6.3.5) [stacktrace.entry.cmp], comparison + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI friend constexpr bool + operator==(const stacktrace_entry& __x, const stacktrace_entry& __y) noexcept { + return __x.native_handle() == __y.native_handle(); + } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI friend constexpr strong_ordering + operator<=>(const stacktrace_entry& __x, const stacktrace_entry& __y) noexcept { + return __x.native_handle() <=> __y.native_handle(); + } +}; + +// (19.6.4.6) +// Non-member functions [stacktrace.basic.nonmem] + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string to_string(const stacktrace_entry& __entry); +_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const stacktrace_entry& __entry); + +// (19.6.5) +// Formatting support [stacktrace.format] + +// TODO: stacktrace formatter: https://github.com/llvm/llvm-project/issues/105257 +template <> +struct _LIBCPP_EXPORTED_FROM_ABI formatter; + +// (19.6.6) +// Hash support [stacktrace.basic.hash] + +template <> +struct _LIBCPP_EXPORTED_FROM_ABI hash { + [[nodiscard]] size_t operator()(stacktrace_entry const& __entry) const noexcept { + auto __addr = __entry.native_handle(); + return hash()(__addr); + } +}; + +namespace __stacktrace { +_LIBCPP_HIDE_FROM_ABI inline stacktrace_entry entry_base::to_stacktrace_entry() const { return {*this}; } +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_ENTRY diff --git a/libcxx/include/__stacktrace/hash.h b/libcxx/include/__stacktrace/hash.h new file mode 100644 index 0000000000000..297507f5415fa --- /dev/null +++ b/libcxx/include/__stacktrace/hash.h @@ -0,0 +1,47 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_BASIC_STACKTRACE_HASH +#define _LIBCPP_BASIC_STACKTRACE_HASH + +#include <__config> +#include <__functional/hash.h> +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/to_string.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// (19.6.6) +// Hash support [stacktrace.basic.hash] + +template +struct _LIBCPP_EXPORTED_FROM_ABI hash> { + [[nodiscard]] size_t operator()(basic_stacktrace<_Allocator> const& __context) const noexcept { + size_t __ret = 1; + for (auto const& __entry : __context.__entries_) { + __ret += hash()(__entry.native_handle()); + } + return __ret; + } +}; + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_BASIC_STACKTRACE_HASH diff --git a/libcxx/include/__stacktrace/nonmem.h b/libcxx/include/__stacktrace/nonmem.h new file mode 100644 index 0000000000000..570237e08ecd5 --- /dev/null +++ b/libcxx/include/__stacktrace/nonmem.h @@ -0,0 +1,55 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_BASIC_STACKTRACE_NONMEM +#define _LIBCPP_BASIC_STACKTRACE_NONMEM + +#include <__config> +#include <__memory/allocator_traits.h> +#include <__utility/swap.h> +#include <__vector/vector.h> +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/to_string.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// (19.6.4.6) +// Non-member functions [stacktrace.basic.nonmem] + +template +_LIBCPP_EXPORTED_FROM_ABI inline void +swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b) noexcept(noexcept(__a.swap(__b))) { + __a.swap(__b); +} + +template +_LIBCPP_EXPORTED_FROM_ABI inline string to_string(const basic_stacktrace<_Allocator>& __stacktrace) { + return __stacktrace::__to_string()(__stacktrace); +} + +template +_LIBCPP_EXPORTED_FROM_ABI inline ostream& operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __stacktrace) { + auto __str = __stacktrace::__to_string()(__stacktrace); + return __os << __str; +} + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_BASIC_STACKTRACE_NONMEM diff --git a/libcxx/include/__stacktrace/to_string.h b/libcxx/include/__stacktrace/to_string.h new file mode 100644 index 0000000000000..920c41133d5df --- /dev/null +++ b/libcxx/include/__stacktrace/to_string.h @@ -0,0 +1,53 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_TO_STRING +#define _LIBCPP_STACKTRACE_TO_STRING + +#include <__config> +#include <__fwd/ostream.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +template +class basic_stacktrace; + +class stacktrace_entry; + +namespace __stacktrace { + +struct __to_string { + _LIBCPP_EXPORTED_FROM_ABI string operator()(stacktrace_entry const& __entry); + + _LIBCPP_EXPORTED_FROM_ABI void operator()(ostream& __os, stacktrace_entry const& __entry); + + _LIBCPP_EXPORTED_FROM_ABI void operator()(ostream& __os, std::stacktrace_entry const* __entries, size_t __count); + + _LIBCPP_EXPORTED_FROM_ABI string operator()(std::stacktrace_entry const* __entries, size_t __count); + + template + _LIBCPP_EXPORTED_FROM_ABI string operator()(basic_stacktrace<_Allocator> const& __st) { + return (*this)(__st.__entries_.data(), __st.__entries_.size()); + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_TO_STRING diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index c431c0cb407f3..c708d69e5dabc 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1336,7 +1336,10 @@ module std [system] { module concepts { header "__format/concepts.h" } module container_adaptor { header "__format/container_adaptor.h" } module enable_insertable { header "__format/enable_insertable.h" } - module escaped_output_table { header "__format/escaped_output_table.h" } + module escaped_output_table { + header "__format/escaped_output_table.h" + export std.format.escaped_output_table // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 + } module extended_grapheme_cluster_table { header "__format/extended_grapheme_cluster_table.h" } module fmt_pair_like { header "__format/fmt_pair_like.h" } module format_arg { header "__format/format_arg.h" } @@ -1373,7 +1376,10 @@ module std [system] { module range_format { header "__format/range_format.h" } module range_formatter { header "__format/range_formatter.h" } module unicode { header "__format/unicode.h" } - module width_estimation_table { header "__format/width_estimation_table.h" } + module width_estimation_table { + header "__format/width_estimation_table.h" + export std.format.width_estimation_table // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 + } module write_escaped { header "__format/write_escaped.h" } header "format" @@ -2010,6 +2016,33 @@ module std [system] { export * } + module stacktrace { + module base { header "__stacktrace/base.h" } + module basic { header "__stacktrace/basic.h" } + module entry { header "__stacktrace/entry.h" } + module hash { header "__stacktrace/hash.h" } + module nonmem { header "__stacktrace/nonmem.h" } + module to_string { header "__stacktrace/to_string.h" } + + header "stacktrace" + export * + // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 + export std.cstddef.byte + export std.cstddef.size_t + export std.format.formatter + export std.functional.function + export std.functional.hash + export std.iterator.iterator + export std.iterator.reverse_iterator + export std.memory.allocator + export std.memory.allocator_traits + export std.memory_resource.polymorphic_allocator + export std.new.allocate + export std.ostream.basic_ostream + export std.type_traits.is_nothrow_constructible + export std.vector.vector + } + module stdexcept { header "stdexcept" export * diff --git a/libcxx/include/stacktrace b/libcxx/include/stacktrace new file mode 100644 index 0000000000000..1fbdb6b4fe293 --- /dev/null +++ b/libcxx/include/stacktrace @@ -0,0 +1,191 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE +#define _LIBCPP_STACKTRACE + +/* + Header synopsis + (19.6.2) + +#include // see [compare.syn] + +namespace std { + // [stacktrace.entry], class stacktrace_entry + class stacktrace_entry; + + // [stacktrace.basic], class template basic_stacktrace + template + class basic_stacktrace; + + // basic_stacktrace typedef-names + using stacktrace = basic_stacktrace>; + + // [stacktrace.basic.nonmem], non-member functions + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); + + ostream& operator<<(ostream& os, const stacktrace_entry& f); + template + ostream& operator<<(ostream& os, const basic_stacktrace& st); + + // [stacktrace.format], formatting support + template<> struct formatter; + template struct formatter>; + + namespace pmr { + using stacktrace = basic_stacktrace>; + } + + // [stacktrace.basic.hash], hash support + template struct hash; + template<> struct hash; + template struct hash>; +} + +// [stacktrace.entry] + +namespace std { + class stacktrace_entry { + public: + using native_handle_type = implementation-defined; + + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; + constexpr stacktrace_entry(const stacktrace_entry& other) noexcept; + constexpr stacktrace_entry& operator=(const stacktrace_entry& other) noexcept; + + ~stacktrace_entry(); + + // [stacktrace.entry.obs], observers + constexpr native_handle_type native_handle() const noexcept; + constexpr explicit operator bool() const noexcept; + + // [stacktrace.entry.query], query + string description() const; + string source_file() const; + uint_least32_t source_line() const; + + // [stacktrace.entry.cmp], comparison + friend constexpr bool operator==(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + friend constexpr strong_ordering operator<=>(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + }; +} + +// [stacktrace.basic] + +namespace std { + template + class basic_stacktrace { + public: + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using const_iterator = implementation-defined; // see [stacktrace.basic.obs] + using iterator = const_iterator; + using reverse_iterator = reverse_iterator; + using const_reverse_iterator = reverse_iterator; + using difference_type = implementation-defined; + using size_type = implementation-defined; + using allocator_type = Allocator; + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); + explicit basic_stacktrace(const allocator_type& alloc) noexcept; + + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); + + ~basic_stacktrace(); + + // [stacktrace.basic.obs], observers + allocator_type get_allocator() const noexcept; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + size_type max_size() const noexcept; + + const_reference operator[](size_type) const; + const_reference at(size_type) const; + + // [stacktrace.basic.cmp], comparisons + template + friend bool operator==(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + template + friend strong_ordering operator<=>(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + + // [stacktrace.basic.mod], modifiers + void swap(basic_stacktrace& other) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); + + private: + vector frames_; // exposition only + }; +} + +*/ + +#include <__config> + +#if _LIBCPP_STD_VER >= 23 + +# include // according to [stacktrace.syn] + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + +# include <__stacktrace/base.h> +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> +# include <__stacktrace/hash.h> +# include <__stacktrace/nonmem.h> +# include <__stacktrace/to_string.h> + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STD_VER >= 23 + +#endif // _LIBCPP_STACKTRACE diff --git a/libcxx/include/version b/libcxx/include/version index aae9277a7dfc6..9016743955cc5 100644 --- a/libcxx/include/version +++ b/libcxx/include/version @@ -531,7 +531,7 @@ __cpp_lib_void_t 201411L // # define __cpp_lib_ranges_zip 202110L // # define __cpp_lib_reference_from_temporary 202202L // # define __cpp_lib_spanstream 202106L -// # define __cpp_lib_stacktrace 202011L +# define __cpp_lib_stacktrace 202011L # define __cpp_lib_stdatomic_h 202011L # define __cpp_lib_string_contains 202011L # define __cpp_lib_string_resize_and_overwrite 202110L diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist index 162757c7e37ec..114f3beb42a71 100644 --- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -534,6 +534,11 @@ {'is_defined': True, 'name': '__ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry11descriptionEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry11source_fileEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry11source_lineEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry13native_handleEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE13do_pos_formatEv', 'type': 'FUNC'} @@ -974,6 +979,11 @@ {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultclEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace7builder16build_stacktraceEmm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -1125,6 +1135,7 @@ {'is_defined': True, 'name': '__ZNSt3__112system_errorD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112system_errorD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112system_errorD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE11__read_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE12__write_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj', 'type': 'FUNC'} @@ -1305,7 +1316,6 @@ {'is_defined': True, 'name': '__ZNSt3__113shared_futureIvED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113shared_futureIvED2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113shared_futureIvEaSERKS1_', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__114__num_get_base10__get_baseERNS_8ios_baseE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__114__num_get_base5__srcE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZNSt3__114__num_put_base12__format_intEPcPKcbj', 'type': 'FUNC'} @@ -1508,7 +1518,6 @@ {'is_defined': True, 'name': '__ZNSt3__117bad_function_callD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117bad_function_callD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117bad_function_callD2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117iostream_categoryEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117moneypunct_bynameIcLb0EE4initEPKc', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117moneypunct_bynameIcLb1EE4initEPKc', 'type': 'FUNC'} @@ -1935,6 +1944,7 @@ {'is_defined': True, 'name': '__ZNSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19strstreamD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19strstreamD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__19to_stringERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEd', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEe', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEf', 'type': 'FUNC'} @@ -1944,6 +1954,7 @@ {'is_defined': True, 'name': '__ZNSt3__19to_stringEm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEx', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEy', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__1lsERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt8bad_castC1Ev', 'type': 'I'} {'is_defined': True, 'name': '__ZNSt8bad_castC2Ev', 'type': 'I'} @@ -2011,6 +2022,9 @@ {'is_defined': True, 'name': '__ZTINSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2225,6 +2239,9 @@ {'is_defined': True, 'name': '__ZTSNSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2447,6 +2464,9 @@ {'is_defined': True, 'name': '__ZTVNSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist index 8c55c4385f6f6..d954b1926115e 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -35,6 +35,7 @@ {'is_defined': False, 'name': '_ZTVSt13runtime_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt14overflow_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt16invalid_argument', 'size': 0, 'type': 'OBJECT'} +{'is_defined': False, 'name': '_ZTVSt9exception', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZdaPv', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdaPvSt11align_val_t', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdlPv', 'type': 'FUNC'} @@ -225,6 +226,11 @@ {'is_defined': True, 'name': '_ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11descriptionEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_fileEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_lineEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry13native_handleEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_pos_formatEv', 'type': 'FUNC'} @@ -622,6 +628,11 @@ {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultclEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace7builder16build_stacktraceEmm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -773,6 +784,7 @@ {'is_defined': True, 'name': '_ZNSt3__112system_errorD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112system_errorD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112system_errorD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE11__read_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE12__write_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj', 'type': 'FUNC'} @@ -953,7 +965,6 @@ {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvEaSERKS1_', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__114__num_get_base10__get_baseERNS_8ios_baseE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__114__num_get_base5__srcE', 'size': 33, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZNSt3__114__num_put_base12__format_intEPcPKcbj', 'type': 'FUNC'} @@ -1156,7 +1167,6 @@ {'is_defined': True, 'name': '_ZNSt3__117bad_function_callD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117bad_function_callD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117bad_function_callD2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117iostream_categoryEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117moneypunct_bynameIcLb0EE4initEPKc', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117moneypunct_bynameIcLb1EE4initEPKc', 'type': 'FUNC'} @@ -1584,6 +1594,7 @@ {'is_defined': True, 'name': '_ZNSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19strstreamD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19strstreamD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__19to_stringERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEd', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEe', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEf', 'type': 'FUNC'} @@ -1593,6 +1604,7 @@ {'is_defined': True, 'name': '_ZNSt3__19to_stringEm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEx', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEy', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__1lsERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZSt17__throw_bad_allocv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZSt17current_exceptionv', 'type': 'FUNC'} @@ -1602,6 +1614,7 @@ {'is_defined': True, 'name': '_ZSt7nothrow', 'size': 1, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__110istrstreamE0_NS_13basic_istreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__110ostrstreamE0_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTCNSt3__112__stacktrace10fd_istreamE0_NS_13basic_istreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_iostreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_iostreamIcNS_11char_traitsIcEEEE16_NS_13basic_ostreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} @@ -1616,6 +1629,9 @@ {'is_defined': True, 'name': '_ZTCNSt3__19strstreamE16_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental15fundamentals_v112bad_any_castE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental19bad_optional_accessE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_getE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_putE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110ctype_baseE', 'size': 16, 'type': 'OBJECT'} @@ -1631,6 +1647,12 @@ {'is_defined': True, 'name': '_ZTINSt3__111__money_putIcEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__111__money_putIwEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__111regex_errorE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace10fd_istreamE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4atosE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4toolE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace6failedE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace9addr2lineE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112bad_weak_ptrE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112codecvt_baseE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112ctype_bynameIcEE', 'size': 24, 'type': 'OBJECT'} @@ -1749,6 +1771,9 @@ {'is_defined': True, 'name': '_ZTISt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental15fundamentals_v112bad_any_castE', 'size': 50, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 64, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 65, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 58, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_getE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_putE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110ctype_baseE', 'size': 21, 'type': 'OBJECT'} @@ -1764,6 +1789,12 @@ {'is_defined': True, 'name': '_ZTSNSt3__111__money_putIcEE', 'size': 25, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__111__money_putIwEE', 'size': 25, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__111regex_errorE', 'size': 22, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace10fd_istreamE', 'size': 35, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4atosE', 'size': 28, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4toolE', 'size': 28, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace6failedE', 'size': 30, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace9addr2lineE', 'size': 33, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112bad_weak_ptrE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112codecvt_baseE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112ctype_bynameIcEE', 'size': 26, 'type': 'OBJECT'} @@ -1882,6 +1913,7 @@ {'is_defined': True, 'name': '_ZTSSt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__110istrstreamE', 'size': 32, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__110ostrstreamE', 'size': 32, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTTNSt3__112__stacktrace10fd_istreamE', 'size': 32, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIwNS_11char_traitsIwEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'} @@ -1895,6 +1927,9 @@ {'is_defined': True, 'name': '_ZTTNSt3__19strstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental15fundamentals_v112bad_any_castE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 88, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 88, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 88, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110istrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb0EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb1EEE', 'size': 112, 'type': 'OBJECT'} @@ -1902,6 +1937,12 @@ {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIwLb1EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110ostrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__111regex_errorE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace10fd_istreamE', 'size': 80, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4atosE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4toolE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace6failedE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace9addr2lineE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112bad_weak_ptrE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIcEE', 'size': 104, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIwEE', 'size': 136, 'type': 'OBJECT'} @@ -1964,6 +2005,8 @@ {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDiEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDsEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IwEE', 'size': 96, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIcEE', 'size': 72, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIwEE', 'size': 72, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr15memory_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr25monotonic_buffer_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr26synchronized_pool_resourceE', 'size': 56, 'type': 'OBJECT'} diff --git a/libcxx/modules/std.compat.cppm.in b/libcxx/modules/std.compat.cppm.in index dd7385bf33a42..91d4b051adac4 100644 --- a/libcxx/modules/std.compat.cppm.in +++ b/libcxx/modules/std.compat.cppm.in @@ -69,9 +69,6 @@ module; # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() -# if __has_include() -# error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" -# endif // __has_include() # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() diff --git a/libcxx/modules/std.cppm.in b/libcxx/modules/std.cppm.in index 984b18321923c..a70199d6f3b17 100644 --- a/libcxx/modules/std.cppm.in +++ b/libcxx/modules/std.cppm.in @@ -102,6 +102,7 @@ module; #include #include #include +#include #include #include #include @@ -153,9 +154,6 @@ module; # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() -# if __has_include() -# error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" -# endif // __has_include() # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() diff --git a/libcxx/modules/std/stacktrace.inc b/libcxx/modules/std/stacktrace.inc index c1184087c0df4..e7b31fd29b3a6 100644 --- a/libcxx/modules/std/stacktrace.inc +++ b/libcxx/modules/std/stacktrace.inc @@ -8,7 +8,8 @@ //===----------------------------------------------------------------------===// export namespace std { -#if 0 +#if _LIBCPP_STD_VER >= 23 + // [stacktrace.entry], class stacktrace_­entry using std::stacktrace_entry; @@ -31,5 +32,6 @@ export namespace std { // [stacktrace.basic.hash], hash support using std::hash; -#endif + +#endif // _LIBCPP_STD_VER >= 23 } // namespace std diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index f59fe0e08fccb..f93b58ebc6ba9 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -40,6 +40,14 @@ set(LIBCXX_SOURCES ryu/d2fixed.cpp ryu/d2s.cpp ryu/f2s.cpp + stacktrace/builder.cpp + stacktrace/linux/impl.cpp + stacktrace/macos/impl.cpp + stacktrace/to_string.cpp + stacktrace/tools/tools.cpp + stacktrace/unwind/impl.cpp + stacktrace/windows/dll.cpp + stacktrace/windows/impl.cpp stdexcept.cpp string.cpp support/runtime/exception_fallback.ipp @@ -335,6 +343,7 @@ endif() add_library(cxx_experimental STATIC ${LIBCXX_EXPERIMENTAL_SOURCES}) target_link_libraries(cxx_experimental PUBLIC cxx-headers) + if (LIBCXX_ENABLE_SHARED) target_link_libraries(cxx_experimental PRIVATE cxx_shared) else() diff --git a/libcxx/src/stacktrace/README.md b/libcxx/src/stacktrace/README.md new file mode 100644 index 0000000000000..9f25eb4ad6e22 --- /dev/null +++ b/libcxx/src/stacktrace/README.md @@ -0,0 +1,6 @@ +# C++23 `` + + +# References + +1. https://eel.is/c++draft/stacktrace diff --git a/libcxx/src/stacktrace/builder.cpp b/libcxx/src/stacktrace/builder.cpp new file mode 100644 index 0000000000000..000135b4ccc38 --- /dev/null +++ b/libcxx/src/stacktrace/builder.cpp @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> +#include <__config_site> + +#include <__stacktrace/base.h> + +#include "stacktrace/linux/impl.h" +#include "stacktrace/macos/impl.h" +#include "stacktrace/tools/tools.h" +#include "stacktrace/unwind/impl.h" +#include "stacktrace/windows/impl.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace __stacktrace { + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void +builder::build_stacktrace(size_t skip, size_t max_depth) { +#if defined(_LIBCPP_WIN32API) + // Windows implementation + win_impl dbghelp{*this}; + dbghelp.collect(skip + 1, max_depth); + if (!__entries_.size()) { + return; + } + dbghelp.ident_modules(); + dbghelp.resolve_lines(); + dbghelp.symbolize(); + +#else + // Non-Windows; assume Unix-like. + + // For spawning `addr2line` or similar external process + spawner pspawn{*this}; + + // `Unwind.h` or `libunwind.h` often available on Linux/OSX etc. + unwind unwind{*this}; + unwind.collect(skip + 1, max_depth); + if (!__entries_.size()) { + return; + } + +# if defined(__APPLE__) + // Specific to MacOS and other Apple SDKs + macos macos{*this}; + macos.ident_modules(); + pspawn.resolve_lines(); + macos.symbolize(); + +# else + // Linux and other other platforms + linux linux{*this}; + linux.ident_modules(); + pspawn.resolve_lines(); + linux.symbolize(); + +# endif +#endif +} + +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/config.h b/libcxx/src/stacktrace/config.h new file mode 100644 index 0000000000000..c4c742d868b69 --- /dev/null +++ b/libcxx/src/stacktrace/config.h @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_CONFIG_H +#define _LIBCPP_STACKTRACE_CONFIG_H + +#include <__config> +#include <__config_site> + +// Check for unwind.h -- could exist on any OS (in theory), but it (or `libunwind`) is likely on Linux systems, and also +// comes with XCode tools on MacOS. +#if __has_include() +# define _LIBCPP_STACKTRACE_UNWIND_IMPL +#endif + +// Whether we can invoke external processes via `posix_spawn` +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME +# define _LIBCPP_STACKTRACE_CAN_SPAWN_TOOLS +#endif + +#endif // _LIBCPP_STACKTRACE_CONFIG_H diff --git a/libcxx/src/stacktrace/linux/impl.cpp b/libcxx/src/stacktrace/linux/impl.cpp new file mode 100644 index 0000000000000..050eb1f0088e7 --- /dev/null +++ b/libcxx/src/stacktrace/linux/impl.cpp @@ -0,0 +1,112 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if defined(__linux__) + +# include +# include +# include +# include +# include + +# include "stacktrace/config.h" +# include "stacktrace/linux/impl.h" +# include "stacktrace/utils/fd.h" +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +void linux::ident_modules() { + auto& images = images::get(); + + // Aside from the left/right sentinels in the array (hence the 2), + // are there any other real images? + if (images.count_ <= 2) { + return; + } + + auto mainProg = images.mainProg(); + if (mainProg) { + builder_.__main_prog_path_ = mainProg->name_; + } + + unsigned index = 1; // Starts at one, and is moved around in this loop + for (auto& entry : builder_.__entries_) { + while (images[index].loaded_at_ > entry.__addr_actual_) { + --index; + } + while (images[index + 1].loaded_at_ <= entry.__addr_actual_) { + ++index; + } + entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; + entry.__file_ = builder_.__alloc_.make_str(images[index].name_); + } +} + +/** +When trying to collect a stacktrace under Linux, there are narrow (but still quite common) cases where we will fail +to resolve symbols. Linux's `dl` doesn't want to read symbols from the non-exported symbol table at runtime, +and older versions of `addr2line` or `llvm-symbolizer` will also not resolve these. + +This implementation the minimum necessary to resolve symbols. It can identify this as an ELF (32 or 64 bits), locate +the symbol and symbol-string table, and fill in any remaining missing symbols. +*/ +void linux::resolve_main_elf_syms(std::string_view main_elf_name) { + // We can statically initialize these, because main_elf_name should be the same every time. + static fd_mmap _mm(main_elf_name); + if (_mm) { + static elf::ELF _this_elf(_mm.addr_); + if (_this_elf) { + for (auto& entry : builder_.__entries_) { + if (entry.__desc_->empty() && entry.__file_ == main_elf_name) { + auto name = _this_elf.getSym(entry.__addr_unslid_).name(); + entry.__desc_ = builder_.__alloc_.make_str(name); + } + } + } + } +} + +bool symbolize_entry(alloc& alloc, entry_base& entry) { + bool ret = false; + Dl_info info; + if (dladdr((void*)entry.__addr_actual_, &info)) { + ret = true; // at least partially successful + if (info.dli_fname && entry.__file_->empty()) { + // provide at least the binary filename in case we cannot lookup source location + entry.__file_ = alloc.make_str(info.dli_fname); + } + if (info.dli_sname && entry.__desc_->empty()) { + // provide at least the mangled name; try to unmangle in a later step + entry.__desc_ = alloc.make_str(info.dli_sname); + } + } + return ret; +} + +// NOTE: We can use `dlfcn` to resolve addresses to symbols, which works great -- +// except for symbols in the main program. If addr2line-style tools are enabled, that step +// might also be able to get symbols directly from the binary's debug info. +void linux::symbolize() { + for (auto& entry : builder_.__entries_) { + symbolize_entry(builder_.__alloc_, entry); + } + // Symbols might be missing, because both (1) Linux's `dladdr` won't try to resolve non-exported symbols, + // which can be the case for the main program executable; and (2) debug info was not preserved. + // As a last resort, this function (see `linux-elf.cpp`) can still access symbol table directly. + image* mainELF = images::get().mainProg(); + if (mainELF && !mainELF->name_.empty()) { + resolve_main_elf_syms(mainELF->name_); + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __linux__ diff --git a/libcxx/src/stacktrace/linux/impl.h b/libcxx/src/stacktrace/linux/impl.h new file mode 100644 index 0000000000000..cd028d070835f --- /dev/null +++ b/libcxx/src/stacktrace/linux/impl.h @@ -0,0 +1,401 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_LINUX_IMPL +#define _LIBCPP_STACKTRACE_LINUX_IMPL + +#include <__stacktrace/base.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct linux { + builder& builder_; + +#if defined(__linux__) + // defined in linux.cpp + void ident_modules(); + void symbolize(); + +private: + void resolve_main_elf_syms(std::string_view elf_name); +#else + // inline-able dummy definitions + void ident_modules() {} + void symbolize() {} +#endif +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#include "stacktrace/config.h" + +#if defined(__linux__) + +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct images { + // How many images this contains, including the left/right sentinels. + unsigned count_{0}; + std::array images_{}; + + int add(dl_phdr_info& info) { + assert(count_ < image::kMaxImages); + auto isFirst = (count_ == 0); + auto& image = images_.at(count_++); + image.loaded_at_ = info.dlpi_addr; + image.slide_ = info.dlpi_addr; + image.name_ = info.dlpi_name; + image.is_main_prog_ = isFirst; // first one is the main ELF + if (image.name_.empty() && isFirst) { + static char buffer[PATH_MAX + 1]; + uint32_t length = sizeof(buffer); + if (readlink("/proc/self/exe", buffer, length) > 0) { + image.name_ = buffer; + } + } + return count_ == image::kMaxImages; // return nonzero if we're at the limit + } + + static int callback(dl_phdr_info* info, size_t, void* self) { return (*(images*)(self)).add(*info); } + + images() { + dl_iterate_phdr(images::callback, this); + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + std::sort(images_.begin(), images_.begin() + count_ - 1); + } + + image& operator[](size_t index) { + assert(index < count_); + return images_.at(index); + } + + image* mainProg() { + for (auto& image : images_) { + if (image.is_main_prog_) { + return ℑ + } + } + return nullptr; + } + + static images& get() { + static images images; + return images; + } +}; + +// Includes ELF constants and structs copied from , with a few changes. + +namespace elf { + +struct Header32 final { + uint8_t ident[16]; /* Magic number and other info */ + uint16_t type; /* Object file type */ + uint16_t machine; /* Architecture */ + uint32_t version; /* Object file version */ + uint32_t entry; /* Entry point virtual address */ + uint32_t phoff; /* Program header table file offset */ + uint32_t shoff; /* Section header table file offset */ + uint32_t flags; /* Processor-specific flags */ + uint16_t ehsize; /* ELF header size in bytes */ + uint16_t phentsize; /* Program header table entry size */ + uint16_t phnum; /* Program header table entry count */ + uint16_t shentsize; /* Section header table entry size */ + uint16_t shnum; /* Section header table entry count */ + uint16_t shstrndx; /* Section header string table index */ +}; + +struct Section32 final { + uint32_t name; /* Section name (string tbl index) */ + uint32_t type; /* Section type */ + uint32_t flags; /* Section flags */ + uint32_t addr; /* Section virtual addr at execution */ + uint32_t offset; /* Section file offset */ + uint32_t size; /* Section size in bytes */ + uint32_t link; /* Link to another section */ + uint32_t info; /* Additional section information */ + uint32_t addralign; /* Section alignment */ + uint32_t entsize; /* Entry size if section holds table */ +}; + +struct Symbol32 final { + uint32_t name; /* Symbol name (string tbl index) */ + uint32_t value; /* Symbol value */ + uint32_t size; /* Symbol size */ + uint8_t info; /* Symbol type and binding */ + uint8_t other; /* Symbol visibility */ + uint16_t shndx; /* Section index */ +}; + +struct Header64 final { + uint8_t ident[16]; /* Magic number and other info */ + uint16_t type; /* Object file type */ + uint16_t machine; /* Architecture */ + uint32_t version; /* Object file version */ + uint64_t entry; /* Entry point virtual address */ + uint64_t phoff; /* Program header table file offset */ + uint64_t shoff; /* Section header table file offset */ + uint32_t flags; /* Processor-specific flags */ + uint16_t ehsize; /* ELF header size in bytes */ + uint16_t phentsize; /* Program header table entry size */ + uint16_t phnum; /* Program header table entry count */ + uint16_t shentsize; /* Section header table entry size */ + uint16_t shnum; /* Section header table entry count */ + uint16_t shstrndx; /* Section header string table index */ +}; + +struct Section64 final { + uint32_t name; /* Section name (string tbl index) */ + uint32_t type; /* Section type */ + uint64_t flags; /* Section flags */ + uint64_t addr; /* Section virtual addr at execution */ + uint64_t offset; /* Section file offset */ + uint64_t size; /* Section size in bytes */ + uint32_t link; /* Link to another section */ + uint32_t info; /* Additional section information */ + uint64_t addralign; /* Section alignment */ + uint64_t entsize; /* Entry size if section holds table */ +}; + +struct Symbol64 final { + uint32_t name; /* Symbol name (string tbl index) */ + uint8_t info; /* Symbol type and binding */ + uint8_t other; /* Symbol visibility */ + uint16_t shndx; /* Section index */ + uint64_t value; /* Symbol value */ + uint64_t size; /* Symbol size */ +}; + +/** Represents an ELF header. Supports the minimum needed to navigate an ELF file's sections and get at the symbol and + * string tables. */ +struct Header final { + std::byte const* ptr_{}; + uintptr_t shoff_{}; + size_t shnum_{}; + size_t shstrndx_{}; + + Header() = default; + Header(Header const&) = default; + Header& operator=(Header const& rhs) { return *new (this) Header(rhs); } + + operator bool() { return ptr_; } + + template + explicit Header(H* h) + : ptr_((std::byte const*)h), + shoff_(uintptr_t(h->shoff)), + shnum_(size_t(h->shnum)), + shstrndx_(size_t(h->shstrndx)) {} +}; + +struct ELF; +struct StringTable; + +struct Section final { + constexpr static uint32_t kSymTab = 2; // symbol table + constexpr static uint32_t kStrTab = 3; // name table for symbols or sections + + ELF* elf_{}; + std::byte const* ptr_{}; + uintptr_t nameIndex_{}; + uint32_t type_{}; + uintptr_t offset_{}; + size_t size_{}; + + Section() = default; + + template + Section(ELF* elf, S* sec) + : elf_(elf), + ptr_((std::byte const*)sec), + nameIndex_(sec->name), + type_(sec->type), + offset_(sec->offset), + size_(sec->size) {} + + operator bool() const { return ptr_; } + + template + T const* data() const { + return (T const*)(elfBase() + offset_); + } + + std::byte const* elfBase() const; + std::string_view name() const; +}; + +struct Symbol final { + constexpr static uint8_t kFunc = 0x02; // STT_FUNC (code object) + + ELF* elf_{}; + std::byte const* ptr_{}; + uintptr_t nameIndex_{}; + uint32_t type_{}; + uintptr_t value_{}; + + Symbol() = default; + Symbol(Symbol const&) = default; + Symbol& operator=(Symbol const& rhs) { return *new (this) Symbol(rhs); } + + operator bool() { return ptr_; } + + bool isCode() const { return type_ == kFunc; } + + template + Symbol(ELF* elf, S* sym) + : elf_(elf), ptr_((std::byte const*)sym), nameIndex_(sym->name), type_(0x0f & sym->info), value_(sym->value) {} + + std::byte const* elfBase() const; + std::string_view name() const; +}; + +/** Represents one of the ELF's `strtab`s. This is a block of string data, with strings appended one after another, and + * NUL-terminated. Strings are indexed according to their starting offset. At offset 0 is typically an empty string. + */ +struct StringTable { + std::string_view data_{}; + + StringTable() = default; + + /* implicit */ StringTable(Section const& sec) : data_(sec.data(), sec.size_) {} + + operator bool() { return data_.size(); } + + std::string_view at(size_t index) { + auto* ret = data_.data() + index; + return {ret, strlen(ret)}; + } +}; + +/** Encapsulates an ELF image specified by byte-address (e.g. from an mmapped file or a program image or shared object + * in memory). If given a supported ELF image, this will test true with `operator bool` to indicate it is supported and + * was able to parse some basic information from the header. */ +struct ELF { + Header header_{}; + Section (*makeSection_)(ELF*, std::byte const*){}; + Symbol (*makeSymbol_)(ELF*, std::byte const*){}; + size_t secSize_{}; + size_t symSize_{}; + StringTable nametab_{}; + Section symtab_{}; + StringTable strtab_{}; + size_t symCount_{}; + + static Section makeSection32(ELF* elf, std::byte const* ptr) { return Section(elf, (Section32 const*)ptr); } + static Section makeSection64(ELF* elf, std::byte const* ptr) { return Section(elf, (Section64 const*)ptr); } + static Symbol makeSymbol32(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol32 const*)ptr); } + static Symbol makeSymbol64(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol64 const*)ptr); } + + operator bool() { return header_; } + + explicit ELF(std::byte const* image) { + auto* p = (uint8_t const*)image; + // Bytes 0..3: magic bytes: 0x7F, 'E', 'L', 'F' + if (*p++ == 0x7f && *p++ == 0x45 && *p++ == 0x4c && *p++ == 0x46) { + auto klass = *p++; // Byte 4 (EI_CLASS): ELF class, 32- or 64-bit (0x01 or 0x02) + auto dataFormat = *p++; // Byte 5 (EI_DATA): (0x01) little- or (0x02) big-endian + auto fileVersion = *p++; // Byte 6 (EI_VERSION): ELF version: expect 1 (latest ELF version) + constexpr static uint16_t kEndianTestWord{0x0201}; + auto hostEndianness = *(uint8_t const*)&kEndianTestWord; + if (dataFormat == hostEndianness && fileVersion == 1) { + if (klass == 0x01) { + header_ = Header((Header32 const*)image); + makeSection_ = makeSection32; + makeSymbol_ = makeSymbol32; + secSize_ = sizeof(Section32); + symSize_ = sizeof(Symbol32); + } else if (klass == 0x02) { + header_ = Header((Header64 const*)image); + makeSection_ = makeSection64; + makeSymbol_ = makeSymbol64; + secSize_ = sizeof(Section64); + symSize_ = sizeof(Symbol64); + } + } + } + if (*this) { + nametab_ = section(header_.shstrndx_); + eachSection([&](auto& sec) mutable -> bool { + if (sec.type_ == Section::kSymTab && sec.name() == ".symtab") { + symtab_ = sec; + } else if (sec.type_ == Section::kStrTab && sec.name() == ".strtab") { + strtab_ = sec; + } + return !symtab_ || !strtab_; + }); + } + if (symtab_) { + symCount_ = symtab_.size_ / symSize_; + } + } + + Section section(size_t index) { + auto* addr = header_.ptr_ + header_.shoff_ + (index * secSize_); + return makeSection_(this, addr); + } + + Symbol symbol(size_t index) { + auto* addr = symtab_.data() + (index * symSize_); + return makeSymbol_(this, addr); + } + + template + using CB = std::function; + + void eachSection(CB
cb) { + for (size_t i = 0; i < header_.shnum_ && cb(section(i)); i++) + ; + } + + void eachSymbol(CB cb) { + for (size_t i = 0; i < symCount_ && cb(symbol(i)); i++) + ; + } + + Symbol getSym(uintptr_t addr) { + Symbol ret{}; + eachSymbol([&](auto& sym) -> bool { + if (sym.value_ <= addr && sym.value_ > ret.value_) { + ret = sym; + } + return true; + }); + return ret; + } +}; + +inline std::byte const* Section::elfBase() const { return elf_->header_.ptr_; } +inline std::byte const* Symbol::elfBase() const { return elf_->header_.ptr_; } + +inline std::string_view Section::name() const { return elf_->nametab_.at(nameIndex_); } +inline std::string_view Symbol::name() const { return elf_->strtab_.at(nameIndex_); } + +} // namespace elf + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __linux__ + +#endif // _LIBCPP_STACKTRACE_LINUX_IMPL diff --git a/libcxx/src/stacktrace/macos/impl.cpp b/libcxx/src/stacktrace/macos/impl.cpp new file mode 100644 index 0000000000000..575a2927a78ca --- /dev/null +++ b/libcxx/src/stacktrace/macos/impl.cpp @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if defined(__APPLE__) + +# include +# include +# include +# include +# include + +# include <__stacktrace/base.h> + +# include "stacktrace/macos/impl.h" +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +void ident_module(alloc& alloc, entry_base& entry, unsigned& index, image* images) { + if (entry.__addr_actual_) { + while (images[index].loaded_at_ > entry.__addr_actual_) { + --index; + } + while (images[index + 1].loaded_at_ <= entry.__addr_actual_) { + ++index; + } + auto& image = images[index]; + if (image) { + entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; + entry.__file_ = alloc.make_str(images[index].name_); + } + } +} + +bool enum_modules(unsigned& count, auto& images) { + count = std::min(image::kMaxImages, size_t(_dyld_image_count())); + for (size_t i = 0; i < count; i++) { + auto& image = images[i]; + image.slide_ = _dyld_get_image_vmaddr_slide(i); + image.loaded_at_ = uintptr_t(_dyld_get_image_header(i)); + image.name_ = _dyld_get_image_name(i); + } + images[count++] = {0uz, 0}; // sentinel at low end + images[count++] = {~0uz, 0}; // sentinel at high end + std::sort(images.begin(), images.begin() + count - 1); + return true; +} + +void macos::ident_modules() { + static unsigned imageCount; + static std::array images; + static bool atomicInitialized = enum_modules(imageCount, images); + (void)atomicInitialized; + + // Aside from the left/right sentinels in the array (hence the 2), + // are there any other real images? + if (imageCount <= 2) { + return; + } + + // First image (the main program) is at index 1 + builder_.__main_prog_path_ = builder_.__alloc_.make_str(images.at(1).name_); + + unsigned index = 1; // Starts at one, and is moved by 'ident_module' + for (auto& entry : builder_.__entries_) { + ident_module(builder_.__alloc_, (entry_base&)entry, index, images.data()); + } +} + +void symbolize_entry(alloc& alloc, entry_base& entry) { + Dl_info info; + if (dladdr((void*)entry.__addr_actual_, &info)) { + if (info.dli_fname && entry.__file_->empty()) { + // provide at least the binary filename in case we cannot lookup source location + entry.__file_ = alloc.make_str(info.dli_fname); + } + if (info.dli_sname && entry.__desc_->empty()) { + // provide at least the mangled name; try to unmangle in a later step + entry.__desc_ = alloc.make_str(info.dli_sname); + } + } +} + +void macos::symbolize() { + for (auto& entry : builder_.__entries_) { + symbolize_entry(builder_.__alloc_, (entry_base&)entry); + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __APPLE__ diff --git a/libcxx/src/stacktrace/macos/impl.h b/libcxx/src/stacktrace/macos/impl.h new file mode 100644 index 0000000000000..42b8b89d7bbb9 --- /dev/null +++ b/libcxx/src/stacktrace/macos/impl.h @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_MACOS_IMPL +#define _LIBCPP_STACKTRACE_MACOS_IMPL + +#include <__config> +#include <__config_site> +#include +#include + +#include "stacktrace/config.h" +#include <__stacktrace/base.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct macos { + builder& builder_; + +#if defined(__APPLE__) + // defined in macos.cpp + void ident_modules(); + void symbolize(); +#else + // inline-able dummy definitions + void ident_modules() {} + void symbolize() {} +#endif // __APPLE__ +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_MACOS_IMPL diff --git a/libcxx/src/stacktrace/to_string.cpp b/libcxx/src/stacktrace/to_string.cpp new file mode 100644 index 0000000000000..6d02b1c973344 --- /dev/null +++ b/libcxx/src/stacktrace/to_string.cpp @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> +#include <__config_site> + +#include +#include +#include +#include +#include + +#include <__stacktrace/basic.h> +#include <__stacktrace/entry.h> +#include <__stacktrace/to_string.h> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// `to_string`-related non-member functions + +_LIBCPP_EXPORTED_FROM_ABI string to_string(const stacktrace_entry& __entry) { + return __stacktrace::__to_string()(__entry); +} + +_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const stacktrace_entry& __entry) { + __stacktrace::__to_string()(__os, __entry); + return __os; +} + +namespace __stacktrace { + +/* + * `to_string` Helpers + */ + +_LIBCPP_EXPORTED_FROM_ABI void __to_string::operator()(ostream& __os, std::stacktrace_entry const& entry) { + // Although 64-bit addresses are 16 nibbles long, they're often <= 0x7fff_ffff_ffff + constexpr static int __k_addr_width = (sizeof(void*) > 4) ? 12 : 8; + + __os << "0x" << std::hex << std::setfill('0') << std::setw(__k_addr_width) << entry.native_handle(); + if (!entry.description().empty()) { + __os << ": " << entry.description(); + } + if (!entry.source_file().empty()) { + __os << ": " << entry.source_file(); + } + if (entry.source_line()) { + __os << ":" << std::dec << entry.source_line(); + } +} + +_LIBCPP_EXPORTED_FROM_ABI void +__to_string::operator()(ostream& __os, std::stacktrace_entry const* __entries, size_t __count) { + /* + * Print each entry as a line, as per `operator()`, with additional whitespace + * at the start of the line, and only a newline added at the end: + * + * frame 1: 0xbeefbeefbeef: _symbol_name: /path/to/file.cc:123 + */ + if (!__count) { + __os << "(empty stacktrace)"; + } else { + for (size_t __i = 0; __i < __count; __i++) { + if (__i) { + // Insert newlines between entries (but not before the first or after the last) + __os << std::endl; + } + __os << " frame " << std::setw(3) << std::setfill(' ') << std::dec << (__i + 1) << ": "; + (*this)(__os, __entries[__i]); + } + } +} + +_LIBCPP_EXPORTED_FROM_ABI string __to_string::operator()(std::stacktrace_entry const& entry) { + stringstream __ss; + (*this)(__ss, entry); + return __ss.str(); +} + +_LIBCPP_EXPORTED_FROM_ABI string __to_string::operator()(std::stacktrace_entry const* __entries, size_t __count) { + stringstream __ss; + (*this)(__ss, __entries, __count); + return __ss.str(); +} + +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp new file mode 100644 index 0000000000000..aa573567377e8 --- /dev/null +++ b/libcxx/src/stacktrace/tools/tools.cpp @@ -0,0 +1,382 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/config.h" + +#if defined(_LIBCPP_STACKTRACE_CAN_SPAWN_TOOLS) + +# include <__config> +# include <__config_site> +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include <__stacktrace/base.h> +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> + +# include "stacktrace/tools/tools.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { + +_LIBCPP_HIDE_FROM_ABI alloc::str hex_string(alloc& alloc, uintptr_t __addr) { + char __ret[19]; // "0x" + 16 digits + NUL + auto __size = snprintf(__ret, sizeof(__ret), "0x%016llx", (unsigned long long)__addr); + return alloc.make_str(__ret, size_t(__size)); +} + +_LIBCPP_HIDE_FROM_ABI alloc::str u64_string(alloc& alloc, uintptr_t __val) { + char __ret[21]; // 20 digits max + NUL + auto __size = snprintf(__ret, sizeof(__ret), "%zu", __val); + return alloc.make_str(__ret, size_t(__size)); +} + +# define STRINGIFY0(x) #x +# define STRINGIFY(x) STRINGIFY0(x) + +void try_tools(alloc& alloc, function cb) { + char const* prog_name; + + if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"))) { + if (cb(llvm_symbolizer{alloc, prog_name})) { + return; + } + } else { +# if defined(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH) + if (cb(llvm_symbolizer{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH)})) { + return; + } +# else + if (cb(llvm_symbolizer{alloc})) { + return; + } +# endif + } + + if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"))) { + if (cb(addr2line{alloc, prog_name})) { + return; + } + } else { +# if defined(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH) + if (cb(addr2line{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH)})) { + return; + } +# else + if (cb(addr2line{alloc})) { + return; + } +# endif + } + + if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"))) { + if (cb(atos{alloc, prog_name})) { + return; + } + } else { +# if defined(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH) + if (cb(atos{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH)})) { + return; + } +# else + if (cb(atos{alloc})) { + return; + } +# endif + } +} + +} // namespace + +void spawner::resolve_lines() { + try_tools(builder_.__alloc_, [&](tool const& prog) { + char buf[512]; + pspawn_tool proc(prog, builder_, buf, sizeof(buf)); + try { + proc.run(); + return true; + } catch (failed const& failed) { + debug() << failed.what(); + if (failed.errno_) { + debug() << " (" << failed.errno_ << " " << strerror(failed.errno_) << ')'; + } + debug() << '\n'; + } + return false; + }); +} + +alloc::list llvm_symbolizer::buildArgs(builder& builder) const { + auto ret = alloc_.make_list(); + ret.push_back(alloc_.make_str(progName_)); + ret.push_back(alloc_.make_str("--demangle")); + ret.push_back(alloc_.make_str("--no-inlines")); + ret.push_back(alloc_.make_str("--verbose")); + ret.push_back(alloc_.make_str("--relativenames")); + ret.push_back(alloc_.make_str("--functions=short")); + for (auto& st_entry : builder.__entries_) { + auto& entry = (entry_base&)st_entry; + auto addr_string = hex_string(alloc_, entry.__addr_unslid_); + if (entry.__file_) { + auto arg = alloc_.make_str(); + arg.reserve(entry.__file_->size() + 40); + arg += "FILE:"; + arg += *entry.__file_; + arg += " "; + arg += addr_string; + ret.push_back(arg); + } else { + ret.push_back(addr_string); + } + } + return ret; +} + +void llvm_symbolizer::parseOutput(builder& builder, __stacktrace::entry_base& entry, std::istream& output) const { + // clang-format off +/* +With "--verbose", parsing is a little easier, or at least, more reliable; +probably the best solution (until we have a JSON parser). +Example output, verbatim, between the '---' lines: +--- +test1 > + Filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp + Function start filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp + Function start line: 114 + Function start address: 0x8dd0 + Line: 116 + Column: 14 + +--- +Note that this includes an extra empty line as a terminator. +*/ + // clang-format on + + auto& alloc = builder.__alloc_; + auto line = alloc.make_str(); + line.reserve(512); + std::string_view tmp; + while (true) { + std::getline(output, line); + while (!line.empty() && isspace(line.back())) { + line.pop_back(); + } + if (line.empty()) { + return; + } + if (!line.starts_with(" ")) { + // The symbol has no leading whitespace, while the other + // lines with "fields" like line, column, filename, etc. + // start with two spaces. + if (line != "??") { + entry.__desc_ = line; + } + } else if (line.starts_with(" Filename:")) { + tmp = line; + tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " + if (tmp != "??") { + entry.__file_ = alloc.make_str(tmp); + } + } else if (line.starts_with(" Line:")) { + tmp = line; + tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " + if (tmp != "??" && tmp != "0") { + uint32_t lineno = 0; + auto pos = 0; + while (isdigit(tmp[pos])) { + lineno = lineno * 10 + (tmp[pos++] - '0'); + } + entry.__line_ = lineno; + } + } + } +} + +alloc::list addr2line::buildArgs(builder& builder) const { + auto& alloc = builder.__alloc_; + auto ret = alloc.make_list(); + if (builder.__main_prog_path_.empty()) { + // Should not have reached here but be graceful anyway + ret.push_back(alloc.make_str("/bin/false")); + return ret; + } + + ret.push_back(alloc.make_str(progName_)); + ret.push_back(alloc.make_str("--functions")); + ret.push_back(alloc.make_str("--demangle")); + ret.push_back(alloc.make_str("--basenames")); + ret.push_back(alloc.make_str("--pretty-print")); // This "human-readable form" is easier to parse + ret.push_back(alloc.make_str("-e")); + ret.push_back(builder.__main_prog_path_); + for (auto& entry : builder.__entries_) { + ret.push_back(hex_string(alloc, ((entry_base&)entry).__addr_unslid_)); + } + return ret; +} + +void addr2line::parseOutput(builder& builder, entry_base& entry, std::istream& output) const { + // clang-format off +/* +Example: +-- +llvm-addr2line -e foo --functions --demangle --basenames --pretty-print --no-inlines 0x11a0 0x1120 0x3d58 0x1284 + +Output: (1 line per input address) +-- +main at foo.cc:15 +register_tm_clones at crtstuff.c:0 +GCC_except_table2 at foo.cc:0 +test::Foo::Foo(int) at foo.cc:11 +*/ + // clang-format on + + auto& alloc = builder.__alloc_; + auto line = alloc.make_str(); + line.reserve(512); + std::getline(output, line); + while (!line.empty() && isspace(line.back())) { + line.pop_back(); + } + if (line.empty()) { + return; + } + // Split at the sequence " at ". Barring weird symbols + // having " at " in them, this should work. + auto sepIndex = line.find(" at "); + if (sepIndex == std::string::npos) { + return; + } + if (sepIndex > 0) { + entry.__desc_ = alloc.make_str(string_view(line).substr(0, sepIndex)); + } + auto fileBegin = sepIndex + 4; + if (fileBegin >= line.size()) { + return; + } + auto fileline = alloc.make_str(string_view(line).substr(fileBegin)); + auto colon = fileline.find_last_of(":"); + if (colon > 0 && !fileline.starts_with("?")) { + entry.__file_ = alloc.make_str(string_view(fileline).substr(0, colon)); + } + + if (colon == std::string::npos) { + return; + } + uint32_t lineno = 0; + auto pos = colon; + while (isdigit(fileline[++pos])) { + lineno = lineno * 10 + (fileline[pos] - '0'); + } + entry.__line_ = lineno; +} + +alloc::list atos::buildArgs(builder& builder) const { + auto& alloc = builder.__alloc_; + auto ret = alloc.make_list(); + ret.push_back(alloc.make_str(progName_)); + ret.push_back(alloc.make_str("-p")); + ret.push_back(u64_string(alloc, getpid())); + // TODO(stackcx23): Allow options in env, e.g. LIBCPP_STACKTRACE_OPTIONS=FullPath + // ret.push_back("--fullPath"); + for (auto& entry : builder.__entries_) { + ret.push_back(hex_string(alloc, ((entry_base&)entry).__addr_actual_)); + } + return ret; +} + +void atos::parseOutput(builder& builder, entry_base& entry, std::istream& output) const { + // Simple example: + // + // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) + // + // Assuming this is always atos's format (except when it returns empty lines) + // we can split the string like so: + // + // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) + // ^^^^-----^^^^^^^^---^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^^- + // sym module filename line + // + // Note that very strange filenames or module names can confuse this. + // We'll do the best we can for a decent result, while definitely ensuring safety + // (i.e. careful with our bound-checking). + // + // Another more interesting example (with an added newline for legibility): + // + // std::__1::basic_ios>::fill[abi:ne190107]() const (in testprog) + // (/opt/homebrew/Cellar/llvm/19.1.7_1/bin/../include/c++/v1/ios:0 + // + // If this more or less fits our expected format we'll take these data, + // even if the line number is 0. + + auto& alloc = builder.__alloc_; + auto line = alloc.make_str(); + line.reserve(512); + std::getline(output, line); + while (!line.empty() && isspace(line.back())) { + line.pop_back(); + } + if (line.empty()) { + return; + } + auto buf = line.data(); + auto size = line.size(); + + auto* end = buf + size; + auto* symEnd = strstr(buf, " (in "); + if (!symEnd) { + return; + } + auto* modBegin = symEnd + 5; + auto* modEnd = strstr(modBegin, ") ("); + if (!modEnd) { + return; + } + auto* fileBegin = modEnd + 3; // filename starts just after that + if (fileBegin >= end) { + return; + } + auto const* lastColon = fileBegin; // we'll search for last colon after filename + char const* nextColon; + while ((nextColon = strstr(lastColon + 1, ":"))) { // skip colons in filename (e.g. in "C:\foo.cpp") + lastColon = nextColon; + } + + std::string_view sym{buf, size_t(symEnd - buf)}; + // In case a previous step could not obtain the symbol name, + // we have the name provided by atos; only use that if we have no symbol + // (no need to copy more strings otherwise). + if (entry.__desc_->empty() && !sym.empty()) { + entry.__desc_ = alloc.make_str(sym); + } + + std::string_view file{fileBegin, size_t(lastColon - fileBegin)}; + if (file != "?" && file != "??" && !file.empty()) { + entry.__file_ = alloc.make_str(file); + } + + unsigned lineno = 0; + for (auto* digit = lastColon + 1; digit < end && isdigit(*digit); ++digit) { + lineno = (lineno * 10) + unsigned(*digit - '0'); + } + entry.__line_ = lineno; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h new file mode 100644 index 0000000000000..620a1e2f4f9a9 --- /dev/null +++ b/libcxx/src/stacktrace/tools/tools.h @@ -0,0 +1,197 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_TOOLS_H +#define _LIBCPP_STACKTRACE_TOOLS_H + +#include <__config> +#include <__config_site> +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/basic.h> +#include <__stacktrace/entry.h> + +#include "stacktrace/utils/debug.h" +#include "stacktrace/utils/failed.h" +#include "stacktrace/utils/fd.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct tool { + alloc& alloc_; + char const* progName_; + + tool(alloc& alloc, char const* progName) : alloc_(alloc), progName_(progName) {} + virtual ~tool() = default; + + /** Construct complete `argv` for the spawned process. + Includes the program name at argv[0], followed by flags */ + virtual alloc::list buildArgs(builder& trace) const = 0; + + /** Parse line(s) output by the tool, and modify `entry`. */ + virtual void parseOutput(builder& trace, entry_base& entry, std::istream& output) const = 0; +}; + +struct llvm_symbolizer : tool { + virtual ~llvm_symbolizer() = default; + explicit llvm_symbolizer(alloc& alloc) : llvm_symbolizer(alloc, "llvm_symbolizer") {} + llvm_symbolizer(alloc& alloc, char const* progName) : tool{alloc, progName} {} + alloc::list buildArgs(builder& trace) const override; + void parseOutput(builder& trace, entry_base& entry, std::istream& output) const override; +}; + +struct addr2line : tool { + virtual ~addr2line() = default; + explicit addr2line(alloc& alloc) : addr2line(alloc, "addr2line") {} + addr2line(alloc& alloc, char const* progName) : tool{alloc, progName} {} + alloc::list buildArgs(builder& trace) const override; + void parseOutput(builder& trace, entry_base& entry, std::istream& stream) const override; +}; + +struct atos : tool { + virtual ~atos() = default; + explicit atos(alloc& alloc) : atos(alloc, "atos") {} + atos(alloc& alloc, char const* progName) : tool{alloc, progName} {} + alloc::list buildArgs(builder& trace) const override; + void parseOutput(builder& trace, entry_base& entry, std::istream& output) const override; +}; + +struct file_actions { + posix_spawn_file_actions_t fa_; + + file_actions() { + if (posix_spawn_file_actions_init(&fa_)) { + throw failed("posix_spawn_file_actions_init", errno); + } + } + + ~file_actions() { posix_spawn_file_actions_destroy(&fa_); } + + void addClose(int fd) { + if (posix_spawn_file_actions_addclose(&fa_, fd)) { + throw failed("posix_spawn_file_actions_addclose", errno); + } + } + void addDup2(int fd, int stdfd) { + if (posix_spawn_file_actions_adddup2(&fa_, fd, stdfd)) { + throw failed("posix_spawn_file_actions_adddup2", errno); + } + } + + fd redirectOutFD() { + int fds[2]; + if (::pipe(fds)) { + throw failed("pipe", errno); + } + addClose(fds[0]); + addDup2(fds[1], 1); + return {fds[0]}; + } + + void redirectInNull() { addDup2(fd::null_fd(), 0); } + void redirectOutNull() { addDup2(fd::null_fd(), 1); } + void redirectErrNull() { addDup2(fd::null_fd(), 2); } +}; + +struct pspawn { + tool const& tool_; + pid_t pid_{0}; + file_actions fa_{}; + + // TODO(stacktrace23): ignore SIGCHLD for spawned subprocess + + ~pspawn() { + if (pid_) { + kill(pid_, SIGTERM); + wait(); + } + } + + void spawn(alloc::list const& argStrings) { + alloc::vec argv = tool_.alloc_.make_vec(); + argv.reserve(argStrings.size() + 1); + for (auto const& str : argStrings) { + argv.push_back(str.data()); + } + argv.push_back(nullptr); + int err; + if ((err = posix_spawnp(&pid_, argv[0], &fa_.fa_, nullptr, const_cast(argv.data()), nullptr))) { + throw failed("posix_spawnp", err); + } + } + + int wait() { + int status; + waitpid(pid_, &status, 0); + return status; + } +}; + +struct pspawn_tool : pspawn { + builder& builder_; + fd fd_; + fd_streambuf buf_; + fd_istream stream_; + + pspawn_tool(tool const& a2l, builder& trace, char* buf, size_t size) + : pspawn{a2l}, builder_(trace), fd_(fa_.redirectOutFD()), buf_(fd_, buf, size), stream_(buf_) { + if (!debug::enabled()) { + fa_.redirectErrNull(); + } + fa_.redirectInNull(); + } + + void run() { + // Cannot run "addr2line" or similar without addresses, since we + // provide them in argv, and if there are none passed in argv, the + // tool will try to read from stdin and hang. + if (builder_.__entries_.empty()) { + return; + } + + auto argStrings = tool_.buildArgs(builder_); + if (debug::enabled()) { + debug() << "Trying to get stacktrace using:"; + for (auto& str : argStrings) { + debug() << " \"" << str << '"'; + } + debug() << '\n'; + } + + spawn(argStrings); + + auto end = builder_.__entries_.end(); + auto it = builder_.__entries_.begin(); + while (it != end) { + auto& entry = (entry_base&)(*it++); + tool_.parseOutput(builder_, entry, stream_); + } + } +}; + +struct spawner { + builder& builder_; + void resolve_lines(); +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_TOOLS_H diff --git a/libcxx/src/stacktrace/unwind/impl.cpp b/libcxx/src/stacktrace/unwind/impl.cpp new file mode 100644 index 0000000000000..90603e0422b9b --- /dev/null +++ b/libcxx/src/stacktrace/unwind/impl.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/config.h" + +#if defined(_LIBCPP_STACKTRACE_UNWIND_IMPL) + +# include + +# include "stacktrace/unwind/impl.h" +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct unwind_backtrace { + builder& builder_; + size_t skip_; + size_t maxDepth_; + + _Unwind_Reason_Code callback(_Unwind_Context* ucx) { + if (skip_) { + --skip_; + return _Unwind_Reason_Code::_URC_NO_REASON; + } + if (!maxDepth_) { + return _Unwind_Reason_Code::_URC_NORMAL_STOP; + } + --maxDepth_; + int ipBefore; + auto ip = _Unwind_GetIPInfo(ucx, &ipBefore); + if (!ip) { + return _Unwind_Reason_Code::_URC_NORMAL_STOP; + } + auto& entry = builder_.__entries_.emplace_back(); + auto& eb = (entry_base&)entry; + eb.__addr_actual_ = (ipBefore ? ip : ip - 1); + eb.__addr_unslid_ = eb.__addr_actual_; // in case we can't un-slide + return _Unwind_Reason_Code::_URC_NO_REASON; + } + + static _Unwind_Reason_Code callback(_Unwind_Context* cx, void* self) { + return ((unwind_backtrace*)self)->callback(cx); + } +}; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void unwind::collect(size_t skip, size_t max_depth) { + unwind_backtrace bt{builder_, skip + 1, max_depth}; // skip this call as well + _Unwind_Backtrace(unwind_backtrace::callback, &bt); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/unwind/impl.h b/libcxx/src/stacktrace/unwind/impl.h new file mode 100644 index 0000000000000..4b552349d75bc --- /dev/null +++ b/libcxx/src/stacktrace/unwind/impl.h @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UNWIND_H +#define _LIBCPP_STACKTRACE_UNWIND_H + +#include <__config> +#include <__config_site> +#include +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/basic.h> +#include <__stacktrace/entry.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct unwind { + builder& builder_; + void collect(size_t skip, size_t max_depth); +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UNWIND_H diff --git a/libcxx/src/stacktrace/utils/debug.h b/libcxx/src/stacktrace/utils/debug.h new file mode 100644 index 0000000000000..cc88224942f6f --- /dev/null +++ b/libcxx/src/stacktrace/utils/debug.h @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_DEBUG +#define _LIBCPP_STACKTRACE_UTILS_DEBUG + +#include <__config> +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/** Debug-message output stream. If `LIBCXX_STACKTRACE_DEBUG` is defined in the environment +or as a macro with exactly the string `1` then this is enabled (prints to `std::cerr`); +otherwise its does nothing by returning a dummy stream. */ +struct _LIBCPP_HIDE_FROM_ABI debug : std::ostream { + _LIBCPP_HIDE_FROM_ABI virtual ~debug() = default; + + _LIBCPP_HIDE_FROM_ABI static bool enabled() { +#if defined(LIBCXX_STACKTRACE_DEBUG) && LIBCXX_STACKTRACE_DEBUG == 1 + return true; +#else + static bool ret = [] { + auto const* val = getenv("LIBCXX_STACKTRACE_DEBUG"); + return val && !strncmp(val, "1", 1); + }(); + return ret; +#endif + } + + /** No-op output stream. */ + struct _LIBCPP_HIDE_FROM_ABI dummy_ostream final : std::ostream { + _LIBCPP_HIDE_FROM_ABI virtual ~dummy_ostream() = default; + friend std::ostream& operator<<(dummy_ostream& bogus, auto const&) { return bogus; } + }; + + friend std::ostream& operator<<(debug& dp, auto const& val) { + static dummy_ostream kdummy; + if (!enabled()) { + return kdummy; + } + std::cerr << val; + return std::cerr; + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_DEBUG diff --git a/libcxx/src/stacktrace/utils/failed.h b/libcxx/src/stacktrace/utils/failed.h new file mode 100644 index 0000000000000..3f8a24dd88eb6 --- /dev/null +++ b/libcxx/src/stacktrace/utils/failed.h @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_FAILED +#define _LIBCPP_STACKTRACE_UTILS_FAILED + +#include <__config> +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct failed : std::runtime_error { + virtual ~failed() = default; + int errno_{0}; + failed() : std::runtime_error({}) {} + failed(char const* msg, int err) : std::runtime_error(msg), errno_(err) {} +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_FAILED diff --git a/libcxx/src/stacktrace/utils/fd.h b/libcxx/src/stacktrace/utils/fd.h new file mode 100644 index 0000000000000..f4be1876e5d37 --- /dev/null +++ b/libcxx/src/stacktrace/utils/fd.h @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_FD +#define _LIBCPP_STACKTRACE_UTILS_FD + +#include <__config> +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stacktrace/utils/failed.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/** Encapsulates a plain old file descriptor `int`. Avoids copies in order to +force some component to "own" this, although it's freely convertible back to +integer form. Default-constructed, closed, and moved-out-of instances will have +the invalid fd `-1`. */ +class _LIBCPP_HIDE_FROM_ABI fd { + int fd_{-1}; + +public: + fd() : fd(-1) {} + fd(int fdint) : fd_(fdint) {} + + fd(fd const&) = delete; + fd& operator=(fd const&) = delete; + + fd(fd&& rhs) { + if (&rhs != this) { + std::exchange(fd_, rhs.fd_); + } + } + + fd& operator=(fd&& rhs) { return *new (this) fd(std::move(rhs)); } + + ~fd() { close(); } + + /** Returns true IFF fd is above zero */ + bool valid() const { return fd_ > 0; } + + /* implicit */ operator int() const { return fd_; } + + void close() { + int fd_old = -1; + std::exchange(fd_old, fd_); + if (fd_old != -1) { + ::close(fd_old); + } + } + + static fd& null_fd() { + static fd ret = {::open("/dev/null", O_RDWR)}; + return ret; + } + + static fd open(std::string_view path) { + fd ret = {::open(path.data(), O_RDONLY)}; + return ret; + } +}; + +/** Wraps a readable fd using the `streambuf` interface. I/O errors arising +from reading the provided fd will result in a `Failed` being thrown. */ +struct _LIBCPP_HIDE_FROM_ABI fd_streambuf final : std::streambuf { + fd& fd_; + char* buf_; + size_t size_; + _LIBCPP_HIDE_FROM_ABI fd_streambuf(fd& fd, char* buf, size_t size) : fd_(fd), buf_(buf), size_(size) {} + _LIBCPP_HIDE_FROM_ABI virtual ~fd_streambuf() = default; + + _LIBCPP_HIDE_FROM_ABI int underflow() override { + int bytesRead = ::read(fd_, buf_, size_); + if (bytesRead < 0) { + throw ::std::__stacktrace::failed("I/O error reading from child process", errno); + } + if (bytesRead == 0) { + return traits_type::eof(); + } + setg(buf_, buf_, buf_ + bytesRead); + return int(*buf_); + } +}; + +/** Wraps an `FDInStreamBuffer` in an `istream` */ +struct fd_istream final : std::istream { + fd_streambuf& buf_; + _LIBCPP_HIDE_FROM_ABI virtual ~fd_istream() = default; + _LIBCPP_HIDE_FROM_ABI explicit fd_istream(fd_streambuf& buf) : std::istream(nullptr), buf_(buf) { rdbuf(&buf_); } +}; + +struct fd_mmap final { + fd fd_{}; + size_t size_{0}; + std::byte const* addr_{nullptr}; + + _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(std::string_view path) : fd_mmap(fd::open(path)) {} + + _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(fd&& fd) : fd_(std::move(fd)) { + if (fd_) { + if ((size_ = ::lseek(fd, 0, SEEK_END))) { + addr_ = (std::byte const*)::mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd_, 0); + } + } + } + + _LIBCPP_HIDE_FROM_ABI operator bool() const { return addr_; } + + _LIBCPP_HIDE_FROM_ABI ~fd_mmap() { + if (addr_) { + ::munmap(const_cast((void const*)addr_), size_); + } + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_FD diff --git a/libcxx/src/stacktrace/utils/image.h b/libcxx/src/stacktrace/utils/image.h new file mode 100644 index 0000000000000..21119d7a66ec9 --- /dev/null +++ b/libcxx/src/stacktrace/utils/image.h @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_IMAGE +#define _LIBCPP_STACKTRACE_UTILS_IMAGE + +#include <__config> +#include <__stacktrace/base.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct image { + constexpr static size_t kMaxImages = 256; + + uintptr_t loaded_at_{}; + intptr_t slide_{}; + std::string_view name_{}; + bool is_main_prog_{}; + + bool operator<(image const& rhs) const { return loaded_at_ < rhs.loaded_at_; } + operator bool() const { return !name_.empty(); } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_IMAGE diff --git a/libcxx/src/stacktrace/windows/dll.cpp b/libcxx/src/stacktrace/windows/dll.cpp new file mode 100644 index 0000000000000..d48bead956041 --- /dev/null +++ b/libcxx/src/stacktrace/windows/dll.cpp @@ -0,0 +1,247 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> + +#if defined(_LIBCPP_WIN32API) + +# include <__stacktrace/base.h> + +# include "stacktrace/alloc.h" +# include "stacktrace/config.h" +# include "stacktrace/utils.h" +# include "stacktrace/windows/dll.h" +# include "stacktrace/windows/impl.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { + +/* +Global objects, shared among all threads and among all stacktrace operations. +The `dbghelp` APIs are not safe to call concurrently (according to their docs) +so we claim a lock in the `WinDebugAPIs` constructor. +*/ + +// Statically-initialized +std::mutex gWindowsAPILock; +DbgHelpDLL dbg; +PSAPIDLL ps; + +// Initialized once, in first WinDebugAPIs construction; +// protected by the above mutex. +HANDLE proc; +HMODULE exe; +IMAGE_NT_HEADERS* ntHeaders; +bool globalInitialized{false}; + +// Globals used across invocations of the functions below. +// Also guarded by the above mutex. +bool symsInitialized{false}; +HMODULE moduleHandles[1024]; +size_t moduleCount; // 0 IFF module enumeration failed + +} // namespace + +win_impl::WinDebugAPIs(builder& trace) : builder_(trace), guard_(gWindowsAPILock) { + if (!globalInitialized) { + // Cannot proceed without these DLLs: + if (!dbg) { + return; + } + if (!ps) { + return; + } + proc = GetCurrentProcess(); + if (!(exe = GetModuleHandle(nullptr))) { + return; + } + if (!(ntHeaders = (*dbg.ImageNtHeader)(exe))) { + return; + } + + globalInitialized = true; + } + + // Initialize symbol machinery. + // Presumably the symbols in this process's space can change between + // stacktraces, so we'll do this each time we take a trace. + // The final `true` means we want the runtime to enumerate all this + // process's modules' symbol tables. + symsInitialized = (*dbg.SymInitialize)(proc, nullptr, true); + DWORD symOptions = (*dbg.SymGetOptions)(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + (*dbg.SymSetOptions)(symOptions); +} + +win_impl::~WinDebugAPIs() { + if (symsInitialized) { + (*dbg.SymCleanup)(proc); + symsInitialized = false; + } +} + +void win_impl::ident_modules() { + if (!globalInitialized) { + return; + } + DWORD needBytes; + auto enumMods = (*ps.EnumProcessModules)(proc, moduleHandles, sizeof(moduleHandles), LPDWORD(&needBytes)); + if (enumMods) { + moduleCount = needBytes / sizeof(HMODULE); + } else { + moduleCount = 0; + Debug() << "EnumProcessModules failed: " << GetLastError() << '\n'; + } +} + +void win_impl::symbolize() { + // Very long symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; + auto* sym = (IMAGEHLP_SYMBOL64*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = kMaxSymName; + uint64_t disp{0}; + if ((*dbg.SymGetSymFromAddr64)(proc, entry.__addr_actual_, &disp, sym)) { + // Copy chars into the destination string which uses the caller-provided allocator. + ((entry_base&)entry).__desc_ = {sym->Name}; + } else { + Debug() << "SymGetSymFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +void win_impl::resolve_lines() { + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + DWORD disp{0}; + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if ((*dbg.SymGetLineFromAddr64)(proc, entry.__addr_actual_, &disp, &line)) { + // Copy chars into the destination string which uses the caller-provided allocator. + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } else { + Debug() << "SymGetLineFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +/* +Inlining is disabled from here on; +this is to ensure `collect` below doesn't get merged into its caller +and mess around with the top of the stack (making `skip` inaccurate). +*/ +# pragma auto_inline(off) + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { + if (!globalInitialized) { + return; + } + + auto thread = GetCurrentThread(); + auto machine = ntHeaders->FileHeader.Machine; + + CONTEXT ccx; + RtlCaptureContext(&ccx); + + STACKFRAME64 frame; + memset(&frame, 0, sizeof(frame)); + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrPC.Offset = ctrace.Rip; + frame.AddrStack.Offset = ctrace.Rsp; + frame.AddrFrame.Offset = ctrace.Rbp; + + while (max_depth && + (*dbg.StackWalk64)( + machine, + proc, + thread, + &frame, + &ccx, + nullptr, + dbg.SymFunctionTableAccess64, + dbg.SymGetModuleBase64, + nullptr)) { + if (skip) { + --skip; + continue; + } + --max_depth; + auto& entry = builder_.__entries_.emplace_back(); + // We don't need to compute the un-slid addr; windbg only needs the actual addresses. + // Assume address is of the instruction after a call instruction, since we can't + // differentiate between a signal, SEH exception handler, or a normal function call. + entry.__addr_actual_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range + } +} + +dll::~dll() { FreeLibrary(module_); } + +dll::dll(char const* name) : name_(name), module_(LoadLibrary(name)) { + if (!module_) { + debug() << "LoadLibrary failed: " << name_ << ": " << GetLastError() << '\n'; + } +} + +dbghelp_dll::~dbghelp_dll() = default; + +dbghelp_dll& dbghelp_dll::get() { + dbghelp_dll ret; + return ret; +} + +dbghelp_dll::dbghelp_dll() : dll("dbghelp.dll") { + // clang-format off +if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } + if (!get_func(&StackWalk64, "StackWalk64")) { return; } + if (!get_func(&SymCleanup, "SymCleanup")) { return; } + if (!get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64")) { return; } + if (!get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64")) { return; } + if (!get_func(&SymGetModuleBase64, "SymGetModuleBase64")) { return; } + if (!get_func(&SymGetOptions, "SymGetOptions")) { return; } + if (!get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64")) { return; } + if (!get_func(&SymInitialize, "SymInitialize")) { return; } + if (!get_func(&SymLoadModule64, "SymLoadModule64")) { return; } + if (!get_func(&SymSetOptions, "SymSetOptions")) { return; } + valid_ = true; + // clang-format on +} + +psapi_dll::~psapi_dll() = default; + +psapi_dll& psapi_dll::get() { + psapi_dll ret; + return ret; +} + +psapi_dll() : dll("psapi.dll") { + // clang-format off +if (!getFunc(&EnumProcessModules, "EnumProcessModules")) { return; } + if (!getFunc(&GetModuleInformation, "GetModuleInformation")) { return; } + if (!getFunc(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } + valid_ = true; + // clang-format on +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_WIN32API diff --git a/libcxx/src/stacktrace/windows/dll.h b/libcxx/src/stacktrace/windows/dll.h new file mode 100644 index 0000000000000..13ed9e42768d5 --- /dev/null +++ b/libcxx/src/stacktrace/windows/dll.h @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_WIN_DLL +#define _LIBCPP_STACKTRACE_WIN_DLL + +#include <__config> +#if defined(_LIBCPP_WIN32API) + +// windows.h must be first +# include +// other windows-specific headers +# include +# define PSAPI_VERSION 1 +# include + +# include <__config_site> +# include +# include +# include +# include + +# include "stacktrace/utils/debug.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +# if defined(_LIBCPP_WIN32API) + +// clang-format off + +struct dll { + char const* name_; + HMODULE module_; + /** Set to true in subclass's ctor if initialized successfully. */ + bool valid_{false}; + + operator bool() const { return valid_; } + + explicit dll(char const* name) + : name_(name), module_(LoadLibrary(name)) { + if (!module_) { + debug() << "LoadLibrary failed: " + << name_ << ": " << GetLastError() << '\n'; + } + } + + virtual ~dll() { FreeLibrary(module_); } + + template + bool get_func(F* func, char const* name) { + if (!(*func = (F)GetProcAddress(module_, name))) { + debug() << "GetProcAddress failed: " + << name << "' (" << name_ << "): " + << GetLastError() << "\n"; + return false; + } + return true; + } +}; + +struct dbghelp_dll final : dll { + virtual ~dbghelp_dll() = default; + static dbghelp_dll& get() { static dbghelp_dll ret; return ret; } + + IMAGE_NT_HEADERS* (*ImageNtHeader)(void*); + bool (*StackWalk64) (DWORD, HANDLE, HANDLE, STACKFRAME64*, void*, void*, void*, void*, void*); + bool (*SymCleanup) (HANDLE); + void* (*SymFunctionTableAccess64)(HANDLE, DWORD64); + bool (*SymGetLineFromAddr64)(HANDLE, DWORD64, DWORD*, IMAGEHLP_LINE64*); + DWORD64 (*SymGetModuleBase64) (HANDLE, DWORD64); + DWORD (*SymGetOptions) (); + bool (*SymGetSymFromAddr64)(HANDLE, DWORD64, DWORD64*, IMAGEHLP_SYMBOL64*); + bool (*SymInitialize) (HANDLE, char const*, bool); + DWORD64 (*SymLoadModule64) (HANDLE, HANDLE, char const*, char const*, void*, DWORD); + DWORD (*SymSetOptions) (DWORD); + + dbghelp_dll() : dll("dbghelp.dll") { + if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } + if (!get_func(&StackWalk64, "StackWalk64")) { return; } + if (!get_func(&SymCleanup, "SymCleanup")) { return; } + if (!get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64")) { return; } + if (!get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64")) { return; } + if (!get_func(&SymGetModuleBase64, "SymGetModuleBase64")) { return; } + if (!get_func(&SymGetOptions, "SymGetOptions")) { return; } + if (!get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64")) { return; } + if (!get_func(&SymInitialize, "SymInitialize")) { return; } + if (!get_func(&SymLoadModule64, "SymLoadModule64")) { return; } + if (!get_func(&SymSetOptions, "SymSetOptions")) { return; } + valid_ = true; + } +}; + +struct psapi_dll final : dll { + virtual ~psapi_dll() = default; + static psapi_dll& get() { static psapi_dll ret; return ret; } + + bool (*EnumProcessModules) (HANDLE, HMODULE*, DWORD, DWORD*); + bool (*GetModuleInformation) (HANDLE, HMODULE, MODULEINFO*, DWORD); + DWORD (*GetModuleBaseName) (HANDLE, HMODULE, char**, DWORD); + + psapi_dll() : dll("psapi.dll") { + if (!get_func(&EnumProcessModules, "EnumProcessModules")) { return; } + if (!get_func(&GetModuleInformation, "GetModuleInformation")) { return; } + if (!get_func(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } + valid_ = true; + } +}; + +#endif + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_WIN32API +#endif // _LIBCPP_STACKTRACE_WIN_DLL diff --git a/libcxx/src/stacktrace/windows/impl.cpp b/libcxx/src/stacktrace/windows/impl.cpp new file mode 100644 index 0000000000000..e8901e469e768 --- /dev/null +++ b/libcxx/src/stacktrace/windows/impl.cpp @@ -0,0 +1,200 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if defined(_LIBCPP_WIN32API) +// windows.h must be first +# include +// other windows-specific headers +# include +# define PSAPI_VERSION 1 +# include + +# include + +# include "stacktrace/utils/debug.h" +# include "stacktrace/windows/dll.h" +# include "stacktrace/windows/impl.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { + +/* +Global objects, shared among all threads and among all stacktrace operations. +The `dbghelp` APIs are not safe to call concurrently (according to their docs) +so we claim a lock in the `WinDebugAPIs` constructor. +*/ + +// Statically-initialized +DbgHelpDLL dbg; +PSAPIDLL ps; + +// Initialized once, in first WinDebugAPIs construction; +// protected by the above mutex. +HANDLE proc; +HMODULE exe; +IMAGE_NT_HEADERS* ntHeaders; +bool globalInitialized{false}; + +// Globals used across invocations of the functions below. +// Also guarded by the above mutex. +bool symsInitialized{false}; +HMODULE moduleHandles[1024]; +size_t moduleCount; // 0 IFF module enumeration failed + +} // namespace + +win_impl::global_init() { + if (!globalInitialized) { + // Cannot proceed without these DLLs: + if (!dbg) { + return; + } + if (!ps) { + return; + } + proc = GetCurrentProcess(); + if (!(exe = GetModuleHandle(nullptr))) { + return; + } + if (!(ntHeaders = (*dbg.ImageNtHeader)(exe))) { + return; + } + + globalInitialized = true; + } + + // Initialize symbol machinery. + // Presumably the symbols in this process's space can change between + // stacktraces, so we'll do this each time we take a trace. + // The final `true` means we want the runtime to enumerate all this + // process's modules' symbol tables. + symsInitialized = (*dbg.SymInitialize)(proc, nullptr, true); + DWORD symOptions = (*dbg.SymGetOptions)(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + (*dbg.SymSetOptions)(symOptions); +} + +win_impl::~win_impl() { + if (symsInitialized) { + (*dbg.SymCleanup)(proc); + symsInitialized = false; + } +} + +void win_impl::ident_modules() { + if (!globalInitialized) { + return; + } + DWORD needBytes; + auto enumMods = (*ps.EnumProcessModules)(proc, moduleHandles, sizeof(moduleHandles), LPDWORD(&needBytes)); + if (enumMods) { + moduleCount = needBytes / sizeof(HMODULE); + } else { + moduleCount = 0; + Debug() << "EnumProcessModules failed: " << GetLastError() << '\n'; + } +} + +void win_impl::symbolize() { + // Very long symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; + auto* sym = (IMAGEHLP_SYMBOL64*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = kMaxSymName; + uint64_t disp{0}; + if ((*dbg.SymGetSymFromAddr64)(proc, entry.__addr_actual_, &disp, sym)) { + // Copy chars into the destination string which uses the caller-provided allocator. + ((entry_base&)entry).__desc_ = {sym->Name}; + } else { + Debug() << "SymGetSymFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +void win_impl::resolve_lines() { + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + DWORD disp{0}; + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if ((*dbg.SymGetLineFromAddr64)(proc, entry.__addr_actual_, &disp, &line)) { + // Copy chars into the destination string which uses the caller-provided allocator. + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } else { + Debug() << "SymGetLineFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +/* +Inlining is disabled from here on; +this is to ensure `collect` below doesn't get merged into its caller +and mess around with the top of the stack (making `skip` inaccurate). +*/ +# pragma auto_inline(off) + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { + if (!globalInitialized) { + return; + } + + auto thread = GetCurrentThread(); + auto machine = ntHeaders->FileHeader.Machine; + + CONTEXT ccx; + RtlCaptureContext(&ccx); + + STACKFRAME64 frame; + memset(&frame, 0, sizeof(frame)); + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrPC.Offset = ctrace.Rip; + frame.AddrStack.Offset = ctrace.Rsp; + frame.AddrFrame.Offset = ctrace.Rbp; + + while (max_depth && + (*dbg.StackWalk64)( + machine, + proc, + thread, + &frame, + &ccx, + nullptr, + dbg.SymFunctionTableAccess64, + dbg.SymGetModuleBase64, + nullptr)) { + if (skip) { + --skip; + continue; + } + --max_depth; + auto& entry = builder_.__entries_.emplace_back(); + // We don't need to compute the un-slid addr; windbg only needs the actual addresses. + // Assume address is of the instruction after a call instruction, since we can't + // differentiate between a signal, SEH exception handler, or a normal function call. + entry.__addr_actual_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/windows/impl.h b/libcxx/src/stacktrace/windows/impl.h new file mode 100644 index 0000000000000..b88a3f9d8a3a1 --- /dev/null +++ b/libcxx/src/stacktrace/windows/impl.h @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_WIN_IMPL_H +#define _LIBCPP_STACKTRACE_WIN_IMPL_H + +#include <__config> +#include <__config_site> +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct builder; + +struct win_impl { + builder& builder_; + +#if defined(_LIBCPP_WIN32API) + static std::mutex mutex_; + std::lock_guard guard_; + + explicit win_impl(builder& builder) : builder_(builder), guard_(mutex_) { global_init(); } + ~win_impl(); + + void global_init(); + void collect(size_t skip, size_t max_depth); + void ident_modules(); + void symbolize(); + void resolve_lines(); +#else + void global_init() {} + void collect(size_t, size_t) {} + void ident_modules() {} + void symbolize() {} + void resolve_lines() {} +#endif // _LIBCPP_WIN32API +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_WIN_IMPL_H diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp new file mode 100644 index 0000000000000..f40037d7e46e6 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g0 + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // This is 'nodebug', so we cannot get the source file and line: + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + // But this should at least produce the executable filename + assert(entry.source_file().contains("simple.o0.nodebug.pass.cpp")); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp new file mode 100644 index 0000000000000..2c7fa9416f31f --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp new file mode 100644 index 0000000000000..1f6e2bd557460 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g -gsplit-dwarf + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cerr << trace << '\n'; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp new file mode 100644 index 0000000000000..fafb0d618b908 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g0 + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // This is 'nodebug', so we cannot get the source file and line: + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + // But this should at least produce the executable filename + assert(entry.source_file().contains("simple.o3.nodebug.pass.cpp")); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp new file mode 100644 index 0000000000000..3690499ae7c25 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp new file mode 100644 index 0000000000000..4b32e33110d1a --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g -gsplit-dwarf + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv index cb23c7a98de1b..e67db3ac47cf9 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -926,6 +926,27 @@ stack limits stack stdexcept stack tuple stack version +stacktrace cctype +stacktrace climits +stacktrace compare +stacktrace cstddef +stacktrace cstdint +stacktrace cstdio +stacktrace cstring +stacktrace cwchar +stacktrace cwctype +stacktrace initializer_list +stacktrace iosfwd +stacktrace limits +stacktrace list +stacktrace optional +stacktrace stdexcept +stacktrace string +stacktrace string_view +stacktrace tuple +stacktrace typeinfo +stacktrace utility +stacktrace version stop_token atomic stop_token climits stop_token cstdint diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv index 5f906338f4b7c..fa29ac86a2f7e 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -911,6 +911,27 @@ stack limits stack stdexcept stack tuple stack version +stacktrace cctype +stacktrace climits +stacktrace compare +stacktrace cstddef +stacktrace cstdint +stacktrace cstdio +stacktrace cstring +stacktrace cwchar +stacktrace cwctype +stacktrace initializer_list +stacktrace iosfwd +stacktrace limits +stacktrace list +stacktrace optional +stacktrace stdexcept +stacktrace string +stacktrace string_view +stacktrace tuple +stacktrace typeinfo +stacktrace utility +stacktrace version stop_token atomic stop_token climits stop_token cstdint diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp new file mode 100644 index 0000000000000..5864d241abf35 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.4) Comparisons [stacktrace.basic.cmp] + + template + friend bool operator==(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2a() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2b() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + auto st1a = test1(); // [test1, main, ...] + assert(st1a == st1a); + + auto st1b = st1a; + assert(st1a == st1b); + + auto st2a = test2a(); // [test1, test2a, main, ...] + assert(st1a != st2a); + + std::stacktrace empty; // [] + assert(st1a != empty); + assert(st2a != empty); + + assert(st2a.size() > st1a.size()); + assert(st1a.size() > empty.size()); + + auto st2b = test2b(); // [test1, test2b, main, ...] + assert(st2a.size() == st2b.size()); + assert(st2a != st2b); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp new file mode 100644 index 0000000000000..5664e9bc41db2 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.4) Comparisons [stacktrace.basic.cmp] + + template + friend strong_ordering operator<=>(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2a() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2b() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + /* + Returns: x.size() <=> y.size() if x.size() != y.size(); + lexicographical_compare_three_way(x.begin(), x.end(), y.begin(), y.end()) otherwise. + */ + + auto st1a = test1(); // [test1, main, ...] + auto st1b = st1a; + assert(st1a == st1b); + auto st2a = test2a(); // [test1, test2a, main, ...] + assert(st1a != st2a); + std::stacktrace empty; // [] + auto st2b = test2b(); // [test1, test2b, main, ...] + assert(st2a != st2b); + + // empty: [] + // st1a: [test1, main, ...] + // st1b: [test1, main, ...] (copy of st1a) + // st2a: [test1, test2a, main:X, ...] + // st2b: [test1, test2b, main:Y, ...], Y > X + + assert(std::strong_ordering::equal == empty <=> empty); + assert(std::strong_ordering::less == empty <=> st1a); + assert(std::strong_ordering::less == empty <=> st1b); + assert(std::strong_ordering::less == empty <=> st2a); + assert(std::strong_ordering::less == empty <=> st2b); + + assert(std::strong_ordering::greater == st1a <=> empty); + assert(std::strong_ordering::equal == st1a <=> st1a); + assert(std::strong_ordering::equal == st1a <=> st1b); + assert(std::strong_ordering::less == st1a <=> st2a); + assert(std::strong_ordering::less == st1a <=> st2b); + + assert(std::strong_ordering::greater == st1b <=> empty); + assert(std::strong_ordering::equal == st1b <=> st1a); + assert(std::strong_ordering::equal == st1b <=> st1b); + assert(std::strong_ordering::less == st1b <=> st2a); + assert(std::strong_ordering::less == st1b <=> st2b); + + assert(std::strong_ordering::greater == st2a <=> empty); + assert(std::strong_ordering::greater == st2a <=> st1a); + assert(std::strong_ordering::greater == st2a <=> st1b); + assert(std::strong_ordering::equal == st2a <=> st2a); + assert(std::strong_ordering::less == st2a <=> st2b); + + assert(std::strong_ordering::greater == st2b <=> empty); + assert(std::strong_ordering::greater == st2b <=> st1a); + assert(std::strong_ordering::greater == st2b <=> st1b); + assert(std::strong_ordering::greater == st2b <=> st2a); + assert(std::strong_ordering::equal == st2b <=> st2b); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp new file mode 100644 index 0000000000000..48da1d0bd30b6 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); +*/ + +#include +#include +#include + +// clang-format off +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE +std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE +std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_copy_move_ctors() { + using A = std::allocator; + A alloc; + auto st = std::basic_stacktrace::current(alloc); + + auto copy_constr = std::basic_stacktrace(st); + assert(st == copy_constr); + + std::basic_stacktrace copy_assign; + copy_assign = std::basic_stacktrace(st); + assert(st == copy_assign); + + auto st2 = test2(alloc); + assert(st2.size()); + std::basic_stacktrace move_constr(std::move(st2)); + assert(move_constr.size()); + assert(!st2.size()); + + auto st3 = test2(alloc); + assert(st3.size()); + std::basic_stacktrace move_assign; + move_assign = std::move(st3); + assert(move_assign.size()); + assert(!st3.size()); + + // TODO(stacktrace23): should we add test cases with `select_on_container_copy_construction`? +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_copy_move_ctors(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp new file mode 100644 index 0000000000000..3b406f1b16a61 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); +*/ + +#include +#include + +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_default_construct() { + std::stacktrace st; + assert(st.empty()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_default_construct(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp new file mode 100644 index 0000000000000..a49f45245f7d2 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + explicit basic_stacktrace(const allocator_type& alloc) noexcept; +*/ + +#include +#include + +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} + +template +struct test_alloc { + using size_type = size_t; + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + + template + struct rebind { + using other = test_alloc; + }; + + std::allocator wrapped_{}; + + test_alloc() = default; + + template + test_alloc(test_alloc const& rhs) : wrapped_(rhs.wrapped_) {} + + bool operator==(auto const& rhs) const { return &rhs == this; } + bool operator==(test_alloc const&) const { return true; } + + T* allocate(size_t n) { return wrapped_.allocate(n); } + auto allocate_at_least(size_t n) { return wrapped_.allocate_at_least(n); } + void deallocate(T* ptr, size_t n) { return wrapped_.deallocate(ptr, n); } +}; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_construct_with_allocator() { + test_alloc alloc; + std::basic_stacktrace st(alloc); + assert(st.empty()); + + st = std::basic_stacktrace::current(alloc); + assert(!st.empty()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_construct_with_allocator(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp new file mode 100644 index 0000000000000..c1c9b3ea70496 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; +*/ + +#include +#include + +uint32_t test1_line; +uint32_t test2_line; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::stacktrace::current(); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current() { + auto st = test2(); + assert(st.size() >= 3); + assert(st[0]); + assert(st[0].native_handle()); + assert(st[0].description().contains("test1")); + assert(st[0].source_file().contains("current_no_args.pass.cpp")); + assert(st[1]); + assert(st[1].native_handle()); + assert(st[1].description().contains("test2")); + assert(st[1].source_file().contains("current_no_args.pass.cpp")); + assert(st[2]); + assert(st[2].native_handle()); + assert(st[2].description().contains("test_current")); + assert(st[2].source_file().contains("current_no_args.pass.cpp")); + + // We unfortunately cannot guarantee the following; in CI, and possibly on users' build machines, + // there may not be an up-to-date version of e.g. `addr2line`. + // assert(st[0].source_file().ends_with("current_no_args.pass.cpp")); + // assert(st[0].source_line() == test1_line); + // assert(st[1].source_file().ends_with("current_no_args.pass.cpp")); + // assert(st[1].source_line() == test2_line); + // assert(st[2].source_file().ends_with("current_no_args.pass.cpp")); + // assert(st[2].source_line() == main_line); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp new file mode 100644 index 0000000000000..55877f0a49785 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; [2] +*/ + +#include +#include + +/* + Let t be a stacktrace as-if obtained via basic_stacktrace::current(alloc). Let n be t.size(). + Returns: A basic_stacktrace object where frames_ is direct-non-list-initialized from arguments + t.begin() + min(n, skip), t.end(), and alloc, or an empty basic_stacktrace object if the + initialization of frames_ failed. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip() { + // Use default allocator for simplicity; alloc is covered above + auto st_skip0 = std::stacktrace::current(); + assert(st_skip0.size() >= 2); + auto st_skip1 = std::stacktrace::current(1); + assert(st_skip1.size() >= 1); + assert(st_skip0.size() == st_skip1.size() + 1); + assert(st_skip0[1] == st_skip1[0]); + auto st_skip_many = std::stacktrace::current(1 << 20); + assert(st_skip_many.empty()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current_with_skip(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp new file mode 100644 index 0000000000000..a999845e92e01 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip_depth() { + // current stack is: [this function, main, (possibly something else, e.g. `_start` from libc)] + // so it's probably 3 functions deep -- but certainly at least 2 deep. + auto st = std::stacktrace::current(); + assert(st.size() >= 2); + auto it = st.begin(); + auto entry1 = *(it++); // represents this function + auto entry2 = *(it++); // represents our caller, `main` + + // get current trace again, but skip the 1st + st = std::stacktrace::current(1, 1); + assert(st.size() >= 1); + assert(*st.begin() == entry2); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current_with_skip_depth(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp new file mode 100644 index 0000000000000..ff6a3404a676a --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g -O0 + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; [1] + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; [2] + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; [3] + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); [4] + explicit basic_stacktrace(const allocator_type& alloc) noexcept; [5] + + basic_stacktrace(const basic_stacktrace& other); [6] + basic_stacktrace(basic_stacktrace&& other) noexcept; [7] + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); [8] + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); [9] + basic_stacktrace& operator=(const basic_stacktrace& other); [10] + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); [11] + + ~basic_stacktrace(); [12] +*/ + +#include +#include +#include + +/* + * This file includes tests which ensure any allocations performed by `basic_stacktrace` + * are done via the user-provided allocator. We intercept the usual ways to allocate, + * counting the number of calls. + */ + +unsigned custom_alloc; +unsigned custom_dealloc; + +void* operator new(size_t size) { return malloc(size); } +void* operator new[](size_t size) { return malloc(size); } +void operator delete(void* ptr) noexcept { free(ptr); } +void operator delete(void* ptr, size_t) noexcept { free(ptr); } +void operator delete[](void* ptr) noexcept { free(ptr); } +void operator delete[](void* ptr, size_t) noexcept { free(ptr); } + +template +struct test_alloc { + using size_type = size_t; + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + + template + struct rebind { + using other = test_alloc; + }; + + std::allocator wrapped_{}; + + test_alloc() = default; + + template + test_alloc(test_alloc const& rhs) : wrapped_(rhs.wrapped_) {} + + bool operator==(auto const& rhs) const { return &rhs == this; } + bool operator==(test_alloc const&) const { return true; } + + T* allocate(size_t n) { + ++custom_alloc; + return wrapped_.allocate(n); + } + + auto allocate_at_least(size_t n) { + ++custom_alloc; + return wrapped_.allocate_at_least(n); + } + + void deallocate(T* ptr, size_t n) { + ++custom_dealloc; + wrapped_.deallocate(ptr, n); + } +}; + +/* + (19.6.4.2) [stacktrace.basic.cons], creation and assignment, + only exercising usage of caller-provided allocator. + + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; [1] + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; [2] + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; [3] + + explicit basic_stacktrace(const allocator_type& alloc) noexcept; [5] + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); [8] + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); [9] + + basic_stacktrace& operator=(const basic_stacktrace& other); [10] + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); [11] +*/ + +void do_current_stacktrace() { + using A = test_alloc; + (void)std::basic_stacktrace::current(A()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + { + do_current_stacktrace(); + assert(custom_alloc > 0); + } + assert(custom_dealloc == custom_alloc); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp new file mode 100644 index 0000000000000..ccdd195e2980f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.6) Hash Support + + template<> struct hash; [1] + template struct hash>; [2] + + The specializations are enabled ([unord.hash]). +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace trace; // initially empty + auto hash_val_empty = std::hash()(trace); + trace = /* reassign */ std::stacktrace::current(); + auto hash_val_nonempty = std::hash()(trace); + assert(hash_val_empty != hash_val_nonempty); + + std::stacktrace_entry empty_entry; + assert(std::hash()(empty_entry) == 0); + auto nonempty_entry = trace[0]; + assert(std::hash()(nonempty_entry) != 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp new file mode 100644 index 0000000000000..147ce1f65810d --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.5) Modifiers [stacktrace.basic.mod] + + template + void swap(basic_stacktrace& other) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); + + Effects: Exchanges the contents of *this and other. +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace empty; + auto current = std::stacktrace::current(); + + std::stacktrace a(empty); + std::stacktrace b(current); + assert(a == empty); + assert(b == current); + + a.swap(b); + assert(a == current); + assert(b == empty); + + // TODO(stacktrace23): should we also test swap w/ `select_on_container_swap` case + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp new file mode 100644 index 0000000000000..def734fa56697 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.6) Non-member functions + + ostream& operator<<(ostream& os, const stacktrace_entry& f); + template + ostream& operator<<(ostream& os, const basic_stacktrace& st); +*/ + +#include +#include +#include + +int main(int, char**) { + auto a = std::stacktrace::current(); + + std::stringstream entry_os; + entry_os << a[0]; + assert(entry_os.str() == std::to_string(a[0])); + + std::stringstream trace_os; + trace_os << a; + assert(trace_os.str() == std::to_string(a)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp new file mode 100644 index 0000000000000..281af1c245245 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.6) Non-member functions + + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace empty; + auto current = std::stacktrace::current(); + + std::stacktrace a(empty); + std::stacktrace b(current); + assert(a == empty); + assert(b == current); + + std::swap(a, b); + assert(a == current); + assert(b == empty); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp new file mode 100644 index 0000000000000..ca68883f6a115 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.6) Non-member functions + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); +*/ + +#include +#include + +int main(int, char**) { + auto a = std::stacktrace::current(); + + assert(std::to_string(a[0]).contains("main")); + assert(std::to_string(a[0]).contains("to_string.pass")); + + assert(std::to_string(a).contains("main")); + assert(std::to_string(a).contains("to_string.pass")); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp new file mode 100644 index 0000000000000..4562f33af2687 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reference at(size_type) const; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + auto st = test3(); + + assert(st.at(0)); + assert(st.at(1)); + assert(st.at(2)); + assert(st.at(3)); + + auto f0 = st.at(0); + auto f1 = st.at(1); + auto f2 = st.at(2); + auto f3 = st.at(3); + + auto it = st.begin(); + assert(*it++ == f0); + assert(it != st.end()); + assert(*it++ == f1); + assert(it != st.end()); + assert(*it++ == f2); + assert(it != st.end()); + assert(*it++ == f3); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp new file mode 100644 index 0000000000000..db3189d7cb007 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.begin() == st.end()); + // no longer empty: + st = test3(); + assert(!st.empty()); + assert(st.begin() != st.end()); + auto f0 = st[0]; + auto it = st.begin(); + assert(*it == f0); + assert(it != st.end()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp new file mode 100644 index 0000000000000..3d084d45ef7b2 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.cbegin() == st.cend()); + // no longer empty: + st = test3(); + assert(st.cbegin() != st.cend()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp new file mode 100644 index 0000000000000..5d4d6fc6737f9 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.crbegin() == st.crend()); + // no longer empty: + st = test3(); + assert(st.crbegin() != st.crend()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp new file mode 100644 index 0000000000000..dc3c6baa8b978 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + bool empty() const noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + assert(st.empty()); + st = test3(); + assert(!st.empty()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp new file mode 100644 index 0000000000000..2b1004f38051d --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + allocator_type get_allocator() const noexcept; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace st; + static_assert(std::same_as); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp new file mode 100644 index 0000000000000..81768ec965cff --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + size_type max_size() const noexcept; +*/ + +#include +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + assert(st.max_size() == (std::vector>().max_size())); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp new file mode 100644 index 0000000000000..aab086b47dd32 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reference operator[](size_type) const; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + auto st = test3(); + + assert(st[0]); + assert(st[1]); + assert(st[2]); + assert(st[3]); + + auto f0 = st[0]; + auto f1 = st[1]; + auto f2 = st[2]; + auto f3 = st[3]; + + auto it = st.begin(); + assert(*it++ == f0); + assert(it != st.end()); + assert(*it++ == f1); + assert(it != st.end()); + assert(*it++ == f2); + assert(it != st.end()); + assert(*it++ == f3); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp new file mode 100644 index 0000000000000..f8446d42b5f03 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.rbegin() == st.rend()); + // no longer empty: + st = test3(); + auto it = st.rbegin(); + assert(it != st.rend()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp new file mode 100644 index 0000000000000..cbfac43b4beaf --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + size_type size() const noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + assert(st.size() == 0); + st = test3(); + assert(st.size() > 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp new file mode 100644 index 0000000000000..39a52fd9fd856 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4) Class template basic_stacktrace [stacktrace.basic] + (19.6.4.1) Overview [stacktrace.basic.overview] + +namespace std { + template + class basic_stacktrace { + public: + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using const_iterator = implementation-defined; // see [stacktrace.basic.obs] + using iterator = const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using difference_type = implementation-defined; + using size_type = implementation-defined; + using allocator_type = Allocator; + + // (19.6.4.2) + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); + explicit basic_stacktrace(const allocator_type& alloc) noexcept; + + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); + + ~basic_stacktrace(); + + // (19.6.4.3) + // [stacktrace.basic.obs], observers + allocator_type get_allocator() const noexcept; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + size_type max_size() const noexcept; + + const_reference operator[](size_type) const; + const_reference at(size_type) const; + + // (19.6.4.4) + // [stacktrace.basic.cmp], comparisons + template + friend bool operator==(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + template + friend strong_ordering operator<=>(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + + // (19.6.4.5) + // [stacktrace.basic.mod], modifiers + void swap(basic_stacktrace& other) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); + + private: + vector frames_; // exposition only + }; + + // (19.6.4.6) + // [stacktrace.basic.nonmem], non-member functions + + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); + + ostream& operator<<(ostream& os, const stacktrace_entry& f); + template + ostream& operator<<(ostream& os, const basic_stacktrace& st); +} + +*/ + +#include +#include +#include +#include +#include + +int main(int, char**) { + // This test will only verify these member types exist and are + // defined as expected; their actual behavior is tested in another .cpp. + + using A = std::allocator; + using S = std::basic_stacktrace; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static_assert(std::forward_iterator); + static_assert(std::forward_iterator); + using CRI = S::const_reverse_iterator; + static_assert(std::is_same_v); + using RI = S::reverse_iterator; + static_assert(std::is_same_v); + + using IterT = S::iterator; + using DiffT = S::difference_type; + static_assert(std::is_same_v); + + static_assert(std::is_integral_v); + static_assert(std::is_same_v); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp new file mode 100644 index 0000000000000..4ddd27ed388c6 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.5) Comparison [stacktrace.entry.cmp] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.cmp], comparison + friend constexpr bool operator==(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; +*/ + +#include +#include +#include + +namespace { +int func1() { return 41; } +int func2() { return 42; } +} // namespace + +int main(int, char**) { + std::stacktrace_entry a; + std::stacktrace_entry b; + std::stacktrace_entry c; + *(uintptr_t*)(&a) = uintptr_t(&func1); + *(uintptr_t*)(&b) = uintptr_t(&func1); + *(uintptr_t*)(&c) = uintptr_t(&func2); + assert(a == b); + assert(a != c); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp new file mode 100644 index 0000000000000..edefe70af1fc9 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.5) Comparison [stacktrace.entry.cmp] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.cmp], comparison + friend constexpr strong_ordering operator<=>(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; +*/ + +#include +#include +#include +#include + +namespace { +int func1() { return 41; } +int func2() { return 42; } +} // namespace + +int main(int, char**) { + auto addr1 = uintptr_t(&func1); + auto addr2 = uintptr_t(&func2); + assert(addr1 != addr2); + if (addr1 > addr2) { + std::swap(addr1, addr2); + } + + std::stacktrace_entry a; + std::stacktrace_entry b; + std::stacktrace_entry c; + *(uintptr_t*)(&a) = uintptr_t(addr1); + *(uintptr_t*)(&b) = uintptr_t(addr1); + *(uintptr_t*)(&c) = uintptr_t(addr2); + + assert(std::strong_ordering::equal == (a <=> b)); + assert(std::strong_ordering::equivalent == (a <=> b)); + assert(std::strong_ordering::greater == (c <=> a)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp new file mode 100644 index 0000000000000..c993a6df64508 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.3.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry& operator=(const stacktrace_entry& other) noexcept; +} +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) noexcept { + static_assert(std::is_nothrow_copy_assignable_v); + std::stacktrace_entry entry_t2; + std::stacktrace_entry entry_t4; + entry_t4 = entry_t2; + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp new file mode 100644 index 0000000000000..85dd6fb7853cc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.3.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry(const stacktrace_entry& other) noexcept; +} +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace_entry entry_t2; + static_assert(std::is_nothrow_copy_constructible_v); + std::stacktrace_entry entry_t3(entry_t2); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp new file mode 100644 index 0000000000000..03bd82cfd9c73 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.3.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; +} +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) noexcept { + // "Postconditions: *this is empty." + static_assert(std::is_default_constructible_v); + static_assert(std::is_nothrow_default_constructible_v); + std::stacktrace_entry entry_t2; + assert(!entry_t2); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp new file mode 100644 index 0000000000000..bf1a23080f307 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.3) Observers [stacktrace.entry.obs] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.obs], observers + constexpr native_handle_type native_handle() const noexcept; +*/ + +#include +#include + +int main(int, char**) noexcept { + std::stacktrace_entry entry_t6; + assert(entry_t6.native_handle() == 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp new file mode 100644 index 0000000000000..f1a685324b22d --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.3) Observers [stacktrace.entry.obs] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.obs], observers + constexpr explicit operator bool() const noexcept; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace_entry entry_t6; + // "Returns: false if and only if *this is empty." + assert(!entry_t6); + // Now set addr to something nonzero + *(uintptr_t*)(&entry_t6) = uintptr_t(&main); + assert(entry_t6.native_handle() == uintptr_t(&main)); + assert(entry_t6); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp new file mode 100644 index 0000000000000..39bc5845e9718 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +#include + +#include +#include +#include + +/* + (19.6.3) Class stacktrace_entry [stacktrace.entry] + (19.6.3.1) Overview [stacktrace.entry.overview] + +namespace std { + class stacktrace_entry { + public: + using native_handle_type = implementation-defined; + + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; + constexpr stacktrace_entry(const stacktrace_entry& other) noexcept; + constexpr stacktrace_entry& operator=(const stacktrace_entry& other) noexcept; + ~stacktrace_entry(); + + // [stacktrace.entry.obs], observers + constexpr native_handle_type native_handle() const noexcept; + constexpr explicit operator bool() const noexcept; + + // [stacktrace.entry.query], query + string description() const; + string source_file() const; + uint_least32_t source_line() const; + + // [stacktrace.entry.cmp], comparison + friend constexpr bool operator==(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + friend constexpr strong_ordering operator<=>(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + }; +} +*/ + +int main(int, char**) { + // [stacktrace.entry.overview] + // "The class stacktrace_entry models regular ([concepts.object]) + // and three_way_comparable ([cmp.concept])." + static_assert(std::regular); + static_assert(std::three_way_comparable); + + // using native_handle_type = implementation-defined; + static_assert(sizeof(std::stacktrace_entry::native_handle_type) >= sizeof(void*)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp new file mode 100644 index 0000000000000..2493e2bd8b490 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +/* + (19.6.3.4) Query [stacktrace.entry.query] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.query], query + string description() const; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + auto desc = e.description(); + assert(desc.empty()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp new file mode 100644 index 0000000000000..fc2d5efb6a31e --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +/* + (19.6.3.4) Query [stacktrace.entry.query] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.query], query + string source_file() const; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + auto src = e.source_file(); + assert(src.empty()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp new file mode 100644 index 0000000000000..e601b74f032f0 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +/* + (19.6.3.4) Query [stacktrace.entry.query] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.query], query + uint_least32_t source_line() const; +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + assert(e.source_line() == 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp new file mode 100644 index 0000000000000..bda374e0b2d90 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.5) Formatting support [stacktrace.format] + + template struct formatter>; +*/ + +#include +// #include + +int main(int, char**) { + /* + For formatter>, format-spec is empty. + + A basic_stacktrace object s is formatted as if by copying to_string(s) through the + output iterator of the context. + */ + + // TODO: stacktrace formatter: https://github.com/llvm/llvm-project/issues/105257 + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.pass.cpp new file mode 100644 index 0000000000000..acd04ae33dfc3 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.5) Formatting support [stacktrace.format] + + template<> struct formatter; +*/ + +#include +// #include + +int main(int, char**) { + /* + formatter interprets format-spec as a stacktrace-entry-format-spec. + The syntax of format specifications is as follows: + + stacktrace-entry-format-spec : + fill-and-align_[opt] width_[opt] + + [Note 1: The productions fill-and-align and width are described in [format.string.std]. - end note] + + A stacktrace_entry object se is formatted as if by copying to_string(se) through the output iterator + of the context with additional padding and adjustments as specified by the format specifiers. + */ + + // TODO: stacktrace formatter: https://github.com/llvm/llvm-project/issues/105257 + + return 0; +} diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp new file mode 100644 index 0000000000000..461087467a270 --- /dev/null +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -0,0 +1,108 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by generate_feature_test_macro_components.py +// and should not be edited manually. + +// + +// Test the feature test macros defined by + +// clang-format off + +#include +#include "test_macros.h" + +#if TEST_STD_VER < 14 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 14 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 17 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 20 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_formatters +# error "__cpp_lib_formatters should be defined in c++23" +# endif +# if __cpp_lib_formatters != 202302L +# error "__cpp_lib_formatters should have the value 202302L in c++23" +# endif +# else +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined because it is unimplemented in libc++!" +# endif +# endif + +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++23" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++23" +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_formatters +# error "__cpp_lib_formatters should be defined in c++26" +# endif +# if __cpp_lib_formatters != 202302L +# error "__cpp_lib_formatters should have the value 202302L in c++26" +# endif +# else +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined because it is unimplemented in libc++!" +# endif +# endif + +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++26" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++26" +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp index 7bd8e8979e6f3..e9b95901b9444 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp @@ -5937,17 +5937,11 @@ # error "__cpp_lib_sstream_from_string_view should not be defined before c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should be defined in c++23" -# endif -# if __cpp_lib_stacktrace != 202011L -# error "__cpp_lib_stacktrace should have the value 202011L in c++23" -# endif -# else -# ifdef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++23" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++23" # endif # ifndef __cpp_lib_starts_ends_with @@ -7900,17 +7894,11 @@ # error "__cpp_lib_sstream_from_string_view should have the value 202306L in c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should be defined in c++26" -# endif -# if __cpp_lib_stacktrace != 202011L -# error "__cpp_lib_stacktrace should have the value 202011L in c++26" -# endif -# else -# ifdef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++26" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++26" # endif # ifndef __cpp_lib_starts_ends_with diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index d9317e00e3f4a..0efe7d06812f8 100644 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -1313,7 +1313,6 @@ def add_version_header(tc): "name": "__cpp_lib_stacktrace", "values": {"c++23": 202011}, "headers": ["stacktrace"], - "unimplemented": True, }, { "name": "__cpp_lib_starts_ends_with", diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py index d06271a7908cc..410ffce8795a2 100644 --- a/libcxx/utils/libcxx/header_information.py +++ b/libcxx/utils/libcxx/header_information.py @@ -170,7 +170,6 @@ def __hash__(self) -> int: "linalg", "rcu", "spanstream", - "stacktrace", "stdfloat", "text_encoding", ])) @@ -198,6 +197,7 @@ def __hash__(self) -> int: "print": "// UNSUPPORTED: no-filesystem, c++03, c++11, c++14, c++17, c++20, availability-fp_to_chars-missing", # TODO PRINT investigate "semaphore": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", "shared_mutex": "// UNSUPPORTED: no-threads, c++03, c++11", + "stacktrace": "// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20", "stdatomic.h": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17, c++20", "stop_token": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", "thread": "// UNSUPPORTED: no-threads, c++03", @@ -244,6 +244,7 @@ def __hash__(self) -> int: "regex": ["compare", "initializer_list"], "set": ["compare", "initializer_list"], "stack": ["compare", "initializer_list"], + "stacktrace": ["compare"], "string_view": ["compare"], "string": ["compare", "initializer_list"], "syncstream": ["ostream"], From 2220781f973e94565c2cfa1b67785542925600c8 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Tue, 8 Jul 2025 20:48:04 -0400 Subject: [PATCH 02/19] Address formatting and other issues --- .../support.limits.general/stacktrace.version.compile.pass.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp index 461087467a270..8d8ce5665565b 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -105,4 +105,3 @@ #endif // TEST_STD_VER > 23 // clang-format on - From 1919614257e177744450952134f2ba42e2f89fe4 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Tue, 8 Jul 2025 22:32:32 -0400 Subject: [PATCH 03/19] update headers and modules --- libcxx/include/module.modulemap.in | 10 ++-------- libcxx/src/stacktrace/linux/impl.h | 3 ++- libcxx/src/stacktrace/macos/impl.h | 3 ++- libcxx/src/stacktrace/tools/tools.cpp | 7 +++---- libcxx/src/stacktrace/unwind/impl.cpp | 5 +++-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index c708d69e5dabc..39f1428888cc7 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1336,10 +1336,7 @@ module std [system] { module concepts { header "__format/concepts.h" } module container_adaptor { header "__format/container_adaptor.h" } module enable_insertable { header "__format/enable_insertable.h" } - module escaped_output_table { - header "__format/escaped_output_table.h" - export std.format.escaped_output_table // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 - } + module escaped_output_table { header "__format/escaped_output_table.h" } module extended_grapheme_cluster_table { header "__format/extended_grapheme_cluster_table.h" } module fmt_pair_like { header "__format/fmt_pair_like.h" } module format_arg { header "__format/format_arg.h" } @@ -1376,10 +1373,7 @@ module std [system] { module range_format { header "__format/range_format.h" } module range_formatter { header "__format/range_formatter.h" } module unicode { header "__format/unicode.h" } - module width_estimation_table { - header "__format/width_estimation_table.h" - export std.format.width_estimation_table // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 - } + module width_estimation_table { header "__format/width_estimation_table.h" } module write_escaped { header "__format/write_escaped.h" } header "format" diff --git a/libcxx/src/stacktrace/linux/impl.h b/libcxx/src/stacktrace/linux/impl.h index cd028d070835f..1f6ca6ef2b413 100644 --- a/libcxx/src/stacktrace/linux/impl.h +++ b/libcxx/src/stacktrace/linux/impl.h @@ -34,7 +34,8 @@ struct linux { } // namespace __stacktrace _LIBCPP_END_NAMESPACE_STD -#include "stacktrace/config.h" +#include <__config> +#include <__config_site> #if defined(__linux__) diff --git a/libcxx/src/stacktrace/macos/impl.h b/libcxx/src/stacktrace/macos/impl.h index 42b8b89d7bbb9..e0a24228a6ade 100644 --- a/libcxx/src/stacktrace/macos/impl.h +++ b/libcxx/src/stacktrace/macos/impl.h @@ -14,7 +14,8 @@ #include #include -#include "stacktrace/config.h" +#include <__config> +#include <__config_site> #include <__stacktrace/base.h> _LIBCPP_BEGIN_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp index aa573567377e8..8f04a1806bbc7 100644 --- a/libcxx/src/stacktrace/tools/tools.cpp +++ b/libcxx/src/stacktrace/tools/tools.cpp @@ -6,12 +6,11 @@ // //===----------------------------------------------------------------------===// -#include "stacktrace/config.h" +#include <__config> +#include <__config_site> -#if defined(_LIBCPP_STACKTRACE_CAN_SPAWN_TOOLS) +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME -# include <__config> -# include <__config_site> # include # include # include diff --git a/libcxx/src/stacktrace/unwind/impl.cpp b/libcxx/src/stacktrace/unwind/impl.cpp index 90603e0422b9b..4742988dac64c 100644 --- a/libcxx/src/stacktrace/unwind/impl.cpp +++ b/libcxx/src/stacktrace/unwind/impl.cpp @@ -6,9 +6,10 @@ // //===----------------------------------------------------------------------===// -#include "stacktrace/config.h" +#include <__config> +#include <__config_site> -#if defined(_LIBCPP_STACKTRACE_UNWIND_IMPL) +#if __has_include() # include From 6ccdac9ea6dbc13b7b29c5ac0be1814fc7a5fa98 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Tue, 8 Jul 2025 23:18:48 -0400 Subject: [PATCH 04/19] Break up large linux impl --- libcxx/include/__stacktrace/base.h | 2 +- libcxx/include/__stacktrace/basic.h | 2 +- libcxx/include/__stacktrace/hash.h | 1 + libcxx/include/module.modulemap.in | 2 + ...bcxxabi.v1.stable.exceptions.nonew.abilist | 11 - libcxx/src/CMakeLists.txt | 2 + libcxx/src/stacktrace/linux/elf.cpp | 98 +++++ libcxx/src/stacktrace/linux/elf.h | 248 ++++++++++++ libcxx/src/stacktrace/linux/images.cpp | 72 ++++ libcxx/src/stacktrace/linux/images.h | 50 +++ libcxx/src/stacktrace/linux/impl.cpp | 6 +- libcxx/src/stacktrace/linux/impl.h | 365 ------------------ .../stacktrace.version.compile.pass.cpp | 1 + 13 files changed, 480 insertions(+), 380 deletions(-) create mode 100644 libcxx/src/stacktrace/linux/elf.cpp create mode 100644 libcxx/src/stacktrace/linux/elf.h create mode 100644 libcxx/src/stacktrace/linux/images.cpp create mode 100644 libcxx/src/stacktrace/linux/images.h diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h index 52e6d17e4e5be..854071001a8b5 100644 --- a/libcxx/include/__stacktrace/base.h +++ b/libcxx/include/__stacktrace/base.h @@ -69,7 +69,7 @@ struct _LIBCPP_HIDE_FROM_ABI alloc final { template bool operator==(_A2 const& __rhs) const { - return &__rhs == this; + return std::addressof(__rhs) == this; } }; diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h index a73c8d56f6c21..ca958e55f0614 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic.h @@ -55,7 +55,7 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { [[no_unique_address]] _Allocator __alloc_; - using __entry_vec = vector; + using __entry_vec _LIBCPP_NODEBUG = vector; __entry_vec __entries_; public: diff --git a/libcxx/include/__stacktrace/hash.h b/libcxx/include/__stacktrace/hash.h index 297507f5415fa..a507c0b68db3f 100644 --- a/libcxx/include/__stacktrace/hash.h +++ b/libcxx/include/__stacktrace/hash.h @@ -12,6 +12,7 @@ #include <__config> #include <__functional/hash.h> +#include #include #include <__stacktrace/base.h> diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 39f1428888cc7..a37b91584bb03 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -2023,7 +2023,9 @@ module std [system] { // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 export std.cstddef.byte export std.cstddef.size_t + export std.format.escaped_output_table export std.format.formatter + export std.format.width_estimation_table export std.functional.function export std.functional.hash export std.iterator.iterator diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist index d954b1926115e..8e8ae5bdd27da 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -35,7 +35,6 @@ {'is_defined': False, 'name': '_ZTVSt13runtime_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt14overflow_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt16invalid_argument', 'size': 0, 'type': 'OBJECT'} -{'is_defined': False, 'name': '_ZTVSt9exception', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZdaPv', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdaPvSt11align_val_t', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdlPv', 'type': 'FUNC'} @@ -226,10 +225,6 @@ {'is_defined': True, 'name': '_ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11descriptionEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_fileEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_lineEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry13native_handleEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} @@ -1927,9 +1922,6 @@ {'is_defined': True, 'name': '_ZTTNSt3__19strstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental15fundamentals_v112bad_any_castE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 88, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 88, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 88, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110istrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb0EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb1EEE', 'size': 112, 'type': 'OBJECT'} @@ -1940,7 +1932,6 @@ {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace10fd_istreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4atosE', 'size': 48, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4toolE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace6failedE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace9addr2lineE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112bad_weak_ptrE', 'size': 40, 'type': 'OBJECT'} @@ -2005,8 +1996,6 @@ {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDiEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDsEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IwEE', 'size': 96, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIcEE', 'size': 72, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIwEE', 'size': 72, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr15memory_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr25monotonic_buffer_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr26synchronized_pool_resourceE', 'size': 56, 'type': 'OBJECT'} diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index f93b58ebc6ba9..92d08b13a26b5 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -41,6 +41,8 @@ set(LIBCXX_SOURCES ryu/d2s.cpp ryu/f2s.cpp stacktrace/builder.cpp + stacktrace/linux/elf.cpp + stacktrace/linux/images.cpp stacktrace/linux/impl.cpp stacktrace/macos/impl.cpp stacktrace/to_string.cpp diff --git a/libcxx/src/stacktrace/linux/elf.cpp b/libcxx/src/stacktrace/linux/elf.cpp new file mode 100644 index 0000000000000..b6d05f33a9624 --- /dev/null +++ b/libcxx/src/stacktrace/linux/elf.cpp @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if defined(__linux__) + +# include "stacktrace/linux/elf.h" + +# include <__stacktrace/base.h> +# include +# include +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace::elf { + +ELF::ELF(std::byte const* image) { + auto* p = (uint8_t const*)image; + // Bytes 0..3: magic bytes: 0x7F, 'E', 'L', 'F' + if (*p++ == 0x7f && *p++ == 0x45 && *p++ == 0x4c && *p++ == 0x46) { + auto klass = *p++; // Byte 4 (EI_CLASS): ELF class, 32- or 64-bit (0x01 or 0x02) + auto dataFormat = *p++; // Byte 5 (EI_DATA): (0x01) little- or (0x02) big-endian + auto fileVersion = *p++; // Byte 6 (EI_VERSION): ELF version: expect 1 (latest ELF version) + constexpr static uint16_t kEndianTestWord{0x0201}; + auto hostEndianness = *(uint8_t const*)&kEndianTestWord; + if (dataFormat == hostEndianness && fileVersion == 1) { + if (klass == 0x01) { + header_ = Header((Header32 const*)image); + makeSection_ = makeSection32; + makeSymbol_ = makeSymbol32; + secSize_ = sizeof(Section32); + symSize_ = sizeof(Symbol32); + } else if (klass == 0x02) { + header_ = Header((Header64 const*)image); + makeSection_ = makeSection64; + makeSymbol_ = makeSymbol64; + secSize_ = sizeof(Section64); + symSize_ = sizeof(Symbol64); + } + } + } + if (*this) { + nametab_ = section(header_.shstrndx_); + eachSection([&](auto& sec) mutable -> bool { + if (sec.type_ == Section::kSymTab && sec.name() == ".symtab") { + symtab_ = sec; + } else if (sec.type_ == Section::kStrTab && sec.name() == ".strtab") { + strtab_ = sec; + } + return !symtab_ || !strtab_; + }); + } + if (symtab_) { + symCount_ = symtab_.size_ / symSize_; + } +} + +Section ELF::section(size_t index) { + auto* addr = header_.ptr_ + header_.shoff_ + (index * secSize_); + return makeSection_(this, addr); +} + +Symbol ELF::symbol(size_t index) { + auto* addr = symtab_.data() + (index * symSize_); + return makeSymbol_(this, addr); +} + +void ELF::eachSection(CB
cb) { + for (size_t i = 0; i < header_.shnum_ && cb(section(i)); i++) + ; +} + +void ELF::eachSymbol(CB cb) { + for (size_t i = 0; i < symCount_ && cb(symbol(i)); i++) + ; +} + +Symbol ELF::getSym(uintptr_t addr) { + Symbol ret{}; + eachSymbol([&](auto& sym) -> bool { + if (sym.value_ <= addr && sym.value_ > ret.value_) { + ret = sym; + } + return true; + }); + return ret; +} + +} // namespace __stacktrace::elf +_LIBCPP_END_NAMESPACE_STD + +#endif // defined(__linux__) diff --git a/libcxx/src/stacktrace/linux/elf.h b/libcxx/src/stacktrace/linux/elf.h new file mode 100644 index 0000000000000..ebf47ed964b97 --- /dev/null +++ b/libcxx/src/stacktrace/linux/elf.h @@ -0,0 +1,248 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_LINUX_ELF +#define _LIBCPP_STACKTRACE_LINUX_ELF + +#include <__stacktrace/base.h> +#include +#include +#include +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace::elf { + +// Includes ELF constants and structs copied from , with a few changes. + +struct Header32 final { + uint8_t ident[16]; /* Magic number and other info */ + uint16_t type; /* Object file type */ + uint16_t machine; /* Architecture */ + uint32_t version; /* Object file version */ + uint32_t entry; /* Entry point virtual address */ + uint32_t phoff; /* Program header table file offset */ + uint32_t shoff; /* Section header table file offset */ + uint32_t flags; /* Processor-specific flags */ + uint16_t ehsize; /* ELF header size in bytes */ + uint16_t phentsize; /* Program header table entry size */ + uint16_t phnum; /* Program header table entry count */ + uint16_t shentsize; /* Section header table entry size */ + uint16_t shnum; /* Section header table entry count */ + uint16_t shstrndx; /* Section header string table index */ +}; + +struct Section32 final { + uint32_t name; /* Section name (string tbl index) */ + uint32_t type; /* Section type */ + uint32_t flags; /* Section flags */ + uint32_t addr; /* Section virtual addr at execution */ + uint32_t offset; /* Section file offset */ + uint32_t size; /* Section size in bytes */ + uint32_t link; /* Link to another section */ + uint32_t info; /* Additional section information */ + uint32_t addralign; /* Section alignment */ + uint32_t entsize; /* Entry size if section holds table */ +}; + +struct Symbol32 final { + uint32_t name; /* Symbol name (string tbl index) */ + uint32_t value; /* Symbol value */ + uint32_t size; /* Symbol size */ + uint8_t info; /* Symbol type and binding */ + uint8_t other; /* Symbol visibility */ + uint16_t shndx; /* Section index */ +}; + +struct Header64 final { + uint8_t ident[16]; /* Magic number and other info */ + uint16_t type; /* Object file type */ + uint16_t machine; /* Architecture */ + uint32_t version; /* Object file version */ + uint64_t entry; /* Entry point virtual address */ + uint64_t phoff; /* Program header table file offset */ + uint64_t shoff; /* Section header table file offset */ + uint32_t flags; /* Processor-specific flags */ + uint16_t ehsize; /* ELF header size in bytes */ + uint16_t phentsize; /* Program header table entry size */ + uint16_t phnum; /* Program header table entry count */ + uint16_t shentsize; /* Section header table entry size */ + uint16_t shnum; /* Section header table entry count */ + uint16_t shstrndx; /* Section header string table index */ +}; + +struct Section64 final { + uint32_t name; /* Section name (string tbl index) */ + uint32_t type; /* Section type */ + uint64_t flags; /* Section flags */ + uint64_t addr; /* Section virtual addr at execution */ + uint64_t offset; /* Section file offset */ + uint64_t size; /* Section size in bytes */ + uint32_t link; /* Link to another section */ + uint32_t info; /* Additional section information */ + uint64_t addralign; /* Section alignment */ + uint64_t entsize; /* Entry size if section holds table */ +}; + +struct Symbol64 final { + uint32_t name; /* Symbol name (string tbl index) */ + uint8_t info; /* Symbol type and binding */ + uint8_t other; /* Symbol visibility */ + uint16_t shndx; /* Section index */ + uint64_t value; /* Symbol value */ + uint64_t size; /* Symbol size */ +}; + +/** Represents an ELF header. Supports the minimum needed to navigate an ELF file's sections and get at the symbol and + * string tables. */ +struct Header final { + std::byte const* ptr_{}; + uintptr_t shoff_{}; + size_t shnum_{}; + size_t shstrndx_{}; + + Header() = default; + Header(Header const&) = default; + Header& operator=(Header const& rhs) { return *new (this) Header(rhs); } + + operator bool() { return ptr_; } + + template + explicit Header(H* h) + : ptr_((std::byte const*)h), + shoff_(uintptr_t(h->shoff)), + shnum_(size_t(h->shnum)), + shstrndx_(size_t(h->shstrndx)) {} +}; + +struct ELF; +struct StringTable; + +struct Section final { + constexpr static uint32_t kSymTab = 2; // symbol table + constexpr static uint32_t kStrTab = 3; // name table for symbols or sections + + ELF* elf_{}; + std::byte const* ptr_{}; + uintptr_t nameIndex_{}; + uint32_t type_{}; + uintptr_t offset_{}; + size_t size_{}; + + Section() = default; + + template + Section(ELF* elf, S* sec) + : elf_(elf), + ptr_((std::byte const*)sec), + nameIndex_(sec->name), + type_(sec->type), + offset_(sec->offset), + size_(sec->size) {} + + operator bool() const { return ptr_; } + + template + T const* data() const { + return (T const*)(elfBase() + offset_); + } + + std::byte const* elfBase() const; + std::string_view name() const; +}; + +struct Symbol final { + constexpr static uint8_t kFunc = 0x02; // STT_FUNC (code object) + + ELF* elf_{}; + std::byte const* ptr_{}; + uintptr_t nameIndex_{}; + uint32_t type_{}; + uintptr_t value_{}; + + Symbol() = default; + Symbol(Symbol const&) = default; + Symbol& operator=(Symbol const& rhs) { return *new (this) Symbol(rhs); } + + operator bool() { return ptr_; } + + bool isCode() const { return type_ == kFunc; } + + template + Symbol(ELF* elf, S* sym) + : elf_(elf), ptr_((std::byte const*)sym), nameIndex_(sym->name), type_(0x0f & sym->info), value_(sym->value) {} + + std::byte const* elfBase() const; + std::string_view name() const; +}; + +/** Represents one of the ELF's `strtab`s. This is a block of string data, with strings appended one after another, and + * NUL-terminated. Strings are indexed according to their starting offset. At offset 0 is typically an empty string. + */ +struct StringTable { + std::string_view data_{}; + + StringTable() = default; + + /* implicit */ StringTable(Section const& sec) : data_(sec.data(), sec.size_) {} + + operator bool() { return data_.size(); } + + std::string_view at(size_t index) { + auto* ret = data_.data() + index; + return {ret, strlen(ret)}; + } +}; + +/** Encapsulates an ELF image specified by byte-address (e.g. from an mmapped file or a program image or shared object + * in memory). If given a supported ELF image, this will test true with `operator bool` to indicate it is supported and + * was able to parse some basic information from the header. */ +struct ELF { + Header header_{}; + Section (*makeSection_)(ELF*, std::byte const*){}; + Symbol (*makeSymbol_)(ELF*, std::byte const*){}; + size_t secSize_{}; + size_t symSize_{}; + StringTable nametab_{}; + Section symtab_{}; + StringTable strtab_{}; + size_t symCount_{}; + + static Section makeSection32(ELF* elf, std::byte const* ptr) { return Section(elf, (Section32 const*)ptr); } + static Section makeSection64(ELF* elf, std::byte const* ptr) { return Section(elf, (Section64 const*)ptr); } + static Symbol makeSymbol32(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol32 const*)ptr); } + static Symbol makeSymbol64(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol64 const*)ptr); } + + operator bool() { return header_; } + + explicit ELF(std::byte const* image); + + Section section(size_t index); + Symbol symbol(size_t index); + + template + using CB = std::function; + + void eachSection(CB
cb); + void eachSymbol(CB cb); + + Symbol getSym(uintptr_t addr); +}; + +inline std::byte const* Section::elfBase() const { return elf_->header_.ptr_; } +inline std::byte const* Symbol::elfBase() const { return elf_->header_.ptr_; } + +inline std::string_view Section::name() const { return elf_->nametab_.at(nameIndex_); } +inline std::string_view Symbol::name() const { return elf_->strtab_.at(nameIndex_); } + +} // namespace __stacktrace::elf +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_LINUX_ELF diff --git a/libcxx/src/stacktrace/linux/images.cpp b/libcxx/src/stacktrace/linux/images.cpp new file mode 100644 index 0000000000000..1ff3dc80253e4 --- /dev/null +++ b/libcxx/src/stacktrace/linux/images.cpp @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if defined(__linux__) + +# include <__stacktrace/base.h> + +# include "stacktrace/linux/images.h" +# include "stacktrace/utils/image.h" + +# include +# include +# include +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +images images::instance; + +int images::add(dl_phdr_info& info) { + assert(count_ < image::kMaxImages); + auto isFirst = (count_ == 0); + auto& image = images_.at(count_++); + image.loaded_at_ = info.dlpi_addr; + image.slide_ = info.dlpi_addr; + image.name_ = info.dlpi_name; + image.is_main_prog_ = isFirst; // first one is the main ELF + if (image.name_.empty() && isFirst) { + static char buffer[PATH_MAX + 1]; + uint32_t length = sizeof(buffer); + if (readlink("/proc/self/exe", buffer, length) > 0) { + image.name_ = buffer; + } + } + return count_ == image::kMaxImages; // return nonzero if we're at the limit +} + +int images::callback(dl_phdr_info* info, size_t, void* self) { return (*(images*)(self)).add(*info); } + +images::images() { + dl_iterate_phdr(images::callback, this); + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + std::sort(images_.begin(), images_.begin() + count_ - 1); +} + +image& images::operator[](size_t index) { + assert(index < count_); + return images_.at(index); +} + +image* images::mainProg() { + for (auto& image : images_) { + if (image.is_main_prog_) { + return ℑ + } + } + return nullptr; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __linux__ diff --git a/libcxx/src/stacktrace/linux/images.h b/libcxx/src/stacktrace/linux/images.h new file mode 100644 index 0000000000000..bd7ac8ff77bf0 --- /dev/null +++ b/libcxx/src/stacktrace/linux/images.h @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_LINUX_IMAGES +#define _LIBCPP_STACKTRACE_LINUX_IMAGES + +#if defined(__linux__) + +# include <__stacktrace/base.h> + +# include +# include +# include +# include +# include +# include + +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct images { + // How many images this contains, including the left/right sentinels. + unsigned count_{0}; + std::array images_{}; + + images(); + + int add(dl_phdr_info& info); + static int callback(dl_phdr_info* info, size_t, void* self); + + image& operator[](size_t index); + + image* mainProg(); + + static images instance; +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __linux__ + +#endif // _LIBCPP_STACKTRACE_LINUX_IMAGES diff --git a/libcxx/src/stacktrace/linux/impl.cpp b/libcxx/src/stacktrace/linux/impl.cpp index 050eb1f0088e7..3f18d015aa93f 100644 --- a/libcxx/src/stacktrace/linux/impl.cpp +++ b/libcxx/src/stacktrace/linux/impl.cpp @@ -15,6 +15,8 @@ # include # include "stacktrace/config.h" +# include "stacktrace/linux/elf.h" +# include "stacktrace/linux/images.h" # include "stacktrace/linux/impl.h" # include "stacktrace/utils/fd.h" # include "stacktrace/utils/image.h" @@ -23,7 +25,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { void linux::ident_modules() { - auto& images = images::get(); + auto& images = images::instance; // Aside from the left/right sentinels in the array (hence the 2), // are there any other real images? @@ -100,7 +102,7 @@ void linux::symbolize() { // Symbols might be missing, because both (1) Linux's `dladdr` won't try to resolve non-exported symbols, // which can be the case for the main program executable; and (2) debug info was not preserved. // As a last resort, this function (see `linux-elf.cpp`) can still access symbol table directly. - image* mainELF = images::get().mainProg(); + image* mainELF = images::instance.mainProg(); if (mainELF && !mainELF->name_.empty()) { resolve_main_elf_syms(mainELF->name_); } diff --git a/libcxx/src/stacktrace/linux/impl.h b/libcxx/src/stacktrace/linux/impl.h index 1f6ca6ef2b413..0a91bf88fcf9b 100644 --- a/libcxx/src/stacktrace/linux/impl.h +++ b/libcxx/src/stacktrace/linux/impl.h @@ -34,369 +34,4 @@ struct linux { } // namespace __stacktrace _LIBCPP_END_NAMESPACE_STD -#include <__config> -#include <__config_site> - -#if defined(__linux__) - -# include -# include -# include -# include -# include -# include -# include -# include -# include - -# include "stacktrace/utils/image.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct images { - // How many images this contains, including the left/right sentinels. - unsigned count_{0}; - std::array images_{}; - - int add(dl_phdr_info& info) { - assert(count_ < image::kMaxImages); - auto isFirst = (count_ == 0); - auto& image = images_.at(count_++); - image.loaded_at_ = info.dlpi_addr; - image.slide_ = info.dlpi_addr; - image.name_ = info.dlpi_name; - image.is_main_prog_ = isFirst; // first one is the main ELF - if (image.name_.empty() && isFirst) { - static char buffer[PATH_MAX + 1]; - uint32_t length = sizeof(buffer); - if (readlink("/proc/self/exe", buffer, length) > 0) { - image.name_ = buffer; - } - } - return count_ == image::kMaxImages; // return nonzero if we're at the limit - } - - static int callback(dl_phdr_info* info, size_t, void* self) { return (*(images*)(self)).add(*info); } - - images() { - dl_iterate_phdr(images::callback, this); - images_[count_++] = {0uz, 0}; // sentinel at low end - images_[count_++] = {~0uz, 0}; // sentinel at high end - std::sort(images_.begin(), images_.begin() + count_ - 1); - } - - image& operator[](size_t index) { - assert(index < count_); - return images_.at(index); - } - - image* mainProg() { - for (auto& image : images_) { - if (image.is_main_prog_) { - return ℑ - } - } - return nullptr; - } - - static images& get() { - static images images; - return images; - } -}; - -// Includes ELF constants and structs copied from , with a few changes. - -namespace elf { - -struct Header32 final { - uint8_t ident[16]; /* Magic number and other info */ - uint16_t type; /* Object file type */ - uint16_t machine; /* Architecture */ - uint32_t version; /* Object file version */ - uint32_t entry; /* Entry point virtual address */ - uint32_t phoff; /* Program header table file offset */ - uint32_t shoff; /* Section header table file offset */ - uint32_t flags; /* Processor-specific flags */ - uint16_t ehsize; /* ELF header size in bytes */ - uint16_t phentsize; /* Program header table entry size */ - uint16_t phnum; /* Program header table entry count */ - uint16_t shentsize; /* Section header table entry size */ - uint16_t shnum; /* Section header table entry count */ - uint16_t shstrndx; /* Section header string table index */ -}; - -struct Section32 final { - uint32_t name; /* Section name (string tbl index) */ - uint32_t type; /* Section type */ - uint32_t flags; /* Section flags */ - uint32_t addr; /* Section virtual addr at execution */ - uint32_t offset; /* Section file offset */ - uint32_t size; /* Section size in bytes */ - uint32_t link; /* Link to another section */ - uint32_t info; /* Additional section information */ - uint32_t addralign; /* Section alignment */ - uint32_t entsize; /* Entry size if section holds table */ -}; - -struct Symbol32 final { - uint32_t name; /* Symbol name (string tbl index) */ - uint32_t value; /* Symbol value */ - uint32_t size; /* Symbol size */ - uint8_t info; /* Symbol type and binding */ - uint8_t other; /* Symbol visibility */ - uint16_t shndx; /* Section index */ -}; - -struct Header64 final { - uint8_t ident[16]; /* Magic number and other info */ - uint16_t type; /* Object file type */ - uint16_t machine; /* Architecture */ - uint32_t version; /* Object file version */ - uint64_t entry; /* Entry point virtual address */ - uint64_t phoff; /* Program header table file offset */ - uint64_t shoff; /* Section header table file offset */ - uint32_t flags; /* Processor-specific flags */ - uint16_t ehsize; /* ELF header size in bytes */ - uint16_t phentsize; /* Program header table entry size */ - uint16_t phnum; /* Program header table entry count */ - uint16_t shentsize; /* Section header table entry size */ - uint16_t shnum; /* Section header table entry count */ - uint16_t shstrndx; /* Section header string table index */ -}; - -struct Section64 final { - uint32_t name; /* Section name (string tbl index) */ - uint32_t type; /* Section type */ - uint64_t flags; /* Section flags */ - uint64_t addr; /* Section virtual addr at execution */ - uint64_t offset; /* Section file offset */ - uint64_t size; /* Section size in bytes */ - uint32_t link; /* Link to another section */ - uint32_t info; /* Additional section information */ - uint64_t addralign; /* Section alignment */ - uint64_t entsize; /* Entry size if section holds table */ -}; - -struct Symbol64 final { - uint32_t name; /* Symbol name (string tbl index) */ - uint8_t info; /* Symbol type and binding */ - uint8_t other; /* Symbol visibility */ - uint16_t shndx; /* Section index */ - uint64_t value; /* Symbol value */ - uint64_t size; /* Symbol size */ -}; - -/** Represents an ELF header. Supports the minimum needed to navigate an ELF file's sections and get at the symbol and - * string tables. */ -struct Header final { - std::byte const* ptr_{}; - uintptr_t shoff_{}; - size_t shnum_{}; - size_t shstrndx_{}; - - Header() = default; - Header(Header const&) = default; - Header& operator=(Header const& rhs) { return *new (this) Header(rhs); } - - operator bool() { return ptr_; } - - template - explicit Header(H* h) - : ptr_((std::byte const*)h), - shoff_(uintptr_t(h->shoff)), - shnum_(size_t(h->shnum)), - shstrndx_(size_t(h->shstrndx)) {} -}; - -struct ELF; -struct StringTable; - -struct Section final { - constexpr static uint32_t kSymTab = 2; // symbol table - constexpr static uint32_t kStrTab = 3; // name table for symbols or sections - - ELF* elf_{}; - std::byte const* ptr_{}; - uintptr_t nameIndex_{}; - uint32_t type_{}; - uintptr_t offset_{}; - size_t size_{}; - - Section() = default; - - template - Section(ELF* elf, S* sec) - : elf_(elf), - ptr_((std::byte const*)sec), - nameIndex_(sec->name), - type_(sec->type), - offset_(sec->offset), - size_(sec->size) {} - - operator bool() const { return ptr_; } - - template - T const* data() const { - return (T const*)(elfBase() + offset_); - } - - std::byte const* elfBase() const; - std::string_view name() const; -}; - -struct Symbol final { - constexpr static uint8_t kFunc = 0x02; // STT_FUNC (code object) - - ELF* elf_{}; - std::byte const* ptr_{}; - uintptr_t nameIndex_{}; - uint32_t type_{}; - uintptr_t value_{}; - - Symbol() = default; - Symbol(Symbol const&) = default; - Symbol& operator=(Symbol const& rhs) { return *new (this) Symbol(rhs); } - - operator bool() { return ptr_; } - - bool isCode() const { return type_ == kFunc; } - - template - Symbol(ELF* elf, S* sym) - : elf_(elf), ptr_((std::byte const*)sym), nameIndex_(sym->name), type_(0x0f & sym->info), value_(sym->value) {} - - std::byte const* elfBase() const; - std::string_view name() const; -}; - -/** Represents one of the ELF's `strtab`s. This is a block of string data, with strings appended one after another, and - * NUL-terminated. Strings are indexed according to their starting offset. At offset 0 is typically an empty string. - */ -struct StringTable { - std::string_view data_{}; - - StringTable() = default; - - /* implicit */ StringTable(Section const& sec) : data_(sec.data(), sec.size_) {} - - operator bool() { return data_.size(); } - - std::string_view at(size_t index) { - auto* ret = data_.data() + index; - return {ret, strlen(ret)}; - } -}; - -/** Encapsulates an ELF image specified by byte-address (e.g. from an mmapped file or a program image or shared object - * in memory). If given a supported ELF image, this will test true with `operator bool` to indicate it is supported and - * was able to parse some basic information from the header. */ -struct ELF { - Header header_{}; - Section (*makeSection_)(ELF*, std::byte const*){}; - Symbol (*makeSymbol_)(ELF*, std::byte const*){}; - size_t secSize_{}; - size_t symSize_{}; - StringTable nametab_{}; - Section symtab_{}; - StringTable strtab_{}; - size_t symCount_{}; - - static Section makeSection32(ELF* elf, std::byte const* ptr) { return Section(elf, (Section32 const*)ptr); } - static Section makeSection64(ELF* elf, std::byte const* ptr) { return Section(elf, (Section64 const*)ptr); } - static Symbol makeSymbol32(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol32 const*)ptr); } - static Symbol makeSymbol64(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol64 const*)ptr); } - - operator bool() { return header_; } - - explicit ELF(std::byte const* image) { - auto* p = (uint8_t const*)image; - // Bytes 0..3: magic bytes: 0x7F, 'E', 'L', 'F' - if (*p++ == 0x7f && *p++ == 0x45 && *p++ == 0x4c && *p++ == 0x46) { - auto klass = *p++; // Byte 4 (EI_CLASS): ELF class, 32- or 64-bit (0x01 or 0x02) - auto dataFormat = *p++; // Byte 5 (EI_DATA): (0x01) little- or (0x02) big-endian - auto fileVersion = *p++; // Byte 6 (EI_VERSION): ELF version: expect 1 (latest ELF version) - constexpr static uint16_t kEndianTestWord{0x0201}; - auto hostEndianness = *(uint8_t const*)&kEndianTestWord; - if (dataFormat == hostEndianness && fileVersion == 1) { - if (klass == 0x01) { - header_ = Header((Header32 const*)image); - makeSection_ = makeSection32; - makeSymbol_ = makeSymbol32; - secSize_ = sizeof(Section32); - symSize_ = sizeof(Symbol32); - } else if (klass == 0x02) { - header_ = Header((Header64 const*)image); - makeSection_ = makeSection64; - makeSymbol_ = makeSymbol64; - secSize_ = sizeof(Section64); - symSize_ = sizeof(Symbol64); - } - } - } - if (*this) { - nametab_ = section(header_.shstrndx_); - eachSection([&](auto& sec) mutable -> bool { - if (sec.type_ == Section::kSymTab && sec.name() == ".symtab") { - symtab_ = sec; - } else if (sec.type_ == Section::kStrTab && sec.name() == ".strtab") { - strtab_ = sec; - } - return !symtab_ || !strtab_; - }); - } - if (symtab_) { - symCount_ = symtab_.size_ / symSize_; - } - } - - Section section(size_t index) { - auto* addr = header_.ptr_ + header_.shoff_ + (index * secSize_); - return makeSection_(this, addr); - } - - Symbol symbol(size_t index) { - auto* addr = symtab_.data() + (index * symSize_); - return makeSymbol_(this, addr); - } - - template - using CB = std::function; - - void eachSection(CB
cb) { - for (size_t i = 0; i < header_.shnum_ && cb(section(i)); i++) - ; - } - - void eachSymbol(CB cb) { - for (size_t i = 0; i < symCount_ && cb(symbol(i)); i++) - ; - } - - Symbol getSym(uintptr_t addr) { - Symbol ret{}; - eachSymbol([&](auto& sym) -> bool { - if (sym.value_ <= addr && sym.value_ > ret.value_) { - ret = sym; - } - return true; - }); - return ret; - } -}; - -inline std::byte const* Section::elfBase() const { return elf_->header_.ptr_; } -inline std::byte const* Symbol::elfBase() const { return elf_->header_.ptr_; } - -inline std::string_view Section::name() const { return elf_->nametab_.at(nameIndex_); } -inline std::string_view Symbol::name() const { return elf_->strtab_.at(nameIndex_); } - -} // namespace elf - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // __linux__ - #endif // _LIBCPP_STACKTRACE_LINUX_IMPL diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp index 8d8ce5665565b..461087467a270 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -105,3 +105,4 @@ #endif // TEST_STD_VER > 23 // clang-format on + From 48e9a9329a35bc1c95bcc9d0033029fb9a0a30e4 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Wed, 9 Jul 2025 13:20:08 -0400 Subject: [PATCH 05/19] Exclude stacktrace headers if C++ < 23 --- libcxx/include/__stacktrace/base.h | 47 ++++++++++--------- libcxx/include/__stacktrace/basic.h | 39 ++++++++------- libcxx/include/__stacktrace/entry.h | 25 +++++----- libcxx/include/__stacktrace/hash.h | 21 +++++---- libcxx/include/__stacktrace/nonmem.h | 23 +++++---- libcxx/include/__stacktrace/to_string.h | 15 +++--- .../stacktrace.version.compile.pass.cpp | 1 - 7 files changed, 94 insertions(+), 77 deletions(-) diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h index 854071001a8b5..92070d35bf6e0 100644 --- a/libcxx/include/__stacktrace/base.h +++ b/libcxx/include/__stacktrace/base.h @@ -7,31 +7,33 @@ // //===----------------------------------------------------------------------===// -#ifndef _LIBCPP_STACKTRACE_BUILDER -#define _LIBCPP_STACKTRACE_BUILDER +#ifndef _LIBCPP_STACKTRACE_BASE +#define _LIBCPP_STACKTRACE_BASE #include <__config> -#include <__cstddef/byte.h> -#include <__cstddef/size_t.h> -#include <__functional/function.h> -#include <__fwd/format.h> -#include <__fwd/ostream.h> -#include <__memory/allocator.h> -#include <__memory/allocator_traits.h> -#include <__new/allocate.h> -#include <__vector/vector.h> -#include -#include -#include -#include -#include - -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif +#if _LIBCPP_STD_VER >= 23 + +# include <__cstddef/byte.h> +# include <__cstddef/size_t.h> +# include <__functional/function.h> +# include <__fwd/format.h> +# include <__fwd/ostream.h> +# include <__memory/allocator.h> +# include <__memory/allocator_traits.h> +# include <__new/allocate.h> +# include <__vector/vector.h> +# include +# include +# include +# include +# include + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif _LIBCPP_PUSH_MACROS -#include <__undef_macros> +# include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD @@ -130,4 +132,5 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS -#endif // _LIBCPP_STACKTRACE_BUILDER +#endif // _LIBCPP_STD_VER >= 23 +#endif // _LIBCPP_STACKTRACE_BASE diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h index ca958e55f0614..6f56d653aa00d 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic.h @@ -11,26 +11,28 @@ #define _LIBCPP_BASIC_STACKTRACE #include <__config> -#include <__functional/hash.h> -#include <__fwd/format.h> -#include <__iterator/iterator.h> -#include <__iterator/reverse_iterator.h> -#include <__memory/allocator_traits.h> -#include <__memory_resource/polymorphic_allocator.h> -#include <__type_traits/is_nothrow_constructible.h> -#include <__vector/vector.h> -#include - -#include <__stacktrace/base.h> -#include <__stacktrace/entry.h> -#include <__stacktrace/to_string.h> - -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif +#if _LIBCPP_STD_VER >= 23 + +# include <__functional/hash.h> +# include <__fwd/format.h> +# include <__iterator/iterator.h> +# include <__iterator/reverse_iterator.h> +# include <__memory/allocator_traits.h> +# include <__memory_resource/polymorphic_allocator.h> +# include <__type_traits/is_nothrow_constructible.h> +# include <__vector/vector.h> +# include + +# include <__stacktrace/base.h> +# include <__stacktrace/entry.h> +# include <__stacktrace/to_string.h> + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif _LIBCPP_PUSH_MACROS -#include <__undef_macros> +# include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD @@ -244,4 +246,5 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS +#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_BASIC_STACKTRACE diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/entry.h index 6290ebea9bc85..fa076eca76de5 100644 --- a/libcxx/include/__stacktrace/entry.h +++ b/libcxx/include/__stacktrace/entry.h @@ -11,21 +11,23 @@ #define _LIBCPP_STACKTRACE_ENTRY #include <__config> -#include <__fwd/format.h> -#include <__fwd/ostream.h> -#include -#include -#include -#include +#if _LIBCPP_STD_VER >= 23 -#include <__stacktrace/base.h> +# include <__fwd/format.h> +# include <__fwd/ostream.h> +# include +# include +# include +# include -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif +# include <__stacktrace/base.h> + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif _LIBCPP_PUSH_MACROS -#include <__undef_macros> +# include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD @@ -110,4 +112,5 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS +#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_STACKTRACE_ENTRY diff --git a/libcxx/include/__stacktrace/hash.h b/libcxx/include/__stacktrace/hash.h index a507c0b68db3f..122ad14144bc4 100644 --- a/libcxx/include/__stacktrace/hash.h +++ b/libcxx/include/__stacktrace/hash.h @@ -11,19 +11,21 @@ #define _LIBCPP_BASIC_STACKTRACE_HASH #include <__config> -#include <__functional/hash.h> -#include -#include +#if _LIBCPP_STD_VER >= 23 -#include <__stacktrace/base.h> -#include <__stacktrace/to_string.h> +# include <__functional/hash.h> +# include +# include -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif +# include <__stacktrace/base.h> +# include <__stacktrace/to_string.h> + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif _LIBCPP_PUSH_MACROS -#include <__undef_macros> +# include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD @@ -45,4 +47,5 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS +#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_BASIC_STACKTRACE_HASH diff --git a/libcxx/include/__stacktrace/nonmem.h b/libcxx/include/__stacktrace/nonmem.h index 570237e08ecd5..81f285b387531 100644 --- a/libcxx/include/__stacktrace/nonmem.h +++ b/libcxx/include/__stacktrace/nonmem.h @@ -11,20 +11,22 @@ #define _LIBCPP_BASIC_STACKTRACE_NONMEM #include <__config> -#include <__memory/allocator_traits.h> -#include <__utility/swap.h> -#include <__vector/vector.h> -#include +#if _LIBCPP_STD_VER >= 23 -#include <__stacktrace/base.h> -#include <__stacktrace/to_string.h> +# include <__memory/allocator_traits.h> +# include <__utility/swap.h> +# include <__vector/vector.h> +# include -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif +# include <__stacktrace/base.h> +# include <__stacktrace/to_string.h> + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif _LIBCPP_PUSH_MACROS -#include <__undef_macros> +# include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD @@ -52,4 +54,5 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS +#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_BASIC_STACKTRACE_NONMEM diff --git a/libcxx/include/__stacktrace/to_string.h b/libcxx/include/__stacktrace/to_string.h index 920c41133d5df..f6fcddf24c5fc 100644 --- a/libcxx/include/__stacktrace/to_string.h +++ b/libcxx/include/__stacktrace/to_string.h @@ -11,15 +11,17 @@ #define _LIBCPP_STACKTRACE_TO_STRING #include <__config> -#include <__fwd/ostream.h> -#include +#if _LIBCPP_STD_VER >= 23 -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif +# include <__fwd/ostream.h> +# include + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif _LIBCPP_PUSH_MACROS -#include <__undef_macros> +# include <__undef_macros> _LIBCPP_BEGIN_NAMESPACE_STD @@ -50,4 +52,5 @@ _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS +#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_STACKTRACE_TO_STRING diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp index 461087467a270..8d8ce5665565b 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -105,4 +105,3 @@ #endif // TEST_STD_VER > 23 // clang-format on - From a30b466fde7a5a41608667c100ff68dc2bb41541 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Wed, 9 Jul 2025 22:49:47 -0400 Subject: [PATCH 06/19] Use proper header format/layout --- libcxx/include/__stacktrace/base.h | 19 ++++++----- libcxx/include/__stacktrace/basic.h | 24 +++++++------- libcxx/include/__stacktrace/entry.h | 18 +++++----- libcxx/include/__stacktrace/hash.h | 24 +++++++------- libcxx/include/__stacktrace/nonmem.h | 24 +++++++------- libcxx/include/__stacktrace/to_string.h | 19 ++++++----- libcxx/include/stacktrace | 33 +++++++++---------- libcxx/modules/std/stacktrace.inc | 3 ++ .../stacktrace.version.compile.pass.cpp | 1 + 9 files changed, 91 insertions(+), 74 deletions(-) diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h index 92070d35bf6e0..4583b2760f246 100644 --- a/libcxx/include/__stacktrace/base.h +++ b/libcxx/include/__stacktrace/base.h @@ -11,6 +11,14 @@ #define _LIBCPP_STACKTRACE_BASE #include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + #if _LIBCPP_STD_VER >= 23 # include <__cstddef/byte.h> @@ -28,13 +36,6 @@ # include # include -# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -# endif - -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry; @@ -128,9 +129,11 @@ struct _LIBCPP_EXPORTED_FROM_ABI builder final { }; } // namespace __stacktrace + _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_STD_VER >= 23 + _LIBCPP_POP_MACROS -#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_STACKTRACE_BASE diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h index 6f56d653aa00d..5798ac9bca628 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic.h @@ -7,10 +7,18 @@ // //===----------------------------------------------------------------------===// -#ifndef _LIBCPP_BASIC_STACKTRACE -#define _LIBCPP_BASIC_STACKTRACE +#ifndef _LIBCPP_STACKTRACE_BASIC +#define _LIBCPP_STACKTRACE_BASIC #include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + #if _LIBCPP_STD_VER >= 23 # include <__functional/hash.h> @@ -27,13 +35,6 @@ # include <__stacktrace/entry.h> # include <__stacktrace/to_string.h> -# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -# endif - -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD // (19.6.4) @@ -244,7 +245,8 @@ using stacktrace = basic_stacktrace>; _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_STD_VER >= 23 + _LIBCPP_POP_MACROS -#endif // _LIBCPP_STD_VER >= 23 -#endif // _LIBCPP_BASIC_STACKTRACE +#endif // _LIBCPP_STACKTRACE_BASIC diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/entry.h index fa076eca76de5..b9d67bcd7adbc 100644 --- a/libcxx/include/__stacktrace/entry.h +++ b/libcxx/include/__stacktrace/entry.h @@ -11,6 +11,14 @@ #define _LIBCPP_STACKTRACE_ENTRY #include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + #if _LIBCPP_STD_VER >= 23 # include <__fwd/format.h> @@ -22,13 +30,6 @@ # include <__stacktrace/base.h> -# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -# endif - -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_base { @@ -110,7 +111,8 @@ _LIBCPP_HIDE_FROM_ABI inline stacktrace_entry entry_base::to_stacktrace_entry() _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_STD_VER >= 23 + _LIBCPP_POP_MACROS -#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_STACKTRACE_ENTRY diff --git a/libcxx/include/__stacktrace/hash.h b/libcxx/include/__stacktrace/hash.h index 122ad14144bc4..0830300ad81b9 100644 --- a/libcxx/include/__stacktrace/hash.h +++ b/libcxx/include/__stacktrace/hash.h @@ -7,10 +7,18 @@ // //===----------------------------------------------------------------------===// -#ifndef _LIBCPP_BASIC_STACKTRACE_HASH -#define _LIBCPP_BASIC_STACKTRACE_HASH +#ifndef _LIBCPP_STACKTRACE_HASH +#define _LIBCPP_STACKTRACE_HASH #include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + #if _LIBCPP_STD_VER >= 23 # include <__functional/hash.h> @@ -20,13 +28,6 @@ # include <__stacktrace/base.h> # include <__stacktrace/to_string.h> -# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -# endif - -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD // (19.6.6) @@ -45,7 +46,8 @@ struct _LIBCPP_EXPORTED_FROM_ABI hash> { _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_STD_VER >= 23 + _LIBCPP_POP_MACROS -#endif // _LIBCPP_STD_VER >= 23 -#endif // _LIBCPP_BASIC_STACKTRACE_HASH +#endif // _LIBCPP_STACKTRACE_HASH diff --git a/libcxx/include/__stacktrace/nonmem.h b/libcxx/include/__stacktrace/nonmem.h index 81f285b387531..e6ee621abbf46 100644 --- a/libcxx/include/__stacktrace/nonmem.h +++ b/libcxx/include/__stacktrace/nonmem.h @@ -7,10 +7,18 @@ // //===----------------------------------------------------------------------===// -#ifndef _LIBCPP_BASIC_STACKTRACE_NONMEM -#define _LIBCPP_BASIC_STACKTRACE_NONMEM +#ifndef _LIBCPP_STACKTRACE_NONMEM +#define _LIBCPP_STACKTRACE_NONMEM #include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + #if _LIBCPP_STD_VER >= 23 # include <__memory/allocator_traits.h> @@ -21,13 +29,6 @@ # include <__stacktrace/base.h> # include <__stacktrace/to_string.h> -# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -# endif - -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD // (19.6.4.6) @@ -52,7 +53,8 @@ _LIBCPP_EXPORTED_FROM_ABI inline ostream& operator<<(ostream& __os, const basic_ _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_STD_VER >= 23 + _LIBCPP_POP_MACROS -#endif // _LIBCPP_STD_VER >= 23 -#endif // _LIBCPP_BASIC_STACKTRACE_NONMEM +#endif // _LIBCPP_STACKTRACE_NONMEM diff --git a/libcxx/include/__stacktrace/to_string.h b/libcxx/include/__stacktrace/to_string.h index f6fcddf24c5fc..442a6c1528228 100644 --- a/libcxx/include/__stacktrace/to_string.h +++ b/libcxx/include/__stacktrace/to_string.h @@ -11,18 +11,19 @@ #define _LIBCPP_STACKTRACE_TO_STRING #include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + #if _LIBCPP_STD_VER >= 23 # include <__fwd/ostream.h> # include -# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -# endif - -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD template @@ -48,9 +49,11 @@ struct __to_string { }; } // namespace __stacktrace + _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_STD_VER >= 23 + _LIBCPP_POP_MACROS -#endif // _LIBCPP_STD_VER >= 23 #endif // _LIBCPP_STACKTRACE_TO_STRING diff --git a/libcxx/include/stacktrace b/libcxx/include/stacktrace index 1fbdb6b4fe293..f7ec8c0deeca1 100644 --- a/libcxx/include/stacktrace +++ b/libcxx/include/stacktrace @@ -164,28 +164,27 @@ namespace std { */ -#include <__config> - -#if _LIBCPP_STD_VER >= 23 +#if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) +# include <__cxx03/__config> +#else +# include <__config> + +# if _LIBCPP_STD_VER >= 23 +# include <__stacktrace/base.h> +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> +# include <__stacktrace/hash.h> +# include <__stacktrace/nonmem.h> +# include <__stacktrace/to_string.h> +# include // per [stacktrace.syn] +# endif -# include // according to [stacktrace.syn] +# include # if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header # endif -_LIBCPP_PUSH_MACROS -# include <__undef_macros> - -# include <__stacktrace/base.h> -# include <__stacktrace/basic.h> -# include <__stacktrace/entry.h> -# include <__stacktrace/hash.h> -# include <__stacktrace/nonmem.h> -# include <__stacktrace/to_string.h> - -_LIBCPP_POP_MACROS - -#endif // _LIBCPP_STD_VER >= 23 +#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) #endif // _LIBCPP_STACKTRACE diff --git a/libcxx/modules/std/stacktrace.inc b/libcxx/modules/std/stacktrace.inc index e7b31fd29b3a6..8b71bed583022 100644 --- a/libcxx/modules/std/stacktrace.inc +++ b/libcxx/modules/std/stacktrace.inc @@ -33,5 +33,8 @@ export namespace std { // [stacktrace.basic.hash], hash support using std::hash; + // [stacktrace.format], formatting support + using std::formatter; + #endif // _LIBCPP_STD_VER >= 23 } // namespace std diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp index 8d8ce5665565b..461087467a270 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -105,3 +105,4 @@ #endif // TEST_STD_VER > 23 // clang-format on + From 74dd772e6db0f98ad6341116e14d17f4f1854d5e Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Thu, 10 Jul 2025 00:25:54 -0400 Subject: [PATCH 07/19] Test for 'noexcept' (and related fixes) --- libcxx/include/__stacktrace/base.h | 70 ++++++---- libcxx/include/__stacktrace/basic.h | 69 ++++------ ...bcxxabi.v1.stable.exceptions.nonew.abilist | 4 +- ...bcxxabi.v1.stable.exceptions.nonew.abilist | 8 +- libcxx/src/CMakeLists.txt | 2 +- .../src/stacktrace/{builder.cpp => base.cpp} | 4 +- libcxx/src/stacktrace/linux/impl.cpp | 20 +-- libcxx/src/stacktrace/linux/impl.h | 2 +- libcxx/src/stacktrace/macos/impl.cpp | 20 +-- libcxx/src/stacktrace/macos/impl.h | 4 +- libcxx/src/stacktrace/tools/tools.cpp | 123 +++++++++--------- libcxx/src/stacktrace/tools/tools.h | 54 ++++---- libcxx/src/stacktrace/unwind/impl.cpp | 6 +- libcxx/src/stacktrace/unwind/impl.h | 2 +- libcxx/src/stacktrace/utils/failed.h | 12 +- libcxx/src/stacktrace/utils/fd.h | 2 +- libcxx/src/stacktrace/windows/dll.cpp | 10 +- libcxx/src/stacktrace/windows/impl.cpp | 6 +- libcxx/src/stacktrace/windows/impl.h | 6 +- .../stacktrace/basic.cmp/equality.pass.cpp | 5 + .../basic.cmp/strong_ordering.pass.cpp | 3 + .../stacktrace/basic.cons/copy.pass.cpp | 85 ++++++++++++ .../basic.cons/copy_and_move.pass.cpp | 82 ------------ .../basic.cons/ctor_no_args.pass.cpp | 32 +++-- .../basic.cons/ctor_with_alloc.pass.cpp | 63 +++------ .../basic.cons/current_no_args.pass.cpp | 1 + .../basic.cons/current_skip.pass.cpp | 1 + .../basic.cons/current_skip_depth.pass.cpp | 1 + .../stacktrace/basic.cons/move.pass.cpp | 88 +++++++++++++ .../basic.cons/only_uses_allocator.pass.cpp | 102 ++++++--------- .../stacktrace/basic.mod/swap.pass.cpp | 41 ++++-- .../stacktrace/basic.nonmem/swap.pass.cpp | 37 ++++-- .../stacktrace/basic.obs/at.pass.cpp | 1 - .../stacktrace/basic.obs/begin_end.pass.cpp | 2 + .../stacktrace/basic.obs/cbegin_cend.pass.cpp | 2 + .../basic.obs/crbegin_crend.pass.cpp | 2 + .../stacktrace/basic.obs/empty.pass.cpp | 1 + .../basic.obs/get_allocator.pass.cpp | 1 + .../stacktrace/basic.obs/max_size.pass.cpp | 1 + .../basic.obs/operator_index.pass.cpp | 2 - .../stacktrace/basic.obs/rbegin_rend.pass.cpp | 2 + .../stacktrace/basic.obs/size.pass.cpp | 1 + .../stacktrace/entry.cmp/equality.pass.cpp | 4 + .../entry.cmp/strong_ordering.pass.cpp | 3 + .../entry.cons/copy_assign.pass.cpp | 11 +- .../entry.cons/copy_construct.pass.cpp | 7 +- .../stacktrace/entry.cons/default.pass.cpp | 11 +- .../entry.obs/native_handle.pass.cpp | 6 +- .../entry.obs/operator_bool.pass.cpp | 12 +- .../std/diagnostics/stacktrace/test_allocs.h | 105 +++++++++++++++ .../stacktrace.version.compile.pass.cpp | 1 - 51 files changed, 681 insertions(+), 459 deletions(-) rename libcxx/src/stacktrace/{builder.cpp => base.cpp} (93%) create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp delete mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp create mode 100644 libcxx/test/std/diagnostics/stacktrace/test_allocs.h diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h index 4583b2760f246..b76b336595e9b 100644 --- a/libcxx/include/__stacktrace/base.h +++ b/libcxx/include/__stacktrace/base.h @@ -42,19 +42,9 @@ class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry; namespace __stacktrace { -struct _LIBCPP_HIDE_FROM_ABI alloc final { - function __alloc_bytes_; - function __dealloc_bytes_; - - template - _LIBCPP_HIDE_FROM_ABI alloc(_Allocator __alloc) { - using _AT = allocator_traits<_Allocator>; - using _BA = typename _AT::template rebind_alloc; - auto __ba = _BA(__alloc); - __alloc_bytes_ = [__ba](size_t __sz) mutable { return __ba.allocate(__sz); }; - __dealloc_bytes_ = [__ba](void* __ptr, size_t __sz) mutable { __ba.deallocate((byte*)__ptr, __sz); }; - } +struct _LIBCPP_HIDE_FROM_ABI entry_base; +struct _LIBCPP_EXPORTED_FROM_ABI base { template struct _LIBCPP_HIDE_FROM_ABI Alloc { function __alloc_bytes_; @@ -66,6 +56,7 @@ struct _LIBCPP_HIDE_FROM_ABI alloc final { template Alloc(Alloc<_T2> const& __rhs) : Alloc(__rhs.__alloc_bytes_, __rhs.__dealloc_bytes_) {} + // XXX Alignment? using value_type = _Tp; [[nodiscard]] _Tp* allocate(size_t __sz) { return (_Tp*)__alloc_bytes_(__sz * sizeof(_Tp)); } void deallocate(_Tp* __ptr, size_t __sz) { __dealloc_bytes_((byte*)__ptr, __sz * sizeof(_Tp)); } @@ -103,30 +94,53 @@ struct _LIBCPP_HIDE_FROM_ABI alloc final { _LIBCPP_HIDE_FROM_ABI list<_Tp> make_list(_Args... __args) { return list(std::forward<_Args>(__args)..., make_alloc<_Tp>()); } + + template + auto _LIBCPP_HIDE_FROM_ABI __alloc_wrap(_Allocator const& __alloc) { + using _AT = allocator_traits<_Allocator>; + using _BA = typename _AT::template rebind_alloc; + auto __ba = _BA(__alloc); + return [__ba = std::move(__ba)](size_t __sz) mutable { return __ba.allocate(__sz); }; + } + + template + auto _LIBCPP_HIDE_FROM_ABI __dealloc_wrap(_Allocator const& __alloc) { + using _AT = allocator_traits<_Allocator>; + using _BA = typename _AT::template rebind_alloc; + auto __ba = _BA(__alloc); + return [__ba = std::move(__ba)](void* __ptr, size_t __sz) mutable { __ba.deallocate((byte*)__ptr, __sz); }; + } + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void + build_stacktrace(size_t __skip, size_t __max_depth); + + base(); + + template + explicit _LIBCPP_EXPORTED_FROM_ABI base(_Allocator __alloc); + + function __alloc_bytes_; + function __dealloc_bytes_; + vec __entries_; + str __main_prog_path_; }; struct _LIBCPP_HIDE_FROM_ABI entry_base { - uintptr_t __addr_actual_{}; // this address, as observed in this current process - uintptr_t __addr_unslid_{}; // address adjusted for ASLR - optional<__stacktrace::alloc::str> __desc_{}; // uses wrapped _Allocator from caller - optional<__stacktrace::alloc::str> __file_{}; // uses wrapped _Allocator from caller + uintptr_t __addr_actual_{}; // this address, as observed in this current process + uintptr_t __addr_unslid_{}; // address adjusted for ASLR + optional<__stacktrace::base::str> __desc_{}; // uses wrapped _Allocator from caller + optional<__stacktrace::base::str> __file_{}; // uses wrapped _Allocator from caller uint_least32_t __line_{}; _LIBCPP_HIDE_FROM_ABI stacktrace_entry to_stacktrace_entry() const; }; -struct _LIBCPP_EXPORTED_FROM_ABI builder final { - alloc __alloc_; // wraps the caller-provided allocator - alloc::vec __entries_; - alloc::str __main_prog_path_; - - template - explicit _LIBCPP_EXPORTED_FROM_ABI builder(_Allocator __alloc) - : __alloc_(__alloc), __entries_(__alloc_.make_vec()), __main_prog_path_(__alloc_.make_str()) {} - - _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void - build_stacktrace(size_t __skip, size_t __max_depth); -}; +template +_LIBCPP_EXPORTED_FROM_ABI base::base(_Allocator __alloc) + : __alloc_bytes_(__alloc_wrap(__alloc)), + __dealloc_bytes_(__dealloc_wrap(__alloc)), + __entries_(make_vec()), + __main_prog_path_(make_str()) {} } // namespace __stacktrace diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h index 5798ac9bca628..6ce1f10c51f64 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic.h @@ -43,20 +43,21 @@ _LIBCPP_BEGIN_NAMESPACE_STD class stacktrace_entry; template -class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { +class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { friend struct hash>; friend struct __stacktrace::__to_string; using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; - constexpr static bool __kPropOnCopy = _ATraits::propagate_on_container_copy_assignment::value; - constexpr static bool __kPropOnMove = _ATraits::propagate_on_container_move_assignment::value; + constexpr static bool __kPropOnCopyAssign = _ATraits::propagate_on_container_copy_assignment::value; + constexpr static bool __kPropOnMoveAssign = _ATraits::propagate_on_container_move_assignment::value; constexpr static bool __kPropOnSwap = _ATraits::propagate_on_container_swap::value; constexpr static bool __kAlwaysEqual = _ATraits::is_always_equal::value; constexpr static bool __kNoThrowDflConstruct = is_nothrow_default_constructible_v<_Allocator>; constexpr static bool __kNoThrowAlloc = noexcept(noexcept(_Allocator().allocate(1)) && noexcept(_Allocator().allocate_at_least(1))); - [[no_unique_address]] _Allocator __alloc_; + [[no_unique_address]] + _Allocator __alloc_; using __entry_vec _LIBCPP_NODEBUG = vector; __entry_vec __entries_; @@ -94,7 +95,7 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { current(size_type __skip, size_type __max_depth, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { - __stacktrace::builder __builder(__caller_alloc); + __stacktrace::base __builder(__caller_alloc); __builder.build_stacktrace(__skip + 1, __max_depth); basic_stacktrace<_Allocator> __ret{__caller_alloc}; __ret.__entries_.reserve(__builder.__entries_.size()); @@ -109,57 +110,39 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace() noexcept(__kNoThrowDflConstruct) : basic_stacktrace(allocator_type()) {} _LIBCPP_EXPORTED_FROM_ABI explicit basic_stacktrace(const allocator_type& __alloc) noexcept - : __alloc_(__alloc), __entries_(__alloc_) {} + : base(__alloc), __entries_(__alloc_) {} - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) = default; + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) + : basic_stacktrace(__other, _ATraits::select_on_container_copy_construction(__other.__alloc_)) {} - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept + : __alloc_(std::move(__other.__alloc_)), __entries_(std::move(__other.__entries_)) {} _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other, allocator_type const& __alloc) - : __alloc_(__alloc), __entries_(__other.__entries_, __alloc) {} + : base(__alloc), __alloc_(__alloc), __entries_(__other.__entries_, __alloc) {} _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other, allocator_type const& __alloc) - : __alloc_(__alloc) { - if (__kAlwaysEqual || __alloc_ == __other.__alloc_) { - __entries_ = std::move(__other.__entries_); - } else { - // "moving" from a container with a different allocator; we're forced to copy items instead - for (auto const& __entry : __other.__entries_) { - __entries_.push_back(__entry); - } - } + : base(__alloc) { + __entries_ = {std::move(__other.__entries_), __alloc_}; } _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& operator=(const basic_stacktrace& __other) { - if (this == std::addressof(__other)) { - return *this; - } - if (__kPropOnCopy) { - __alloc_ = __other.__alloc_; + if (this != std::addressof(__other)) { + if (__kPropOnCopyAssign) { + __alloc_ = __other.__alloc_; + } + __entries_ = {__other.__entries_, __alloc_}; } - __entries_ = {__other.__entries_, __alloc_}; return *this; } _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& - operator=(basic_stacktrace&& __other) noexcept(__kPropOnMove || __kAlwaysEqual) { - if (this == std::addressof(__other)) { - return *this; - } - if (__kPropOnMove) { - __alloc_ = __other.__alloc_; - __entries_ = std::move(__other.__entries_); - } else { - auto __allocs_eq = __kAlwaysEqual || __alloc_ == __other.__alloc_; - if (__allocs_eq) { - __entries_ = std::move(__other.__entries_); - } else { - // "moving" from a container with a different allocator; - // we're forced to copy items instead - for (auto const& __entry : __other.__entries_) { - __entries_.push_back(__entry); - } + operator=(basic_stacktrace&& __other) noexcept(__kPropOnMoveAssign || __kAlwaysEqual) { + if (this != std::addressof(__other)) { + if (__kPropOnMoveAssign) { + __alloc_ = std::move(__other.__alloc_); } + __entries_ = {std::move(__other.__entries_), __alloc_}; } return *this; } @@ -229,7 +212,9 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { // (19.6.4.5) // [stacktrace.basic.mod], modifiers - _LIBCPP_EXPORTED_FROM_ABI void swap(basic_stacktrace<_Allocator>& __other) noexcept { + _LIBCPP_EXPORTED_FROM_ABI void swap(basic_stacktrace& __other) noexcept( + allocator_traits<_Allocator>::propagate_on_container_swap::value || + allocator_traits<_Allocator>::is_always_equal::value) { std::swap(__entries_, __other.__entries_); if (__kPropOnSwap) { std::swap(__alloc_, __other.__alloc_); diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist index 114f3beb42a71..83a2d41f8680e 100644 --- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -983,7 +983,9 @@ {'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace7builder16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseC2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist index 8e8ae5bdd27da..df4daa6343de7 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -627,7 +627,9 @@ {'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNSt3__112__stacktrace7builder16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -1624,9 +1626,11 @@ {'is_defined': True, 'name': '_ZTCNSt3__19strstreamE16_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental15fundamentals_v112bad_any_castE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental19bad_optional_accessE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFPSt4bytemEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_getE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_putE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110ctype_baseE', 'size': 16, 'type': 'OBJECT'} @@ -1766,9 +1770,11 @@ {'is_defined': True, 'name': '_ZTISt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental15fundamentals_v112bad_any_castE', 'size': 50, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFPSt4bytemEEE', 'size': 41, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 64, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 65, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 58, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 42, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_getE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_putE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110ctype_baseE', 'size': 21, 'type': 'OBJECT'} diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 92d08b13a26b5..d0a40a813f7d0 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -40,7 +40,7 @@ set(LIBCXX_SOURCES ryu/d2fixed.cpp ryu/d2s.cpp ryu/f2s.cpp - stacktrace/builder.cpp + stacktrace/base.cpp stacktrace/linux/elf.cpp stacktrace/linux/images.cpp stacktrace/linux/impl.cpp diff --git a/libcxx/src/stacktrace/builder.cpp b/libcxx/src/stacktrace/base.cpp similarity index 93% rename from libcxx/src/stacktrace/builder.cpp rename to libcxx/src/stacktrace/base.cpp index 000135b4ccc38..7ddb865968be5 100644 --- a/libcxx/src/stacktrace/builder.cpp +++ b/libcxx/src/stacktrace/base.cpp @@ -21,8 +21,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { +base::base() : base(std::allocator()) {} + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void -builder::build_stacktrace(size_t skip, size_t max_depth) { +base::build_stacktrace(size_t skip, size_t max_depth) { #if defined(_LIBCPP_WIN32API) // Windows implementation win_impl dbghelp{*this}; diff --git a/libcxx/src/stacktrace/linux/impl.cpp b/libcxx/src/stacktrace/linux/impl.cpp index 3f18d015aa93f..c01fcb02f30ef 100644 --- a/libcxx/src/stacktrace/linux/impl.cpp +++ b/libcxx/src/stacktrace/linux/impl.cpp @@ -35,11 +35,11 @@ void linux::ident_modules() { auto mainProg = images.mainProg(); if (mainProg) { - builder_.__main_prog_path_ = mainProg->name_; + base_.__main_prog_path_ = mainProg->name_; } unsigned index = 1; // Starts at one, and is moved around in this loop - for (auto& entry : builder_.__entries_) { + for (auto& entry : base_.__entries_) { while (images[index].loaded_at_ > entry.__addr_actual_) { --index; } @@ -47,7 +47,7 @@ void linux::ident_modules() { ++index; } entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; - entry.__file_ = builder_.__alloc_.make_str(images[index].name_); + entry.__file_ = base_.make_str(images[index].name_); } } @@ -65,28 +65,28 @@ void linux::resolve_main_elf_syms(std::string_view main_elf_name) { if (_mm) { static elf::ELF _this_elf(_mm.addr_); if (_this_elf) { - for (auto& entry : builder_.__entries_) { + for (auto& entry : base_.__entries_) { if (entry.__desc_->empty() && entry.__file_ == main_elf_name) { auto name = _this_elf.getSym(entry.__addr_unslid_).name(); - entry.__desc_ = builder_.__alloc_.make_str(name); + entry.__desc_ = base_.make_str(name); } } } } } -bool symbolize_entry(alloc& alloc, entry_base& entry) { +bool symbolize_entry(base& base, entry_base& entry) { bool ret = false; Dl_info info; if (dladdr((void*)entry.__addr_actual_, &info)) { ret = true; // at least partially successful if (info.dli_fname && entry.__file_->empty()) { // provide at least the binary filename in case we cannot lookup source location - entry.__file_ = alloc.make_str(info.dli_fname); + entry.__file_ = base.make_str(info.dli_fname); } if (info.dli_sname && entry.__desc_->empty()) { // provide at least the mangled name; try to unmangle in a later step - entry.__desc_ = alloc.make_str(info.dli_sname); + entry.__desc_ = base.make_str(info.dli_sname); } } return ret; @@ -96,8 +96,8 @@ bool symbolize_entry(alloc& alloc, entry_base& entry) { // except for symbols in the main program. If addr2line-style tools are enabled, that step // might also be able to get symbols directly from the binary's debug info. void linux::symbolize() { - for (auto& entry : builder_.__entries_) { - symbolize_entry(builder_.__alloc_, entry); + for (auto& entry : base_.__entries_) { + symbolize_entry(base_, entry); } // Symbols might be missing, because both (1) Linux's `dladdr` won't try to resolve non-exported symbols, // which can be the case for the main program executable; and (2) debug info was not preserved. diff --git a/libcxx/src/stacktrace/linux/impl.h b/libcxx/src/stacktrace/linux/impl.h index 0a91bf88fcf9b..5ace2db1a3fa8 100644 --- a/libcxx/src/stacktrace/linux/impl.h +++ b/libcxx/src/stacktrace/linux/impl.h @@ -15,7 +15,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { struct linux { - builder& builder_; + base& base_; #if defined(__linux__) // defined in linux.cpp diff --git a/libcxx/src/stacktrace/macos/impl.cpp b/libcxx/src/stacktrace/macos/impl.cpp index 575a2927a78ca..d47381c795f53 100644 --- a/libcxx/src/stacktrace/macos/impl.cpp +++ b/libcxx/src/stacktrace/macos/impl.cpp @@ -22,7 +22,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { -void ident_module(alloc& alloc, entry_base& entry, unsigned& index, image* images) { +void ident_module(base& base, entry_base& entry, unsigned& index, image* images) { if (entry.__addr_actual_) { while (images[index].loaded_at_ > entry.__addr_actual_) { --index; @@ -33,7 +33,7 @@ void ident_module(alloc& alloc, entry_base& entry, unsigned& index, image* image auto& image = images[index]; if (image) { entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; - entry.__file_ = alloc.make_str(images[index].name_); + entry.__file_ = base.make_str(images[index].name_); } } } @@ -65,31 +65,31 @@ void macos::ident_modules() { } // First image (the main program) is at index 1 - builder_.__main_prog_path_ = builder_.__alloc_.make_str(images.at(1).name_); + base_.__main_prog_path_ = base_.make_str(images.at(1).name_); unsigned index = 1; // Starts at one, and is moved by 'ident_module' - for (auto& entry : builder_.__entries_) { - ident_module(builder_.__alloc_, (entry_base&)entry, index, images.data()); + for (auto& entry : base_.__entries_) { + ident_module(base_, (entry_base&)entry, index, images.data()); } } -void symbolize_entry(alloc& alloc, entry_base& entry) { +void symbolize_entry(base& base, entry_base& entry) { Dl_info info; if (dladdr((void*)entry.__addr_actual_, &info)) { if (info.dli_fname && entry.__file_->empty()) { // provide at least the binary filename in case we cannot lookup source location - entry.__file_ = alloc.make_str(info.dli_fname); + entry.__file_ = base.make_str(info.dli_fname); } if (info.dli_sname && entry.__desc_->empty()) { // provide at least the mangled name; try to unmangle in a later step - entry.__desc_ = alloc.make_str(info.dli_sname); + entry.__desc_ = base.make_str(info.dli_sname); } } } void macos::symbolize() { - for (auto& entry : builder_.__entries_) { - symbolize_entry(builder_.__alloc_, (entry_base&)entry); + for (auto& entry : base_.__entries_) { + symbolize_entry(base_, entry); } } diff --git a/libcxx/src/stacktrace/macos/impl.h b/libcxx/src/stacktrace/macos/impl.h index e0a24228a6ade..fcc8a11501255 100644 --- a/libcxx/src/stacktrace/macos/impl.h +++ b/libcxx/src/stacktrace/macos/impl.h @@ -14,15 +14,13 @@ #include #include -#include <__config> -#include <__config_site> #include <__stacktrace/base.h> _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { struct macos { - builder& builder_; + base& base_; #if defined(__APPLE__) // defined in macos.cpp diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp index 8f04a1806bbc7..f1d6e966e56fe 100644 --- a/libcxx/src/stacktrace/tools/tools.cpp +++ b/libcxx/src/stacktrace/tools/tools.cpp @@ -33,67 +33,67 @@ namespace __stacktrace { namespace { -_LIBCPP_HIDE_FROM_ABI alloc::str hex_string(alloc& alloc, uintptr_t __addr) { +_LIBCPP_HIDE_FROM_ABI base::str hex_string(base& base, uintptr_t __addr) { char __ret[19]; // "0x" + 16 digits + NUL auto __size = snprintf(__ret, sizeof(__ret), "0x%016llx", (unsigned long long)__addr); - return alloc.make_str(__ret, size_t(__size)); + return base.make_str(__ret, size_t(__size)); } -_LIBCPP_HIDE_FROM_ABI alloc::str u64_string(alloc& alloc, uintptr_t __val) { +_LIBCPP_HIDE_FROM_ABI base::str u64_string(base& base, uintptr_t __val) { char __ret[21]; // 20 digits max + NUL auto __size = snprintf(__ret, sizeof(__ret), "%zu", __val); - return alloc.make_str(__ret, size_t(__size)); + return base.make_str(__ret, size_t(__size)); } # define STRINGIFY0(x) #x # define STRINGIFY(x) STRINGIFY0(x) -void try_tools(alloc& alloc, function cb) { +void try_tools(base& base, function cb) { char const* prog_name; if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"))) { - if (cb(llvm_symbolizer{alloc, prog_name})) { + if (cb(llvm_symbolizer{base, prog_name})) { return; } } else { # if defined(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH) - if (cb(llvm_symbolizer{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH)})) { + if (cb(llvm_symbolizer{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH)})) { return; } # else - if (cb(llvm_symbolizer{alloc})) { + if (cb(llvm_symbolizer{base})) { return; } # endif } if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"))) { - if (cb(addr2line{alloc, prog_name})) { + if (cb(addr2line{base, prog_name})) { return; } } else { # if defined(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH) - if (cb(addr2line{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH)})) { + if (cb(addr2line{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH)})) { return; } # else - if (cb(addr2line{alloc})) { + if (cb(addr2line{base})) { return; } # endif } if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"))) { - if (cb(atos{alloc, prog_name})) { + if (cb(atos{base, prog_name})) { return; } } else { # if defined(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH) - if (cb(atos{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH)})) { + if (cb(atos{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH)})) { return; } # else - if (cb(atos{alloc})) { + if (cb(atos{base})) { return; } # endif @@ -103,9 +103,9 @@ void try_tools(alloc& alloc, function cb) { } // namespace void spawner::resolve_lines() { - try_tools(builder_.__alloc_, [&](tool const& prog) { + try_tools(base_, [&](tool const& prog) { char buf[512]; - pspawn_tool proc(prog, builder_, buf, sizeof(buf)); + pspawn_tool proc(prog, base_, buf, sizeof(buf)); try { proc.run(); return true; @@ -120,19 +120,19 @@ void spawner::resolve_lines() { }); } -alloc::list llvm_symbolizer::buildArgs(builder& builder) const { - auto ret = alloc_.make_list(); - ret.push_back(alloc_.make_str(progName_)); - ret.push_back(alloc_.make_str("--demangle")); - ret.push_back(alloc_.make_str("--no-inlines")); - ret.push_back(alloc_.make_str("--verbose")); - ret.push_back(alloc_.make_str("--relativenames")); - ret.push_back(alloc_.make_str("--functions=short")); - for (auto& st_entry : builder.__entries_) { +base::list llvm_symbolizer::buildArgs(base& base) const { + auto ret = base_.make_list(); + ret.push_back(base_.make_str(progName_)); + ret.push_back(base_.make_str("--demangle")); + ret.push_back(base_.make_str("--no-inlines")); + ret.push_back(base_.make_str("--verbose")); + ret.push_back(base_.make_str("--relativenames")); + ret.push_back(base_.make_str("--functions=short")); + for (auto& st_entry : base.__entries_) { auto& entry = (entry_base&)st_entry; - auto addr_string = hex_string(alloc_, entry.__addr_unslid_); + auto addr_string = hex_string(base_, entry.__addr_unslid_); if (entry.__file_) { - auto arg = alloc_.make_str(); + auto arg = base_.make_str(); arg.reserve(entry.__file_->size() + 40); arg += "FILE:"; arg += *entry.__file_; @@ -146,7 +146,7 @@ alloc::list llvm_symbolizer::buildArgs(builder& builder) const { return ret; } -void llvm_symbolizer::parseOutput(builder& builder, __stacktrace::entry_base& entry, std::istream& output) const { +void llvm_symbolizer::parseOutput(base& base, __stacktrace::entry_base& entry, std::istream& output) const { // clang-format off /* With "--verbose", parsing is a little easier, or at least, more reliable; @@ -166,8 +166,7 @@ Note that this includes an extra empty line as a terminator. */ // clang-format on - auto& alloc = builder.__alloc_; - auto line = alloc.make_str(); + auto line = base.make_str(); line.reserve(512); std::string_view tmp; while (true) { @@ -189,7 +188,7 @@ Note that this includes an extra empty line as a terminator. tmp = line; tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " if (tmp != "??") { - entry.__file_ = alloc.make_str(tmp); + entry.__file_ = base.make_str(tmp); } } else if (line.starts_with(" Line:")) { tmp = line; @@ -206,29 +205,28 @@ Note that this includes an extra empty line as a terminator. } } -alloc::list addr2line::buildArgs(builder& builder) const { - auto& alloc = builder.__alloc_; - auto ret = alloc.make_list(); - if (builder.__main_prog_path_.empty()) { +base::list addr2line::buildArgs(base& base) const { + auto ret = base.make_list(); + if (base.__main_prog_path_.empty()) { // Should not have reached here but be graceful anyway - ret.push_back(alloc.make_str("/bin/false")); + ret.push_back(base.make_str("/bin/false")); return ret; } - ret.push_back(alloc.make_str(progName_)); - ret.push_back(alloc.make_str("--functions")); - ret.push_back(alloc.make_str("--demangle")); - ret.push_back(alloc.make_str("--basenames")); - ret.push_back(alloc.make_str("--pretty-print")); // This "human-readable form" is easier to parse - ret.push_back(alloc.make_str("-e")); - ret.push_back(builder.__main_prog_path_); - for (auto& entry : builder.__entries_) { - ret.push_back(hex_string(alloc, ((entry_base&)entry).__addr_unslid_)); + ret.push_back(base.make_str(progName_)); + ret.push_back(base.make_str("--functions")); + ret.push_back(base.make_str("--demangle")); + ret.push_back(base.make_str("--basenames")); + ret.push_back(base.make_str("--pretty-print")); // This "human-readable form" is easier to parse + ret.push_back(base.make_str("-e")); + ret.push_back(base.__main_prog_path_); + for (auto& entry : base.__entries_) { + ret.push_back(hex_string(base, ((entry_base&)entry).__addr_unslid_)); } return ret; } -void addr2line::parseOutput(builder& builder, entry_base& entry, std::istream& output) const { +void addr2line::parseOutput(base& base, entry_base& entry, std::istream& output) const { // clang-format off /* Example: @@ -244,8 +242,7 @@ test::Foo::Foo(int) at foo.cc:11 */ // clang-format on - auto& alloc = builder.__alloc_; - auto line = alloc.make_str(); + auto line = base.make_str(); line.reserve(512); std::getline(output, line); while (!line.empty() && isspace(line.back())) { @@ -261,16 +258,16 @@ test::Foo::Foo(int) at foo.cc:11 return; } if (sepIndex > 0) { - entry.__desc_ = alloc.make_str(string_view(line).substr(0, sepIndex)); + entry.__desc_ = base.make_str(string_view(line).substr(0, sepIndex)); } auto fileBegin = sepIndex + 4; if (fileBegin >= line.size()) { return; } - auto fileline = alloc.make_str(string_view(line).substr(fileBegin)); + auto fileline = base.make_str(string_view(line).substr(fileBegin)); auto colon = fileline.find_last_of(":"); if (colon > 0 && !fileline.starts_with("?")) { - entry.__file_ = alloc.make_str(string_view(fileline).substr(0, colon)); + entry.__file_ = base.make_str(string_view(fileline).substr(0, colon)); } if (colon == std::string::npos) { @@ -284,21 +281,20 @@ test::Foo::Foo(int) at foo.cc:11 entry.__line_ = lineno; } -alloc::list atos::buildArgs(builder& builder) const { - auto& alloc = builder.__alloc_; - auto ret = alloc.make_list(); - ret.push_back(alloc.make_str(progName_)); - ret.push_back(alloc.make_str("-p")); - ret.push_back(u64_string(alloc, getpid())); +base::list atos::buildArgs(base& base) const { + auto ret = base.make_list(); + ret.push_back(base.make_str(progName_)); + ret.push_back(base.make_str("-p")); + ret.push_back(u64_string(base, getpid())); // TODO(stackcx23): Allow options in env, e.g. LIBCPP_STACKTRACE_OPTIONS=FullPath // ret.push_back("--fullPath"); - for (auto& entry : builder.__entries_) { - ret.push_back(hex_string(alloc, ((entry_base&)entry).__addr_actual_)); + for (auto& entry : base.__entries_) { + ret.push_back(hex_string(base, ((entry_base&)entry).__addr_actual_)); } return ret; } -void atos::parseOutput(builder& builder, entry_base& entry, std::istream& output) const { +void atos::parseOutput(base& base, entry_base& entry, std::istream& output) const { // Simple example: // // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) @@ -322,8 +318,7 @@ void atos::parseOutput(builder& builder, entry_base& entry, std::istream& output // If this more or less fits our expected format we'll take these data, // even if the line number is 0. - auto& alloc = builder.__alloc_; - auto line = alloc.make_str(); + auto line = base.make_str(); line.reserve(512); std::getline(output, line); while (!line.empty() && isspace(line.back())) { @@ -360,12 +355,12 @@ void atos::parseOutput(builder& builder, entry_base& entry, std::istream& output // we have the name provided by atos; only use that if we have no symbol // (no need to copy more strings otherwise). if (entry.__desc_->empty() && !sym.empty()) { - entry.__desc_ = alloc.make_str(sym); + entry.__desc_ = base.make_str(sym); } std::string_view file{fileBegin, size_t(lastColon - fileBegin)}; if (file != "?" && file != "??" && !file.empty()) { - entry.__file_ = alloc.make_str(file); + entry.__file_ = base.make_str(file); } unsigned lineno = 0; diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h index 620a1e2f4f9a9..6f1a7935a183a 100644 --- a/libcxx/src/stacktrace/tools/tools.h +++ b/libcxx/src/stacktrace/tools/tools.h @@ -35,42 +35,42 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { struct tool { - alloc& alloc_; + base& base_; char const* progName_; - tool(alloc& alloc, char const* progName) : alloc_(alloc), progName_(progName) {} + tool(base& base, char const* progName) : base_(base), progName_(progName) {} virtual ~tool() = default; /** Construct complete `argv` for the spawned process. Includes the program name at argv[0], followed by flags */ - virtual alloc::list buildArgs(builder& trace) const = 0; + virtual base::list buildArgs(base& trace) const = 0; /** Parse line(s) output by the tool, and modify `entry`. */ - virtual void parseOutput(builder& trace, entry_base& entry, std::istream& output) const = 0; + virtual void parseOutput(base& trace, entry_base& entry, std::istream& output) const = 0; }; struct llvm_symbolizer : tool { virtual ~llvm_symbolizer() = default; - explicit llvm_symbolizer(alloc& alloc) : llvm_symbolizer(alloc, "llvm_symbolizer") {} - llvm_symbolizer(alloc& alloc, char const* progName) : tool{alloc, progName} {} - alloc::list buildArgs(builder& trace) const override; - void parseOutput(builder& trace, entry_base& entry, std::istream& output) const override; + explicit llvm_symbolizer(base& base) : llvm_symbolizer(base, "llvm_symbolizer") {} + llvm_symbolizer(base& base, char const* progName) : tool{base, progName} {} + base::list buildArgs(base& trace) const override; + void parseOutput(base& trace, entry_base& entry, std::istream& output) const override; }; struct addr2line : tool { virtual ~addr2line() = default; - explicit addr2line(alloc& alloc) : addr2line(alloc, "addr2line") {} - addr2line(alloc& alloc, char const* progName) : tool{alloc, progName} {} - alloc::list buildArgs(builder& trace) const override; - void parseOutput(builder& trace, entry_base& entry, std::istream& stream) const override; + explicit addr2line(base& base) : addr2line(base, "addr2line") {} + addr2line(base& base, char const* progName) : tool{base, progName} {} + base::list buildArgs(base& trace) const override; + void parseOutput(base& trace, entry_base& entry, std::istream& stream) const override; }; struct atos : tool { virtual ~atos() = default; - explicit atos(alloc& alloc) : atos(alloc, "atos") {} - atos(alloc& alloc, char const* progName) : tool{alloc, progName} {} - alloc::list buildArgs(builder& trace) const override; - void parseOutput(builder& trace, entry_base& entry, std::istream& output) const override; + explicit atos(base& base) : atos(base, "atos") {} + atos(base& base, char const* progName) : tool{base, progName} {} + base::list buildArgs(base& trace) const override; + void parseOutput(base& trace, entry_base& entry, std::istream& output) const override; }; struct file_actions { @@ -124,8 +124,8 @@ struct pspawn { } } - void spawn(alloc::list const& argStrings) { - alloc::vec argv = tool_.alloc_.make_vec(); + void spawn(base::list const& argStrings) { + base::vec argv = tool_.base_.make_vec(); argv.reserve(argStrings.size() + 1); for (auto const& str : argStrings) { argv.push_back(str.data()); @@ -145,13 +145,13 @@ struct pspawn { }; struct pspawn_tool : pspawn { - builder& builder_; + base& base_; fd fd_; fd_streambuf buf_; fd_istream stream_; - pspawn_tool(tool const& a2l, builder& trace, char* buf, size_t size) - : pspawn{a2l}, builder_(trace), fd_(fa_.redirectOutFD()), buf_(fd_, buf, size), stream_(buf_) { + pspawn_tool(tool const& a2l, base& trace, char* buf, size_t size) + : pspawn{a2l}, base_(trace), fd_(fa_.redirectOutFD()), buf_(fd_, buf, size), stream_(buf_) { if (!debug::enabled()) { fa_.redirectErrNull(); } @@ -162,11 +162,11 @@ struct pspawn_tool : pspawn { // Cannot run "addr2line" or similar without addresses, since we // provide them in argv, and if there are none passed in argv, the // tool will try to read from stdin and hang. - if (builder_.__entries_.empty()) { + if (base_.__entries_.empty()) { return; } - auto argStrings = tool_.buildArgs(builder_); + auto argStrings = tool_.buildArgs(base_); if (debug::enabled()) { debug() << "Trying to get stacktrace using:"; for (auto& str : argStrings) { @@ -177,17 +177,17 @@ struct pspawn_tool : pspawn { spawn(argStrings); - auto end = builder_.__entries_.end(); - auto it = builder_.__entries_.begin(); + auto end = base_.__entries_.end(); + auto it = base_.__entries_.begin(); while (it != end) { auto& entry = (entry_base&)(*it++); - tool_.parseOutput(builder_, entry, stream_); + tool_.parseOutput(base_, entry, stream_); } } }; struct spawner { - builder& builder_; + base& base_; void resolve_lines(); }; diff --git a/libcxx/src/stacktrace/unwind/impl.cpp b/libcxx/src/stacktrace/unwind/impl.cpp index 4742988dac64c..332712348e4b3 100644 --- a/libcxx/src/stacktrace/unwind/impl.cpp +++ b/libcxx/src/stacktrace/unwind/impl.cpp @@ -21,7 +21,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { struct unwind_backtrace { - builder& builder_; + base& base_; size_t skip_; size_t maxDepth_; @@ -39,7 +39,7 @@ struct unwind_backtrace { if (!ip) { return _Unwind_Reason_Code::_URC_NORMAL_STOP; } - auto& entry = builder_.__entries_.emplace_back(); + auto& entry = base_.__entries_.emplace_back(); auto& eb = (entry_base&)entry; eb.__addr_actual_ = (ipBefore ? ip : ip - 1); eb.__addr_unslid_ = eb.__addr_actual_; // in case we can't un-slide @@ -52,7 +52,7 @@ struct unwind_backtrace { }; _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void unwind::collect(size_t skip, size_t max_depth) { - unwind_backtrace bt{builder_, skip + 1, max_depth}; // skip this call as well + unwind_backtrace bt{base_, skip + 1, max_depth}; // skip this call as well _Unwind_Backtrace(unwind_backtrace::callback, &bt); } diff --git a/libcxx/src/stacktrace/unwind/impl.h b/libcxx/src/stacktrace/unwind/impl.h index 4b552349d75bc..1eab787dff745 100644 --- a/libcxx/src/stacktrace/unwind/impl.h +++ b/libcxx/src/stacktrace/unwind/impl.h @@ -22,7 +22,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { struct unwind { - builder& builder_; + base& base_; void collect(size_t skip, size_t max_depth); }; diff --git a/libcxx/src/stacktrace/utils/failed.h b/libcxx/src/stacktrace/utils/failed.h index 3f8a24dd88eb6..dcd89507339c1 100644 --- a/libcxx/src/stacktrace/utils/failed.h +++ b/libcxx/src/stacktrace/utils/failed.h @@ -11,16 +11,18 @@ #include <__config> #include -#include +#include _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { -struct failed : std::runtime_error { - virtual ~failed() = default; +struct failed : std::exception { + char const* msg_{}; int errno_{0}; - failed() : std::runtime_error({}) {} - failed(char const* msg, int err) : std::runtime_error(msg), errno_(err) {} + failed(char const* msg, int err) : std::exception(), msg_(msg), errno_(err) {} + + virtual ~failed() noexcept = default; + const char* what() const noexcept override { return msg_; } }; } // namespace __stacktrace diff --git a/libcxx/src/stacktrace/utils/fd.h b/libcxx/src/stacktrace/utils/fd.h index f4be1876e5d37..75f169dc5fa14 100644 --- a/libcxx/src/stacktrace/utils/fd.h +++ b/libcxx/src/stacktrace/utils/fd.h @@ -110,7 +110,7 @@ struct fd_mmap final { _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(fd&& fd) : fd_(std::move(fd)) { if (fd_) { - if ((size_ = ::lseek(fd, 0, SEEK_END))) { + if ((size_ = ::lseek(fd_, 0, SEEK_END))) { addr_ = (std::byte const*)::mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd_, 0); } } diff --git a/libcxx/src/stacktrace/windows/dll.cpp b/libcxx/src/stacktrace/windows/dll.cpp index d48bead956041..fad8b591ec4e5 100644 --- a/libcxx/src/stacktrace/windows/dll.cpp +++ b/libcxx/src/stacktrace/windows/dll.cpp @@ -12,7 +12,7 @@ # include <__stacktrace/base.h> -# include "stacktrace/alloc.h" +# include "stacktrace/base.h" # include "stacktrace/config.h" # include "stacktrace/utils.h" # include "stacktrace/windows/dll.h" @@ -49,7 +49,7 @@ size_t moduleCount; // 0 IFF module enumeration failed } // namespace -win_impl::WinDebugAPIs(builder& trace) : builder_(trace), guard_(gWindowsAPILock) { +win_impl::WinDebugAPIs(base& trace) : base_(trace), guard_(gWindowsAPILock) { if (!globalInitialized) { // Cannot proceed without these DLLs: if (!dbg) { @@ -108,7 +108,7 @@ void win_impl::symbolize() { return; } - for (auto& entry : builder_.__entries_) { + for (auto& entry : base_.__entries_) { char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; auto* sym = (IMAGEHLP_SYMBOL64*)space; sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); @@ -128,7 +128,7 @@ void win_impl::resolve_lines() { return; } - for (auto& entry : builder_.__entries_) { + for (auto& entry : base_.__entries_) { DWORD disp{0}; IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); @@ -185,7 +185,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_ continue; } --max_depth; - auto& entry = builder_.__entries_.emplace_back(); + auto& entry = base_.__entries_.emplace_back(); // We don't need to compute the un-slid addr; windbg only needs the actual addresses. // Assume address is of the instruction after a call instruction, since we can't // differentiate between a signal, SEH exception handler, or a normal function call. diff --git a/libcxx/src/stacktrace/windows/impl.cpp b/libcxx/src/stacktrace/windows/impl.cpp index e8901e469e768..476690d7f0760 100644 --- a/libcxx/src/stacktrace/windows/impl.cpp +++ b/libcxx/src/stacktrace/windows/impl.cpp @@ -109,7 +109,7 @@ void win_impl::symbolize() { return; } - for (auto& entry : builder_.__entries_) { + for (auto& entry : base_.__entries_) { char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; auto* sym = (IMAGEHLP_SYMBOL64*)space; sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); @@ -129,7 +129,7 @@ void win_impl::resolve_lines() { return; } - for (auto& entry : builder_.__entries_) { + for (auto& entry : base_.__entries_) { DWORD disp{0}; IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); @@ -186,7 +186,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_ continue; } --max_depth; - auto& entry = builder_.__entries_.emplace_back(); + auto& entry = base_.__entries_.emplace_back(); // We don't need to compute the un-slid addr; windbg only needs the actual addresses. // Assume address is of the instruction after a call instruction, since we can't // differentiate between a signal, SEH exception handler, or a normal function call. diff --git a/libcxx/src/stacktrace/windows/impl.h b/libcxx/src/stacktrace/windows/impl.h index b88a3f9d8a3a1..95df844c28ca3 100644 --- a/libcxx/src/stacktrace/windows/impl.h +++ b/libcxx/src/stacktrace/windows/impl.h @@ -18,16 +18,16 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { -struct builder; +struct base; struct win_impl { - builder& builder_; + base& base_; #if defined(_LIBCPP_WIN32API) static std::mutex mutex_; std::lock_guard guard_; - explicit win_impl(builder& builder) : builder_(builder), guard_(mutex_) { global_init(); } + explicit win_impl(base& base) : base_(base), guard_(mutex_) { global_init(); } ~win_impl(); void global_init(); diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp index 5864d241abf35..bd1418220d275 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp @@ -28,6 +28,9 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2b() { return test1() _LIBCPP_NO_TAIL_CALLS int main(int, char**) { auto st1a = test1(); // [test1, main, ...] + + static_assert(noexcept(st1a == st1a)); + assert(st1a == st1a); auto st1b = st1a; @@ -47,5 +50,7 @@ int main(int, char**) { assert(st2a.size() == st2b.size()); assert(st2a != st2b); + static_assert(noexcept(st2a == st2b)); + return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp index 5664e9bc41db2..30a8e36dbd812 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp @@ -34,6 +34,9 @@ int main(int, char**) { auto st1a = test1(); // [test1, main, ...] auto st1b = st1a; + + static_assert(noexcept(st1a <=> st1b)); + assert(st1a == st1b); auto st2a = test2a(); // [test1, test2a, main, ...] assert(st1a != st2a); diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp new file mode 100644 index 0000000000000..642626475eb5f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g -Og + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); +*/ + +#include +#include + +#include "../test_allocs.h" + +void test_copy_construct() { + auto a = std::stacktrace::current(); + std::stacktrace b{a}; + assert(a == b); +} + +void test_copy_assign() { + { + using A = + TestAlloc; + auto s1 = std::basic_stacktrace::current(); + std::basic_stacktrace s2{s1}; + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should not propagate + assert(a1 != a2); + } + { + using A = + TestAlloc; + auto s1 = std::basic_stacktrace::current(); + std::basic_stacktrace s2{s1}; + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } + { + using A = + TestAlloc; + auto s1 = std::basic_stacktrace::current(); + std::basic_stacktrace s2{s1}; + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_copy_construct(); + test_copy_assign(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp deleted file mode 100644 index 48da1d0bd30b6..0000000000000 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.pass.cpp +++ /dev/null @@ -1,82 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -// REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g - -/* - (19.6.4.2) - - // [stacktrace.basic.cons], creation and assignment - basic_stacktrace(const basic_stacktrace& other); - basic_stacktrace(basic_stacktrace&& other) noexcept; - basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); - basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); - basic_stacktrace& operator=(const basic_stacktrace& other); - basic_stacktrace& operator=(basic_stacktrace&& other) - noexcept(allocator_traits::propagate_on_container_move_assignment::value || - allocator_traits::is_always_equal::value); -*/ - -#include -#include -#include - -// clang-format off -uint32_t test1_line; -uint32_t test2_line; - -template -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE -std::basic_stacktrace test1(A& alloc) { - test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) - auto ret = std::basic_stacktrace::current(alloc); - return ret; -} - -template -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE -std::basic_stacktrace test2(A& alloc) { - test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) - auto ret = test1(alloc); - return ret; -} - -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_copy_move_ctors() { - using A = std::allocator; - A alloc; - auto st = std::basic_stacktrace::current(alloc); - - auto copy_constr = std::basic_stacktrace(st); - assert(st == copy_constr); - - std::basic_stacktrace copy_assign; - copy_assign = std::basic_stacktrace(st); - assert(st == copy_assign); - - auto st2 = test2(alloc); - assert(st2.size()); - std::basic_stacktrace move_constr(std::move(st2)); - assert(move_constr.size()); - assert(!st2.size()); - - auto st3 = test2(alloc); - assert(st3.size()); - std::basic_stacktrace move_assign; - move_assign = std::move(st3); - assert(move_assign.size()); - assert(!st3.size()); - - // TODO(stacktrace23): should we add test cases with `select_on_container_copy_construction`? -} - -_LIBCPP_NO_TAIL_CALLS -int main(int, char**) { - test_copy_move_ctors(); - return 0; -} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp index 3b406f1b16a61..ed1aac95fc83e 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp @@ -18,31 +18,29 @@ #include #include +#include -uint32_t test1_line; -uint32_t test2_line; +#include "../test_allocs.h" -template -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test1(A& alloc) { - test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) - auto ret = std::basic_stacktrace::current(alloc); - return ret; +void test_default_construct() { + std::stacktrace st; + assert(st.empty()); } -template -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test2(A& alloc) { - test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) - auto ret = test1(alloc); - return ret; -} +void test_default_construct_noexcept() { + static_assert(noexcept(std::stacktrace())); -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_default_construct() { - std::stacktrace st; - assert(st.empty()); + using A1 = std::allocator; + static_assert(std::is_nothrow_default_constructible_v); + static_assert(noexcept(std::basic_stacktrace())); + + using A2 = TestAlloc; + static_assert(!std::is_nothrow_default_constructible_v); + static_assert(!noexcept(std::basic_stacktrace())); } -_LIBCPP_NO_TAIL_CALLS int main(int, char**) { test_default_construct(); + test_default_construct_noexcept(); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp index a49f45245f7d2..baf2ac17fe89e 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp @@ -18,62 +18,29 @@ #include #include +#include -uint32_t test1_line; -uint32_t test2_line; +#include "../test_allocs.h" -template -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test1(A& alloc) { - test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) - auto ret = std::basic_stacktrace::current(alloc); - return ret; -} - -template -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test2(A& alloc) { - test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) - auto ret = test1(alloc); - return ret; +void test_construct_with_alloc() { + std::stacktrace st; + assert(st.empty()); } -template -struct test_alloc { - using size_type = size_t; - using value_type = T; - using pointer = T*; - using const_pointer = T const*; - - template - struct rebind { - using other = test_alloc; - }; - - std::allocator wrapped_{}; +void test_construct_with_alloc_noexcept() { + static_assert(noexcept(std::stacktrace())); - test_alloc() = default; - - template - test_alloc(test_alloc const& rhs) : wrapped_(rhs.wrapped_) {} - - bool operator==(auto const& rhs) const { return &rhs == this; } - bool operator==(test_alloc const&) const { return true; } - - T* allocate(size_t n) { return wrapped_.allocate(n); } - auto allocate_at_least(size_t n) { return wrapped_.allocate_at_least(n); } - void deallocate(T* ptr, size_t n) { return wrapped_.deallocate(ptr, n); } -}; - -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_construct_with_allocator() { - test_alloc alloc; - std::basic_stacktrace st(alloc); - assert(st.empty()); + using A1 = std::allocator; + static_assert(std::is_nothrow_constructible_v); + static_assert(noexcept(std::basic_stacktrace(A1()))); - st = std::basic_stacktrace::current(alloc); - assert(!st.empty()); + using A2 = TestAlloc; + static_assert(!std::is_nothrow_constructible_v); + static_assert(!noexcept(std::basic_stacktrace(A2()))); } -_LIBCPP_NO_TAIL_CALLS int main(int, char**) { - test_construct_with_allocator(); + test_construct_with_alloc(); + test_construct_with_alloc_noexcept(); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp index c1c9b3ea70496..401bc97bcb7c1 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp @@ -62,6 +62,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current() { _LIBCPP_NO_TAIL_CALLS int main(int, char**) { + static_assert(noexcept(std::stacktrace::current())); test_current(); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp index 55877f0a49785..55dc16c1cdc59 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp @@ -40,6 +40,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip() { _LIBCPP_NO_TAIL_CALLS int main(int, char**) { + static_assert(noexcept(std::stacktrace::current(0))); test_current_with_skip(); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp index a999845e92e01..ccd7c57acb425 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp @@ -37,6 +37,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip_depth() { _LIBCPP_NO_TAIL_CALLS int main(int, char**) { + static_assert(noexcept(std::stacktrace::current(0, 0))); test_current_with_skip_depth(); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp new file mode 100644 index 0000000000000..f859fff61e4dc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); +*/ + +#include +#include + +#include "../test_allocs.h" + +void test_move_construct() { + auto a = std::stacktrace::current(); + std::stacktrace b{a}; + assert(a == b); +} + +void test_move_assign() { + { + using A = + TestAlloc; + auto s0 = std::basic_stacktrace::current(); + std::basic_stacktrace s1{s0}; + std::basic_stacktrace s2(std::move(s0)); + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should not propagate + assert(a1 != a2); + } + { + using A = + TestAlloc; + auto s0 = std::basic_stacktrace::current(); + std::basic_stacktrace s1{s0}; + std::basic_stacktrace s2(std::move(s0)); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } + { + using A = + TestAlloc; + auto s0 = std::basic_stacktrace::current(); + std::basic_stacktrace s1{s0}; + std::basic_stacktrace s2(std::move(s0)); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_move_construct(); + test_move_assign(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp index ff6a3404a676a..e0d9beef98ec8 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp @@ -9,31 +9,6 @@ // REQUIRES: std-at-least-c++23 // ADDITIONAL_COMPILE_FLAGS: -g -O0 -/* - (19.6.4.2) - - // [stacktrace.basic.cons], creation and assignment - static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; [1] - static basic_stacktrace current(size_type skip, - const allocator_type& alloc = allocator_type()) noexcept; [2] - static basic_stacktrace current(size_type skip, size_type max_depth, - const allocator_type& alloc = allocator_type()) noexcept; [3] - - basic_stacktrace() noexcept(is_nothrow_default_constructible_v); [4] - explicit basic_stacktrace(const allocator_type& alloc) noexcept; [5] - - basic_stacktrace(const basic_stacktrace& other); [6] - basic_stacktrace(basic_stacktrace&& other) noexcept; [7] - basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); [8] - basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); [9] - basic_stacktrace& operator=(const basic_stacktrace& other); [10] - basic_stacktrace& operator=(basic_stacktrace&& other) - noexcept(allocator_traits::propagate_on_container_move_assignment::value || - allocator_traits::is_always_equal::value); [11] - - ~basic_stacktrace(); [12] -*/ - #include #include #include @@ -41,18 +16,38 @@ /* * This file includes tests which ensure any allocations performed by `basic_stacktrace` * are done via the user-provided allocator. We intercept the usual ways to allocate, - * counting the number of calls. + * counting the number of calls, through and not through the allocator. */ +unsigned new_count; +unsigned del_count; unsigned custom_alloc; unsigned custom_dealloc; -void* operator new(size_t size) { return malloc(size); } -void* operator new[](size_t size) { return malloc(size); } -void operator delete(void* ptr) noexcept { free(ptr); } -void operator delete(void* ptr, size_t) noexcept { free(ptr); } -void operator delete[](void* ptr) noexcept { free(ptr); } -void operator delete[](void* ptr, size_t) noexcept { free(ptr); } +void* operator new(size_t size) { + ++new_count; + return malloc(size); +} +void* operator new[](size_t size) { + ++new_count; + return malloc(size); +} +void operator delete(void* ptr) noexcept { + ++del_count; + free(ptr); +} +void operator delete(void* ptr, size_t) noexcept { + ++del_count; + free(ptr); +} +void operator delete[](void* ptr) noexcept { + ++del_count; + free(ptr); +} +void operator delete[](void* ptr, size_t) noexcept { + ++del_count; + free(ptr); +} template struct test_alloc { @@ -92,37 +87,22 @@ struct test_alloc { } }; -/* - (19.6.4.2) [stacktrace.basic.cons], creation and assignment, - only exercising usage of caller-provided allocator. - - static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; [1] - static basic_stacktrace current(size_type skip, - const allocator_type& alloc = allocator_type()) noexcept; [2] - static basic_stacktrace current(size_type skip, size_type max_depth, - const allocator_type& alloc = allocator_type()) noexcept; [3] - - explicit basic_stacktrace(const allocator_type& alloc) noexcept; [5] - basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); [8] - basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); [9] - - basic_stacktrace& operator=(const basic_stacktrace& other); [10] - basic_stacktrace& operator=(basic_stacktrace&& other) - noexcept(allocator_traits::propagate_on_container_move_assignment::value || - allocator_traits::is_always_equal::value); [11] -*/ - -void do_current_stacktrace() { - using A = test_alloc; - (void)std::basic_stacktrace::current(A()); -} - _LIBCPP_NO_TAIL_CALLS int main(int, char**) { + // Clear these counters in case anything was created/deleted prior to `main`, + // and in case taking a stacktrace involved initialization of something and is + // outside our control. + (void)std::stacktrace::current(); + new_count = del_count = 0; + { - do_current_stacktrace(); - assert(custom_alloc > 0); - } - assert(custom_dealloc == custom_alloc); + using A = test_alloc; + auto st = std::basic_stacktrace::current(); + assert(custom_alloc > 0); // Ensure allocator was called at some point + } // Exit this scope to destroy stacktrace (as well as allocator) + + assert(custom_alloc == new_count); // All "new" calls should have been through allocator + assert(custom_alloc == custom_dealloc); // and all allocations should be deallocated + return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp index 147ce1f65810d..1de9d83acfea6 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp @@ -22,20 +22,43 @@ #include #include +#include "../test_allocs.h" + int main(int, char**) { std::stacktrace empty; - auto current = std::stacktrace::current(); - - std::stacktrace a(empty); - std::stacktrace b(current); - assert(a == empty); - assert(b == current); + auto a = std::stacktrace::current(); + std::stacktrace b(empty); + assert(!a.empty()); + assert(b.empty()); a.swap(b); - assert(a == current); - assert(b == empty); + assert(a.empty()); + assert(!b.empty()); + + // Check `noexcept`: `swap` is noexcept if either: + // (1) the allocator propagates on swap + // (2) if instances of that allocator type are always equal. + + // `AllocPropagate` satisfies the first (but not the second); stacktrace swap should be noexcept + AllocPropagate prop1; + AllocPropagate prop2; + auto prop_st1 = std::basic_stacktrace(prop1); + auto prop_st2 = std::basic_stacktrace(prop2); + static_assert(noexcept(prop_st1.swap(prop_st2))); + + // `AllocNoPropagate` satisfies neither; stacktrace swap should not be noexcept + AllocNoPropagate no_prop1; + AllocNoPropagate no_prop2; + auto no_prop_st1 = std::basic_stacktrace(no_prop1); + auto no_prop_st2 = std::basic_stacktrace(no_prop2); + static_assert(!noexcept(no_prop_st1.swap(no_prop_st2))); - // TODO(stacktrace23): should we also test swap w/ `select_on_container_swap` case + // `AllocAlwaysEqual` satisfies second; stacktrace swap should be noexcept + AllocAlwaysEqual always_eq1; + AllocAlwaysEqual always_eq2; + auto always_eq_st1 = std::basic_stacktrace(always_eq1); + auto always_eq_st2 = std::basic_stacktrace(always_eq2); + static_assert(noexcept(always_eq_st1.swap(always_eq_st2))); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp index 281af1c245245..3d49094a525fc 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp @@ -19,18 +19,39 @@ #include #include +#include "../test_allocs.h" + int main(int, char**) { std::stacktrace empty; - auto current = std::stacktrace::current(); - - std::stacktrace a(empty); - std::stacktrace b(current); - assert(a == empty); - assert(b == current); + auto a = std::stacktrace::current(); + std::stacktrace b(empty); + assert(!a.empty()); + assert(b.empty()); std::swap(a, b); - assert(a == current); - assert(b == empty); + assert(a.empty()); + assert(!b.empty()); + + // `AllocPropagate` satisfies the first (but not the second); stacktrace swap should be noexcept + AllocPropagate prop1; + AllocPropagate prop2; + auto prop_st1 = std::basic_stacktrace(prop1); + auto prop_st2 = std::basic_stacktrace(prop2); + static_assert(noexcept(std::swap(prop_st1, prop_st2))); + + // `AllocNoPropagate` satisfies neither; stacktrace swap should not be noexcept + AllocNoPropagate no_prop1; + AllocNoPropagate no_prop2; + auto no_prop_st1 = std::basic_stacktrace(no_prop1); + auto no_prop_st2 = std::basic_stacktrace(no_prop2); + static_assert(!noexcept(std::swap(no_prop_st1, no_prop_st2))); + + // `AllocAlwaysEqual` satisfies second; stacktrace swap should be noexcept + AllocAlwaysEqual always_eq1; + AllocAlwaysEqual always_eq2; + auto always_eq_st1 = std::basic_stacktrace(always_eq1); + auto always_eq_st2 = std::basic_stacktrace(always_eq2); + static_assert(noexcept(std::swap(always_eq_st1, always_eq_st2))); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp index 4562f33af2687..f9604c761b17c 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp @@ -15,7 +15,6 @@ */ #include -#include #include _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp index db3189d7cb007..23c3e44a36c26 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp @@ -26,6 +26,8 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.begin())); + static_assert(noexcept(st.end())); static_assert(std::random_access_iterator); assert(st.begin() == st.end()); // no longer empty: diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp index 3d084d45ef7b2..237bf314fb233 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp @@ -26,6 +26,8 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.cbegin())); + static_assert(noexcept(st.cend())); static_assert(std::random_access_iterator); assert(st.cbegin() == st.cend()); // no longer empty: diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp index 5d4d6fc6737f9..fa4e646992439 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp @@ -26,6 +26,8 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.crbegin())); + static_assert(noexcept(st.crend())); static_assert(std::random_access_iterator); assert(st.crbegin() == st.crend()); // no longer empty: diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp index dc3c6baa8b978..f60be2f13f6ad 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp @@ -24,6 +24,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.empty())); assert(st.empty()); st = test3(); assert(!st.empty()); diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp index 2b1004f38051d..a199657963ee2 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp @@ -20,6 +20,7 @@ int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.get_allocator())); static_assert(std::same_as); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp index 81768ec965cff..fbdfaa854caa7 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp @@ -26,6 +26,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.max_size())); assert(st.max_size() == (std::vector>().max_size())); return 0; diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp index aab086b47dd32..69bd00372d109 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp @@ -15,7 +15,6 @@ */ #include -#include #include _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } @@ -25,7 +24,6 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { auto st = test3(); - assert(st[0]); assert(st[1]); assert(st[2]); diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp index f8446d42b5f03..31c1b7449482c 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp @@ -26,6 +26,8 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.rbegin())); + static_assert(noexcept(st.rend())); static_assert(std::random_access_iterator); assert(st.rbegin() == st.rend()); // no longer empty: diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp index cbfac43b4beaf..6fa04e5b7b55a 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp @@ -24,6 +24,7 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); _LIBCPP_NO_TAIL_CALLS int main(int, char**) { std::stacktrace st; + static_assert(noexcept(st.size())); assert(st.size() == 0); st = test3(); assert(st.size() > 0); diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp index 4ddd27ed388c6..37edce6f7f313 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp @@ -32,9 +32,13 @@ int main(int, char**) { std::stacktrace_entry a; std::stacktrace_entry b; std::stacktrace_entry c; + *(uintptr_t*)(&a) = uintptr_t(&func1); *(uintptr_t*)(&b) = uintptr_t(&func1); *(uintptr_t*)(&c) = uintptr_t(&func2); + + static_assert(noexcept(a == b)); + assert(a == b); assert(a != c); diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp index edefe70af1fc9..fb89592a5177f 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp @@ -40,10 +40,13 @@ int main(int, char**) { std::stacktrace_entry a; std::stacktrace_entry b; std::stacktrace_entry c; + *(uintptr_t*)(&a) = uintptr_t(addr1); *(uintptr_t*)(&b) = uintptr_t(addr1); *(uintptr_t*)(&c) = uintptr_t(addr2); + static_assert(noexcept(a <=> b)); + assert(std::strong_ordering::equal == (a <=> b)); assert(std::strong_ordering::equivalent == (a <=> b)); assert(std::strong_ordering::greater == (c <=> a)); diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp index c993a6df64508..4a025695bc1bf 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp @@ -24,11 +24,14 @@ namespace std { #include _LIBCPP_NO_TAIL_CALLS -int main(int, char**) noexcept { +int main(int, char**) { static_assert(std::is_nothrow_copy_assignable_v); - std::stacktrace_entry entry_t2; - std::stacktrace_entry entry_t4; - entry_t4 = entry_t2; + + std::stacktrace_entry e1 = std::stacktrace::current()[0]; + std::stacktrace_entry e2; + static_assert(noexcept(e2 = e1)); + e2 = e1; + assert(e2 == e1); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp index 85dd6fb7853cc..9862a63a01479 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp @@ -25,9 +25,12 @@ namespace std { _LIBCPP_NO_TAIL_CALLS int main(int, char**) { - std::stacktrace_entry entry_t2; static_assert(std::is_nothrow_copy_constructible_v); - std::stacktrace_entry entry_t3(entry_t2); + + std::stacktrace_entry e1 = std::stacktrace::current()[0]; + static_assert(noexcept(std::stacktrace_entry(e1))); + std::stacktrace_entry e2(e1); + assert(e2 == e1); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp index 03bd82cfd9c73..2f3376a34e4ce 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp @@ -23,13 +23,12 @@ namespace std { #include #include -_LIBCPP_NO_TAIL_CALLS -int main(int, char**) noexcept { - // "Postconditions: *this is empty." - static_assert(std::is_default_constructible_v); +int main(int, char**) { static_assert(std::is_nothrow_default_constructible_v); - std::stacktrace_entry entry_t2; - assert(!entry_t2); + + std::stacktrace_entry entry; + // "Postconditions: *this is empty." + assert(!entry); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp index bf1a23080f307..fa11b419ff9ee 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp @@ -21,8 +21,8 @@ namespace std { #include int main(int, char**) noexcept { - std::stacktrace_entry entry_t6; - assert(entry_t6.native_handle() == 0); - + std::stacktrace_entry e; + static_assert(noexcept(e.native_handle())); + assert(e.native_handle() == 0); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp index f1a685324b22d..5bc64a6a907bc 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp @@ -22,13 +22,15 @@ namespace std { #include int main(int, char**) { - std::stacktrace_entry entry_t6; + std::stacktrace_entry e; // "Returns: false if and only if *this is empty." - assert(!entry_t6); + assert(!e); // Now set addr to something nonzero - *(uintptr_t*)(&entry_t6) = uintptr_t(&main); - assert(entry_t6.native_handle() == uintptr_t(&main)); - assert(entry_t6); + *(uintptr_t*)(&e) = uintptr_t(&main); + assert(e.native_handle() == uintptr_t(&main)); + assert(e); + + static_assert(noexcept(bool(e))); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/test_allocs.h b/libcxx/test/std/diagnostics/stacktrace/test_allocs.h new file mode 100644 index 0000000000000..274b6362d4e36 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/test_allocs.h @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +/* +Allocator class useful for testing various propagation, always-equal scenarios. +*/ + +#ifndef _LIBCPP_STACKTRACE_TEST_ALLOCS_H +#define _LIBCPP_STACKTRACE_TEST_ALLOCS_H + +#include +#include + +template +struct TestAlloc { + using size_type = size_t; + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + + using Self = TestAlloc; + + template + using Other = TestAlloc; + + using propagate_on_container_copy_assignment = typename std::bool_constant<_KPropagate>; + using propagate_on_container_move_assignment = typename std::bool_constant<_KPropagate>; + using propagate_on_container_swap = typename std::bool_constant<_KPropagate>; + using is_always_equal = typename std::bool_constant<_KAlwaysEqual>; + + auto select_on_container_copy_construction(this auto& self) { return _KPropagate ? self : Self(); } + + template + struct rebind { + using other = Other; + }; + + static std::shared_ptr> new_arena() { + return std::make_shared>(); + } + + static std::shared_ptr> global_arena() { + static auto ret = new_arena(); + return ret; + } + + /** Type-erased allocator used for servicing allocate and deallocate. + Two `TestAlloc`'s are equal IFF they contain the same arena pointer. + Always-equal `TestAlloc`'s get a pointer to a shared `global_arena`. */ + std::shared_ptr> arena_; + + /** Instances are equal IFF they have the same arena pointer (even if this is "always_equals", + since such instances point to the global arena). */ + bool operator==(auto const& rhs) const noexcept { return arena_.get() == rhs.arena_.get(); } + + /** Construct with a new arena, or, if always-equal, the global arena. */ + TestAlloc() noexcept(_KNoExCtors) : arena_(_KAlwaysEqual ? global_arena() : new_arena()) {} + + template + TestAlloc(Other const& rhs) : arena_(rhs.arena_) {} + + template + TestAlloc& operator=(Other const& rhs) { + arena_ = rhs.arena_; + } + + std::allocator& arena() { return *(std::allocator*)arena_.get(); } + + T* allocate(size_t n) noexcept(_KNoExAlloc) { return arena().allocate(n); } + auto allocate_at_least(size_t n) noexcept(_KNoExAlloc) { return arena().allocate_at_least(n); } + void deallocate(T* ptr, size_t n) noexcept(_KNoExAlloc) { return arena().deallocate(ptr, n); } +}; + +// For convenience and readability: + +template +using AllocPropagate = + TestAlloc; + +template +using AllocNoPropagate = + TestAlloc; + +template +using AllocAlwaysEqual = + TestAlloc; + +#endif diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp index 461087467a270..8d8ce5665565b 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -105,4 +105,3 @@ #endif // TEST_STD_VER > 23 // clang-format on - From 318dab6dc4ba1fa3c92716a950c71c70b9d1679c Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Tue, 15 Jul 2025 16:33:30 -0400 Subject: [PATCH 08/19] drop 'debug'; fix windows --- libcxx/include/__stacktrace/base.h | 5 + libcxx/src/stacktrace/config.h | 26 ---- libcxx/src/stacktrace/linux/impl.cpp | 3 +- libcxx/src/stacktrace/tools/tools.cpp | 5 - libcxx/src/stacktrace/tools/tools.h | 13 +- libcxx/src/stacktrace/utils/debug.h | 55 -------- libcxx/src/stacktrace/windows/dll.cpp | 182 ++----------------------- libcxx/src/stacktrace/windows/dll.h | 61 ++------- libcxx/src/stacktrace/windows/impl.cpp | 109 ++++++--------- libcxx/src/stacktrace/windows/impl.h | 34 ++--- 10 files changed, 93 insertions(+), 400 deletions(-) delete mode 100644 libcxx/src/stacktrace/config.h delete mode 100644 libcxx/src/stacktrace/utils/debug.h diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h index b76b336595e9b..d63b4087532d7 100644 --- a/libcxx/include/__stacktrace/base.h +++ b/libcxx/include/__stacktrace/base.h @@ -56,6 +56,11 @@ struct _LIBCPP_EXPORTED_FROM_ABI base { template Alloc(Alloc<_T2> const& __rhs) : Alloc(__rhs.__alloc_bytes_, __rhs.__dealloc_bytes_) {} + Alloc() + : __alloc_bytes_([](size_t __sz) { return std::allocator().allocate(__sz); }), + __dealloc_bytes_([](std::byte* __ptr, size_t __sz) { std::allocator().deallocate(__ptr, __sz); }) { + } + // XXX Alignment? using value_type = _Tp; [[nodiscard]] _Tp* allocate(size_t __sz) { return (_Tp*)__alloc_bytes_(__sz * sizeof(_Tp)); } diff --git a/libcxx/src/stacktrace/config.h b/libcxx/src/stacktrace/config.h deleted file mode 100644 index c4c742d868b69..0000000000000 --- a/libcxx/src/stacktrace/config.h +++ /dev/null @@ -1,26 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_CONFIG_H -#define _LIBCPP_STACKTRACE_CONFIG_H - -#include <__config> -#include <__config_site> - -// Check for unwind.h -- could exist on any OS (in theory), but it (or `libunwind`) is likely on Linux systems, and also -// comes with XCode tools on MacOS. -#if __has_include() -# define _LIBCPP_STACKTRACE_UNWIND_IMPL -#endif - -// Whether we can invoke external processes via `posix_spawn` -#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME -# define _LIBCPP_STACKTRACE_CAN_SPAWN_TOOLS -#endif - -#endif // _LIBCPP_STACKTRACE_CONFIG_H diff --git a/libcxx/src/stacktrace/linux/impl.cpp b/libcxx/src/stacktrace/linux/impl.cpp index c01fcb02f30ef..c33e670a3584d 100644 --- a/libcxx/src/stacktrace/linux/impl.cpp +++ b/libcxx/src/stacktrace/linux/impl.cpp @@ -8,13 +8,12 @@ #if defined(__linux__) +# include <__stacktrace/base.h> # include # include # include -# include # include -# include "stacktrace/config.h" # include "stacktrace/linux/elf.h" # include "stacktrace/linux/images.h" # include "stacktrace/linux/impl.h" diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp index f1d6e966e56fe..f81e95657d0e5 100644 --- a/libcxx/src/stacktrace/tools/tools.cpp +++ b/libcxx/src/stacktrace/tools/tools.cpp @@ -110,11 +110,6 @@ void spawner::resolve_lines() { proc.run(); return true; } catch (failed const& failed) { - debug() << failed.what(); - if (failed.errno_) { - debug() << " (" << failed.errno_ << " " << strerror(failed.errno_) << ')'; - } - debug() << '\n'; } return false; }); diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h index 6f1a7935a183a..f38a344803dcb 100644 --- a/libcxx/src/stacktrace/tools/tools.h +++ b/libcxx/src/stacktrace/tools/tools.h @@ -27,7 +27,6 @@ #include <__stacktrace/basic.h> #include <__stacktrace/entry.h> -#include "stacktrace/utils/debug.h" #include "stacktrace/utils/failed.h" #include "stacktrace/utils/fd.h" @@ -152,9 +151,7 @@ struct pspawn_tool : pspawn { pspawn_tool(tool const& a2l, base& trace, char* buf, size_t size) : pspawn{a2l}, base_(trace), fd_(fa_.redirectOutFD()), buf_(fd_, buf, size), stream_(buf_) { - if (!debug::enabled()) { - fa_.redirectErrNull(); - } + fa_.redirectErrNull(); fa_.redirectInNull(); } @@ -167,14 +164,6 @@ struct pspawn_tool : pspawn { } auto argStrings = tool_.buildArgs(base_); - if (debug::enabled()) { - debug() << "Trying to get stacktrace using:"; - for (auto& str : argStrings) { - debug() << " \"" << str << '"'; - } - debug() << '\n'; - } - spawn(argStrings); auto end = base_.__entries_.end(); diff --git a/libcxx/src/stacktrace/utils/debug.h b/libcxx/src/stacktrace/utils/debug.h deleted file mode 100644 index cc88224942f6f..0000000000000 --- a/libcxx/src/stacktrace/utils/debug.h +++ /dev/null @@ -1,55 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_UTILS_DEBUG -#define _LIBCPP_STACKTRACE_UTILS_DEBUG - -#include <__config> -#include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -/** Debug-message output stream. If `LIBCXX_STACKTRACE_DEBUG` is defined in the environment -or as a macro with exactly the string `1` then this is enabled (prints to `std::cerr`); -otherwise its does nothing by returning a dummy stream. */ -struct _LIBCPP_HIDE_FROM_ABI debug : std::ostream { - _LIBCPP_HIDE_FROM_ABI virtual ~debug() = default; - - _LIBCPP_HIDE_FROM_ABI static bool enabled() { -#if defined(LIBCXX_STACKTRACE_DEBUG) && LIBCXX_STACKTRACE_DEBUG == 1 - return true; -#else - static bool ret = [] { - auto const* val = getenv("LIBCXX_STACKTRACE_DEBUG"); - return val && !strncmp(val, "1", 1); - }(); - return ret; -#endif - } - - /** No-op output stream. */ - struct _LIBCPP_HIDE_FROM_ABI dummy_ostream final : std::ostream { - _LIBCPP_HIDE_FROM_ABI virtual ~dummy_ostream() = default; - friend std::ostream& operator<<(dummy_ostream& bogus, auto const&) { return bogus; } - }; - - friend std::ostream& operator<<(debug& dp, auto const& val) { - static dummy_ostream kdummy; - if (!enabled()) { - return kdummy; - } - std::cerr << val; - return std::cerr; - } -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_UTILS_DEBUG diff --git a/libcxx/src/stacktrace/windows/dll.cpp b/libcxx/src/stacktrace/windows/dll.cpp index fad8b591ec4e5..3722955a939af 100644 --- a/libcxx/src/stacktrace/windows/dll.cpp +++ b/libcxx/src/stacktrace/windows/dll.cpp @@ -12,9 +12,6 @@ # include <__stacktrace/base.h> -# include "stacktrace/base.h" -# include "stacktrace/config.h" -# include "stacktrace/utils.h" # include "stacktrace/windows/dll.h" # include "stacktrace/windows/impl.h" @@ -23,188 +20,29 @@ namespace __stacktrace { namespace { -/* -Global objects, shared among all threads and among all stacktrace operations. -The `dbghelp` APIs are not safe to call concurrently (according to their docs) -so we claim a lock in the `WinDebugAPIs` constructor. -*/ - -// Statically-initialized -std::mutex gWindowsAPILock; -DbgHelpDLL dbg; -PSAPIDLL ps; - -// Initialized once, in first WinDebugAPIs construction; -// protected by the above mutex. +// Initialized once, in first `win_impl` construction. +// Protected by mutex within the `win_impl` constructor. HANDLE proc; HMODULE exe; IMAGE_NT_HEADERS* ntHeaders; bool globalInitialized{false}; // Globals used across invocations of the functions below. -// Also guarded by the above mutex. +// Protected by mutex within the `win_impl` constructor. bool symsInitialized{false}; HMODULE moduleHandles[1024]; size_t moduleCount; // 0 IFF module enumeration failed } // namespace -win_impl::WinDebugAPIs(base& trace) : base_(trace), guard_(gWindowsAPILock) { - if (!globalInitialized) { - // Cannot proceed without these DLLs: - if (!dbg) { - return; - } - if (!ps) { - return; - } - proc = GetCurrentProcess(); - if (!(exe = GetModuleHandle(nullptr))) { - return; - } - if (!(ntHeaders = (*dbg.ImageNtHeader)(exe))) { - return; - } - - globalInitialized = true; - } - - // Initialize symbol machinery. - // Presumably the symbols in this process's space can change between - // stacktraces, so we'll do this each time we take a trace. - // The final `true` means we want the runtime to enumerate all this - // process's modules' symbol tables. - symsInitialized = (*dbg.SymInitialize)(proc, nullptr, true); - DWORD symOptions = (*dbg.SymGetOptions)(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - (*dbg.SymSetOptions)(symOptions); -} - -win_impl::~WinDebugAPIs() { - if (symsInitialized) { - (*dbg.SymCleanup)(proc); - symsInitialized = false; - } -} - -void win_impl::ident_modules() { - if (!globalInitialized) { - return; - } - DWORD needBytes; - auto enumMods = (*ps.EnumProcessModules)(proc, moduleHandles, sizeof(moduleHandles), LPDWORD(&needBytes)); - if (enumMods) { - moduleCount = needBytes / sizeof(HMODULE); - } else { - moduleCount = 0; - Debug() << "EnumProcessModules failed: " << GetLastError() << '\n'; - } -} - -void win_impl::symbolize() { - // Very long symbols longer than this amount will be truncated. - static constexpr size_t kMaxSymName = 256; - if (!globalInitialized) { - return; - } - - for (auto& entry : base_.__entries_) { - char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; - auto* sym = (IMAGEHLP_SYMBOL64*)space; - sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - sym->MaxNameLength = kMaxSymName; - uint64_t disp{0}; - if ((*dbg.SymGetSymFromAddr64)(proc, entry.__addr_actual_, &disp, sym)) { - // Copy chars into the destination string which uses the caller-provided allocator. - ((entry_base&)entry).__desc_ = {sym->Name}; - } else { - Debug() << "SymGetSymFromAddr64 failed: " << GetLastError() << '\n'; - } - } -} - -void win_impl::resolve_lines() { - if (!globalInitialized) { - return; - } - - for (auto& entry : base_.__entries_) { - DWORD disp{0}; - IMAGEHLP_LINE64 line; - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - if ((*dbg.SymGetLineFromAddr64)(proc, entry.__addr_actual_, &disp, &line)) { - // Copy chars into the destination string which uses the caller-provided allocator. - entry.__file_ = line.FileName; - entry.__line_ = line.LineNumber; - } else { - Debug() << "SymGetLineFromAddr64 failed: " << GetLastError() << '\n'; - } - } -} - -/* -Inlining is disabled from here on; -this is to ensure `collect` below doesn't get merged into its caller -and mess around with the top of the stack (making `skip` inaccurate). -*/ -# pragma auto_inline(off) - -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { - if (!globalInitialized) { - return; - } - - auto thread = GetCurrentThread(); - auto machine = ntHeaders->FileHeader.Machine; - - CONTEXT ccx; - RtlCaptureContext(&ccx); - - STACKFRAME64 frame; - memset(&frame, 0, sizeof(frame)); - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrStack.Mode = AddrModeFlat; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrPC.Offset = ctrace.Rip; - frame.AddrStack.Offset = ctrace.Rsp; - frame.AddrFrame.Offset = ctrace.Rbp; - - while (max_depth && - (*dbg.StackWalk64)( - machine, - proc, - thread, - &frame, - &ccx, - nullptr, - dbg.SymFunctionTableAccess64, - dbg.SymGetModuleBase64, - nullptr)) { - if (skip) { - --skip; - continue; - } - --max_depth; - auto& entry = base_.__entries_.emplace_back(); - // We don't need to compute the un-slid addr; windbg only needs the actual addresses. - // Assume address is of the instruction after a call instruction, since we can't - // differentiate between a signal, SEH exception handler, or a normal function call. - entry.__addr_actual_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range - } -} - dll::~dll() { FreeLibrary(module_); } -dll::dll(char const* name) : name_(name), module_(LoadLibrary(name)) { - if (!module_) { - debug() << "LoadLibrary failed: " << name_ << ": " << GetLastError() << '\n'; - } -} +dll::dll(char const* name) : name_(name), module_(LoadLibrary(name)) {} dbghelp_dll::~dbghelp_dll() = default; dbghelp_dll& dbghelp_dll::get() { - dbghelp_dll ret; + static dbghelp_dll ret; return ret; } @@ -228,15 +66,15 @@ if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } psapi_dll::~psapi_dll() = default; psapi_dll& psapi_dll::get() { - psapi_dll ret; + static psapi_dll ret; return ret; } -psapi_dll() : dll("psapi.dll") { +psapi_dll::psapi_dll() : dll("psapi.dll") { // clang-format off -if (!getFunc(&EnumProcessModules, "EnumProcessModules")) { return; } - if (!getFunc(&GetModuleInformation, "GetModuleInformation")) { return; } - if (!getFunc(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } +if (!get_func(&EnumProcessModules, "EnumProcessModules")) { return; } + if (!get_func(&GetModuleInformation, "GetModuleInformation")) { return; } + if (!get_func(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } valid_ = true; // clang-format on } diff --git a/libcxx/src/stacktrace/windows/dll.h b/libcxx/src/stacktrace/windows/dll.h index 13ed9e42768d5..7d10a7c8c66df 100644 --- a/libcxx/src/stacktrace/windows/dll.h +++ b/libcxx/src/stacktrace/windows/dll.h @@ -19,13 +19,10 @@ # define PSAPI_VERSION 1 # include -# include <__config_site> +# include <__stacktrace/base.h> # include # include # include -# include - -# include "stacktrace/utils/debug.h" _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { @@ -40,33 +37,23 @@ struct dll { /** Set to true in subclass's ctor if initialized successfully. */ bool valid_{false}; - operator bool() const { return valid_; } - - explicit dll(char const* name) - : name_(name), module_(LoadLibrary(name)) { - if (!module_) { - debug() << "LoadLibrary failed: " - << name_ << ": " << GetLastError() << '\n'; - } - } + virtual ~dll(); + explicit dll(char const* name); - virtual ~dll() { FreeLibrary(module_); } + operator bool() const { return valid_; } template bool get_func(F* func, char const* name) { - if (!(*func = (F)GetProcAddress(module_, name))) { - debug() << "GetProcAddress failed: " - << name << "' (" << name_ << "): " - << GetLastError() << "\n"; - return false; - } - return true; + *func = (F)GetProcAddress(module_, name); + return func != nullptr; } }; struct dbghelp_dll final : dll { - virtual ~dbghelp_dll() = default; - static dbghelp_dll& get() { static dbghelp_dll ret; return ret; } + virtual ~dbghelp_dll(); + dbghelp_dll(); + + static dbghelp_dll& get(); IMAGE_NT_HEADERS* (*ImageNtHeader)(void*); bool (*StackWalk64) (DWORD, HANDLE, HANDLE, STACKFRAME64*, void*, void*, void*, void*, void*); @@ -79,37 +66,17 @@ struct dbghelp_dll final : dll { bool (*SymInitialize) (HANDLE, char const*, bool); DWORD64 (*SymLoadModule64) (HANDLE, HANDLE, char const*, char const*, void*, DWORD); DWORD (*SymSetOptions) (DWORD); - - dbghelp_dll() : dll("dbghelp.dll") { - if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } - if (!get_func(&StackWalk64, "StackWalk64")) { return; } - if (!get_func(&SymCleanup, "SymCleanup")) { return; } - if (!get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64")) { return; } - if (!get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64")) { return; } - if (!get_func(&SymGetModuleBase64, "SymGetModuleBase64")) { return; } - if (!get_func(&SymGetOptions, "SymGetOptions")) { return; } - if (!get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64")) { return; } - if (!get_func(&SymInitialize, "SymInitialize")) { return; } - if (!get_func(&SymLoadModule64, "SymLoadModule64")) { return; } - if (!get_func(&SymSetOptions, "SymSetOptions")) { return; } - valid_ = true; - } }; struct psapi_dll final : dll { - virtual ~psapi_dll() = default; - static psapi_dll& get() { static psapi_dll ret; return ret; } + virtual ~psapi_dll(); + psapi_dll(); + + static psapi_dll& get(); bool (*EnumProcessModules) (HANDLE, HMODULE*, DWORD, DWORD*); bool (*GetModuleInformation) (HANDLE, HMODULE, MODULEINFO*, DWORD); DWORD (*GetModuleBaseName) (HANDLE, HMODULE, char**, DWORD); - - psapi_dll() : dll("psapi.dll") { - if (!get_func(&EnumProcessModules, "EnumProcessModules")) { return; } - if (!get_func(&GetModuleInformation, "GetModuleInformation")) { return; } - if (!get_func(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } - valid_ = true; - } }; #endif diff --git a/libcxx/src/stacktrace/windows/impl.cpp b/libcxx/src/stacktrace/windows/impl.cpp index 476690d7f0760..e9425271cee97 100644 --- a/libcxx/src/stacktrace/windows/impl.cpp +++ b/libcxx/src/stacktrace/windows/impl.cpp @@ -6,7 +6,9 @@ // //===----------------------------------------------------------------------===// +#include <__config> #if defined(_LIBCPP_WIN32API) + // windows.h must be first # include // other windows-specific headers @@ -14,44 +16,27 @@ # define PSAPI_VERSION 1 # include -# include - -# include "stacktrace/utils/debug.h" # include "stacktrace/windows/dll.h" # include "stacktrace/windows/impl.h" _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { -namespace { - -/* -Global objects, shared among all threads and among all stacktrace operations. -The `dbghelp` APIs are not safe to call concurrently (according to their docs) -so we claim a lock in the `WinDebugAPIs` constructor. -*/ - -// Statically-initialized -DbgHelpDLL dbg; -PSAPIDLL ps; - -// Initialized once, in first WinDebugAPIs construction; -// protected by the above mutex. -HANDLE proc; -HMODULE exe; -IMAGE_NT_HEADERS* ntHeaders; -bool globalInitialized{false}; +win_impl::~win_impl() { + auto& dbg = dbghelp_dll::get(); + if (initialized_) { + (*dbg.SymCleanup)(proc_); + initialized_ = false; + } +} -// Globals used across invocations of the functions below. -// Also guarded by the above mutex. -bool symsInitialized{false}; -HMODULE moduleHandles[1024]; -size_t moduleCount; // 0 IFF module enumeration failed +win_impl::win_impl(base& base) : base_(base) { + std::lock_guard guard(mutex_); -} // namespace + auto& dbg = dbghelp_dll::get(); + auto& ps = psapi_dll::get(); -win_impl::global_init() { - if (!globalInitialized) { + if (!initialized_) { // Cannot proceed without these DLLs: if (!dbg) { return; @@ -59,86 +44,79 @@ win_impl::global_init() { if (!ps) { return; } - proc = GetCurrentProcess(); - if (!(exe = GetModuleHandle(nullptr))) { + proc_ = GetCurrentProcess(); + if (!(exe_ = GetModuleHandle(nullptr))) { return; } - if (!(ntHeaders = (*dbg.ImageNtHeader)(exe))) { + if (!(nt_headers_ = (*dbg.ImageNtHeader)(exe_))) { return; } - globalInitialized = true; + initialized_ = true; } - // Initialize symbol machinery. - // Presumably the symbols in this process's space can change between - // stacktraces, so we'll do this each time we take a trace. // The final `true` means we want the runtime to enumerate all this // process's modules' symbol tables. - symsInitialized = (*dbg.SymInitialize)(proc, nullptr, true); + initialized_ = (*dbg.SymInitialize)(proc_, nullptr, true); DWORD symOptions = (*dbg.SymGetOptions)(); symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; (*dbg.SymSetOptions)(symOptions); } -win_impl::~win_impl() { - if (symsInitialized) { - (*dbg.SymCleanup)(proc); - symsInitialized = false; - } -} - void win_impl::ident_modules() { - if (!globalInitialized) { + if (!initialized_) { return; } + + auto& ps = psapi_dll::get(); DWORD needBytes; - auto enumMods = (*ps.EnumProcessModules)(proc, moduleHandles, sizeof(moduleHandles), LPDWORD(&needBytes)); + + auto enumMods = (*ps.EnumProcessModules)(proc_, module_handles_, sizeof(module_handles_), LPDWORD(&needBytes)); if (enumMods) { - moduleCount = needBytes / sizeof(HMODULE); + module_count_ = needBytes / sizeof(HMODULE); } else { - moduleCount = 0; - Debug() << "EnumProcessModules failed: " << GetLastError() << '\n'; + module_count_ = 0; } } void win_impl::symbolize() { - // Very long symbols longer than this amount will be truncated. - static constexpr size_t kMaxSymName = 256; - if (!globalInitialized) { + if (!initialized_) { return; } + // Very long symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + + auto& dbg = dbghelp_dll::get(); + for (auto& entry : base_.__entries_) { char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; auto* sym = (IMAGEHLP_SYMBOL64*)space; sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); sym->MaxNameLength = kMaxSymName; uint64_t disp{0}; - if ((*dbg.SymGetSymFromAddr64)(proc, entry.__addr_actual_, &disp, sym)) { + if ((*dbg.SymGetSymFromAddr64)(proc_, entry.__addr_actual_, &disp, sym)) { // Copy chars into the destination string which uses the caller-provided allocator. ((entry_base&)entry).__desc_ = {sym->Name}; - } else { - Debug() << "SymGetSymFromAddr64 failed: " << GetLastError() << '\n'; } } } void win_impl::resolve_lines() { - if (!globalInitialized) { + if (!initialized_) { return; } + auto& dbg = dbghelp_dll::get(); + for (auto& entry : base_.__entries_) { DWORD disp{0}; IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - if ((*dbg.SymGetLineFromAddr64)(proc, entry.__addr_actual_, &disp, &line)) { + if ((*dbg.SymGetLineFromAddr64)(proc_, entry.__addr_actual_, &disp, &line)) { // Copy chars into the destination string which uses the caller-provided allocator. entry.__file_ = line.FileName; entry.__line_ = line.LineNumber; - } else { - Debug() << "SymGetLineFromAddr64 failed: " << GetLastError() << '\n'; } } } @@ -151,12 +129,13 @@ and mess around with the top of the stack (making `skip` inaccurate). # pragma auto_inline(off) _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { - if (!globalInitialized) { + if (!initialized_) { return; } + auto& dbg = dbghelp_dll::get(); auto thread = GetCurrentThread(); - auto machine = ntHeaders->FileHeader.Machine; + auto machine = nt_headers_->FileHeader.Machine; CONTEXT ccx; RtlCaptureContext(&ccx); @@ -166,14 +145,14 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_ frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrPC.Offset = ctrace.Rip; - frame.AddrStack.Offset = ctrace.Rsp; - frame.AddrFrame.Offset = ctrace.Rbp; + frame.AddrPC.Offset = ccx.Rip; + frame.AddrStack.Offset = ccx.Rsp; + frame.AddrFrame.Offset = ccx.Rbp; while (max_depth && (*dbg.StackWalk64)( machine, - proc, + proc_, thread, &frame, &ccx, diff --git a/libcxx/src/stacktrace/windows/impl.h b/libcxx/src/stacktrace/windows/impl.h index 95df844c28ca3..6dea1b98b1ad0 100644 --- a/libcxx/src/stacktrace/windows/impl.h +++ b/libcxx/src/stacktrace/windows/impl.h @@ -10,10 +10,11 @@ #define _LIBCPP_STACKTRACE_WIN_IMPL_H #include <__config> -#include <__config_site> -#include -#include -#include +#if defined(_LIBCPP_WIN32API) + +# include +# include +# include _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { @@ -23,28 +24,29 @@ struct base; struct win_impl { base& base_; -#if defined(_LIBCPP_WIN32API) static std::mutex mutex_; - std::lock_guard guard_; - - explicit win_impl(base& base) : base_(base), guard_(mutex_) { global_init(); } + static HANDLE proc_; + static HMODULE exe_; + static IMAGE_NT_HEADERS* nt_headers_; + static bool initialized_; + static HMODULE module_handles_[1024]; + static size_t module_count_; // 0 IFF module enumeration failed + + /* + The `dbghelp` APIs are not safe to call concurrently (according to their docs) + so we claim a lock in constructor. + */ + explicit win_impl(base& base); ~win_impl(); - void global_init(); void collect(size_t skip, size_t max_depth); void ident_modules(); void symbolize(); void resolve_lines(); -#else - void global_init() {} - void collect(size_t, size_t) {} - void ident_modules() {} - void symbolize() {} - void resolve_lines() {} -#endif // _LIBCPP_WIN32API }; } // namespace __stacktrace _LIBCPP_END_NAMESPACE_STD +#endif // _LIBCPP_WIN32API #endif // _LIBCPP_STACKTRACE_WIN_IMPL_H From 297391689116978ab2a29473b3313e6a4cf03080 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Wed, 16 Jul 2025 17:09:45 -0400 Subject: [PATCH 09/19] Avoid throwing exceptions for -fno-exceptions builds --- libcxx/src/stacktrace/tools/tools.cpp | 86 +++++++++++++++++++------ libcxx/src/stacktrace/tools/tools.h | 91 ++++++++------------------- libcxx/src/stacktrace/utils/failed.h | 31 --------- libcxx/src/stacktrace/utils/fd.h | 14 ++--- 4 files changed, 98 insertions(+), 124 deletions(-) delete mode 100644 libcxx/src/stacktrace/utils/failed.h diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp index f81e95657d0e5..d8b455112001c 100644 --- a/libcxx/src/stacktrace/tools/tools.cpp +++ b/libcxx/src/stacktrace/tools/tools.cpp @@ -48,70 +48,118 @@ _LIBCPP_HIDE_FROM_ABI base::str u64_string(base& base, uintptr_t __val) { # define STRINGIFY0(x) #x # define STRINGIFY(x) STRINGIFY0(x) -void try_tools(base& base, function cb) { +bool try_tools(base& base, function cb) { char const* prog_name; if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"))) { if (cb(llvm_symbolizer{base, prog_name})) { - return; + return true; } } else { # if defined(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH) if (cb(llvm_symbolizer{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH)})) { - return; + return true; } # else if (cb(llvm_symbolizer{base})) { - return; + return true; } # endif } if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"))) { if (cb(addr2line{base, prog_name})) { - return; + return true; } } else { # if defined(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH) if (cb(addr2line{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH)})) { - return; + return true; } # else if (cb(addr2line{base})) { - return; + return true; } # endif } if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"))) { if (cb(atos{base, prog_name})) { - return; + return true; } } else { # if defined(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH) if (cb(atos{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH)})) { - return; + return true; } # else if (cb(atos{base})) { - return; + return true; } # endif } + + return false; // nothing succeeded } } // namespace -void spawner::resolve_lines() { - try_tools(base_, [&](tool const& prog) { +bool file_actions::initFileActions() { + if (!fa_initialized_) { + if (posix_spawn_file_actions_init(&fa_)) { + return false; + } + fa_initialized_ = true; + } + return true; +} + +file_actions::~file_actions() { posix_spawn_file_actions_destroy(&fa_); } + +bool file_actions::addClose(int fd) { return initFileActions() && (posix_spawn_file_actions_addclose(&fa_, fd) == 0); } + +bool file_actions::addDup2(int fd, int std_fd) { + return initFileActions() && (posix_spawn_file_actions_adddup2(&fa_, fd, std_fd) == 0); +} + +fd file_actions::redirectOutFD() { + int fds[2]; + if (::pipe(fds)) { + return {}; // return invalid FD + } + addClose(fds[0]); + addDup2(fds[1], 1); + return {fds[0]}; +} + +pspawn::~pspawn() { + if (pid_) { + kill(pid_, SIGTERM); + wait(); + } +} + +bool pspawn::spawn(base::list const& argStrings) { + base::vec argv = tool_.base_.make_vec(); + argv.reserve(argStrings.size() + 1); + for (auto const& str : argStrings) { + argv.push_back(str.data()); + } + argv.push_back(nullptr); + return posix_spawnp(&pid_, argv[0], &fa_.fa_, nullptr, const_cast(argv.data()), nullptr) == 0; +} + +int pspawn::wait() { + int status; + waitpid(pid_, &status, 0); + return status; +} + +bool spawner::resolve_lines() { + return try_tools(base_, [&](tool const& prog) { char buf[512]; pspawn_tool proc(prog, base_, buf, sizeof(buf)); - try { - proc.run(); - return true; - } catch (failed const& failed) { - } - return false; + return proc.run(); }); } @@ -170,7 +218,7 @@ Note that this includes an extra empty line as a terminator. line.pop_back(); } if (line.empty()) { - return; + return; // done } if (!line.starts_with(" ")) { // The symbol has no leading whitespace, while the other diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h index f38a344803dcb..8fe6d2938a4ca 100644 --- a/libcxx/src/stacktrace/tools/tools.h +++ b/libcxx/src/stacktrace/tools/tools.h @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -27,7 +26,6 @@ #include <__stacktrace/basic.h> #include <__stacktrace/entry.h> -#include "stacktrace/utils/failed.h" #include "stacktrace/utils/fd.h" _LIBCPP_BEGIN_NAMESPACE_STD @@ -74,39 +72,18 @@ struct atos : tool { struct file_actions { posix_spawn_file_actions_t fa_; + bool fa_initialized_{false}; - file_actions() { - if (posix_spawn_file_actions_init(&fa_)) { - throw failed("posix_spawn_file_actions_init", errno); - } - } + ~file_actions(); - ~file_actions() { posix_spawn_file_actions_destroy(&fa_); } + bool initFileActions(); + bool addClose(int fd); + bool addDup2(int fd, int std_fd); - void addClose(int fd) { - if (posix_spawn_file_actions_addclose(&fa_, fd)) { - throw failed("posix_spawn_file_actions_addclose", errno); - } - } - void addDup2(int fd, int stdfd) { - if (posix_spawn_file_actions_adddup2(&fa_, fd, stdfd)) { - throw failed("posix_spawn_file_actions_adddup2", errno); - } - } - - fd redirectOutFD() { - int fds[2]; - if (::pipe(fds)) { - throw failed("pipe", errno); - } - addClose(fds[0]); - addDup2(fds[1], 1); - return {fds[0]}; - } - - void redirectInNull() { addDup2(fd::null_fd(), 0); } - void redirectOutNull() { addDup2(fd::null_fd(), 1); } - void redirectErrNull() { addDup2(fd::null_fd(), 2); } + fd redirectOutFD(); + bool redirectInNull() { return addDup2(fd::null_fd(), 0); } + bool redirectOutNull() { return addDup2(fd::null_fd(), 1); } + bool redirectErrNull() { return addDup2(fd::null_fd(), 2); } }; struct pspawn { @@ -114,33 +91,10 @@ struct pspawn { pid_t pid_{0}; file_actions fa_{}; - // TODO(stacktrace23): ignore SIGCHLD for spawned subprocess + ~pspawn(); - ~pspawn() { - if (pid_) { - kill(pid_, SIGTERM); - wait(); - } - } - - void spawn(base::list const& argStrings) { - base::vec argv = tool_.base_.make_vec(); - argv.reserve(argStrings.size() + 1); - for (auto const& str : argStrings) { - argv.push_back(str.data()); - } - argv.push_back(nullptr); - int err; - if ((err = posix_spawnp(&pid_, argv[0], &fa_.fa_, nullptr, const_cast(argv.data()), nullptr))) { - throw failed("posix_spawnp", err); - } - } - - int wait() { - int status; - waitpid(pid_, &status, 0); - return status; - } + bool spawn(base::list const& argStrings); + int wait(); }; struct pspawn_tool : pspawn { @@ -155,16 +109,22 @@ struct pspawn_tool : pspawn { fa_.redirectInNull(); } - void run() { - // Cannot run "addr2line" or similar without addresses, since we - // provide them in argv, and if there are none passed in argv, the - // tool will try to read from stdin and hang. + bool run() { + // Cannot run "addr2line" or similar without addresses, since we provide them in argv, + // and if there are none passed in argv, the tool will try to read from stdin and hang. + // Nothing to do, so return true for "success". if (base_.__entries_.empty()) { - return; + return true; + } + + if (!fd_) { + return false; } auto argStrings = tool_.buildArgs(base_); - spawn(argStrings); + if (!spawn(argStrings)) { + return false; + } auto end = base_.__entries_.end(); auto it = base_.__entries_.begin(); @@ -172,12 +132,13 @@ struct pspawn_tool : pspawn { auto& entry = (entry_base&)(*it++); tool_.parseOutput(base_, entry, stream_); } + return true; } }; struct spawner { base& base_; - void resolve_lines(); + bool resolve_lines(); }; } // namespace __stacktrace diff --git a/libcxx/src/stacktrace/utils/failed.h b/libcxx/src/stacktrace/utils/failed.h deleted file mode 100644 index dcd89507339c1..0000000000000 --- a/libcxx/src/stacktrace/utils/failed.h +++ /dev/null @@ -1,31 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_UTILS_FAILED -#define _LIBCPP_STACKTRACE_UTILS_FAILED - -#include <__config> -#include -#include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct failed : std::exception { - char const* msg_{}; - int errno_{0}; - failed(char const* msg, int err) : std::exception(), msg_(msg), errno_(err) {} - - virtual ~failed() noexcept = default; - const char* what() const noexcept override { return msg_; } -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_UTILS_FAILED diff --git a/libcxx/src/stacktrace/utils/fd.h b/libcxx/src/stacktrace/utils/fd.h index 75f169dc5fa14..a2add29868cf6 100644 --- a/libcxx/src/stacktrace/utils/fd.h +++ b/libcxx/src/stacktrace/utils/fd.h @@ -19,8 +19,6 @@ #include #include -#include "stacktrace/utils/failed.h" - _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { @@ -28,10 +26,9 @@ namespace __stacktrace { force some component to "own" this, although it's freely convertible back to integer form. Default-constructed, closed, and moved-out-of instances will have the invalid fd `-1`. */ -class _LIBCPP_HIDE_FROM_ABI fd { +struct _LIBCPP_HIDE_FROM_ABI fd { int fd_{-1}; -public: fd() : fd(-1) {} fd(int fdint) : fd_(fdint) {} @@ -73,20 +70,19 @@ class _LIBCPP_HIDE_FROM_ABI fd { }; /** Wraps a readable fd using the `streambuf` interface. I/O errors arising -from reading the provided fd will result in a `Failed` being thrown. */ +from reading the provided fd will result in EOF. */ struct _LIBCPP_HIDE_FROM_ABI fd_streambuf final : std::streambuf { fd& fd_; char* buf_; size_t size_; + _LIBCPP_HIDE_FROM_ABI fd_streambuf(fd& fd, char* buf, size_t size) : fd_(fd), buf_(buf), size_(size) {} _LIBCPP_HIDE_FROM_ABI virtual ~fd_streambuf() = default; _LIBCPP_HIDE_FROM_ABI int underflow() override { int bytesRead = ::read(fd_, buf_, size_); - if (bytesRead < 0) { - throw ::std::__stacktrace::failed("I/O error reading from child process", errno); - } - if (bytesRead == 0) { + if (bytesRead <= 0) { + // error or EOF: return eof to stop return traits_type::eof(); } setg(buf_, buf_, buf_ + bytesRead); From 60c6f702b46d1ca57ef2e0ad7821c3b9d6912a68 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Wed, 16 Jul 2025 17:33:21 -0400 Subject: [PATCH 10/19] Disable only-uses-allocator test for *-SAN's --- .../stacktrace/basic.cons/only_uses_allocator.pass.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp index e0d9beef98ec8..01d0b365b6bd6 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp @@ -8,6 +8,7 @@ // REQUIRES: std-at-least-c++23 // ADDITIONAL_COMPILE_FLAGS: -g -O0 +// UNSUPPORTED: asan, msan, tsan, hwasan, sanitizer-new-delete #include #include @@ -17,6 +18,8 @@ * This file includes tests which ensure any allocations performed by `basic_stacktrace` * are done via the user-provided allocator. We intercept the usual ways to allocate, * counting the number of calls, through and not through the allocator. + * + * (This won't work properly with sanitizers, hence the `UNSUPPORTED` above.) */ unsigned new_count; From c39ab4866bb2dbac42df592062d569de43c45567 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Wed, 16 Jul 2025 18:00:18 -0400 Subject: [PATCH 11/19] Fix usage: 'optional.has_value' --- libcxx/include/__stacktrace/entry.h | 12 ++++++------ ...libcxxabi.v1.stable.exceptions.nonew.abilist | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/entry.h index b9d67bcd7adbc..e90fb8cfba7e0 100644 --- a/libcxx/include/__stacktrace/entry.h +++ b/libcxx/include/__stacktrace/entry.h @@ -56,16 +56,16 @@ class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_b // (19.6.3.4) [stacktrace.entry.query], query [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string description() const { - if (__desc_->empty()) { - return ""; + if (__desc_.has_value()) { + return {__desc_->data(), __desc_->size()}; } - return {__desc_->data(), __desc_->size()}; + return {}; } [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { - if (__desc_->empty()) { - return ""; + if (__desc_.has_value()) { + return {__file_->data(), __file_->size()}; } - return {__file_->data(), __file_->size()}; + return {}; } [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI uint_least32_t source_line() const { return __line_; } diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist index df4daa6343de7..f5012c8cf0614 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -35,6 +35,7 @@ {'is_defined': False, 'name': '_ZTVSt13runtime_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt14overflow_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt16invalid_argument', 'size': 0, 'type': 'OBJECT'} +{'is_defined': False, 'name': '_ZTVSt9exception', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZdaPv', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdaPvSt11align_val_t', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdlPv', 'type': 'FUNC'} @@ -225,6 +226,10 @@ {'is_defined': True, 'name': '_ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11descriptionEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_fileEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_lineEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry13native_handleEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} @@ -630,6 +635,7 @@ {'is_defined': True, 'name': '_ZNSt3__112__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2INS_9allocatorINS_16stacktrace_entryEEEEET_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -1650,7 +1656,6 @@ {'is_defined': True, 'name': '_ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4atosE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4toolE', 'size': 16, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace6failedE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112__stacktrace9addr2lineE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112bad_weak_ptrE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112codecvt_baseE', 'size': 16, 'type': 'OBJECT'} @@ -1794,7 +1799,6 @@ {'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4atosE', 'size': 28, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4toolE', 'size': 28, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace6failedE', 'size': 30, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace9addr2lineE', 'size': 33, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112bad_weak_ptrE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112codecvt_baseE', 'size': 23, 'type': 'OBJECT'} @@ -1928,6 +1932,11 @@ {'is_defined': True, 'name': '_ZTTNSt3__19strstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental15fundamentals_v112bad_any_castE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFPSt4bytemEEE', 'size': 88, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 88, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 88, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 88, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 88, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110istrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb0EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb1EEE', 'size': 112, 'type': 'OBJECT'} @@ -1938,7 +1947,7 @@ {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace10fd_istreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4atosE', 'size': 48, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace6failedE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4toolE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace9addr2lineE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112bad_weak_ptrE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIcEE', 'size': 104, 'type': 'OBJECT'} @@ -2002,6 +2011,8 @@ {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDiEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDsEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IwEE', 'size': 96, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIcEE', 'size': 72, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIwEE', 'size': 72, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr15memory_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr25monotonic_buffer_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr26synchronized_pool_resourceE', 'size': 56, 'type': 'OBJECT'} From eb666d2d652cc788b93d9dd7936f619f62985425 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Thu, 17 Jul 2025 11:36:54 -0400 Subject: [PATCH 12/19] Fix ABI lists --- ...nu.libcxxabi.v1.stable.exceptions.nonew.abilist | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist index f5012c8cf0614..7932533c88996 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -35,7 +35,6 @@ {'is_defined': False, 'name': '_ZTVSt13runtime_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt14overflow_error', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZTVSt16invalid_argument', 'size': 0, 'type': 'OBJECT'} -{'is_defined': False, 'name': '_ZTVSt9exception', 'size': 0, 'type': 'OBJECT'} {'is_defined': False, 'name': '_ZdaPv', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdaPvSt11align_val_t', 'type': 'FUNC'} {'is_defined': False, 'name': '_ZdlPv', 'type': 'FUNC'} @@ -226,10 +225,6 @@ {'is_defined': True, 'name': '_ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11descriptionEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_fileEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry11source_lineEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__116stacktrace_entry13native_handleEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} @@ -635,7 +630,6 @@ {'is_defined': True, 'name': '_ZNSt3__112__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2INS_9allocatorINS_16stacktrace_entryEEEEET_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -1932,11 +1926,6 @@ {'is_defined': True, 'name': '_ZTTNSt3__19strstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental15fundamentals_v112bad_any_castE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFPSt4bytemEEE', 'size': 88, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 88, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 88, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 88, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 88, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110istrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb0EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIcLb1EEE', 'size': 112, 'type': 'OBJECT'} @@ -1947,7 +1936,6 @@ {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace10fd_istreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4atosE', 'size': 48, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4toolE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace9addr2lineE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112bad_weak_ptrE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIcEE', 'size': 104, 'type': 'OBJECT'} @@ -2011,8 +1999,6 @@ {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDiEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IDsEE', 'size': 96, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__120__codecvt_utf8_utf16IwEE', 'size': 96, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIcEE', 'size': 72, 'type': 'OBJECT'} -{'is_defined': True, 'name': '_ZTVNSt3__120__time_get_c_storageIwEE', 'size': 72, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr15memory_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr25monotonic_buffer_resourceE', 'size': 56, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__13pmr26synchronized_pool_resourceE', 'size': 56, 'type': 'OBJECT'} From ee428a990c21b8f2de5296952edbce07fdfe7078 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Thu, 17 Jul 2025 12:43:15 -0400 Subject: [PATCH 13/19] Hardened 'basic_stacktrace::current' (p3697r0) --- libcxx/include/__stacktrace/basic.h | 12 +++++-- .../assert.current.no_overflow.pass.cpp | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h index 6ce1f10c51f64..84260af68b4e3 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic.h @@ -10,6 +10,7 @@ #ifndef _LIBCPP_STACKTRACE_BASIC #define _LIBCPP_STACKTRACE_BASIC +#include "__assert" #include <__config> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -81,20 +82,27 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { // (19.6.4.2) // Creation and assignment [stacktrace.basic.cons] + // Should be generous, but not so large that it would easily lead to an overflow + // when added to a given skip amount. + constexpr static size_type __default_max_depth = 1024; + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace current(const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { - return current(1, /* no __max_depth */ ~0, __caller_alloc); + return current(1, __default_max_depth, __caller_alloc); } _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace current(size_type __skip, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { - return current(__skip + 1, /* no __max_depth */ ~0, __caller_alloc); + return current(__skip + 1, __default_max_depth, __caller_alloc); } _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace current(size_type __skip, size_type __max_depth, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __skip <= __skip + __max_depth, "sum of skip and max_depth too large; overflows size_type"); + __stacktrace::base __builder(__caller_alloc); __builder.build_stacktrace(__skip + 1, __max_depth); basic_stacktrace<_Allocator> __ret{__caller_alloc}; diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp new file mode 100644 index 0000000000000..b0c7d6db0b402 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23, has-unix-headers, libcpp-hardening-mode={{extensive|debug}} +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +/* + Hardened requirements for the `current` call with given `skip` and `max_depth` amounts: + https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3697r0.html#basic_stacktrace + Specifically: "Hardened preconditions: skip <= skip + max_depth is true." + + // (19.6.4.2): [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; +*/ + +#include + +#include "check_assertion.h" + +int main(int, char**) { + TEST_LIBCPP_ASSERT_FAILURE( + std::stacktrace::current(1, 0xffffffffffffffff), "sum of skip and max_depth too large; overflows size_type"); + + return 0; +} From d4d3f5240c2b1c69843e793de5e1d3e19dcb7118 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Thu, 17 Jul 2025 15:27:52 -0400 Subject: [PATCH 14/19] Fix bugs in st_entry, and in tests --- libcxx/include/__stacktrace/entry.h | 2 +- .../std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp | 2 +- .../diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/entry.h index e90fb8cfba7e0..7b97a49ce83d7 100644 --- a/libcxx/include/__stacktrace/entry.h +++ b/libcxx/include/__stacktrace/entry.h @@ -62,7 +62,7 @@ class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_b return {}; } [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { - if (__desc_.has_value()) { + if (__file_.has_value()) { return {__file_->data(), __file_->size()}; } return {}; diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp index 4a025695bc1bf..40e01faeed8ec 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp @@ -27,7 +27,7 @@ _LIBCPP_NO_TAIL_CALLS int main(int, char**) { static_assert(std::is_nothrow_copy_assignable_v); - std::stacktrace_entry e1 = std::stacktrace::current()[0]; + auto& e1 = std::stacktrace::current()[0]; std::stacktrace_entry e2; static_assert(noexcept(e2 = e1)); e2 = e1; diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp index 9862a63a01479..968c3df3be0cc 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp @@ -27,7 +27,7 @@ _LIBCPP_NO_TAIL_CALLS int main(int, char**) { static_assert(std::is_nothrow_copy_constructible_v); - std::stacktrace_entry e1 = std::stacktrace::current()[0]; + auto& e1 = std::stacktrace::current()[0]; static_assert(noexcept(std::stacktrace_entry(e1))); std::stacktrace_entry e2(e1); assert(e2 == e1); From c38d48435f879f53cba59d2e66962e187105b36c Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Sat, 19 Jul 2025 12:33:12 -0400 Subject: [PATCH 15/19] Update ABI list --- ...xxabi.v1.stable.noexceptions.nonew.abilist | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist index 51caa07a74330..cb93142f99f90 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist @@ -196,6 +196,7 @@ {'is_defined': True, 'name': '_ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNKSt3__117moneypunct_bynameIcLb0EE13do_pos_formatEv', 'type': 'FUNC'} @@ -593,6 +594,13 @@ {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultclEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -744,6 +752,7 @@ {'is_defined': True, 'name': '_ZNSt3__112system_errorD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112system_errorD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112system_errorD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE11__read_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE12__write_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj', 'type': 'FUNC'} @@ -923,7 +932,6 @@ {'is_defined': True, 'name': '_ZNSt3__113random_deviceclEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvEaSERKS1_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__114__num_get_base10__get_baseERNS_8ios_baseE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__114__num_get_base5__srcE', 'size': 33, 'type': 'OBJECT'} @@ -1127,7 +1135,6 @@ {'is_defined': True, 'name': '_ZNSt3__117bad_function_callD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117bad_function_callD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117bad_function_callD2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117iostream_categoryEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117moneypunct_bynameIcLb0EE4initEPKc', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117moneypunct_bynameIcLb1EE4initEPKc', 'type': 'FUNC'} @@ -1555,6 +1562,7 @@ {'is_defined': True, 'name': '_ZNSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19strstreamD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19strstreamD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__19to_stringERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEd', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEe', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEf', 'type': 'FUNC'} @@ -1564,6 +1572,7 @@ {'is_defined': True, 'name': '_ZNSt3__19to_stringEm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEx', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEy', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__1lsERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZSt17__throw_bad_allocv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZSt17current_exceptionv', 'type': 'FUNC'} @@ -1573,6 +1582,7 @@ {'is_defined': True, 'name': '_ZSt7nothrow', 'size': 1, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__110istrstreamE0_NS_13basic_istreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__110ostrstreamE0_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTCNSt3__112__stacktrace10fd_istreamE0_NS_13basic_istreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_iostreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_iostreamIcNS_11char_traitsIcEEEE16_NS_13basic_ostreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} @@ -1587,6 +1597,11 @@ {'is_defined': True, 'name': '_ZTCNSt3__19strstreamE16_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental15fundamentals_v112bad_any_castE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental19bad_optional_accessE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFPSt4bytemEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_getE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_putE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110ctype_baseE', 'size': 16, 'type': 'OBJECT'} @@ -1602,6 +1617,11 @@ {'is_defined': True, 'name': '_ZTINSt3__111__money_putIcEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__111__money_putIwEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__111regex_errorE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace10fd_istreamE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4atosE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4toolE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace9addr2lineE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112bad_weak_ptrE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112codecvt_baseE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112ctype_bynameIcEE', 'size': 24, 'type': 'OBJECT'} @@ -1720,6 +1740,11 @@ {'is_defined': True, 'name': '_ZTISt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental15fundamentals_v112bad_any_castE', 'size': 50, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFPSt4bytemEEE', 'size': 41, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 64, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 65, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 58, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 42, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_getE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_putE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110ctype_baseE', 'size': 21, 'type': 'OBJECT'} @@ -1735,6 +1760,11 @@ {'is_defined': True, 'name': '_ZTSNSt3__111__money_putIcEE', 'size': 25, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__111__money_putIwEE', 'size': 25, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__111regex_errorE', 'size': 22, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace10fd_istreamE', 'size': 35, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4atosE', 'size': 28, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4toolE', 'size': 28, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace9addr2lineE', 'size': 33, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112bad_weak_ptrE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112codecvt_baseE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112ctype_bynameIcEE', 'size': 26, 'type': 'OBJECT'} @@ -1853,6 +1883,7 @@ {'is_defined': True, 'name': '_ZTSSt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__110istrstreamE', 'size': 32, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__110ostrstreamE', 'size': 32, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTTNSt3__112__stacktrace10fd_istreamE', 'size': 32, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIwNS_11char_traitsIwEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'} @@ -1873,6 +1904,10 @@ {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIwLb1EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110ostrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__111regex_errorE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace10fd_istreamE', 'size': 80, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4atosE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace9addr2lineE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112bad_weak_ptrE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIcEE', 'size': 104, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIwEE', 'size': 136, 'type': 'OBJECT'} From da9f12d7366b04a0d4ca8077e187f2e0aab62f03 Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Sat, 19 Jul 2025 16:37:54 -0400 Subject: [PATCH 16/19] Fix ctors --- libcxx/include/__stacktrace/basic.h | 88 ++++++++++++------- .../basic.cons/ctor_with_alloc.pass.cpp | 3 +- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h index 84260af68b4e3..77e8fb53cbe5c 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic.h @@ -48,12 +48,11 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { friend struct hash>; friend struct __stacktrace::__to_string; - using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; - constexpr static bool __kPropOnCopyAssign = _ATraits::propagate_on_container_copy_assignment::value; - constexpr static bool __kPropOnMoveAssign = _ATraits::propagate_on_container_move_assignment::value; - constexpr static bool __kPropOnSwap = _ATraits::propagate_on_container_swap::value; - constexpr static bool __kAlwaysEqual = _ATraits::is_always_equal::value; - constexpr static bool __kNoThrowDflConstruct = is_nothrow_default_constructible_v<_Allocator>; + using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; + constexpr static bool __kPropOnCopyAssign = _ATraits::propagate_on_container_copy_assignment::value; + constexpr static bool __kPropOnMoveAssign = _ATraits::propagate_on_container_move_assignment::value; + constexpr static bool __kPropOnSwap = _ATraits::propagate_on_container_swap::value; + constexpr static bool __kAlwaysEqual = _ATraits::is_always_equal::value; constexpr static bool __kNoThrowAlloc = noexcept(noexcept(_Allocator().allocate(1)) && noexcept(_Allocator().allocate_at_least(1))); @@ -115,42 +114,63 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { _LIBCPP_EXPORTED_FROM_ABI constexpr ~basic_stacktrace() = default; - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace() noexcept(__kNoThrowDflConstruct) : basic_stacktrace(allocator_type()) {} - - _LIBCPP_EXPORTED_FROM_ABI explicit basic_stacktrace(const allocator_type& __alloc) noexcept - : base(__alloc), __entries_(__alloc_) {} - - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) - : basic_stacktrace(__other, _ATraits::select_on_container_copy_construction(__other.__alloc_)) {} - - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept - : __alloc_(std::move(__other.__alloc_)), __entries_(std::move(__other.__entries_)) {} - - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other, allocator_type const& __alloc) - : base(__alloc), __alloc_(__alloc), __entries_(__other.__entries_, __alloc) {} - - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other, allocator_type const& __alloc) - : base(__alloc) { - __entries_ = {std::move(__other.__entries_), __alloc_}; - } - - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& operator=(const basic_stacktrace& __other) { - if (this != std::addressof(__other)) { + // clang-format off + + _LIBCPP_EXPORTED_FROM_ABI explicit + basic_stacktrace(const allocator_type& __alloc) /* not noexcept */ + : base(__alloc) + , __alloc_(__alloc) + , __entries_(__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace(basic_stacktrace const& __other, + allocator_type const& __alloc) /* not noexcept */ + : base(__alloc) + , __alloc_(__alloc) + , __entries_(__other.__entries_, __alloc) {} + + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace(basic_stacktrace&& __other, + allocator_type const& __alloc) /* not noexcept */ + : base(__alloc) + , __alloc_(__alloc) + , __entries_{std::move(__other.__entries_), __alloc_} {} + + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace() noexcept(is_nothrow_default_constructible_v) + : basic_stacktrace(allocator_type()) {} + + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace(basic_stacktrace const& __other) noexcept + : basic_stacktrace(__other, + _ATraits::select_on_container_copy_construction(__other.__alloc_)) {} + + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace(basic_stacktrace&& __other) noexcept + : basic_stacktrace(std::move(__other), + __other.__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace& operator=(const basic_stacktrace& __other) /* not noexcept */ { + if (std::addressof(__other) != this) { if (__kPropOnCopyAssign) { - __alloc_ = __other.__alloc_; + new (this) basic_stacktrace(__other, __other.__alloc_); + } else { + new (this) basic_stacktrace(__other); } - __entries_ = {__other.__entries_, __alloc_}; } return *this; } - _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& - operator=(basic_stacktrace&& __other) noexcept(__kPropOnMoveAssign || __kAlwaysEqual) { - if (this != std::addressof(__other)) { + _LIBCPP_EXPORTED_FROM_ABI + basic_stacktrace& operator=(basic_stacktrace&& __other) + noexcept(__kPropOnMoveAssign || __kAlwaysEqual) { + if (std::addressof(__other) != this) { if (__kPropOnMoveAssign) { - __alloc_ = std::move(__other.__alloc_); + new (this) basic_stacktrace(std::move(__other), __other.__alloc_); + } else { + new (this) basic_stacktrace(std::move(__other)); } - __entries_ = {std::move(__other.__entries_), __alloc_}; } return *this; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp index baf2ac17fe89e..e4cdc29d68986 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp @@ -32,10 +32,9 @@ void test_construct_with_alloc_noexcept() { using A1 = std::allocator; static_assert(std::is_nothrow_constructible_v); - static_assert(noexcept(std::basic_stacktrace(A1()))); - using A2 = TestAlloc; static_assert(!std::is_nothrow_constructible_v); + static_assert(!noexcept(std::basic_stacktrace(A2()))); } From aee99e26eeda3ec1b65233ce86336e1fe00369bd Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Sat, 19 Jul 2025 18:34:32 -0400 Subject: [PATCH 17/19] Rework allocation using the caller's allocator --- libcxx/include/CMakeLists.txt | 10 +- libcxx/include/__stacktrace/base.h | 158 ------- .../{basic.h => basic_stacktrace.h} | 207 ++++++--- libcxx/include/__stacktrace/hash.h | 53 --- libcxx/include/__stacktrace/images.h | 97 ++++ libcxx/include/__stacktrace/memory.h | 248 ++++++++++ libcxx/include/__stacktrace/nonmem.h | 60 --- .../{entry.h => stacktrace_entry.h} | 63 ++- libcxx/include/__stacktrace/to_string.h | 59 --- libcxx/include/module.modulemap.in | 9 +- libcxx/include/stacktrace | 8 +- ...bcxxabi.v1.stable.exceptions.nonew.abilist | 52 ++- libcxx/src/CMakeLists.txt | 20 +- libcxx/src/stacktrace.cpp | 75 +++ libcxx/src/stacktrace/README.md | 6 - libcxx/src/stacktrace/base.cpp | 72 --- libcxx/src/stacktrace/fd.h | 138 ++++++ libcxx/src/stacktrace/images.cpp | 99 ++++ libcxx/src/stacktrace/impl_generic.cpp | 115 +++++ libcxx/src/stacktrace/impl_windows.cpp | 258 +++++++++++ libcxx/src/stacktrace/linux/elf.cpp | 98 ---- libcxx/src/stacktrace/linux/elf.h | 248 ---------- libcxx/src/stacktrace/linux/images.cpp | 72 --- libcxx/src/stacktrace/linux/images.h | 50 -- libcxx/src/stacktrace/linux/impl.cpp | 113 ----- libcxx/src/stacktrace/linux/impl.h | 37 -- libcxx/src/stacktrace/macos/impl.cpp | 99 ---- libcxx/src/stacktrace/macos/impl.h | 39 -- libcxx/src/stacktrace/to_string.cpp | 93 ---- libcxx/src/stacktrace/tools/apple_atos.cpp | 90 ++++ libcxx/src/stacktrace/tools/gnu_addr2line.cpp | 133 ++++++ .../src/stacktrace/tools/llvm_symbolizer.cpp | 107 +++++ libcxx/src/stacktrace/tools/tools.cpp | 419 ----------------- libcxx/src/stacktrace/tools/tools.h | 432 ++++++++++++++---- libcxx/src/stacktrace/unwind/impl.cpp | 62 --- libcxx/src/stacktrace/unwind/impl.h | 32 -- libcxx/src/stacktrace/unwinding.h | 118 +++++ libcxx/src/stacktrace/utils/fd.h | 127 ----- libcxx/src/stacktrace/utils/image.h | 33 -- libcxx/src/stacktrace/windows/dll.cpp | 85 ---- libcxx/src/stacktrace/windows/dll.h | 88 ---- libcxx/src/stacktrace/windows/impl.cpp | 179 -------- libcxx/src/stacktrace/windows/impl.h | 52 --- .../stacktrace}/only_uses_allocator.pass.cpp | 10 +- .../stacktrace/simple.o0.nodebug.pass.cpp | 3 +- .../stacktrace/simple.o0.nosplit.pass.cpp | 11 +- .../stacktrace/simple.o0.split.pass.cpp | 9 +- .../stacktrace/simple.o3.nodebug.pass.cpp | 3 +- .../stacktrace/simple.o3.nosplit.pass.cpp | 11 +- .../stacktrace/simple.o3.split.pass.cpp | 11 +- .../stacktrace/use_available_progs.pass.cpp | 173 +++++++ .../stacktrace/basic.cmp/equality.pass.cpp | 1 + .../basic.cmp/strong_ordering.pass.cpp | 1 + .../assert.current.no_overflow.pass.cpp | 3 +- .../stacktrace/basic.cons/copy.pass.cpp | 2 +- .../basic.cons/current_no_args.pass.cpp | 3 + .../basic.cons/current_skip_depth.pass.cpp | 16 +- .../stacktrace/basic.mod/swap.pass.cpp | 1 + .../basic.nonmem/operator_left_shift.pass.cpp | 1 + .../stacktrace/basic.nonmem/swap.pass.cpp | 1 + .../basic.nonmem/to_string.pass.cpp | 6 +- .../stacktrace/basic.obs/at.pass.cpp | 1 + .../stacktrace/basic.obs/begin_end.pass.cpp | 1 + .../stacktrace/basic.obs/cbegin_cend.pass.cpp | 1 + .../basic.obs/crbegin_crend.pass.cpp | 1 + .../stacktrace/basic.obs/empty.pass.cpp | 1 + .../basic.obs/get_allocator.pass.cpp | 1 + .../stacktrace/basic.obs/max_size.pass.cpp | 1 + .../basic.obs/operator_index.pass.cpp | 1 + .../stacktrace/basic.obs/rbegin_rend.pass.cpp | 1 + .../stacktrace/basic.obs/size.pass.cpp | 1 + .../entry.cons/copy_assign.pass.cpp | 1 - .../entry.cons/copy_construct.pass.cpp | 1 - .../stacktrace/entry.cons/default.pass.cpp | 1 - .../entry.query/description.pass.cpp | 1 - .../entry.query/source_file.pass.cpp | 1 - .../entry.query/source_line.pass.cpp | 1 - .../std/diagnostics/stacktrace/test_allocs.h | 34 +- 78 files changed, 2289 insertions(+), 2640 deletions(-) delete mode 100644 libcxx/include/__stacktrace/base.h rename libcxx/include/__stacktrace/{basic.h => basic_stacktrace.h} (53%) delete mode 100644 libcxx/include/__stacktrace/hash.h create mode 100644 libcxx/include/__stacktrace/images.h create mode 100644 libcxx/include/__stacktrace/memory.h delete mode 100644 libcxx/include/__stacktrace/nonmem.h rename libcxx/include/__stacktrace/{entry.h => stacktrace_entry.h} (73%) delete mode 100644 libcxx/include/__stacktrace/to_string.h create mode 100644 libcxx/src/stacktrace.cpp delete mode 100644 libcxx/src/stacktrace/README.md delete mode 100644 libcxx/src/stacktrace/base.cpp create mode 100644 libcxx/src/stacktrace/fd.h create mode 100644 libcxx/src/stacktrace/images.cpp create mode 100644 libcxx/src/stacktrace/impl_generic.cpp create mode 100644 libcxx/src/stacktrace/impl_windows.cpp delete mode 100644 libcxx/src/stacktrace/linux/elf.cpp delete mode 100644 libcxx/src/stacktrace/linux/elf.h delete mode 100644 libcxx/src/stacktrace/linux/images.cpp delete mode 100644 libcxx/src/stacktrace/linux/images.h delete mode 100644 libcxx/src/stacktrace/linux/impl.cpp delete mode 100644 libcxx/src/stacktrace/linux/impl.h delete mode 100644 libcxx/src/stacktrace/macos/impl.cpp delete mode 100644 libcxx/src/stacktrace/macos/impl.h delete mode 100644 libcxx/src/stacktrace/to_string.cpp create mode 100644 libcxx/src/stacktrace/tools/apple_atos.cpp create mode 100644 libcxx/src/stacktrace/tools/gnu_addr2line.cpp create mode 100644 libcxx/src/stacktrace/tools/llvm_symbolizer.cpp delete mode 100644 libcxx/src/stacktrace/tools/tools.cpp delete mode 100644 libcxx/src/stacktrace/unwind/impl.cpp delete mode 100644 libcxx/src/stacktrace/unwind/impl.h create mode 100644 libcxx/src/stacktrace/unwinding.h delete mode 100644 libcxx/src/stacktrace/utils/fd.h delete mode 100644 libcxx/src/stacktrace/utils/image.h delete mode 100644 libcxx/src/stacktrace/windows/dll.cpp delete mode 100644 libcxx/src/stacktrace/windows/dll.h delete mode 100644 libcxx/src/stacktrace/windows/impl.cpp delete mode 100644 libcxx/src/stacktrace/windows/impl.h rename libcxx/test/{std/diagnostics/stacktrace/basic.cons => libcxx/stacktrace}/only_uses_allocator.pass.cpp (97%) create mode 100644 libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index f5b1b90e500b7..79ec05a822424 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -740,12 +740,10 @@ set(files __ranges/zip_transform_view.h __ranges/zip_view.h __split_buffer - __stacktrace/base.h - __stacktrace/basic.h - __stacktrace/entry.h - __stacktrace/hash.h - __stacktrace/nonmem.h - __stacktrace/to_string.h + __stacktrace/basic_stacktrace.h + __stacktrace/images.h + __stacktrace/memory.h + __stacktrace/stacktrace_entry.h __std_mbstate_t.h __stop_token/atomic_unique_lock.h __stop_token/intrusive_list_view.h diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h deleted file mode 100644 index d63b4087532d7..0000000000000 --- a/libcxx/include/__stacktrace/base.h +++ /dev/null @@ -1,158 +0,0 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_BASE -#define _LIBCPP_STACKTRACE_BASE - -#include <__config> - -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif - -_LIBCPP_PUSH_MACROS -#include <__undef_macros> - -#if _LIBCPP_STD_VER >= 23 - -# include <__cstddef/byte.h> -# include <__cstddef/size_t.h> -# include <__functional/function.h> -# include <__fwd/format.h> -# include <__fwd/ostream.h> -# include <__memory/allocator.h> -# include <__memory/allocator_traits.h> -# include <__new/allocate.h> -# include <__vector/vector.h> -# include -# include -# include -# include -# include - -_LIBCPP_BEGIN_NAMESPACE_STD - -class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry; - -namespace __stacktrace { - -struct _LIBCPP_HIDE_FROM_ABI entry_base; - -struct _LIBCPP_EXPORTED_FROM_ABI base { - template - struct _LIBCPP_HIDE_FROM_ABI Alloc { - function __alloc_bytes_; - function __dealloc_bytes_; - - Alloc(function __alloc_bytes, function __dealloc_bytes) - : __alloc_bytes_(__alloc_bytes), __dealloc_bytes_(__dealloc_bytes) {} - - template - Alloc(Alloc<_T2> const& __rhs) : Alloc(__rhs.__alloc_bytes_, __rhs.__dealloc_bytes_) {} - - Alloc() - : __alloc_bytes_([](size_t __sz) { return std::allocator().allocate(__sz); }), - __dealloc_bytes_([](std::byte* __ptr, size_t __sz) { std::allocator().deallocate(__ptr, __sz); }) { - } - - // XXX Alignment? - using value_type = _Tp; - [[nodiscard]] _Tp* allocate(size_t __sz) { return (_Tp*)__alloc_bytes_(__sz * sizeof(_Tp)); } - void deallocate(_Tp* __ptr, size_t __sz) { __dealloc_bytes_((byte*)__ptr, __sz * sizeof(_Tp)); } - - template - bool operator==(_A2 const& __rhs) const { - return std::addressof(__rhs) == this; - } - }; - - template - Alloc<_Tp> _LIBCPP_HIDE_FROM_ABI make_alloc() { - return {__alloc_bytes_, __dealloc_bytes_}; - } - - using str = basic_string, Alloc>; - - template - str _LIBCPP_HIDE_FROM_ABI make_str(_Args... __args) { - return str(std::forward<_Args>(__args)..., make_alloc()); - } - - template - using vec = vector<_Tp, Alloc<_Tp>>; - - template - _LIBCPP_HIDE_FROM_ABI vec<_Tp> make_vec(_Args... __args) { - return vec(std::forward<_Args>(__args)..., make_alloc<_Tp>()); - } - - template - using list = ::std::list<_Tp, Alloc<_Tp>>; - - template - _LIBCPP_HIDE_FROM_ABI list<_Tp> make_list(_Args... __args) { - return list(std::forward<_Args>(__args)..., make_alloc<_Tp>()); - } - - template - auto _LIBCPP_HIDE_FROM_ABI __alloc_wrap(_Allocator const& __alloc) { - using _AT = allocator_traits<_Allocator>; - using _BA = typename _AT::template rebind_alloc; - auto __ba = _BA(__alloc); - return [__ba = std::move(__ba)](size_t __sz) mutable { return __ba.allocate(__sz); }; - } - - template - auto _LIBCPP_HIDE_FROM_ABI __dealloc_wrap(_Allocator const& __alloc) { - using _AT = allocator_traits<_Allocator>; - using _BA = typename _AT::template rebind_alloc; - auto __ba = _BA(__alloc); - return [__ba = std::move(__ba)](void* __ptr, size_t __sz) mutable { __ba.deallocate((byte*)__ptr, __sz); }; - } - - _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void - build_stacktrace(size_t __skip, size_t __max_depth); - - base(); - - template - explicit _LIBCPP_EXPORTED_FROM_ABI base(_Allocator __alloc); - - function __alloc_bytes_; - function __dealloc_bytes_; - vec __entries_; - str __main_prog_path_; -}; - -struct _LIBCPP_HIDE_FROM_ABI entry_base { - uintptr_t __addr_actual_{}; // this address, as observed in this current process - uintptr_t __addr_unslid_{}; // address adjusted for ASLR - optional<__stacktrace::base::str> __desc_{}; // uses wrapped _Allocator from caller - optional<__stacktrace::base::str> __file_{}; // uses wrapped _Allocator from caller - uint_least32_t __line_{}; - - _LIBCPP_HIDE_FROM_ABI stacktrace_entry to_stacktrace_entry() const; -}; - -template -_LIBCPP_EXPORTED_FROM_ABI base::base(_Allocator __alloc) - : __alloc_bytes_(__alloc_wrap(__alloc)), - __dealloc_bytes_(__dealloc_wrap(__alloc)), - __entries_(make_vec()), - __main_prog_path_(make_str()) {} - -} // namespace __stacktrace - -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STD_VER >= 23 - -_LIBCPP_POP_MACROS - -#endif // _LIBCPP_STACKTRACE_BASE diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic_stacktrace.h similarity index 53% rename from libcxx/include/__stacktrace/basic.h rename to libcxx/include/__stacktrace/basic_stacktrace.h index 77e8fb53cbe5c..95beb5baca830 100644 --- a/libcxx/include/__stacktrace/basic.h +++ b/libcxx/include/__stacktrace/basic_stacktrace.h @@ -10,7 +10,7 @@ #ifndef _LIBCPP_STACKTRACE_BASIC #define _LIBCPP_STACKTRACE_BASIC -#include "__assert" +#include "memory.h" #include <__config> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -22,22 +22,65 @@ _LIBCPP_PUSH_MACROS #if _LIBCPP_STD_VER >= 23 +# include <__assert> +# include <__cstddef/size_t.h> +# include <__functional/function.h> # include <__functional/hash.h> # include <__fwd/format.h> +# include <__fwd/ostream.h> # include <__iterator/iterator.h> # include <__iterator/reverse_iterator.h> # include <__memory/allocator_traits.h> # include <__memory_resource/polymorphic_allocator.h> +# include <__new/allocate.h> # include <__type_traits/is_nothrow_constructible.h> # include <__vector/vector.h> +# include +# include +# include +# include # include -# include <__stacktrace/base.h> -# include <__stacktrace/entry.h> -# include <__stacktrace/to_string.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> _LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct _LIBCPP_HIDE_FROM_ABI base { + constexpr static size_t __default_max_depth = 64; + constexpr static size_t __absolute_max_depth = 256; + constexpr static size_t __k_init_pool_on_stack = 1 << 12; + + std::function __entries_size_; + std::function __emplace_entry_; + std::function __entries_data_; + std::function __entry_at_; + + template + base(_Vp* __entries) + : __entries_size_([=]() { return __entries->size(); }), + __emplace_entry_([=]() -> entry_base& { return (entry_base&)__entries->emplace_back(); }), + __entries_data_([=]() -> entry_base* { return (entry_base*)__entries->data(); }), + __entry_at_([=](size_t __i) -> entry_base& { return (entry_base&)__entries->at(__i); }) {} + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void + current(arena& __arena, size_t __skip, size_t __max_depth); + + _LIBCPP_EXPORTED_FROM_ABI void find_images(arena& __arena); + _LIBCPP_EXPORTED_FROM_ABI void find_symbols(arena& __arena); + _LIBCPP_EXPORTED_FROM_ABI void find_source_locs(arena& __arena); + + entry_base* entries_begin() { return __entries_data_(); } + entry_base* entries_end() { return __entries_data_() + __entries_size_(); } + + _LIBCPP_EXPORTED_FROM_ABI std::ostream& write_to(std::ostream& __os) const; + _LIBCPP_EXPORTED_FROM_ABI string to_string() const; +}; + +} // namespace __stacktrace + // (19.6.4) // Class template basic_stacktrace [stacktrace.basic] @@ -46,7 +89,6 @@ class stacktrace_entry; template class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { friend struct hash>; - friend struct __stacktrace::__to_string; using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; constexpr static bool __kPropOnCopyAssign = _ATraits::propagate_on_container_copy_assignment::value; @@ -59,99 +101,93 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { [[no_unique_address]] _Allocator __alloc_; - using __entry_vec _LIBCPP_NODEBUG = vector; - __entry_vec __entries_; + using entry_vec _LIBCPP_NODEBUG = std::vector; + entry_vec __entries_; public: // (19.6.4.1) // Overview [stacktrace.basic.overview] - using value_type = stacktrace_entry; - using const_reference = const value_type&; - using reference = value_type&; - using difference_type = ptrdiff_t; - using size_type = size_t; - using allocator_type = _Allocator; - using const_iterator = __entry_vec::const_iterator; - using iterator = const_iterator; - + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using difference_type = ptrdiff_t; + using size_type = size_t; + using allocator_type = _Allocator; + using const_iterator = entry_vec::const_iterator; + using iterator = const_iterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; // (19.6.4.2) // Creation and assignment [stacktrace.basic.cons] - // Should be generous, but not so large that it would easily lead to an overflow - // when added to a given skip amount. - constexpr static size_type __default_max_depth = 1024; - _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace current(const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { - return current(1, __default_max_depth, __caller_alloc); + size_type __skip = 1; + size_type __max_depth = __default_max_depth; + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __skip <= __skip + __max_depth, "sum of skip and max_depth overflows size_type"); + basic_stacktrace __ret{__caller_alloc}; + __stacktrace::stack_bytes<__k_init_pool_on_stack> __stack_bytes; + __stacktrace::byte_pool __stack_pool = __stack_bytes.pool(); + __stacktrace::arena __arena(__stack_pool, __caller_alloc); + ((base&)__ret).current(__arena, __skip, __max_depth); + return __ret; } _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace current(size_type __skip, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { - return current(__skip + 1, __default_max_depth, __caller_alloc); + ++__skip; + size_type __max_depth = __default_max_depth; + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __skip <= __skip + __max_depth, "sum of skip and max_depth overflows size_type"); + basic_stacktrace __ret{__caller_alloc}; + __stacktrace::stack_bytes<__k_init_pool_on_stack> __stack_bytes; + __stacktrace::byte_pool __stack_pool = __stack_bytes.pool(); + __stacktrace::arena __arena(__stack_pool, __caller_alloc); + ((base&)__ret).current(__arena, __skip, __max_depth); + return __ret; } _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace current(size_type __skip, size_type __max_depth, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + ++__skip; _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( - __skip <= __skip + __max_depth, "sum of skip and max_depth too large; overflows size_type"); - - __stacktrace::base __builder(__caller_alloc); - __builder.build_stacktrace(__skip + 1, __max_depth); - basic_stacktrace<_Allocator> __ret{__caller_alloc}; - __ret.__entries_.reserve(__builder.__entries_.size()); - for (auto& __base : __builder.__entries_) { - __ret.__entries_.emplace_back(__base.to_stacktrace_entry()); + __skip <= __skip + __max_depth, "sum of skip and max_depth overflows size_type"); + basic_stacktrace __ret{__caller_alloc}; + if (__max_depth) [[likely]] { + __stacktrace::stack_bytes<__k_init_pool_on_stack> __stack_bytes; + __stacktrace::byte_pool __stack_pool = __stack_bytes.pool(); + __stacktrace::arena __arena(__stack_pool, __caller_alloc); + ((base&)__ret).current(__arena, __skip, __max_depth); } return __ret; } _LIBCPP_EXPORTED_FROM_ABI constexpr ~basic_stacktrace() = default; - // clang-format off - - _LIBCPP_EXPORTED_FROM_ABI explicit - basic_stacktrace(const allocator_type& __alloc) /* not noexcept */ - : base(__alloc) - , __alloc_(__alloc) - , __entries_(__alloc_) {} - - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace(basic_stacktrace const& __other, - allocator_type const& __alloc) /* not noexcept */ - : base(__alloc) - , __alloc_(__alloc) - , __entries_(__other.__entries_, __alloc) {} - - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace(basic_stacktrace&& __other, - allocator_type const& __alloc) /* not noexcept */ - : base(__alloc) - , __alloc_(__alloc) - , __entries_{std::move(__other.__entries_), __alloc_} {} - - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace() noexcept(is_nothrow_default_constructible_v) + _LIBCPP_EXPORTED_FROM_ABI explicit basic_stacktrace(const allocator_type& __alloc) + : base(&__entries_), __alloc_(__alloc), __entries_(__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other, allocator_type const& __alloc) + : base(&__entries_), __alloc_(__alloc), __entries_(__other.__entries_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other, allocator_type const& __alloc) + : base(&__entries_), __alloc_(__alloc), __entries_(std::move(__other.__entries_)) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace() noexcept(is_nothrow_default_constructible_v) : basic_stacktrace(allocator_type()) {} - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace(basic_stacktrace const& __other) noexcept - : basic_stacktrace(__other, - _ATraits::select_on_container_copy_construction(__other.__alloc_)) {} + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) noexcept + : basic_stacktrace(__other, _ATraits::select_on_container_copy_construction(__other.__alloc_)) {} - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace(basic_stacktrace&& __other) noexcept - : basic_stacktrace(std::move(__other), - __other.__alloc_) {} + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept + : basic_stacktrace(std::move(__other), __other.__alloc_) {} - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace& operator=(const basic_stacktrace& __other) /* not noexcept */ { + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& operator=(const basic_stacktrace& __other) { if (std::addressof(__other) != this) { if (__kPropOnCopyAssign) { new (this) basic_stacktrace(__other, __other.__alloc_); @@ -162,12 +198,12 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { return *this; } - _LIBCPP_EXPORTED_FROM_ABI - basic_stacktrace& operator=(basic_stacktrace&& __other) - noexcept(__kPropOnMoveAssign || __kAlwaysEqual) { + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& + operator=(basic_stacktrace&& __other) noexcept(__kPropOnMoveAssign || __kAlwaysEqual) { if (std::addressof(__other) != this) { if (__kPropOnMoveAssign) { - new (this) basic_stacktrace(std::move(__other), __other.__alloc_); + auto __alloc = __other.__alloc_; + new (this) basic_stacktrace(std::move(__other), __alloc); } else { new (this) basic_stacktrace(std::move(__other)); } @@ -175,8 +211,6 @@ class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { return *this; } - // clang-format on - // (19.6.4.3) // [stacktrace.basic.obs], observers @@ -256,6 +290,39 @@ namespace pmr { using stacktrace = basic_stacktrace>; } // namespace pmr +// (19.6.4.6) +// Non-member functions [stacktrace.basic.nonmem] + +template +_LIBCPP_EXPORTED_FROM_ABI inline void +swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b) noexcept(noexcept(__a.swap(__b))) { + __a.swap(__b); +} + +template +_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __stacktrace) { + return ((__stacktrace::base const&)__stacktrace).write_to(__os); +} + +template +_LIBCPP_EXPORTED_FROM_ABI string to_string(const basic_stacktrace<_Allocator>& __stacktrace) { + return ((__stacktrace::base const&)__stacktrace).to_string(); +} + +// (19.6.6) +// Hash support [stacktrace.basic.hash] + +template +struct _LIBCPP_EXPORTED_FROM_ABI hash> { + [[nodiscard]] size_t operator()(basic_stacktrace<_Allocator> const& __context) const noexcept { + size_t __ret = 1; + for (auto const& __entry : __context.__entries_) { + __ret += hash()(__entry.native_handle()); + } + return __ret; + } +}; + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP_STD_VER >= 23 diff --git a/libcxx/include/__stacktrace/hash.h b/libcxx/include/__stacktrace/hash.h deleted file mode 100644 index 0830300ad81b9..0000000000000 --- a/libcxx/include/__stacktrace/hash.h +++ /dev/null @@ -1,53 +0,0 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_HASH -#define _LIBCPP_STACKTRACE_HASH - -#include <__config> - -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif - -_LIBCPP_PUSH_MACROS -#include <__undef_macros> - -#if _LIBCPP_STD_VER >= 23 - -# include <__functional/hash.h> -# include -# include - -# include <__stacktrace/base.h> -# include <__stacktrace/to_string.h> - -_LIBCPP_BEGIN_NAMESPACE_STD - -// (19.6.6) -// Hash support [stacktrace.basic.hash] - -template -struct _LIBCPP_EXPORTED_FROM_ABI hash> { - [[nodiscard]] size_t operator()(basic_stacktrace<_Allocator> const& __context) const noexcept { - size_t __ret = 1; - for (auto const& __entry : __context.__entries_) { - __ret += hash()(__entry.native_handle()); - } - return __ret; - } -}; - -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STD_VER >= 23 - -_LIBCPP_POP_MACROS - -#endif // _LIBCPP_STACKTRACE_HASH diff --git a/libcxx/include/__stacktrace/images.h b/libcxx/include/__stacktrace/images.h new file mode 100644 index 0000000000000..ce943e673477a --- /dev/null +++ b/libcxx/include/__stacktrace/images.h @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_IMAGES_H +#define _LIBCPP_STACKTRACE_IMAGES_H + +#include <__stacktrace/memory.h> +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct image; +struct images; + +struct image { + constexpr static size_t __max_string_len = 1024; + + uintptr_t loaded_at_{}; + uintptr_t slide_{}; + fixed_str<__max_string_len> name_{}; + bool is_main_prog_{}; + + bool operator<(image const& __rhs) const { return loaded_at_ < __rhs.loaded_at_; } + operator bool() const { return name_[0]; } +}; + +/** + * Contains an array `images_`, which will include `prog_image` objects (see above) + * collected in an OS-dependent way. After construction these images will be sorted + * according to their load address; there will also be two sentinels with dummy + * addresses (0x0000... and 0xFFFF...) to simplify search functions. + * + * After construction, images_ and count_ look like: + * [0] [1] [2] [3] ... [count_ - 1] + * (sentinel) foo.exe libc++so.1 libc.so.6 (sentinel) + * 0x000000000000 0x000100000000 0x7def00000000 0x7c0300000000 0xffffffffffff + */ +struct _LIBCPP_EXPORTED_FROM_ABI images { + constexpr static size_t k_max_images = 256; + std::array images_{}; // space for the L/R sentinels + unsigned count_{0}; // image count, including sentinels + + /** An OS-specific constructor is defined. */ + _LIBCPP_EXPORTED_FROM_ABI images(); + + /** Get prog_image by index (0 <= index < count_) */ + _LIBCPP_EXPORTED_FROM_ABI image& operator[](size_t __index) { + _LIBCPP_ASSERT(__index < count_, "index out of range"); + return images_.at(__index); + } + + /** Image representing the main program (nullptr if we couldn't figure that out) */ + _LIBCPP_EXPORTED_FROM_ABI image* main_prog_image() { + for (size_t __i = 1; __i < count_ - 1; __i++) { + auto& __image = images_[__i]; + if (__image.is_main_prog_) { + return &__image; + } + } + return nullptr; + } + + /** Search the sorted images array for one containing this address. */ + _LIBCPP_EXPORTED_FROM_ABI void find(size_t* __index, uintptr_t __addr) { + // `index` slides left/right as we search through images. + // It's (probably) likely several consecutive entries are from the same image, so + // each iteration's `find` uses the same starting point, making it (probably) constant-time. + // XXX Is this more efficient in practice than e.g. `std::set` and `upper_bound`? + if (*__index < 1) { + *__index = 1; + } + if (*__index > count_ - 1) { + *__index = count_ - 1; + } + while (images_[*__index]) { + if (__addr < images_[*__index].loaded_at_) { + --*__index; + } else if (__addr >= images_[*__index + 1].loaded_at_) { + ++*__index; + } else { + break; + } + } + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_IMAGES_H diff --git a/libcxx/include/__stacktrace/memory.h b/libcxx/include/__stacktrace/memory.h new file mode 100644 index 0000000000000..ab8725424d7ed --- /dev/null +++ b/libcxx/include/__stacktrace/memory.h @@ -0,0 +1,248 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_CSTR_BUFFER_H +#define _LIBCPP_STACKTRACE_CSTR_BUFFER_H + +#include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#if _LIBCPP_STD_VER >= 23 + +# include <__assert> +# include +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/* +A few memory-related utilities: + + * An `arena` which provides allocator-like / malloc-like functionality for us, + for objects used internally as well as objects returned to the caller. + Uses the caller's provided allocator, so none of these involve heap allocations + "outside of" the caller-provided allocator. + * A `str` class, inheriting from `std::string`, ensuring allocations happen via `arena` + * A `fixed_str` class, not related to the arena, but instead backed by a `char[n]` + array within that same struct, so it doesn't perform any [de]allocations; it only + uses its own character array. + +A small amount of glue / hacks are done here to allow the rest of the stacktrace-related +code to use familiar string etc. operations, while encapsulating away the details of where +memory might come from, since we need to be careful about unexpected allocations. +*/ + +// clang-format off + +struct byte_pool final { + byte* ptr_; + function destroy_; + byte_pool* link_; + byte* end_; + + byte_pool(byte* __bytes, + size_t __size, + function __destroy = [] {}, + byte_pool* __link = nullptr) noexcept + : ptr_(__bytes), destroy_(__destroy), link_(__link), end_(__bytes + __size) {} + + byte* operator()(size_t __sz, size_t __align) noexcept { + auto __ptr = uintptr_t(ptr_); // convert curr ptr to integer, to do math + auto __misalign = __ptr % __align; // if current ptr not aligned, + if (__misalign) { __ptr += (__align - __misalign); } // waste a few bytes to ensure alignment + auto __ret = __ptr; // we would return this aligned position + __ptr += __sz; // next object will start here + if (__ptr > uintptr_t(end_)) { return nullptr; } // if this exceeds our space, then fail + ptr_ = (byte*) __ptr; // otherwise update current position + return (byte*) __ret; // returned aligned position as byte ptr + } +}; + +template +struct stack_bytes final { + byte bytes_[_Sz]; + + ~stack_bytes() = default; + stack_bytes() noexcept = default; + stack_bytes(const stack_bytes&) = delete; + stack_bytes(stack_bytes&&) = delete; + + byte_pool pool() { return {bytes_, _Sz, []{}, nullptr}; } +}; + +struct arena { + function new_bytes_; // new byte-array factory + function del_bytes_; // byte-array destroyer + byte_pool* curr_pool_; // byte pool currently "in effect" + byte_pool* next_pool_; // allocated (from curr_pool_) but not initialized + size_t allocs_ {}; // number of successful allocations + size_t deallocs_ {}; // incremented on each dealloc; dtor ensures these are equal! + + // An arena is scoped to a `basic_stacktrace::current` invocation, so this is usable by only one thread. + // Additionally, it's used internally throughout many function calls, so for convenience, store it here. + // Also avoids the need for state inside `alloc`, since it can use this pointer instead of an internal one. + static thread_local arena* active_arena_ptr_; + + static arena& get_active() { + auto* __ret = active_arena_ptr_; + _LIBCPP_ASSERT(__ret, "no active arena for this thread"); + return *__ret; + } + + ~arena() { + _LIBCPP_ASSERT(active_arena_ptr_ == this, "different arena unexpectively set as the active one"); + active_arena_ptr_ = nullptr; + _LIBCPP_ASSERT(deallocs_ == allocs_, "destructed arena still has live objects"); + while (curr_pool_) { curr_pool_->destroy_(); curr_pool_ = curr_pool_->link_; } + } + + arena(auto&& __new_bytes, auto&& __del_bytes, byte_pool& __initial_pool) noexcept + : new_bytes_(__new_bytes), del_bytes_(__del_bytes), curr_pool_(&__initial_pool) { + prep_next_pool(); + _LIBCPP_ASSERT(!active_arena_ptr_, "already an active arena"); + active_arena_ptr_ = this; + } + + template + static auto as_byte_alloc(_UA const& __user_alloc) { + return (typename allocator_traits<_UA>::template rebind_alloc)(__user_alloc); + } + + template + arena(byte_pool& __initial_pool, _UA const& __user_alloc) + : arena( + [&__user_alloc] (size_t __sz) { return as_byte_alloc(__user_alloc).allocate(__sz); }, + [&__user_alloc] (void* __ptr, size_t __sz) { return as_byte_alloc(__user_alloc).deallocate((byte*)__ptr, __sz); }, + __initial_pool) {} + + arena(arena const&) = delete; + arena& operator=(arena const&) = delete; + + void prep_next_pool() noexcept { + // Allocate (via current pool) a new byte_pool record, while we have enough space. + // When the current pool runs out of space, this one will be ready to use. + next_pool_ = (byte_pool*) (*curr_pool_)(sizeof(byte_pool), alignof(byte_pool)); + _LIBCPP_ASSERT(next_pool_, "could not allocate next pool"); + } + + void expand(size_t __atleast) noexcept { + constexpr static size_t __k_default_new_pool = 1 << 12; + auto __size = max(__atleast, __k_default_new_pool); + // "next_pool_" was already allocated, just need to initialize it + auto* __bytes = new_bytes_(__size); + _LIBCPP_ASSERT(__bytes, "could not allocate more bytes for arena"); + curr_pool_ = new (next_pool_) byte_pool(__bytes, __size, [=, this] { del_bytes_(__bytes, __size); }, curr_pool_); + prep_next_pool(); + } + + /** Does nothing; all memory is released when arena is destroyed. */ + void dealloc(std::byte*, size_t) noexcept { ++deallocs_; } + + std::byte* alloc(size_t __size, size_t __align) noexcept { + auto* __ret = (*curr_pool_)(__size, __align); + if (__ret) [[likely]] { goto success; } + // Need a new pool to accommodate this request + internal structs + expand(__size + __align + sizeof(byte_pool) + alignof(byte_pool)); // upper bound + __ret = (*curr_pool_)(__size, __align); + _LIBCPP_ASSERT(__ret, "arena failed to allocate"); +success: + ++allocs_; + return __ret; + } +}; + +template +struct alloc { + using value_type = _Tp; + + _Tp* allocate(size_t __n) { + auto& __arena = arena::get_active(); + return (_Tp*)__arena.alloc(__n * sizeof(_Tp), alignof(_Tp)); + } + + void deallocate(_Tp* __ptr, size_t __n) { + auto& __arena = arena::get_active(); + __arena.dealloc((std::byte*)__ptr, __n * sizeof(_Tp)); + } +}; + +template +struct fixed_buf { + using value_type = _Tp; + template struct rebind { using other = fixed_buf<_Up, _Sz>; }; + + _Tp __buf_[_Sz]; + size_t __size_; + void deallocate(_Tp*, size_t) {} + _Tp* allocate(size_t) { return __buf_; } +}; + +template +struct fixed_str : std::basic_string, fixed_buf> { + using _Base _LIBCPP_NODEBUG = std::basic_string, fixed_buf>; + using _Base::operator=; + + fixed_buf __fb_; + fixed_str() : _Base(__fb_) { + this->resize(_Sz - 1); + this->resize(0); + } + fixed_str(fixed_str const& __rhs) : fixed_str() { _Base::operator=(__rhs); } + fixed_str& operator=(fixed_str const& __rhs) = default; +}; + +struct str : std::basic_string, alloc> { + using _Base _LIBCPP_NODEBUG = std::basic_string, alloc>; + using _Base::basic_string; + using _Base::operator=; + + bool valid() const { return data() != nullptr; } + + operator bool() const { return valid() && !empty(); } + + template + static str makef(char const* __fmt, _AL&&... __args) { + str __ret{}; + +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wformat-security" +# pragma clang diagnostic ignored "-Wformat-nonliteral" +# endif + auto __need = std::snprintf(nullptr, 0, __fmt, __args...); + __ret.resize_and_overwrite(__need + 1, [&](char* __data, size_t __size) { + return std::snprintf(__data, __size + 1, __fmt, std::forward<_AL>(__args)...); + }); +# ifdef __clang__ +# pragma clang diagnostic pop +# endif + + return __ret; + } +}; + +// clang-format on + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_CSTR_BUFFER_H diff --git a/libcxx/include/__stacktrace/nonmem.h b/libcxx/include/__stacktrace/nonmem.h deleted file mode 100644 index e6ee621abbf46..0000000000000 --- a/libcxx/include/__stacktrace/nonmem.h +++ /dev/null @@ -1,60 +0,0 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_NONMEM -#define _LIBCPP_STACKTRACE_NONMEM - -#include <__config> - -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif - -_LIBCPP_PUSH_MACROS -#include <__undef_macros> - -#if _LIBCPP_STD_VER >= 23 - -# include <__memory/allocator_traits.h> -# include <__utility/swap.h> -# include <__vector/vector.h> -# include - -# include <__stacktrace/base.h> -# include <__stacktrace/to_string.h> - -_LIBCPP_BEGIN_NAMESPACE_STD - -// (19.6.4.6) -// Non-member functions [stacktrace.basic.nonmem] - -template -_LIBCPP_EXPORTED_FROM_ABI inline void -swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b) noexcept(noexcept(__a.swap(__b))) { - __a.swap(__b); -} - -template -_LIBCPP_EXPORTED_FROM_ABI inline string to_string(const basic_stacktrace<_Allocator>& __stacktrace) { - return __stacktrace::__to_string()(__stacktrace); -} - -template -_LIBCPP_EXPORTED_FROM_ABI inline ostream& operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __stacktrace) { - auto __str = __stacktrace::__to_string()(__stacktrace); - return __os << __str; -} - -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STD_VER >= 23 - -_LIBCPP_POP_MACROS - -#endif // _LIBCPP_STACKTRACE_NONMEM diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/stacktrace_entry.h similarity index 73% rename from libcxx/include/__stacktrace/entry.h rename to libcxx/include/__stacktrace/stacktrace_entry.h index 7b97a49ce83d7..cec802f2c739d 100644 --- a/libcxx/include/__stacktrace/entry.h +++ b/libcxx/include/__stacktrace/stacktrace_entry.h @@ -23,19 +23,37 @@ _LIBCPP_PUSH_MACROS # include <__fwd/format.h> # include <__fwd/ostream.h> +# include <__stacktrace/memory.h> # include # include -# include # include -# include <__stacktrace/base.h> - _LIBCPP_BEGIN_NAMESPACE_STD -class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_base { - friend struct __stacktrace::entry_base; - _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry(entry_base const& __base) : entry_base(__base) {} +class stacktrace_entry; + +namespace __stacktrace { + +struct _LIBCPP_HIDE_FROM_ABI image; + +struct _LIBCPP_EXPORTED_FROM_ABI entry_base { + constexpr static size_t __max_sym_len = 512; + constexpr static size_t __max_file_len = PATH_MAX; + + uintptr_t __addr_{}; + fixed_str<__max_sym_len> __desc_; + fixed_str<__max_file_len> __file_; + uint_least32_t __line_{}; + image* __image_{}; + + std::ostream& _LIBCPP_EXPORTED_FROM_ABI write_to(std::ostream& __os) const; + string _LIBCPP_EXPORTED_FROM_ABI to_string() const; + uintptr_t _LIBCPP_EXPORTED_FROM_ABI adjusted_addr() const; +}; + +} // namespace __stacktrace +class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_base { public: // (19.6.3.1) Overview [stacktrace.entry.overview] using native_handle_type = uintptr_t; @@ -48,25 +66,15 @@ class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_b // (19.6.3.3) [stacktrace.entry.obs], observers [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI constexpr native_handle_type native_handle() const noexcept { - return __addr_actual_; + return __addr_; } [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI constexpr explicit operator bool() const noexcept { return native_handle() != 0; } // (19.6.3.4) [stacktrace.entry.query], query - [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string description() const { - if (__desc_.has_value()) { - return {__desc_->data(), __desc_->size()}; - } - return {}; - } - [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { - if (__file_.has_value()) { - return {__file_->data(), __file_->size()}; - } - return {}; - } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string description() const { return string(__desc_); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { return string(__file_); } [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI uint_least32_t source_line() const { return __line_; } // (19.6.3.5) [stacktrace.entry.cmp], comparison @@ -81,11 +89,22 @@ class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_b } }; +/** `stacktrace_entry` and `stacktrace_entry` have the same layout, +so a pointer to one can be safely casted as the other. */ +static_assert(sizeof(stacktrace_entry) == sizeof(stacktrace_entry)); + // (19.6.4.6) // Non-member functions [stacktrace.basic.nonmem] [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string to_string(const stacktrace_entry& __entry); -_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const stacktrace_entry& __entry); + +_LIBCPP_EXPORTED_FROM_ABI inline ostream& operator<<(ostream& __os, std::stacktrace_entry const& __entry) { + return ((__stacktrace::entry_base const*)&__entry)->write_to(__os); +} + +_LIBCPP_EXPORTED_FROM_ABI inline string to_string(std::stacktrace_entry const& __entry) { + return ((__stacktrace::entry_base const*)&__entry)->to_string(); +} // (19.6.5) // Formatting support [stacktrace.format] @@ -105,10 +124,6 @@ struct _LIBCPP_EXPORTED_FROM_ABI hash { } }; -namespace __stacktrace { -_LIBCPP_HIDE_FROM_ABI inline stacktrace_entry entry_base::to_stacktrace_entry() const { return {*this}; } -} // namespace __stacktrace - _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP_STD_VER >= 23 diff --git a/libcxx/include/__stacktrace/to_string.h b/libcxx/include/__stacktrace/to_string.h deleted file mode 100644 index 442a6c1528228..0000000000000 --- a/libcxx/include/__stacktrace/to_string.h +++ /dev/null @@ -1,59 +0,0 @@ -// -*- C++ -*- -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_TO_STRING -#define _LIBCPP_STACKTRACE_TO_STRING - -#include <__config> - -#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) -# pragma GCC system_header -#endif - -_LIBCPP_PUSH_MACROS -#include <__undef_macros> - -#if _LIBCPP_STD_VER >= 23 - -# include <__fwd/ostream.h> -# include - -_LIBCPP_BEGIN_NAMESPACE_STD - -template -class basic_stacktrace; - -class stacktrace_entry; - -namespace __stacktrace { - -struct __to_string { - _LIBCPP_EXPORTED_FROM_ABI string operator()(stacktrace_entry const& __entry); - - _LIBCPP_EXPORTED_FROM_ABI void operator()(ostream& __os, stacktrace_entry const& __entry); - - _LIBCPP_EXPORTED_FROM_ABI void operator()(ostream& __os, std::stacktrace_entry const* __entries, size_t __count); - - _LIBCPP_EXPORTED_FROM_ABI string operator()(std::stacktrace_entry const* __entries, size_t __count); - - template - _LIBCPP_EXPORTED_FROM_ABI string operator()(basic_stacktrace<_Allocator> const& __st) { - return (*this)(__st.__entries_.data(), __st.__entries_.size()); - } -}; - -} // namespace __stacktrace - -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STD_VER >= 23 - -_LIBCPP_POP_MACROS - -#endif // _LIBCPP_STACKTRACE_TO_STRING diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index a37b91584bb03..61e425c4628f8 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -2011,12 +2011,9 @@ module std [system] { } module stacktrace { - module base { header "__stacktrace/base.h" } - module basic { header "__stacktrace/basic.h" } - module entry { header "__stacktrace/entry.h" } - module hash { header "__stacktrace/hash.h" } - module nonmem { header "__stacktrace/nonmem.h" } - module to_string { header "__stacktrace/to_string.h" } + module basic_stacktrace { header "__stacktrace/basic_stacktrace.h" } + module memory { header "__stacktrace/memory.h" } + module stacktrace_entry { header "__stacktrace/stacktrace_entry.h" } header "stacktrace" export * diff --git a/libcxx/include/stacktrace b/libcxx/include/stacktrace index f7ec8c0deeca1..5bcbdb50a6373 100644 --- a/libcxx/include/stacktrace +++ b/libcxx/include/stacktrace @@ -170,12 +170,8 @@ namespace std { # include <__config> # if _LIBCPP_STD_VER >= 23 -# include <__stacktrace/base.h> -# include <__stacktrace/basic.h> -# include <__stacktrace/entry.h> -# include <__stacktrace/hash.h> -# include <__stacktrace/nonmem.h> -# include <__stacktrace/to_string.h> +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/stacktrace_entry.h> # include // per [stacktrace.syn] # endif diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist index 83a2d41f8680e..d9916b3edfd5a 100644 --- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -364,6 +364,9 @@ {'is_defined': False, 'name': '___cxa_vec_new3', 'type': 'U'} {'is_defined': False, 'name': '___dynamic_cast', 'type': 'U'} {'is_defined': False, 'name': '___gxx_personality_v0', 'type': 'U'} +{'is_defined': True, 'name': '__ZGVZNSt3__112__stacktrace24__has_working_executableINS0_15llvm_symbolizerEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZGVZNSt3__112__stacktrace24__has_working_executableINS0_4atosEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZGVZNSt3__112__stacktrace24__has_working_executableINS0_9addr2lineEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZNKSt10bad_typeid4whatEv', 'type': 'I'} {'is_defined': True, 'name': '__ZNKSt11logic_error4whatEv', 'type': 'I'} {'is_defined': True, 'name': '__ZNKSt12bad_any_cast4whatEv', 'type': 'FUNC'} @@ -413,6 +416,11 @@ {'is_defined': True, 'name': '__ZNKSt3__110moneypunctIwLb1EE16do_negative_signEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__110moneypunctIwLb1EE16do_positive_signEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__110moneypunctIwLb1EE16do_thousands_sepEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace10entry_base13adjusted_addrEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace10entry_base8write_toERNS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace10entry_base9to_stringEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace4baseB8ne2200008write_toERNS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace4baseB8ne2200009to_stringEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__112bad_weak_ptr4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE12find_last_ofEPKcmm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE13find_first_ofEPKcmm', 'type': 'FUNC'} @@ -534,10 +542,6 @@ {'is_defined': True, 'name': '__ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry11descriptionEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry11source_fileEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry11source_lineEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNKSt3__116stacktrace_entry13native_handleEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} @@ -979,13 +983,25 @@ {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultclEv', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseC2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace10__run_toolINS0_15llvm_symbolizerEEEbRNS0_4baseB8ne220000ERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace10__run_toolINS0_4atosEEEbRNS0_4baseB8ne220000ERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace10__run_toolINS0_9addr2lineEEEbRNS0_4baseB8ne220000ERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace17__executable_nameINS0_15llvm_symbolizerEE3getEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace17__executable_nameINS0_4atosEE3getEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace17__executable_nameINS0_9addr2lineEE3getEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace18__executable_worksEPKc', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace24__has_working_executableINS0_15llvm_symbolizerEEEbv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace24__has_working_executableINS0_4atosEEEbv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace24__has_working_executableINS0_9addr2lineEEEbv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne22000011find_imagesERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne22000012find_symbolsERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne22000016find_source_locsERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne2200007currentERNS0_5arenaEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6images15main_prog_imageB8ne220000Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6images4findEPmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6imagesC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6imagesC2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6imagesixB8ne220000Em', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -1946,7 +1962,6 @@ {'is_defined': True, 'name': '__ZNSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19strstreamD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19strstreamD2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__19to_stringERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEd', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEe', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEf', 'type': 'FUNC'} @@ -2024,9 +2039,6 @@ {'is_defined': True, 'name': '__ZTINSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2241,9 +2253,6 @@ {'is_defined': True, 'name': '__ZTSNSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2466,9 +2475,6 @@ {'is_defined': True, 'name': '__ZTVNSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} -{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2588,6 +2594,7 @@ {'is_defined': True, 'name': '__ZTVSt9bad_alloc', 'type': 'I'} {'is_defined': True, 'name': '__ZTVSt9exception', 'type': 'I'} {'is_defined': True, 'name': '__ZTVSt9type_info', 'type': 'I'} +{'is_defined': True, 'name': '__ZTWNSt3__112__stacktrace5arena17active_arena_ptr_E', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZThn16_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZThn16_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZThn16_NSt3__19strstreamD0Ev', 'type': 'FUNC'} @@ -2608,6 +2615,9 @@ {'is_defined': True, 'name': '__ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZTv0_n24_NSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZZNSt3__112__stacktrace24__has_working_executableINS0_15llvm_symbolizerEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZZNSt3__112__stacktrace24__has_working_executableINS0_4atosEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZZNSt3__112__stacktrace24__has_working_executableINS0_9addr2lineEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZdaPv', 'type': 'I'} {'is_defined': True, 'name': '__ZdaPvRKSt9nothrow_t', 'type': 'I'} {'is_defined': True, 'name': '__ZdaPvSt11align_val_t', 'type': 'I'} diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index d0a40a813f7d0..6b7d97b2b516f 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -40,16 +40,16 @@ set(LIBCXX_SOURCES ryu/d2fixed.cpp ryu/d2s.cpp ryu/f2s.cpp - stacktrace/base.cpp - stacktrace/linux/elf.cpp - stacktrace/linux/images.cpp - stacktrace/linux/impl.cpp - stacktrace/macos/impl.cpp - stacktrace/to_string.cpp - stacktrace/tools/tools.cpp - stacktrace/unwind/impl.cpp - stacktrace/windows/dll.cpp - stacktrace/windows/impl.cpp + stacktrace.cpp + stacktrace/fd.h + stacktrace/images.cpp + stacktrace/impl_generic.cpp + stacktrace/impl_windows.cpp + stacktrace/tools/apple_atos.cpp + stacktrace/tools/gnu_addr2line.cpp + stacktrace/tools/llvm_symbolizer.cpp + stacktrace/tools/tools.h + stacktrace/unwinding.h stdexcept.cpp string.cpp support/runtime/exception_fallback.ipp diff --git a/libcxx/src/stacktrace.cpp b/libcxx/src/stacktrace.cpp new file mode 100644 index 0000000000000..9c751a46ffecc --- /dev/null +++ b/libcxx/src/stacktrace.cpp @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> +#include <__stacktrace/basic_stacktrace.h> +#include <__stacktrace/images.h> +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace __stacktrace { + +_LIBCPP_EXPORTED_FROM_ABI thread_local arena* arena::active_arena_ptr_{nullptr}; + +_LIBCPP_EXPORTED_FROM_ABI ostream& entry_base::write_to(ostream& __os) const { + // Although 64-bit addresses are 16 nibbles long, they're often <= 0x7fff_ffff_ffff + constexpr static int __k_addr_width = (sizeof(void*) > 4) ? 12 : 8; + + __os << "0x" << std::hex << std::setfill('0') << std::setw(__k_addr_width) << __addr_; + if (__desc_.size()) { + __os << ": " << __desc_; + } + if (__file_.size()) { + __os << ": " << __file_; + } + if (__line_) { + __os << ":" << std::dec << __line_; + } + return __os; +} + +_LIBCPP_EXPORTED_FROM_ABI ostream& base::write_to(std::ostream& __os) const { + auto __count = __entries_size_(); + if (!__count) { + __os << "(empty stacktrace)"; + } else { + for (size_t __i = 0; __i < __count; __i++) { + // Insert newlines between entries (but not before the first or after the last) + if (__i) { + __os << '\n'; + } + __os << " frame " << std::setw(3) << std::setfill(' ') << std::dec << (__i + 1) << ": " + << (stacktrace_entry&)__entry_at_(__i); + } + } + return __os; +} + +_LIBCPP_EXPORTED_FROM_ABI string entry_base::to_string() const { + stringstream __ss; + write_to(__ss); + return __ss.str(); +} + +_LIBCPP_EXPORTED_FROM_ABI string base::to_string() const { + stringstream __ss; + write_to(__ss); + return __ss.str(); +} + +uintptr_t entry_base::adjusted_addr() const { + auto sub = __image_ ? __image_->slide_ : 0; + return __addr_ - sub; +} + +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/README.md b/libcxx/src/stacktrace/README.md deleted file mode 100644 index 9f25eb4ad6e22..0000000000000 --- a/libcxx/src/stacktrace/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# C++23 `` - - -# References - -1. https://eel.is/c++draft/stacktrace diff --git a/libcxx/src/stacktrace/base.cpp b/libcxx/src/stacktrace/base.cpp deleted file mode 100644 index 7ddb865968be5..0000000000000 --- a/libcxx/src/stacktrace/base.cpp +++ /dev/null @@ -1,72 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include <__config> -#include <__config_site> - -#include <__stacktrace/base.h> - -#include "stacktrace/linux/impl.h" -#include "stacktrace/macos/impl.h" -#include "stacktrace/tools/tools.h" -#include "stacktrace/unwind/impl.h" -#include "stacktrace/windows/impl.h" - -_LIBCPP_BEGIN_NAMESPACE_STD - -namespace __stacktrace { - -base::base() : base(std::allocator()) {} - -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void -base::build_stacktrace(size_t skip, size_t max_depth) { -#if defined(_LIBCPP_WIN32API) - // Windows implementation - win_impl dbghelp{*this}; - dbghelp.collect(skip + 1, max_depth); - if (!__entries_.size()) { - return; - } - dbghelp.ident_modules(); - dbghelp.resolve_lines(); - dbghelp.symbolize(); - -#else - // Non-Windows; assume Unix-like. - - // For spawning `addr2line` or similar external process - spawner pspawn{*this}; - - // `Unwind.h` or `libunwind.h` often available on Linux/OSX etc. - unwind unwind{*this}; - unwind.collect(skip + 1, max_depth); - if (!__entries_.size()) { - return; - } - -# if defined(__APPLE__) - // Specific to MacOS and other Apple SDKs - macos macos{*this}; - macos.ident_modules(); - pspawn.resolve_lines(); - macos.symbolize(); - -# else - // Linux and other other platforms - linux linux{*this}; - linux.ident_modules(); - pspawn.resolve_lines(); - linux.symbolize(); - -# endif -#endif -} - -} // namespace __stacktrace - -_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/fd.h b/libcxx/src/stacktrace/fd.h new file mode 100644 index 0000000000000..cd5b8053d0644 --- /dev/null +++ b/libcxx/src/stacktrace/fd.h @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_FD +#define _LIBCPP_STACKTRACE_FD + +#include <__config> +#include +#include +#include +#include +#include +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/* Wraps a C file descriptor, converting to/from `int`s. + * Not copyable; should only be moved, so that it only has one "owner". */ +struct _LIBCPP_HIDE_FROM_ABI fd { + int fd_{-1}; // invalid iff negative + + fd() = default; + ~fd() { close(); } + + // To / from plain old ints + fd(int x) : fd_(x) {} + operator int() const { return fd_; } // implicit + + // No copying (other than contorting to/from ints) + fd(fd const&) = delete; + fd& operator=(fd const&) = delete; + + // Moving is ok, and the moved-from object is invalidated (gets fd of -1) + fd(fd&& rhs) { std::swap(fd_, rhs.fd_); } + fd& operator=(fd&& rhs) { return (std::addressof(rhs) == this) ? *this : *new (this) fd(std::move(rhs)); } + + bool valid() const { return fd_ >= 0; } + + void close() { + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } + } + + /** Open `/dev/null` for reading and writing */ + static fd& null_fd() { + static fd ret{::open("/dev/null", O_RDWR)}; + return ret; + } + + static fd open_ro(std::string_view path) { + fd ret = {::open(path.data(), O_RDONLY)}; + return ret; + } + + /** Create pipe pair via `pipe`, assign into these two destination `fd`s */ + static int pipe_pair(fd& read_fd, fd& write_fd) { + int fd_ints[2]; + if (::pipe(fd_ints) == -1) { + return errno; + } + read_fd = fd_ints[0]; + write_fd = fd_ints[1]; + return 0; + } + + struct _LIBCPP_HIDE_FROM_ABI streambuf; + struct _LIBCPP_HIDE_FROM_ABI istream; + struct _LIBCPP_HIDE_FROM_ABI mmap; +}; + +/** Wraps a readable fd using the `streambuf` interface. */ +struct _LIBCPP_HIDE_FROM_ABI fd::streambuf final : std::streambuf { + fd& fd_; + char* buf_; + size_t size_; + + _LIBCPP_HIDE_FROM_ABI streambuf(fd& fd, char* buf, size_t size) : fd_(fd), buf_(buf), size_(size) {} + _LIBCPP_HIDE_FROM_ABI virtual ~streambuf() = default; + + _LIBCPP_HIDE_FROM_ABI int underflow() override { + int count = ::read(fd_, buf_, size_); + if (count <= 0) { + // error or EOF: return eof to stop + return traits_type::eof(); + } + auto ret = int(*buf_); + setg(buf_, buf_, buf_ + count); + return ret; + } +}; + +/** Wraps an `FDInStreamBuffer` in an `istream` */ +struct fd::istream final : std::istream { + fd::streambuf& buf_; + _LIBCPP_HIDE_FROM_ABI virtual ~istream() = default; + _LIBCPP_HIDE_FROM_ABI explicit istream(fd::streambuf& buf) : std::istream(nullptr), buf_(buf) { rdbuf(&buf_); } +}; + +/** Read-only memory mapping. Requires an `fd`, or a path to open an `fd` out of. Takes ownership and destruction duty + * of the fd. */ +struct fd::mmap final { + fd fd_{}; + size_t size_{0}; + std::byte const* addr_{nullptr}; + + _LIBCPP_HIDE_FROM_ABI explicit mmap(std::string_view path) : mmap(fd::open_ro(path)) {} + + _LIBCPP_HIDE_FROM_ABI explicit mmap(fd&& fd) : fd_(std::move(fd)) { + if (fd_) { + if ((size_ = ::lseek(fd_, 0, SEEK_END))) { + addr_ = (std::byte const*)::mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd_, 0); + } + } + } + + _LIBCPP_HIDE_FROM_ABI operator bool() const { return addr_; } + + _LIBCPP_HIDE_FROM_ABI ~mmap() { + if (addr_) { + ::munmap(const_cast((void const*)addr_), size_); + } + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_FD diff --git a/libcxx/src/stacktrace/images.cpp b/libcxx/src/stacktrace/images.cpp new file mode 100644 index 0000000000000..5385fdb898457 --- /dev/null +++ b/libcxx/src/stacktrace/images.cpp @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// +// OS-specific construction +// + +#include "__config" +#if defined(__APPLE__) +// MacOS-specific: use the `dyld` loader to access info about loaded Mach-O images. +# include <__stacktrace/images.h> +# include <__stacktrace/memory.h> +# include +# include +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +// TODO: consider cases where e.g. libraries are loaded/unloaded, and recomputing image list. + +_LIBCPP_EXPORTED_FROM_ABI images::images() { + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + auto dyld_count = _dyld_image_count(); + for (unsigned i = 0; i < dyld_count && count_ < k_max_images; i++) { + auto& image = images_[count_++]; + image.slide_ = uintptr_t(_dyld_get_image_vmaddr_slide(i)); + image.loaded_at_ = uintptr_t(_dyld_get_image_header(i)); + image.is_main_prog_ = (i == 0); + image.name_ = _dyld_get_image_name(i); + } + std::sort(images_.begin(), images_.begin() + count_); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#elif !defined(_WIN32) +// Non-MacOS and non-Windows, including Linux: assume environment has these headers. +# include <__stacktrace/images.h> +# include <__stacktrace/memory.h> +# include +# include +# include +# include +# include + +// TODO: consider cases where e.g. libraries are loaded/unloaded, and recomputing image list. + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { +int add_image(dl_phdr_info* info, size_t, void* images_v) { + auto& imgs = *(images*)images_v; + if (imgs.count_ == images::k_max_images) { + return 0; + } + auto is_first = (imgs.count_ == 0); + auto& image = imgs.images_.at(imgs.count_++); + // Absolute address at which this ELF is loaded + image.loaded_at_ = info->dlpi_addr; + // This also happens to be the "slide" amount since ELF has zero-relative offsets + image.slide_ = info->dlpi_addr; + image.name_ = info->dlpi_name; + // `dl_iterate_phdr` gives us the main program image first + image.is_main_prog_ = is_first; + if (image.name_.empty() && is_first) { + char buf[PATH_MAX + 1]; + // Disregards errno, but leaves `name_` empty + auto len = readlink("/proc/self/exe", buf, sizeof(buf)); + if (len != -1 && unsigned(len) < sizeof(buf) - 1) { + image.name_ = buf; + } + } + // If we're at the limit, return nonzero to stop iterating + return imgs.count_ == images::k_max_images; +} +} // namespace + +_LIBCPP_EXPORTED_FROM_ABI images::images() { + dl_iterate_phdr(add_image, this); + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + std::sort(images_.begin(), images_.begin() + count_); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/impl_generic.cpp b/libcxx/src/stacktrace/impl_generic.cpp new file mode 100644 index 0000000000000..f3a818622b108 --- /dev/null +++ b/libcxx/src/stacktrace/impl_generic.cpp @@ -0,0 +1,115 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +/* +"Generic" implementation for any platform that doesn't have its own special implementation. +(Currently this means any platform other than Windows) +*/ + +#if !defined(_WIN32) + +# include <__config> +# include <__stacktrace/stacktrace_entry.h> + +# include "stacktrace/tools/tools.h" +# include "stacktrace/unwinding.h" +# include <__stacktrace/images.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void +base::current(arena& arena, size_t skip, size_t max_depth) { + if (!max_depth) [[unlikely]] { + return; + } + + /* + Build up the stacktrace entries and fill out their fields the best we can. + An `entry_base` structure looks more or less like: + + __addr_ uintptr_t Instruction address (of a call instruction, faulting insn, ...) + __desc_ string "Description" which we'll say is synonymous with "symbol" + __file_ string Source filename, or if that's not available, program/library path + __line_ int Line number within the source file, or failing that, zero + __image_ image* The image, loaded in by the OS, containing that address (program, library) + + On Windows, there are DLLs which take care of all this (dbghelp, psapi), with essentially + zero overlap with any other OS, so it's in its own file (impl_windows). Otherwise, i.e. + on non-Windows platforms, taking a stacktrace looks like: + + 0. Create the basic_stacktrace, its internal vector, using provided allocator. + (This was handled by the `current` member functions inside `basic_stacktrace`.) + 1. Collect instruction addresses to build up the entries, observing skip and max_depth. + The unwinding library we have available to us should take care of walking the call stack + and finding addresses. + 2. Resolve addresses into the program images (executable, libraries); normalize the addresses + from step 1, as they were seen in the wild, into program-image-relative addresses + (i.e. deal with ASLR) + 3. Resolve adjusted addresses into their symbols; some environments provide this out of the box + (MacOS) and others make this less easy (Linux) + 4. To get the source file and line number, we have to dig through debug information (DWARF format); + we might need the help of an external tool or library. + 4A: Ideally we would have a library inside libcxx, possibly refactored from somewhere within + compiler-rt, lldb, llvm-symbolizer, that could handle all of this. + (XXX we don't have this currently) + 4B: If the local system happens to have a library that does this, that will work too. + Look for: libbacktrace, libdwarf, etc. + (XXX we don't do this yet) + 4C: Use an external tool (i.e. spawn a child process) which can do this. + + */ + + // (1) Collect instruction addresses; build vector, populate their `__addr_`'s + unwind_addrs(*this, skip + 1, max_depth); + if (!__entries_size_()) { + return; + } + + // (2) Map these addresses to their respective program images, populate `__image_` + find_images(arena); + + // (3) Use system loader and/or `dl` to get symbols + find_symbols(arena); + + // (4C) Use an external tool to get source file/line, as well as any missing symbols + find_source_locs(arena); +} + +_LIBCPP_EXPORTED_FROM_ABI void base::find_images(arena& arena) { + images images; + size_t i = 0; + auto* it = entries_begin(); + auto* end = entries_end(); + while (it != end) { + auto& entry = *it++; + images.find(&i, entry.__addr_); + if (auto& image = images[i]) { + entry.__image_ = ℑ + // While we're in this loop, get the executable's path, and tentatively use this for source file. + entry.__file_ = image.name_; + } + } +} + +_LIBCPP_EXPORTED_FROM_ABI void base::find_symbols(arena& arena) {} + +_LIBCPP_EXPORTED_FROM_ABI void base::find_source_locs(arena& arena) { +# if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + (void)(false // + || __run_tool(*this, arena) // preferred on MacOS + || __run_tool(*this, arena) // prefer this in other (non-MacOS, non-Windows) + || __run_tool(*this, arena) // a good fallback; dev machines tend to have gcc if not llvm + ); +# endif +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/impl_windows.cpp b/libcxx/src/stacktrace/impl_windows.cpp new file mode 100644 index 0000000000000..864d129f4becd --- /dev/null +++ b/libcxx/src/stacktrace/impl_windows.cpp @@ -0,0 +1,258 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if defined(_WIN32) + +# include <__config> +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +// clang-format off + +// +#include "windows.h" +// +#include "dbghelp.h" +#include "psapi.h" +// +#include "common.h" + +#include +#include + +namespace { + +struct dll { + HMODULE module_ {}; + bool loaded_ {}; + + explicit dll(char const* name) : module_(LoadLibrary(name)) {} + + ~dll() { if (module_) { FreeLibrary(module_); } } + + template bool get_func(F* func, char const* name) { + *func = (F) GetProcAddress(module_, name); + return func != nullptr; + } +}; + +struct dbghelp_dll final : dll { + IMAGE_NT_HEADERS* (*ImageNtHeader)(void*); + bool (*StackWalk64) (DWORD, HANDLE, HANDLE, STACKFRAME64*, void*, void*, void*, void*, void*); + bool (*SymCleanup) (HANDLE); + void* (*SymFunctionTableAccess64)(HANDLE, DWORD64); + bool (*SymGetLineFromAddr64)(HANDLE, DWORD64, DWORD*, IMAGEHLP_LINE64*); + DWORD64 (*SymGetModuleBase64) (HANDLE, DWORD64); + DWORD (*SymGetOptions) (); + bool (*SymGetSearchPath) (HANDLE, char const*, DWORD); + bool (*SymGetSymFromAddr64)(HANDLE, DWORD64, DWORD64*, IMAGEHLP_SYMBOL64*); + bool (*SymInitialize) (HANDLE, char const*, bool); + DWORD64 (*SymLoadModule64) (HANDLE, HANDLE, char const*, char const*, void*, DWORD); + DWORD (*SymSetOptions) (DWORD); + bool (*SymSetSearchPath) (HANDLE, char const*); + + dbghelp_dll() : dll("dbghelp.dll") { + loaded_ = true + && get_func(&ImageNtHeader, "ImageNtHeader") + && get_func(&StackWalk64, "StackWalk64") + && get_func(&SymCleanup, "SymCleanup") + && get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64") + && get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64") + && get_func(&SymGetModuleBase64, "SymGetModuleBase64") + && get_func(&SymGetOptions, "SymGetOptions") + && get_func(&SymGetSearchPath, "SymGetSearchPath") + && get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64") + && get_func(&SymInitialize, "SymInitialize") + && get_func(&SymLoadModule64, "SymLoadModule64") + && get_func(&SymSetOptions, "SymSetOptions") + && get_func(&SymSetSearchPath, "SymSetSearchPath") + ; + } +}; + +struct psapi_dll final : dll { + bool (*EnumProcessModules) (HANDLE, HMODULE*, DWORD, DWORD*); + bool (*GetModuleInformation) (HANDLE, HMODULE, MODULEINFO*, DWORD); + DWORD (*GetModuleBaseName) (HANDLE, HMODULE, char**, DWORD); + + psapi_dll() : dll("psapi.dll") { + loaded_ = true + && get_func(&EnumProcessModules, "EnumProcessModules") + && get_func(&GetModuleInformation, "GetModuleInformation") + && get_func(&GetModuleBaseName, "GetModuleBaseNameA") + ; + } +}; + +struct sym_init_scope { + dbghelp_dll& dbghelp_; + HANDLE proc_; + + sym_init_scope(dbghelp_dll& dbghelp, HANDLE proc) + : dbghelp_(dbghelp), proc_(proc) { + (*dbghelp_.SymInitialize)(proc_, nullptr, true); + } + ~sym_init_scope() { + (*dbghelp_.SymCleanup)(proc_); + } +}; + +} // namespace + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void +base::current(arena&, size_t skip, size_t max_depth) { + if (!max_depth) [[unlikely]] { + return; + } + + static psapi_dll psapi; + static dbghelp_dll dbghelp; + if (!psapi.loaded_ || !dbghelp.loaded_) { return; } + + // Not thread-safe according to docs + static std::mutex api_mutex; + std::lock_guard api_guard(api_mutex); + + HANDLE proc; + proc = GetCurrentProcess(); + + HMODULE exe; + if (!(exe = GetModuleHandle(nullptr))) { return; } + + sym_init_scope symscope(dbghelp, proc); + + TCHAR sym_path[MAX_PATH * 4]; + if (!(*dbghelp.SymGetSearchPath)(proc, sym_path, sizeof(sym_path))) { return; } + + TCHAR exe_dir[MAX_PATH]; + if (!GetModuleFileName(nullptr, exe_dir, sizeof(exe_dir))) { return; } + size_t exe_dir_len = strlen(exe_dir); + while (exe_dir_len > 0 && exe_dir[exe_dir_len - 1] != '\\') { exe_dir[--exe_dir_len] = 0; } + if (exe_dir_len > 0) { exe_dir[--exe_dir_len] = 0; } // strip last backslash + + if (!strstr(sym_path, exe_dir)) { + (void) strncat(sym_path, ";", sizeof(sym_path)); + (void) strncat(sym_path, exe_dir, sizeof(sym_path)); + if (!(*dbghelp.SymSetSearchPath)(proc, sym_path)) { return; } + } + + IMAGE_NT_HEADERS* nt_headers; + if (!(nt_headers = (*dbghelp.ImageNtHeader)(exe))) { return; } + + (*dbghelp.SymSetOptions)( + (*dbghelp.SymGetOptions)() + | SYMOPT_LOAD_LINES + | SYMOPT_UNDNAME); + + auto thread = GetCurrentThread(); + auto machine = nt_headers->FileHeader.Machine; + + CONTEXT ccx; + RtlCaptureContext(&ccx); + + STACKFRAME64 frame; + memset(&frame, 0, sizeof(frame)); + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; +#if defined(_M_IX86) + frame.AddrPC.Offset = ccx.Eip; + frame.AddrStack.Offset = ccx.Esp; + frame.AddrFrame.Offset = ccx.Ebp; +#elif defined(_M_AMD64) + frame.AddrPC.Offset = ccx.Rip; + frame.AddrStack.Offset = ccx.Rsp; + frame.AddrFrame.Offset = ccx.Rbp; +#elif defined(_M_ARM) + frame.AddrPC.Offset = ccx.Pc; + frame.AddrStack.Offset = ccx.Sp; + frame.AddrFrame.Offset = ccx.Fp; +#elif defined(_M_ARM64) + frame.AddrPC.Offset = ccx.Pc; + frame.AddrStack.Offset = ccx.Sp; + frame.AddrFrame.Offset = ccx.Fp; +#else +#error Unhandled CPU/arch for stacktrace +#endif + + ++skip; // skip call to this `populate` func + while (max_depth) { + if (!(*dbghelp.StackWalk64)( + machine, proc, thread, &frame, &ccx, nullptr, + dbghelp.SymFunctionTableAccess64, dbghelp.SymGetModuleBase64, + nullptr)) { + break; } + + if (skip && skip--) { continue; } + if (!frame.AddrPC.Offset) { break; } + + auto& entry = this->__emplace_entry_(); + // Note: can't differentiate between a signal, SEH exception handler, or a normal function call + entry.__addr_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range + + --max_depth; + } + + DWORD need_bytes = 0; + HMODULE module_handles[1024] {0}; + size_t module_count = 0; // 0 IFF module enumeration failed + if (!(*psapi.EnumProcessModules)( + proc, module_handles, sizeof(module_handles), LPDWORD(&need_bytes))) { + return; + } + module_count = need_bytes / sizeof(HMODULE); + + // Symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + + auto* it = entries_begin(); + auto* end = entries_end(); + while (it != end) { +#if defined(_M_ARM64) || defined(_M_AMD64) + auto& entry = *it++; + char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName + 1]; + auto* sym = (IMAGEHLP_SYMBOL64*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = kMaxSymName; + uint64_t symdisp{0}; + DWORD linedisp{0}; + IMAGEHLP_LINE64 line; + if ((*dbghelp.SymGetSymFromAddr64)(proc, entry.__addr_, &symdisp, sym)) { + entry.__desc_ = sym->Name; + } + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if ((*dbghelp.SymGetLineFromAddr64)(proc, entry.__addr_, &linedisp, &line)) { + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } +#else + char space[sizeof(IMAGEHLP_SYMBOL) + kMaxSymName + 1]; + auto* sym = (IMAGEHLP_SYMBOL*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); + sym->MaxNameLength = kMaxSymName; + uint32_t symdisp{0}; + DWORD linedisp{0}; + IMAGEHLP_LINE line; + if ((*dbghelp.SymGetSymFromAddr)(proc, entry.__addr_, &symdisp, sym)) { + entry.__desc_ = sym->Name; + } + line.SizeOfStruct = sizeof(IMAGEHLP_LINE); + if ((*dbghelp.SymGetLineFromAddr)(proc, entry.__addr_, &linedisp, &line)) { + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } +#endif + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/linux/elf.cpp b/libcxx/src/stacktrace/linux/elf.cpp deleted file mode 100644 index b6d05f33a9624..0000000000000 --- a/libcxx/src/stacktrace/linux/elf.cpp +++ /dev/null @@ -1,98 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#if defined(__linux__) - -# include "stacktrace/linux/elf.h" - -# include <__stacktrace/base.h> -# include -# include -# include -# include -# include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace::elf { - -ELF::ELF(std::byte const* image) { - auto* p = (uint8_t const*)image; - // Bytes 0..3: magic bytes: 0x7F, 'E', 'L', 'F' - if (*p++ == 0x7f && *p++ == 0x45 && *p++ == 0x4c && *p++ == 0x46) { - auto klass = *p++; // Byte 4 (EI_CLASS): ELF class, 32- or 64-bit (0x01 or 0x02) - auto dataFormat = *p++; // Byte 5 (EI_DATA): (0x01) little- or (0x02) big-endian - auto fileVersion = *p++; // Byte 6 (EI_VERSION): ELF version: expect 1 (latest ELF version) - constexpr static uint16_t kEndianTestWord{0x0201}; - auto hostEndianness = *(uint8_t const*)&kEndianTestWord; - if (dataFormat == hostEndianness && fileVersion == 1) { - if (klass == 0x01) { - header_ = Header((Header32 const*)image); - makeSection_ = makeSection32; - makeSymbol_ = makeSymbol32; - secSize_ = sizeof(Section32); - symSize_ = sizeof(Symbol32); - } else if (klass == 0x02) { - header_ = Header((Header64 const*)image); - makeSection_ = makeSection64; - makeSymbol_ = makeSymbol64; - secSize_ = sizeof(Section64); - symSize_ = sizeof(Symbol64); - } - } - } - if (*this) { - nametab_ = section(header_.shstrndx_); - eachSection([&](auto& sec) mutable -> bool { - if (sec.type_ == Section::kSymTab && sec.name() == ".symtab") { - symtab_ = sec; - } else if (sec.type_ == Section::kStrTab && sec.name() == ".strtab") { - strtab_ = sec; - } - return !symtab_ || !strtab_; - }); - } - if (symtab_) { - symCount_ = symtab_.size_ / symSize_; - } -} - -Section ELF::section(size_t index) { - auto* addr = header_.ptr_ + header_.shoff_ + (index * secSize_); - return makeSection_(this, addr); -} - -Symbol ELF::symbol(size_t index) { - auto* addr = symtab_.data() + (index * symSize_); - return makeSymbol_(this, addr); -} - -void ELF::eachSection(CB
cb) { - for (size_t i = 0; i < header_.shnum_ && cb(section(i)); i++) - ; -} - -void ELF::eachSymbol(CB cb) { - for (size_t i = 0; i < symCount_ && cb(symbol(i)); i++) - ; -} - -Symbol ELF::getSym(uintptr_t addr) { - Symbol ret{}; - eachSymbol([&](auto& sym) -> bool { - if (sym.value_ <= addr && sym.value_ > ret.value_) { - ret = sym; - } - return true; - }); - return ret; -} - -} // namespace __stacktrace::elf -_LIBCPP_END_NAMESPACE_STD - -#endif // defined(__linux__) diff --git a/libcxx/src/stacktrace/linux/elf.h b/libcxx/src/stacktrace/linux/elf.h deleted file mode 100644 index ebf47ed964b97..0000000000000 --- a/libcxx/src/stacktrace/linux/elf.h +++ /dev/null @@ -1,248 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_LINUX_ELF -#define _LIBCPP_STACKTRACE_LINUX_ELF - -#include <__stacktrace/base.h> -#include -#include -#include -#include -#include -#include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace::elf { - -// Includes ELF constants and structs copied from , with a few changes. - -struct Header32 final { - uint8_t ident[16]; /* Magic number and other info */ - uint16_t type; /* Object file type */ - uint16_t machine; /* Architecture */ - uint32_t version; /* Object file version */ - uint32_t entry; /* Entry point virtual address */ - uint32_t phoff; /* Program header table file offset */ - uint32_t shoff; /* Section header table file offset */ - uint32_t flags; /* Processor-specific flags */ - uint16_t ehsize; /* ELF header size in bytes */ - uint16_t phentsize; /* Program header table entry size */ - uint16_t phnum; /* Program header table entry count */ - uint16_t shentsize; /* Section header table entry size */ - uint16_t shnum; /* Section header table entry count */ - uint16_t shstrndx; /* Section header string table index */ -}; - -struct Section32 final { - uint32_t name; /* Section name (string tbl index) */ - uint32_t type; /* Section type */ - uint32_t flags; /* Section flags */ - uint32_t addr; /* Section virtual addr at execution */ - uint32_t offset; /* Section file offset */ - uint32_t size; /* Section size in bytes */ - uint32_t link; /* Link to another section */ - uint32_t info; /* Additional section information */ - uint32_t addralign; /* Section alignment */ - uint32_t entsize; /* Entry size if section holds table */ -}; - -struct Symbol32 final { - uint32_t name; /* Symbol name (string tbl index) */ - uint32_t value; /* Symbol value */ - uint32_t size; /* Symbol size */ - uint8_t info; /* Symbol type and binding */ - uint8_t other; /* Symbol visibility */ - uint16_t shndx; /* Section index */ -}; - -struct Header64 final { - uint8_t ident[16]; /* Magic number and other info */ - uint16_t type; /* Object file type */ - uint16_t machine; /* Architecture */ - uint32_t version; /* Object file version */ - uint64_t entry; /* Entry point virtual address */ - uint64_t phoff; /* Program header table file offset */ - uint64_t shoff; /* Section header table file offset */ - uint32_t flags; /* Processor-specific flags */ - uint16_t ehsize; /* ELF header size in bytes */ - uint16_t phentsize; /* Program header table entry size */ - uint16_t phnum; /* Program header table entry count */ - uint16_t shentsize; /* Section header table entry size */ - uint16_t shnum; /* Section header table entry count */ - uint16_t shstrndx; /* Section header string table index */ -}; - -struct Section64 final { - uint32_t name; /* Section name (string tbl index) */ - uint32_t type; /* Section type */ - uint64_t flags; /* Section flags */ - uint64_t addr; /* Section virtual addr at execution */ - uint64_t offset; /* Section file offset */ - uint64_t size; /* Section size in bytes */ - uint32_t link; /* Link to another section */ - uint32_t info; /* Additional section information */ - uint64_t addralign; /* Section alignment */ - uint64_t entsize; /* Entry size if section holds table */ -}; - -struct Symbol64 final { - uint32_t name; /* Symbol name (string tbl index) */ - uint8_t info; /* Symbol type and binding */ - uint8_t other; /* Symbol visibility */ - uint16_t shndx; /* Section index */ - uint64_t value; /* Symbol value */ - uint64_t size; /* Symbol size */ -}; - -/** Represents an ELF header. Supports the minimum needed to navigate an ELF file's sections and get at the symbol and - * string tables. */ -struct Header final { - std::byte const* ptr_{}; - uintptr_t shoff_{}; - size_t shnum_{}; - size_t shstrndx_{}; - - Header() = default; - Header(Header const&) = default; - Header& operator=(Header const& rhs) { return *new (this) Header(rhs); } - - operator bool() { return ptr_; } - - template - explicit Header(H* h) - : ptr_((std::byte const*)h), - shoff_(uintptr_t(h->shoff)), - shnum_(size_t(h->shnum)), - shstrndx_(size_t(h->shstrndx)) {} -}; - -struct ELF; -struct StringTable; - -struct Section final { - constexpr static uint32_t kSymTab = 2; // symbol table - constexpr static uint32_t kStrTab = 3; // name table for symbols or sections - - ELF* elf_{}; - std::byte const* ptr_{}; - uintptr_t nameIndex_{}; - uint32_t type_{}; - uintptr_t offset_{}; - size_t size_{}; - - Section() = default; - - template - Section(ELF* elf, S* sec) - : elf_(elf), - ptr_((std::byte const*)sec), - nameIndex_(sec->name), - type_(sec->type), - offset_(sec->offset), - size_(sec->size) {} - - operator bool() const { return ptr_; } - - template - T const* data() const { - return (T const*)(elfBase() + offset_); - } - - std::byte const* elfBase() const; - std::string_view name() const; -}; - -struct Symbol final { - constexpr static uint8_t kFunc = 0x02; // STT_FUNC (code object) - - ELF* elf_{}; - std::byte const* ptr_{}; - uintptr_t nameIndex_{}; - uint32_t type_{}; - uintptr_t value_{}; - - Symbol() = default; - Symbol(Symbol const&) = default; - Symbol& operator=(Symbol const& rhs) { return *new (this) Symbol(rhs); } - - operator bool() { return ptr_; } - - bool isCode() const { return type_ == kFunc; } - - template - Symbol(ELF* elf, S* sym) - : elf_(elf), ptr_((std::byte const*)sym), nameIndex_(sym->name), type_(0x0f & sym->info), value_(sym->value) {} - - std::byte const* elfBase() const; - std::string_view name() const; -}; - -/** Represents one of the ELF's `strtab`s. This is a block of string data, with strings appended one after another, and - * NUL-terminated. Strings are indexed according to their starting offset. At offset 0 is typically an empty string. - */ -struct StringTable { - std::string_view data_{}; - - StringTable() = default; - - /* implicit */ StringTable(Section const& sec) : data_(sec.data(), sec.size_) {} - - operator bool() { return data_.size(); } - - std::string_view at(size_t index) { - auto* ret = data_.data() + index; - return {ret, strlen(ret)}; - } -}; - -/** Encapsulates an ELF image specified by byte-address (e.g. from an mmapped file or a program image or shared object - * in memory). If given a supported ELF image, this will test true with `operator bool` to indicate it is supported and - * was able to parse some basic information from the header. */ -struct ELF { - Header header_{}; - Section (*makeSection_)(ELF*, std::byte const*){}; - Symbol (*makeSymbol_)(ELF*, std::byte const*){}; - size_t secSize_{}; - size_t symSize_{}; - StringTable nametab_{}; - Section symtab_{}; - StringTable strtab_{}; - size_t symCount_{}; - - static Section makeSection32(ELF* elf, std::byte const* ptr) { return Section(elf, (Section32 const*)ptr); } - static Section makeSection64(ELF* elf, std::byte const* ptr) { return Section(elf, (Section64 const*)ptr); } - static Symbol makeSymbol32(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol32 const*)ptr); } - static Symbol makeSymbol64(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol64 const*)ptr); } - - operator bool() { return header_; } - - explicit ELF(std::byte const* image); - - Section section(size_t index); - Symbol symbol(size_t index); - - template - using CB = std::function; - - void eachSection(CB
cb); - void eachSymbol(CB cb); - - Symbol getSym(uintptr_t addr); -}; - -inline std::byte const* Section::elfBase() const { return elf_->header_.ptr_; } -inline std::byte const* Symbol::elfBase() const { return elf_->header_.ptr_; } - -inline std::string_view Section::name() const { return elf_->nametab_.at(nameIndex_); } -inline std::string_view Symbol::name() const { return elf_->strtab_.at(nameIndex_); } - -} // namespace __stacktrace::elf -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_LINUX_ELF diff --git a/libcxx/src/stacktrace/linux/images.cpp b/libcxx/src/stacktrace/linux/images.cpp deleted file mode 100644 index 1ff3dc80253e4..0000000000000 --- a/libcxx/src/stacktrace/linux/images.cpp +++ /dev/null @@ -1,72 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#if defined(__linux__) - -# include <__stacktrace/base.h> - -# include "stacktrace/linux/images.h" -# include "stacktrace/utils/image.h" - -# include -# include -# include -# include -# include -# include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -images images::instance; - -int images::add(dl_phdr_info& info) { - assert(count_ < image::kMaxImages); - auto isFirst = (count_ == 0); - auto& image = images_.at(count_++); - image.loaded_at_ = info.dlpi_addr; - image.slide_ = info.dlpi_addr; - image.name_ = info.dlpi_name; - image.is_main_prog_ = isFirst; // first one is the main ELF - if (image.name_.empty() && isFirst) { - static char buffer[PATH_MAX + 1]; - uint32_t length = sizeof(buffer); - if (readlink("/proc/self/exe", buffer, length) > 0) { - image.name_ = buffer; - } - } - return count_ == image::kMaxImages; // return nonzero if we're at the limit -} - -int images::callback(dl_phdr_info* info, size_t, void* self) { return (*(images*)(self)).add(*info); } - -images::images() { - dl_iterate_phdr(images::callback, this); - images_[count_++] = {0uz, 0}; // sentinel at low end - images_[count_++] = {~0uz, 0}; // sentinel at high end - std::sort(images_.begin(), images_.begin() + count_ - 1); -} - -image& images::operator[](size_t index) { - assert(index < count_); - return images_.at(index); -} - -image* images::mainProg() { - for (auto& image : images_) { - if (image.is_main_prog_) { - return ℑ - } - } - return nullptr; -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // __linux__ diff --git a/libcxx/src/stacktrace/linux/images.h b/libcxx/src/stacktrace/linux/images.h deleted file mode 100644 index bd7ac8ff77bf0..0000000000000 --- a/libcxx/src/stacktrace/linux/images.h +++ /dev/null @@ -1,50 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_LINUX_IMAGES -#define _LIBCPP_STACKTRACE_LINUX_IMAGES - -#if defined(__linux__) - -# include <__stacktrace/base.h> - -# include -# include -# include -# include -# include -# include - -# include "stacktrace/utils/image.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct images { - // How many images this contains, including the left/right sentinels. - unsigned count_{0}; - std::array images_{}; - - images(); - - int add(dl_phdr_info& info); - static int callback(dl_phdr_info* info, size_t, void* self); - - image& operator[](size_t index); - - image* mainProg(); - - static images instance; -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // __linux__ - -#endif // _LIBCPP_STACKTRACE_LINUX_IMAGES diff --git a/libcxx/src/stacktrace/linux/impl.cpp b/libcxx/src/stacktrace/linux/impl.cpp deleted file mode 100644 index c33e670a3584d..0000000000000 --- a/libcxx/src/stacktrace/linux/impl.cpp +++ /dev/null @@ -1,113 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#if defined(__linux__) - -# include <__stacktrace/base.h> -# include -# include -# include -# include - -# include "stacktrace/linux/elf.h" -# include "stacktrace/linux/images.h" -# include "stacktrace/linux/impl.h" -# include "stacktrace/utils/fd.h" -# include "stacktrace/utils/image.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -void linux::ident_modules() { - auto& images = images::instance; - - // Aside from the left/right sentinels in the array (hence the 2), - // are there any other real images? - if (images.count_ <= 2) { - return; - } - - auto mainProg = images.mainProg(); - if (mainProg) { - base_.__main_prog_path_ = mainProg->name_; - } - - unsigned index = 1; // Starts at one, and is moved around in this loop - for (auto& entry : base_.__entries_) { - while (images[index].loaded_at_ > entry.__addr_actual_) { - --index; - } - while (images[index + 1].loaded_at_ <= entry.__addr_actual_) { - ++index; - } - entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; - entry.__file_ = base_.make_str(images[index].name_); - } -} - -/** -When trying to collect a stacktrace under Linux, there are narrow (but still quite common) cases where we will fail -to resolve symbols. Linux's `dl` doesn't want to read symbols from the non-exported symbol table at runtime, -and older versions of `addr2line` or `llvm-symbolizer` will also not resolve these. - -This implementation the minimum necessary to resolve symbols. It can identify this as an ELF (32 or 64 bits), locate -the symbol and symbol-string table, and fill in any remaining missing symbols. -*/ -void linux::resolve_main_elf_syms(std::string_view main_elf_name) { - // We can statically initialize these, because main_elf_name should be the same every time. - static fd_mmap _mm(main_elf_name); - if (_mm) { - static elf::ELF _this_elf(_mm.addr_); - if (_this_elf) { - for (auto& entry : base_.__entries_) { - if (entry.__desc_->empty() && entry.__file_ == main_elf_name) { - auto name = _this_elf.getSym(entry.__addr_unslid_).name(); - entry.__desc_ = base_.make_str(name); - } - } - } - } -} - -bool symbolize_entry(base& base, entry_base& entry) { - bool ret = false; - Dl_info info; - if (dladdr((void*)entry.__addr_actual_, &info)) { - ret = true; // at least partially successful - if (info.dli_fname && entry.__file_->empty()) { - // provide at least the binary filename in case we cannot lookup source location - entry.__file_ = base.make_str(info.dli_fname); - } - if (info.dli_sname && entry.__desc_->empty()) { - // provide at least the mangled name; try to unmangle in a later step - entry.__desc_ = base.make_str(info.dli_sname); - } - } - return ret; -} - -// NOTE: We can use `dlfcn` to resolve addresses to symbols, which works great -- -// except for symbols in the main program. If addr2line-style tools are enabled, that step -// might also be able to get symbols directly from the binary's debug info. -void linux::symbolize() { - for (auto& entry : base_.__entries_) { - symbolize_entry(base_, entry); - } - // Symbols might be missing, because both (1) Linux's `dladdr` won't try to resolve non-exported symbols, - // which can be the case for the main program executable; and (2) debug info was not preserved. - // As a last resort, this function (see `linux-elf.cpp`) can still access symbol table directly. - image* mainELF = images::instance.mainProg(); - if (mainELF && !mainELF->name_.empty()) { - resolve_main_elf_syms(mainELF->name_); - } -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // __linux__ diff --git a/libcxx/src/stacktrace/linux/impl.h b/libcxx/src/stacktrace/linux/impl.h deleted file mode 100644 index 5ace2db1a3fa8..0000000000000 --- a/libcxx/src/stacktrace/linux/impl.h +++ /dev/null @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_LINUX_IMPL -#define _LIBCPP_STACKTRACE_LINUX_IMPL - -#include <__stacktrace/base.h> - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct linux { - base& base_; - -#if defined(__linux__) - // defined in linux.cpp - void ident_modules(); - void symbolize(); - -private: - void resolve_main_elf_syms(std::string_view elf_name); -#else - // inline-able dummy definitions - void ident_modules() {} - void symbolize() {} -#endif -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_LINUX_IMPL diff --git a/libcxx/src/stacktrace/macos/impl.cpp b/libcxx/src/stacktrace/macos/impl.cpp deleted file mode 100644 index d47381c795f53..0000000000000 --- a/libcxx/src/stacktrace/macos/impl.cpp +++ /dev/null @@ -1,99 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#if defined(__APPLE__) - -# include -# include -# include -# include -# include - -# include <__stacktrace/base.h> - -# include "stacktrace/macos/impl.h" -# include "stacktrace/utils/image.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -void ident_module(base& base, entry_base& entry, unsigned& index, image* images) { - if (entry.__addr_actual_) { - while (images[index].loaded_at_ > entry.__addr_actual_) { - --index; - } - while (images[index + 1].loaded_at_ <= entry.__addr_actual_) { - ++index; - } - auto& image = images[index]; - if (image) { - entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; - entry.__file_ = base.make_str(images[index].name_); - } - } -} - -bool enum_modules(unsigned& count, auto& images) { - count = std::min(image::kMaxImages, size_t(_dyld_image_count())); - for (size_t i = 0; i < count; i++) { - auto& image = images[i]; - image.slide_ = _dyld_get_image_vmaddr_slide(i); - image.loaded_at_ = uintptr_t(_dyld_get_image_header(i)); - image.name_ = _dyld_get_image_name(i); - } - images[count++] = {0uz, 0}; // sentinel at low end - images[count++] = {~0uz, 0}; // sentinel at high end - std::sort(images.begin(), images.begin() + count - 1); - return true; -} - -void macos::ident_modules() { - static unsigned imageCount; - static std::array images; - static bool atomicInitialized = enum_modules(imageCount, images); - (void)atomicInitialized; - - // Aside from the left/right sentinels in the array (hence the 2), - // are there any other real images? - if (imageCount <= 2) { - return; - } - - // First image (the main program) is at index 1 - base_.__main_prog_path_ = base_.make_str(images.at(1).name_); - - unsigned index = 1; // Starts at one, and is moved by 'ident_module' - for (auto& entry : base_.__entries_) { - ident_module(base_, (entry_base&)entry, index, images.data()); - } -} - -void symbolize_entry(base& base, entry_base& entry) { - Dl_info info; - if (dladdr((void*)entry.__addr_actual_, &info)) { - if (info.dli_fname && entry.__file_->empty()) { - // provide at least the binary filename in case we cannot lookup source location - entry.__file_ = base.make_str(info.dli_fname); - } - if (info.dli_sname && entry.__desc_->empty()) { - // provide at least the mangled name; try to unmangle in a later step - entry.__desc_ = base.make_str(info.dli_sname); - } - } -} - -void macos::symbolize() { - for (auto& entry : base_.__entries_) { - symbolize_entry(base_, entry); - } -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // __APPLE__ diff --git a/libcxx/src/stacktrace/macos/impl.h b/libcxx/src/stacktrace/macos/impl.h deleted file mode 100644 index fcc8a11501255..0000000000000 --- a/libcxx/src/stacktrace/macos/impl.h +++ /dev/null @@ -1,39 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_MACOS_IMPL -#define _LIBCPP_STACKTRACE_MACOS_IMPL - -#include <__config> -#include <__config_site> -#include -#include - -#include <__stacktrace/base.h> - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct macos { - base& base_; - -#if defined(__APPLE__) - // defined in macos.cpp - void ident_modules(); - void symbolize(); -#else - // inline-able dummy definitions - void ident_modules() {} - void symbolize() {} -#endif // __APPLE__ -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_MACOS_IMPL diff --git a/libcxx/src/stacktrace/to_string.cpp b/libcxx/src/stacktrace/to_string.cpp deleted file mode 100644 index 6d02b1c973344..0000000000000 --- a/libcxx/src/stacktrace/to_string.cpp +++ /dev/null @@ -1,93 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include <__config> -#include <__config_site> - -#include -#include -#include -#include -#include - -#include <__stacktrace/basic.h> -#include <__stacktrace/entry.h> -#include <__stacktrace/to_string.h> - -_LIBCPP_BEGIN_NAMESPACE_STD - -// `to_string`-related non-member functions - -_LIBCPP_EXPORTED_FROM_ABI string to_string(const stacktrace_entry& __entry) { - return __stacktrace::__to_string()(__entry); -} - -_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const stacktrace_entry& __entry) { - __stacktrace::__to_string()(__os, __entry); - return __os; -} - -namespace __stacktrace { - -/* - * `to_string` Helpers - */ - -_LIBCPP_EXPORTED_FROM_ABI void __to_string::operator()(ostream& __os, std::stacktrace_entry const& entry) { - // Although 64-bit addresses are 16 nibbles long, they're often <= 0x7fff_ffff_ffff - constexpr static int __k_addr_width = (sizeof(void*) > 4) ? 12 : 8; - - __os << "0x" << std::hex << std::setfill('0') << std::setw(__k_addr_width) << entry.native_handle(); - if (!entry.description().empty()) { - __os << ": " << entry.description(); - } - if (!entry.source_file().empty()) { - __os << ": " << entry.source_file(); - } - if (entry.source_line()) { - __os << ":" << std::dec << entry.source_line(); - } -} - -_LIBCPP_EXPORTED_FROM_ABI void -__to_string::operator()(ostream& __os, std::stacktrace_entry const* __entries, size_t __count) { - /* - * Print each entry as a line, as per `operator()`, with additional whitespace - * at the start of the line, and only a newline added at the end: - * - * frame 1: 0xbeefbeefbeef: _symbol_name: /path/to/file.cc:123 - */ - if (!__count) { - __os << "(empty stacktrace)"; - } else { - for (size_t __i = 0; __i < __count; __i++) { - if (__i) { - // Insert newlines between entries (but not before the first or after the last) - __os << std::endl; - } - __os << " frame " << std::setw(3) << std::setfill(' ') << std::dec << (__i + 1) << ": "; - (*this)(__os, __entries[__i]); - } - } -} - -_LIBCPP_EXPORTED_FROM_ABI string __to_string::operator()(std::stacktrace_entry const& entry) { - stringstream __ss; - (*this)(__ss, entry); - return __ss.str(); -} - -_LIBCPP_EXPORTED_FROM_ABI string __to_string::operator()(std::stacktrace_entry const* __entries, size_t __count) { - stringstream __ss; - (*this)(__ss, __entries, __count); - return __ss.str(); -} - -} // namespace __stacktrace - -_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/tools/apple_atos.cpp b/libcxx/src/stacktrace/tools/apple_atos.cpp new file mode 100644 index 0000000000000..11873e28b2f75 --- /dev/null +++ b/libcxx/src/stacktrace/tools/apple_atos.cpp @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> + +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include + +# include "stacktrace/tools/tools.h" + +// clang-format off + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +bool atos::build_argv() { + push_arg("/usr/bin/env"); + push_arg(tool_prog_); + push_arg("-p"); + push_arg("%d", getpid()); + auto* it = base_.entries_begin(); + auto* end = base_.entries_end(); + while (it != end) { + auto& entry = *(entry_base*)(it++); + push_arg("%p", (void*)entry.__addr_); + } + return true; +} + +void atos::parse(entry_base& entry, std::string_view view) const { + // With debug info we should get everything we need in one line: + // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) + + // view: main (in t.tmp.exe) (simple.o0.nosplit.pass.cpp:19) + // advance i to: ^ + size_t i = 0; + while (i < view.size() && !isspace(view[i])) { ++i; } + entry.__desc_ = view.substr(0, i); + view = lstrip(ldrop(view, i)); + + // view: (in t.tmp.exe) (simple.o0.nosplit.pass.cpp:19) + // looking for: ^^^ + auto pos = view.find(") ("); + if (pos == std::string_view::npos) { return; } + view = ldrop(view, pos + 3); // simple.o0.nosplit.pass.cpp:19) + view = drop_suffix(view, ")"); // simple.o0.nosplit.pass.cpp:19 + pos = view.find_last_of(":"); // ^here + if (pos == std::string_view::npos) { return; } + entry.__file_ = view.substr(0, pos); + auto lineno = view.substr(pos + 1); + entry.__line_ = lineno.empty() ? 0 : stoi(string(lineno)); +} + +template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& arena) { + atos tool{base, arena}; + if (!tool.build_argv()) { return false; } + spawner spawner{tool, base}; + if (spawner.errno_) { return false; } + + fixed_str line; // our read buffer + auto* entry_iter = base.entries_begin(); // position at first entry + while (spawner.stream_.good()) { // loop until we get EOF from tool stdout + std::getline(spawner.stream_, line); // consume a line from stdout + auto view = tool_base::strip(line); // remove trailing and leading whitespace + if (view.empty()) { continue; } // skip blank lines + tool.parse(*entry_iter, view); + ++entry_iter; // one line per entry + } + + return true; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/gnu_addr2line.cpp b/libcxx/src/stacktrace/tools/gnu_addr2line.cpp new file mode 100644 index 0000000000000..b99797ea58fe6 --- /dev/null +++ b/libcxx/src/stacktrace/tools/gnu_addr2line.cpp @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> + +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "stacktrace/tools/tools.h" +# include <__stacktrace/images.h> + +// clang-format off + +// XXX addr2line only supports one input file to resolve addresses for; +// XXX should invoke once for each program image we get in our stacktrace? + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +bool addr2line::build_argv() { + auto* main_image = images().main_prog_image(); + _LIBCPP_ASSERT(main_image, "could not determine main program image"); + _LIBCPP_ASSERT(!main_image->name_.empty(), "could not determine main program image name"); + if (!(main_image && !main_image->name_.empty())) { + return false; + } + push_arg("/usr/bin/env"); + push_arg(tool_prog_); + push_arg("--functions"); + push_arg("--demangle"); + push_arg("--basenames"); + push_arg("-e"); + push_arg(main_image->name_); + auto* it = base_.entries_begin(); + auto* end = base_.entries_end(); + while (it != end) { + auto& entry = *(entry_base*)(it++); + push_arg("%p", (void*)entry.adjusted_addr()); + } + return true; +} + +/* +Example: +-- +addr2line \ + --functions --demangle --basenames \ + -e $BUILDDIR/libcxx/test/libcxx/stacktrace/Output/use_available_progs.pass.cpp.dir/t.tmp.exe \ + 0x000100000610 0x000100000618 0x000100000620 + +NOTE: might not demangle even if we ask for `--demangle` +NOTE: currently seeing a malloc double-free in homebrew (macos) binutils 2.45 build of addr2line + (which we ignore) + +Output: (2 lines per input address) +--- +Z5func0v +use_available_progs.pass.cpp:78 +Z5func1v +use_available_progs.pass.cpp:81 +Z5func2v +use_available_progs.pass.cpp:84 +*/ + +void addr2line::parse_sym(entry_base& entry, std::string_view view) const { + if (!view.starts_with("??")) { + // XXX should check for "_Z" prefix (mangled symbol) and use cxxabi.h / demangle? + entry.__desc_ = view; + } +} + +void addr2line::parse_loc(entry_base& entry, std::string_view view) const { + if (!view.starts_with("??")) { + auto colon = view.find_last_of(":"); + if (colon != string_view::npos) { + entry.__file_ = view.substr(0, colon); + entry.__line_ = atoi(view.data() + colon + 1); + } + } +} + +template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& arena) { + addr2line tool{base, arena}; + if (!tool.build_argv()) { return false; } + spawner spawner{tool, base}; + if (spawner.errno_) { return false; } + + fixed_str line; // our read buffer + auto* entry_iter = base.entries_begin(); // position at first entry + while (spawner.stream_.good()) { // loop until we get EOF from tool stdout + std::string_view view; + + std::getline(spawner.stream_, line); // consume one line + view = tool_base::strip(line); // remove trailing and leading whitespace + if (view.empty()) { continue; } // blank line: restart loop, checking for EOF + tool.parse_sym(*entry_iter, view); // expecting symbol name + + std::getline(spawner.stream_, line); // consume one line + view = tool_base::strip(line); // remove trailing and leading whitespace + if (view.empty()) { continue; } // blank line: restart loop, checking for EOF + tool.parse_loc(*entry_iter, view); // expecting "/path/to/sourcefile.cpp:42" + + ++entry_iter; // one entry per two lines + } + + return true; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp b/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp new file mode 100644 index 0000000000000..779342e48e6f3 --- /dev/null +++ b/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> + +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/images.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include + +# include "stacktrace/tools/tools.h" + +// clang-format off + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +bool llvm_symbolizer::build_argv() { + push_arg("/usr/bin/env"); + push_arg(tool_prog_); + push_arg("--demangle"); + push_arg("--no-inlines"); + push_arg("--verbose"); + push_arg("--relativenames"); + push_arg("--functions=short"); + auto* it = base_.entries_begin(); + auto* end = base_.entries_end(); + while (it != end) { + auto& entry = *(entry_base*)(it++); + if (entry.__image_ && !entry.__image_->name_.empty()) { + fixed_str image_path = entry.__image_->name_; + push_arg("FILE:%s %p", image_path.data(), (void*)entry.adjusted_addr()); + } else { + push_arg("%p", (void*)entry.adjusted_addr()); + } + } + return true; +} + +void llvm_symbolizer::parse(entry_base** entry_iter, std::string_view view) const { + /* + Parsing is most reliable with `--verbose` option (short of having a JSON parser). Example: + + test1 > + Filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp + Function start filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp + Function start line: 114 + Function start address: 0x8dd0 + Line: 116 + Column: 14 + */ + + if (!view.starts_with(" ")) { // line without leading whitespace starts a new entry + ++*entry_iter; // advance to next entry + _LIBCPP_ASSERT(*entry_iter >= base_.entries_begin(), "out of range"); + _LIBCPP_ASSERT(*entry_iter < base_.entries_end(), "out of range"); + auto& entry = **entry_iter; + if (view != "??") { entry.__desc_ = view; } + + } else if (view.starts_with(" Filename:")) { + auto& entry = **entry_iter; + auto tmp = view.substr(view.find_first_of(":") + 2); // skip ": " + if (tmp != "??") { entry.__file_ = tmp; } + + } else if (view.starts_with(" Line:")) { + auto& entry = **entry_iter; + auto tmp = view; + tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " + if (tmp != "??" && tmp != "0") { entry.__line_ = atoi(tmp.data()); } + } +} + +template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& arena) { + llvm_symbolizer tool{base, arena}; + if (!tool.build_argv()) { return false; } + spawner spawner{tool, base}; + if (spawner.errno_) { return false; } + + fixed_str line; // our read buffer + auto* entry_iter = base.entries_begin() - 1; // "before first" entry + while (spawner.stream_.good()) { // loop until we get EOF from tool stdout + std::getline(spawner.stream_, line); // consume a line from stdout + auto view = tool_base::rstrip(line); // remove trailing (but not leading) whitespace + if (tool_base::rstrip(view).empty()) { continue; } // skip if line had nothing, or _only_ whitespace + tool.parse(&entry_iter, view); // send to parser (who might update entry_iter) + } + + return true; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp deleted file mode 100644 index d8b455112001c..0000000000000 --- a/libcxx/src/stacktrace/tools/tools.cpp +++ /dev/null @@ -1,419 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include <__config> -#include <__config_site> - -#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME - -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include - -# include <__stacktrace/base.h> -# include <__stacktrace/basic.h> -# include <__stacktrace/entry.h> - -# include "stacktrace/tools/tools.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -namespace { - -_LIBCPP_HIDE_FROM_ABI base::str hex_string(base& base, uintptr_t __addr) { - char __ret[19]; // "0x" + 16 digits + NUL - auto __size = snprintf(__ret, sizeof(__ret), "0x%016llx", (unsigned long long)__addr); - return base.make_str(__ret, size_t(__size)); -} - -_LIBCPP_HIDE_FROM_ABI base::str u64_string(base& base, uintptr_t __val) { - char __ret[21]; // 20 digits max + NUL - auto __size = snprintf(__ret, sizeof(__ret), "%zu", __val); - return base.make_str(__ret, size_t(__size)); -} - -# define STRINGIFY0(x) #x -# define STRINGIFY(x) STRINGIFY0(x) - -bool try_tools(base& base, function cb) { - char const* prog_name; - - if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"))) { - if (cb(llvm_symbolizer{base, prog_name})) { - return true; - } - } else { -# if defined(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH) - if (cb(llvm_symbolizer{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH)})) { - return true; - } -# else - if (cb(llvm_symbolizer{base})) { - return true; - } -# endif - } - - if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"))) { - if (cb(addr2line{base, prog_name})) { - return true; - } - } else { -# if defined(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH) - if (cb(addr2line{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH)})) { - return true; - } -# else - if (cb(addr2line{base})) { - return true; - } -# endif - } - - if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"))) { - if (cb(atos{base, prog_name})) { - return true; - } - } else { -# if defined(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH) - if (cb(atos{base, STRINGIFY(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH)})) { - return true; - } -# else - if (cb(atos{base})) { - return true; - } -# endif - } - - return false; // nothing succeeded -} - -} // namespace - -bool file_actions::initFileActions() { - if (!fa_initialized_) { - if (posix_spawn_file_actions_init(&fa_)) { - return false; - } - fa_initialized_ = true; - } - return true; -} - -file_actions::~file_actions() { posix_spawn_file_actions_destroy(&fa_); } - -bool file_actions::addClose(int fd) { return initFileActions() && (posix_spawn_file_actions_addclose(&fa_, fd) == 0); } - -bool file_actions::addDup2(int fd, int std_fd) { - return initFileActions() && (posix_spawn_file_actions_adddup2(&fa_, fd, std_fd) == 0); -} - -fd file_actions::redirectOutFD() { - int fds[2]; - if (::pipe(fds)) { - return {}; // return invalid FD - } - addClose(fds[0]); - addDup2(fds[1], 1); - return {fds[0]}; -} - -pspawn::~pspawn() { - if (pid_) { - kill(pid_, SIGTERM); - wait(); - } -} - -bool pspawn::spawn(base::list const& argStrings) { - base::vec argv = tool_.base_.make_vec(); - argv.reserve(argStrings.size() + 1); - for (auto const& str : argStrings) { - argv.push_back(str.data()); - } - argv.push_back(nullptr); - return posix_spawnp(&pid_, argv[0], &fa_.fa_, nullptr, const_cast(argv.data()), nullptr) == 0; -} - -int pspawn::wait() { - int status; - waitpid(pid_, &status, 0); - return status; -} - -bool spawner::resolve_lines() { - return try_tools(base_, [&](tool const& prog) { - char buf[512]; - pspawn_tool proc(prog, base_, buf, sizeof(buf)); - return proc.run(); - }); -} - -base::list llvm_symbolizer::buildArgs(base& base) const { - auto ret = base_.make_list(); - ret.push_back(base_.make_str(progName_)); - ret.push_back(base_.make_str("--demangle")); - ret.push_back(base_.make_str("--no-inlines")); - ret.push_back(base_.make_str("--verbose")); - ret.push_back(base_.make_str("--relativenames")); - ret.push_back(base_.make_str("--functions=short")); - for (auto& st_entry : base.__entries_) { - auto& entry = (entry_base&)st_entry; - auto addr_string = hex_string(base_, entry.__addr_unslid_); - if (entry.__file_) { - auto arg = base_.make_str(); - arg.reserve(entry.__file_->size() + 40); - arg += "FILE:"; - arg += *entry.__file_; - arg += " "; - arg += addr_string; - ret.push_back(arg); - } else { - ret.push_back(addr_string); - } - } - return ret; -} - -void llvm_symbolizer::parseOutput(base& base, __stacktrace::entry_base& entry, std::istream& output) const { - // clang-format off -/* -With "--verbose", parsing is a little easier, or at least, more reliable; -probably the best solution (until we have a JSON parser). -Example output, verbatim, between the '---' lines: ---- -test1 > - Filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp - Function start filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp - Function start line: 114 - Function start address: 0x8dd0 - Line: 116 - Column: 14 - ---- -Note that this includes an extra empty line as a terminator. -*/ - // clang-format on - - auto line = base.make_str(); - line.reserve(512); - std::string_view tmp; - while (true) { - std::getline(output, line); - while (!line.empty() && isspace(line.back())) { - line.pop_back(); - } - if (line.empty()) { - return; // done - } - if (!line.starts_with(" ")) { - // The symbol has no leading whitespace, while the other - // lines with "fields" like line, column, filename, etc. - // start with two spaces. - if (line != "??") { - entry.__desc_ = line; - } - } else if (line.starts_with(" Filename:")) { - tmp = line; - tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " - if (tmp != "??") { - entry.__file_ = base.make_str(tmp); - } - } else if (line.starts_with(" Line:")) { - tmp = line; - tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " - if (tmp != "??" && tmp != "0") { - uint32_t lineno = 0; - auto pos = 0; - while (isdigit(tmp[pos])) { - lineno = lineno * 10 + (tmp[pos++] - '0'); - } - entry.__line_ = lineno; - } - } - } -} - -base::list addr2line::buildArgs(base& base) const { - auto ret = base.make_list(); - if (base.__main_prog_path_.empty()) { - // Should not have reached here but be graceful anyway - ret.push_back(base.make_str("/bin/false")); - return ret; - } - - ret.push_back(base.make_str(progName_)); - ret.push_back(base.make_str("--functions")); - ret.push_back(base.make_str("--demangle")); - ret.push_back(base.make_str("--basenames")); - ret.push_back(base.make_str("--pretty-print")); // This "human-readable form" is easier to parse - ret.push_back(base.make_str("-e")); - ret.push_back(base.__main_prog_path_); - for (auto& entry : base.__entries_) { - ret.push_back(hex_string(base, ((entry_base&)entry).__addr_unslid_)); - } - return ret; -} - -void addr2line::parseOutput(base& base, entry_base& entry, std::istream& output) const { - // clang-format off -/* -Example: --- -llvm-addr2line -e foo --functions --demangle --basenames --pretty-print --no-inlines 0x11a0 0x1120 0x3d58 0x1284 - -Output: (1 line per input address) --- -main at foo.cc:15 -register_tm_clones at crtstuff.c:0 -GCC_except_table2 at foo.cc:0 -test::Foo::Foo(int) at foo.cc:11 -*/ - // clang-format on - - auto line = base.make_str(); - line.reserve(512); - std::getline(output, line); - while (!line.empty() && isspace(line.back())) { - line.pop_back(); - } - if (line.empty()) { - return; - } - // Split at the sequence " at ". Barring weird symbols - // having " at " in them, this should work. - auto sepIndex = line.find(" at "); - if (sepIndex == std::string::npos) { - return; - } - if (sepIndex > 0) { - entry.__desc_ = base.make_str(string_view(line).substr(0, sepIndex)); - } - auto fileBegin = sepIndex + 4; - if (fileBegin >= line.size()) { - return; - } - auto fileline = base.make_str(string_view(line).substr(fileBegin)); - auto colon = fileline.find_last_of(":"); - if (colon > 0 && !fileline.starts_with("?")) { - entry.__file_ = base.make_str(string_view(fileline).substr(0, colon)); - } - - if (colon == std::string::npos) { - return; - } - uint32_t lineno = 0; - auto pos = colon; - while (isdigit(fileline[++pos])) { - lineno = lineno * 10 + (fileline[pos] - '0'); - } - entry.__line_ = lineno; -} - -base::list atos::buildArgs(base& base) const { - auto ret = base.make_list(); - ret.push_back(base.make_str(progName_)); - ret.push_back(base.make_str("-p")); - ret.push_back(u64_string(base, getpid())); - // TODO(stackcx23): Allow options in env, e.g. LIBCPP_STACKTRACE_OPTIONS=FullPath - // ret.push_back("--fullPath"); - for (auto& entry : base.__entries_) { - ret.push_back(hex_string(base, ((entry_base&)entry).__addr_actual_)); - } - return ret; -} - -void atos::parseOutput(base& base, entry_base& entry, std::istream& output) const { - // Simple example: - // - // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) - // - // Assuming this is always atos's format (except when it returns empty lines) - // we can split the string like so: - // - // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) - // ^^^^-----^^^^^^^^---^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^^- - // sym module filename line - // - // Note that very strange filenames or module names can confuse this. - // We'll do the best we can for a decent result, while definitely ensuring safety - // (i.e. careful with our bound-checking). - // - // Another more interesting example (with an added newline for legibility): - // - // std::__1::basic_ios>::fill[abi:ne190107]() const (in testprog) - // (/opt/homebrew/Cellar/llvm/19.1.7_1/bin/../include/c++/v1/ios:0 - // - // If this more or less fits our expected format we'll take these data, - // even if the line number is 0. - - auto line = base.make_str(); - line.reserve(512); - std::getline(output, line); - while (!line.empty() && isspace(line.back())) { - line.pop_back(); - } - if (line.empty()) { - return; - } - auto buf = line.data(); - auto size = line.size(); - - auto* end = buf + size; - auto* symEnd = strstr(buf, " (in "); - if (!symEnd) { - return; - } - auto* modBegin = symEnd + 5; - auto* modEnd = strstr(modBegin, ") ("); - if (!modEnd) { - return; - } - auto* fileBegin = modEnd + 3; // filename starts just after that - if (fileBegin >= end) { - return; - } - auto const* lastColon = fileBegin; // we'll search for last colon after filename - char const* nextColon; - while ((nextColon = strstr(lastColon + 1, ":"))) { // skip colons in filename (e.g. in "C:\foo.cpp") - lastColon = nextColon; - } - - std::string_view sym{buf, size_t(symEnd - buf)}; - // In case a previous step could not obtain the symbol name, - // we have the name provided by atos; only use that if we have no symbol - // (no need to copy more strings otherwise). - if (entry.__desc_->empty() && !sym.empty()) { - entry.__desc_ = base.make_str(sym); - } - - std::string_view file{fileBegin, size_t(lastColon - fileBegin)}; - if (file != "?" && file != "??" && !file.empty()) { - entry.__file_ = base.make_str(file); - } - - unsigned lineno = 0; - for (auto* digit = lastColon + 1; digit < end && isdigit(*digit); ++digit) { - lineno = (lineno * 10) + unsigned(*digit - '0'); - } - entry.__line_ = lineno; -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h index 8fe6d2938a4ca..46e5747990ab1 100644 --- a/libcxx/src/stacktrace/tools/tools.h +++ b/libcxx/src/stacktrace/tools/tools.h @@ -6,142 +6,376 @@ // //===----------------------------------------------------------------------===// -#ifndef _LIBCPP_STACKTRACE_TOOLS_H -#define _LIBCPP_STACKTRACE_TOOLS_H +#ifndef _LIBCPP_STACKTRACE_TOOLS_TOOL_DEFN +#define _LIBCPP_STACKTRACE_TOOLS_TOOL_DEFN #include <__config> -#include <__config_site> -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include <__stacktrace/base.h> -#include <__stacktrace/basic.h> -#include <__stacktrace/entry.h> - -#include "stacktrace/utils/fd.h" +#include + +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "stacktrace/fd.h" _LIBCPP_BEGIN_NAMESPACE_STD namespace __stacktrace { -struct tool { +struct tool_base { + constexpr static size_t k_max_argv_ = base::__absolute_max_depth + 10; base& base_; - char const* progName_; + arena& arena_; + char const* tool_prog_; + str argvs_[k_max_argv_]{}; // will hold our generated arg strings + char* argv_[k_max_argv_]{nullptr}; // refers to argvs_ strings as char** (includes null terminator) + size_t argc_{0}; // number of args. Note: argv_[argc_] is nullptr + + tool_base(base& base, arena& arena, char const* tool_prog) : base_(base), arena_(arena), tool_prog_(tool_prog) { + argv_[0] = nullptr; + } - tool(base& base, char const* progName) : base_(base), progName_(progName) {} - virtual ~tool() = default; + void push_arg(std::string_view sv) { + _LIBCPP_ASSERT(argc_ < k_max_argv_ - 1, "too many args"); + argvs_[argc_] = sv; // Have to copy the string_view into a new string + argv_[argc_] = argvs_[argc_].data(); // then we have a char pointer into that string + argv_[++argc_] = nullptr; // ensure there's always trailing null after last arg + } - /** Construct complete `argv` for the spawned process. - Includes the program name at argv[0], followed by flags */ - virtual base::list buildArgs(base& trace) const = 0; + void push_arg(str __str) { push_arg(std::string_view{__str.data(), __str.size()}); } - /** Parse line(s) output by the tool, and modify `entry`. */ - virtual void parseOutput(base& trace, entry_base& entry, std::istream& output) const = 0; -}; + template + void push_arg(char const* format, _Args&&... args) { + push_arg(str::makef(format, std::forward<_Args>(args)...)); + } -struct llvm_symbolizer : tool { - virtual ~llvm_symbolizer() = default; - explicit llvm_symbolizer(base& base) : llvm_symbolizer(base, "llvm_symbolizer") {} - llvm_symbolizer(base& base, char const* progName) : tool{base, progName} {} - base::list buildArgs(base& trace) const override; - void parseOutput(base& trace, entry_base& entry, std::istream& output) const override; -}; + // Helper functions for dealing with string views. + // All these take a string_view (by copy) and return a modified view. Inputs are validated + // and if invalid, the function returns an empty view (instead of throwing). -struct addr2line : tool { - virtual ~addr2line() = default; - explicit addr2line(base& base) : addr2line(base, "addr2line") {} - addr2line(base& base, char const* progName) : tool{base, progName} {} - base::list buildArgs(base& trace) const override; - void parseOutput(base& trace, entry_base& entry, std::istream& stream) const override; -}; + /** Drop `n` chars from the start of the string; empty string if `n` exceeds string size */ + static string_view ldrop(string_view sv, size_t n = 1) { + sv.remove_prefix(std::min(sv.size(), n)); + return sv; + } + + /** Drop `n` chars from the end of the string; empty string if `n` exceeds string size */ + static string_view rdrop(string_view sv, size_t n = 1) { + sv.remove_suffix(std::min(sv.size(), n)); + return sv; + } + + /** Strip whitespace from the start of the string */ + static string_view lstrip(string_view sv) { + while (!sv.empty() && isspace(sv.front())) { + sv = ldrop(sv); + }; + return sv; + } + + /** Strip whitespace from the back of the string */ + static string_view rstrip(string_view sv) { + while (!sv.empty() && isspace(sv.back())) { + sv = rdrop(sv); + }; + return sv; + } + + /** Strip whitespace from the start and end of the string */ + static string_view strip(string_view sv) { return lstrip(rstrip(sv)); } + + /** Drop prefix if exists; if not found, and if required, return empty (failure); else original arg */ + static string_view drop_prefix(string_view sv, string_view pre, bool required = true) { + if (sv.starts_with(pre)) { + return ldrop(sv, pre.size()); + } + return required ? string_view{} : sv; + } -struct atos : tool { - virtual ~atos() = default; - explicit atos(base& base) : atos(base, "atos") {} - atos(base& base, char const* progName) : tool{base, progName} {} - base::list buildArgs(base& trace) const override; - void parseOutput(base& trace, entry_base& entry, std::istream& output) const override; + /** Drop suffix if exists; if not found, and if required, return empty (failure); else original arg */ + static string_view drop_suffix(string_view sv, string_view suf, bool required = true) { + if (sv.ends_with(suf)) { + return rdrop(sv, suf.size()); + } + return required ? string_view{} : sv; + } }; +/** Set up a `posix_spawn_file_actions_t` for use with a symbolizer with redirected stdout. */ struct file_actions { - posix_spawn_file_actions_t fa_; - bool fa_initialized_{false}; + optional fa_{}; + fd stdout_read_; // read end of subprocess's stdout, IFF redir_stdout used + fd stdout_write_; // write end of subprocess's stdout, IFF redir_stdout used + errno_t errno_{}; // set to nonzero if any of these C calls failed - ~file_actions(); + bool failed() const { return errno_; } - bool initFileActions(); - bool addClose(int fd); - bool addDup2(int fd, int std_fd); + posix_spawn_file_actions_t* fa() { + if (!fa_) { + fa_.emplace(); + if (posix_spawn_file_actions_init(&fa_.value())) { + errno_ = errno; + _LIBCPP_ASSERT(false, "file_actions_init failed"); + fa_.reset(); + } + } + return fa_ ? &fa_.value() : nullptr; + } - fd redirectOutFD(); - bool redirectInNull() { return addDup2(fd::null_fd(), 0); } - bool redirectOutNull() { return addDup2(fd::null_fd(), 1); } - bool redirectErrNull() { return addDup2(fd::null_fd(), 2); } -}; + ~file_actions() { + if (fa_) { + // Do best-effort teardown, ignore errors + (void)posix_spawn_file_actions_destroy(&fa_.value()); + fa_.reset(); + } + } -struct pspawn { - tool const& tool_; - pid_t pid_{0}; - file_actions fa_{}; + file_actions() = default; + file_actions(file_actions const&) = delete; + file_actions& operator=(file_actions const&) = delete; - ~pspawn(); + file_actions(file_actions&& rhs) { + fa_ = std::move(rhs.fa_); + rhs.fa_.reset(); + stdout_read_ = std::move(rhs.stdout_read_); + stdout_write_ = std::move(rhs.stdout_write_); + errno_ = rhs.errno_; + } - bool spawn(base::list const& argStrings); - int wait(); -}; + file_actions& operator=(file_actions&& rhs) { + return (std::addressof(rhs) == this) ? *this : *(new (this) file_actions(std::move(rhs))); + } -struct pspawn_tool : pspawn { - base& base_; - fd fd_; - fd_streambuf buf_; - fd_istream stream_; - - pspawn_tool(tool const& a2l, base& trace, char* buf, size_t size) - : pspawn{a2l}, base_(trace), fd_(fa_.redirectOutFD()), buf_(fd_, buf, size), stream_(buf_) { - fa_.redirectErrNull(); - fa_.redirectInNull(); - } - - bool run() { - // Cannot run "addr2line" or similar without addresses, since we provide them in argv, - // and if there are none passed in argv, the tool will try to read from stdin and hang. - // Nothing to do, so return true for "success". - if (base_.__entries_.empty()) { - return true; + // These have no effect if this is already in `failed` state. + + file_actions& no_stdin() { + if (!failed() && posix_spawn_file_actions_adddup2(fa(), fd::null_fd(), STDIN_FILENO)) { + _LIBCPP_ASSERT(false, "no_stdin: adddup2 failed"); + errno_ = errno; + } + return *this; + } + + file_actions& no_stdout() { + if (!failed() && posix_spawn_file_actions_adddup2(fa(), fd::null_fd(), STDOUT_FILENO)) { + _LIBCPP_ASSERT(false, "no_stdout: adddup2 failed"); + errno_ = errno; + } + return *this; + } + + file_actions& no_stderr() { + if (!failed() && posix_spawn_file_actions_adddup2(fa(), fd::null_fd(), STDERR_FILENO)) { + _LIBCPP_ASSERT(false, "no_stderr: adddup2 failed"); + errno_ = errno; } + return *this; + } - if (!fd_) { - return false; + file_actions& redir_stdout() { + if (!failed() && fd::pipe_pair(stdout_read_, stdout_write_)) { + _LIBCPP_ASSERT(false, "redir_stdout: pipe failed"); + errno_ = errno; + } else if (!failed() && posix_spawn_file_actions_adddup2(fa(), stdout_write_, STDOUT_FILENO)) { + _LIBCPP_ASSERT(false, "redir_stdout: adddup2 failed"); + errno_ = errno; + } else if (!failed() && posix_spawn_file_actions_addclose(fa(), stdout_read_)) { + _LIBCPP_ASSERT(false, "redir_stdout: pipe failed"); + errno_ = errno; } + return *this; + } +}; + +/** While in-scope, this enables SIGCHLD default handling (allowing `waitpid` to work). +Restores the old signal action on destruction. - auto argStrings = tool_.buildArgs(base_); - if (!spawn(argStrings)) { - return false; +XXX Thread safety issue almost certainly exists here +*/ +struct sigchld_enable { + struct sigaction old_; + + ~sigchld_enable() { + int res = sigaction(SIGCHLD, &old_, nullptr); // restore old behavior + _LIBCPP_ASSERT(!res, "~sigchld_enable: sigaction failed"); + } + + sigchld_enable() { + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = SIG_DFL; + int res = sigaction(SIGCHLD, &act, &old_); + _LIBCPP_ASSERT(!res, "sigchld_enable: sigaction failed"); + } +}; + +struct pid_waiter { + pid_t pid_{}; + int status_{}; // value is valid iff wait() completed + errno_t errno_{}; // set to nonzero if any of these C calls failed + bool done_{}; + + operator pid_t() const { return pid_; } + bool running() const { return pid_ && !kill(pid_, 0); } + bool failed() const { return errno_; } + + [[nodiscard]] int wait() { + while (!done_) { // Until successful waitpid, or a hard error: + int result; + if (waitpid(pid_, &result, 0) == pid_) { // attempt a blocking wait, updates status_ + if (WIFEXITED(result)) { // process exited? (not signaled) + status_ = WEXITSTATUS(result); // get exit code + done_ = true; // + } else if (WIFSIGNALED(result)) { // if signaled: + status_ = -WTERMSIG(result); // use negative to indicate signal + done_ = true; // + } + } else if (errno != EINTR) { // for errors other than interrupted syscall (which we retry), + errno_ = errno; // record the error, putting this in `failed` state + done_ = true; // don't bother attempting another wait + status_ = -1; // nonzero bogus value + } } + return status_; + } - auto end = base_.__entries_.end(); - auto it = base_.__entries_.begin(); - while (it != end) { - auto& entry = (entry_base&)(*it++); - tool_.parseOutput(base_, entry, stream_); + ~pid_waiter() { + if (pid_ && !done_) { + // this represents a valid but non-waited pid + if (running()) { + kill(pid_, SIGKILL); + } + (void)/* ignore status */ wait(); } - return true; } }; struct spawner { + tool_base& tool_; base& base_; - bool resolve_lines(); + file_actions fa_{}; // redirects stdout for us + char cbuf_[PATH_MAX + 512]; // buffer space for the streambuf: + fd::streambuf sbuf_; // streambuf interface for the istream: + fd::istream stream_; // istream interface from which we can `getline` + sigchld_enable chld_enable_; // temporarily enables SIGCHLD so `waitpid` works + pid_waiter pid_{0}; // set during successful `spawn`, can `waitpid` automatically + errno_t errno_{}; // set to nonzero if any of these C calls failed + + bool failed() const { return errno_; } + + spawner(tool_base& tool, base& base) + : tool_{tool}, + base_(base), + fa_(std::move(file_actions().no_stdin().no_stderr().redir_stdout())), + sbuf_(fa_.stdout_read_, cbuf_, sizeof(cbuf_)), + stream_(sbuf_) { + // Inherit any errors from during fileactions setup + errno_ = fa_.errno_; + if (!failed() && posix_spawnp(&pid_.pid_, tool_.argv_[0], fa_.fa(), nullptr, tool_.argv_, nullptr)) { + _LIBCPP_ASSERT(false, "spawner: posix_spawnp failed"); + errno_ = errno; + } else if (!failed() && close(fa_.stdout_write_)) { + _LIBCPP_ASSERT(false, "spawner: close failed"); + errno_ = errno; + } + } +}; + +template +struct _LIBCPP_EXPORTED_FROM_ABI __executable_name { + static char const* get() { + auto* env_var = T::__override_prog_env; + if (env_var) { + auto* env_val = getenv(env_var); + if (env_val) { + return env_val; + } + } + return T::__default_prog_name; + } +}; + +/** Run `/usr/bin/env $TOOL --help`. Succeeds iff `env` can find the tool in path, and tool exits without error. */ +inline bool _LIBCPP_EXPORTED_FROM_ABI __executable_works(char const* prog_name) { + char const* argv[4] = {"/usr/bin/env", prog_name, "--help", nullptr}; + pid_waiter pid; + auto fa = std::move(file_actions().no_stdin().no_stdout().no_stderr()); + return posix_spawn(&pid.pid_, argv[0], fa.fa(), nullptr, const_cast(argv), nullptr) + ? false // spawn failed (don't care why), can't run prog + : !pid.wait(); // otherwise, tool should return without error +} + +/** Checks (and memoizes) whether tool's binary exists and runs */ +template +inline bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable() { + static bool ret = __executable_works(__executable_name::get()); + return ret; +} + +template +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct llvm_symbolizer; +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +template <> +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct addr2line; +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +template <> +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct atos; +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +template <> +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct llvm_symbolizer : tool_base { + constexpr static char const* __default_prog_name = "llvm_symbolizer"; + constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"; + + llvm_symbolizer(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} + bool build_argv(); + void parse(entry_base** entry_iter, std::string_view view) const; +}; + +struct addr2line : tool_base { + constexpr static char const* __default_prog_name = "addr2line"; + constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"; + + addr2line(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} + bool build_argv(); + void parse_sym(entry_base& entry, std::string_view view) const; + void parse_loc(entry_base& entry, std::string_view view) const; +}; + +struct atos : tool_base { + constexpr static char const* __default_prog_name = "atos"; + constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"; + + atos(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} + bool build_argv(); + void parse(entry_base& entry, std::string_view view) const; }; } // namespace __stacktrace _LIBCPP_END_NAMESPACE_STD -#endif // _LIBCPP_STACKTRACE_TOOLS_H +#endif // __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +#endif // _LIBCPP_STACKTRACE_TOOLS_TOOL_DEFN diff --git a/libcxx/src/stacktrace/unwind/impl.cpp b/libcxx/src/stacktrace/unwind/impl.cpp deleted file mode 100644 index 332712348e4b3..0000000000000 --- a/libcxx/src/stacktrace/unwind/impl.cpp +++ /dev/null @@ -1,62 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include <__config> -#include <__config_site> - -#if __has_include() - -# include - -# include "stacktrace/unwind/impl.h" -# include <__stacktrace/basic.h> -# include <__stacktrace/entry.h> - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct unwind_backtrace { - base& base_; - size_t skip_; - size_t maxDepth_; - - _Unwind_Reason_Code callback(_Unwind_Context* ucx) { - if (skip_) { - --skip_; - return _Unwind_Reason_Code::_URC_NO_REASON; - } - if (!maxDepth_) { - return _Unwind_Reason_Code::_URC_NORMAL_STOP; - } - --maxDepth_; - int ipBefore; - auto ip = _Unwind_GetIPInfo(ucx, &ipBefore); - if (!ip) { - return _Unwind_Reason_Code::_URC_NORMAL_STOP; - } - auto& entry = base_.__entries_.emplace_back(); - auto& eb = (entry_base&)entry; - eb.__addr_actual_ = (ipBefore ? ip : ip - 1); - eb.__addr_unslid_ = eb.__addr_actual_; // in case we can't un-slide - return _Unwind_Reason_Code::_URC_NO_REASON; - } - - static _Unwind_Reason_Code callback(_Unwind_Context* cx, void* self) { - return ((unwind_backtrace*)self)->callback(cx); - } -}; - -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void unwind::collect(size_t skip, size_t max_depth) { - unwind_backtrace bt{base_, skip + 1, max_depth}; // skip this call as well - _Unwind_Backtrace(unwind_backtrace::callback, &bt); -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif diff --git a/libcxx/src/stacktrace/unwind/impl.h b/libcxx/src/stacktrace/unwind/impl.h deleted file mode 100644 index 1eab787dff745..0000000000000 --- a/libcxx/src/stacktrace/unwind/impl.h +++ /dev/null @@ -1,32 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_UNWIND_H -#define _LIBCPP_STACKTRACE_UNWIND_H - -#include <__config> -#include <__config_site> -#include -#include - -#include <__stacktrace/base.h> -#include <__stacktrace/basic.h> -#include <__stacktrace/entry.h> - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct unwind { - base& base_; - void collect(size_t skip, size_t max_depth); -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_UNWIND_H diff --git a/libcxx/src/stacktrace/unwinding.h b/libcxx/src/stacktrace/unwinding.h new file mode 100644 index 0000000000000..087b04e7b742d --- /dev/null +++ b/libcxx/src/stacktrace/unwinding.h @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef __LIBCPP_STACKTRACE_UNWIND_ADDRS_H +#define __LIBCPP_STACKTRACE_UNWIND_ADDRS_H + +#include <__stacktrace/basic_stacktrace.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { +void unwind_addrs(std::__stacktrace::base& base, size_t skip, size_t depth); +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#ifndef _WIN32 + +/* +Implements `unwind_addrs` using an unwind library, generally `libunwind`. +This will work with the interface provided in either `libunwind.h` or `Unwind.h`; +they provide different ways of using the same library code under the hood. + +On Windows this file will provide no definitions, since a separate implementation +exists for that OS. +*/ + +# if __has_include() + +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE inline void unwind_addrs(base& base, size_t skip, size_t depth) { + if (!depth) { + return; + } + unw_context_t cx; + unw_getcontext(&cx); + unw_cursor_t cur; + unw_init_local(&cur, &cx); + while (unw_step(&cur) > 0) { + if (skip && skip--) { + continue; + } + if (!depth--) { + break; + } + auto& entry = base.__emplace_entry_(); + unw_get_reg(&cur, UNW_REG_IP, &entry.__addr_); + if (!unw_is_signal_frame(&cur)) { + --entry.__addr_; + } + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +# elif __has_include() + +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct unwind_backtrace { + base& base_; + size_t skip_; + size_t maxDepth_; + + _Unwind_Reason_Code callback(_Unwind_Context* ucx) { + if (skip_) { + --skip_; + return _Unwind_Reason_Code::_URC_NO_REASON; + } + if (!maxDepth_) { + return _Unwind_Reason_Code::_URC_NORMAL_STOP; + } + --maxDepth_; + int ipBefore; + auto ip = _Unwind_GetIPInfo(ucx, &ipBefore); + if (!ip) { + return _Unwind_Reason_Code::_URC_NORMAL_STOP; + } + auto& entry = base_.__emplace_entry_(); + auto& eb = (entry_base&)entry; + eb.__addr_ = (ipBefore ? ip : ip - 1); + return _Unwind_Reason_Code::_URC_NO_REASON; + } + + static _Unwind_Reason_Code callback(_Unwind_Context* cx, void* self) { + return ((unwind_backtrace*)self)->callback(cx); + } +}; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE inline void unwind_addrs(base& base, size_t skip, size_t depth) { + if (!depth) { + return; + } + unwind_backtrace bt{base, skip + 1, depth}; // skip this call as well + _Unwind_Backtrace(unwind_backtrace::callback, &bt); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +# else +# error need or +# endif + +#endif // _WIN32 + +#endif // __LIBCPP_STACKTRACE_UNWIND_ADDRS_H diff --git a/libcxx/src/stacktrace/utils/fd.h b/libcxx/src/stacktrace/utils/fd.h deleted file mode 100644 index a2add29868cf6..0000000000000 --- a/libcxx/src/stacktrace/utils/fd.h +++ /dev/null @@ -1,127 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_UTILS_FD -#define _LIBCPP_STACKTRACE_UTILS_FD - -#include <__config> -#include -#include -#include -#include -#include -#include -#include -#include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -/** Encapsulates a plain old file descriptor `int`. Avoids copies in order to -force some component to "own" this, although it's freely convertible back to -integer form. Default-constructed, closed, and moved-out-of instances will have -the invalid fd `-1`. */ -struct _LIBCPP_HIDE_FROM_ABI fd { - int fd_{-1}; - - fd() : fd(-1) {} - fd(int fdint) : fd_(fdint) {} - - fd(fd const&) = delete; - fd& operator=(fd const&) = delete; - - fd(fd&& rhs) { - if (&rhs != this) { - std::exchange(fd_, rhs.fd_); - } - } - - fd& operator=(fd&& rhs) { return *new (this) fd(std::move(rhs)); } - - ~fd() { close(); } - - /** Returns true IFF fd is above zero */ - bool valid() const { return fd_ > 0; } - - /* implicit */ operator int() const { return fd_; } - - void close() { - int fd_old = -1; - std::exchange(fd_old, fd_); - if (fd_old != -1) { - ::close(fd_old); - } - } - - static fd& null_fd() { - static fd ret = {::open("/dev/null", O_RDWR)}; - return ret; - } - - static fd open(std::string_view path) { - fd ret = {::open(path.data(), O_RDONLY)}; - return ret; - } -}; - -/** Wraps a readable fd using the `streambuf` interface. I/O errors arising -from reading the provided fd will result in EOF. */ -struct _LIBCPP_HIDE_FROM_ABI fd_streambuf final : std::streambuf { - fd& fd_; - char* buf_; - size_t size_; - - _LIBCPP_HIDE_FROM_ABI fd_streambuf(fd& fd, char* buf, size_t size) : fd_(fd), buf_(buf), size_(size) {} - _LIBCPP_HIDE_FROM_ABI virtual ~fd_streambuf() = default; - - _LIBCPP_HIDE_FROM_ABI int underflow() override { - int bytesRead = ::read(fd_, buf_, size_); - if (bytesRead <= 0) { - // error or EOF: return eof to stop - return traits_type::eof(); - } - setg(buf_, buf_, buf_ + bytesRead); - return int(*buf_); - } -}; - -/** Wraps an `FDInStreamBuffer` in an `istream` */ -struct fd_istream final : std::istream { - fd_streambuf& buf_; - _LIBCPP_HIDE_FROM_ABI virtual ~fd_istream() = default; - _LIBCPP_HIDE_FROM_ABI explicit fd_istream(fd_streambuf& buf) : std::istream(nullptr), buf_(buf) { rdbuf(&buf_); } -}; - -struct fd_mmap final { - fd fd_{}; - size_t size_{0}; - std::byte const* addr_{nullptr}; - - _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(std::string_view path) : fd_mmap(fd::open(path)) {} - - _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(fd&& fd) : fd_(std::move(fd)) { - if (fd_) { - if ((size_ = ::lseek(fd_, 0, SEEK_END))) { - addr_ = (std::byte const*)::mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd_, 0); - } - } - } - - _LIBCPP_HIDE_FROM_ABI operator bool() const { return addr_; } - - _LIBCPP_HIDE_FROM_ABI ~fd_mmap() { - if (addr_) { - ::munmap(const_cast((void const*)addr_), size_); - } - } -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_UTILS_FD diff --git a/libcxx/src/stacktrace/utils/image.h b/libcxx/src/stacktrace/utils/image.h deleted file mode 100644 index 21119d7a66ec9..0000000000000 --- a/libcxx/src/stacktrace/utils/image.h +++ /dev/null @@ -1,33 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_UTILS_IMAGE -#define _LIBCPP_STACKTRACE_UTILS_IMAGE - -#include <__config> -#include <__stacktrace/base.h> - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct image { - constexpr static size_t kMaxImages = 256; - - uintptr_t loaded_at_{}; - intptr_t slide_{}; - std::string_view name_{}; - bool is_main_prog_{}; - - bool operator<(image const& rhs) const { return loaded_at_ < rhs.loaded_at_; } - operator bool() const { return !name_.empty(); } -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_STACKTRACE_UTILS_IMAGE diff --git a/libcxx/src/stacktrace/windows/dll.cpp b/libcxx/src/stacktrace/windows/dll.cpp deleted file mode 100644 index 3722955a939af..0000000000000 --- a/libcxx/src/stacktrace/windows/dll.cpp +++ /dev/null @@ -1,85 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include <__config> - -#if defined(_LIBCPP_WIN32API) - -# include <__stacktrace/base.h> - -# include "stacktrace/windows/dll.h" -# include "stacktrace/windows/impl.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -namespace { - -// Initialized once, in first `win_impl` construction. -// Protected by mutex within the `win_impl` constructor. -HANDLE proc; -HMODULE exe; -IMAGE_NT_HEADERS* ntHeaders; -bool globalInitialized{false}; - -// Globals used across invocations of the functions below. -// Protected by mutex within the `win_impl` constructor. -bool symsInitialized{false}; -HMODULE moduleHandles[1024]; -size_t moduleCount; // 0 IFF module enumeration failed - -} // namespace - -dll::~dll() { FreeLibrary(module_); } - -dll::dll(char const* name) : name_(name), module_(LoadLibrary(name)) {} - -dbghelp_dll::~dbghelp_dll() = default; - -dbghelp_dll& dbghelp_dll::get() { - static dbghelp_dll ret; - return ret; -} - -dbghelp_dll::dbghelp_dll() : dll("dbghelp.dll") { - // clang-format off -if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } - if (!get_func(&StackWalk64, "StackWalk64")) { return; } - if (!get_func(&SymCleanup, "SymCleanup")) { return; } - if (!get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64")) { return; } - if (!get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64")) { return; } - if (!get_func(&SymGetModuleBase64, "SymGetModuleBase64")) { return; } - if (!get_func(&SymGetOptions, "SymGetOptions")) { return; } - if (!get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64")) { return; } - if (!get_func(&SymInitialize, "SymInitialize")) { return; } - if (!get_func(&SymLoadModule64, "SymLoadModule64")) { return; } - if (!get_func(&SymSetOptions, "SymSetOptions")) { return; } - valid_ = true; - // clang-format on -} - -psapi_dll::~psapi_dll() = default; - -psapi_dll& psapi_dll::get() { - static psapi_dll ret; - return ret; -} - -psapi_dll::psapi_dll() : dll("psapi.dll") { - // clang-format off -if (!get_func(&EnumProcessModules, "EnumProcessModules")) { return; } - if (!get_func(&GetModuleInformation, "GetModuleInformation")) { return; } - if (!get_func(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } - valid_ = true; - // clang-format on -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_WIN32API diff --git a/libcxx/src/stacktrace/windows/dll.h b/libcxx/src/stacktrace/windows/dll.h deleted file mode 100644 index 7d10a7c8c66df..0000000000000 --- a/libcxx/src/stacktrace/windows/dll.h +++ /dev/null @@ -1,88 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_WIN_DLL -#define _LIBCPP_STACKTRACE_WIN_DLL - -#include <__config> -#if defined(_LIBCPP_WIN32API) - -// windows.h must be first -# include -// other windows-specific headers -# include -# define PSAPI_VERSION 1 -# include - -# include <__stacktrace/base.h> -# include -# include -# include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -# if defined(_LIBCPP_WIN32API) - -// clang-format off - -struct dll { - char const* name_; - HMODULE module_; - /** Set to true in subclass's ctor if initialized successfully. */ - bool valid_{false}; - - virtual ~dll(); - explicit dll(char const* name); - - operator bool() const { return valid_; } - - template - bool get_func(F* func, char const* name) { - *func = (F)GetProcAddress(module_, name); - return func != nullptr; - } -}; - -struct dbghelp_dll final : dll { - virtual ~dbghelp_dll(); - dbghelp_dll(); - - static dbghelp_dll& get(); - - IMAGE_NT_HEADERS* (*ImageNtHeader)(void*); - bool (*StackWalk64) (DWORD, HANDLE, HANDLE, STACKFRAME64*, void*, void*, void*, void*, void*); - bool (*SymCleanup) (HANDLE); - void* (*SymFunctionTableAccess64)(HANDLE, DWORD64); - bool (*SymGetLineFromAddr64)(HANDLE, DWORD64, DWORD*, IMAGEHLP_LINE64*); - DWORD64 (*SymGetModuleBase64) (HANDLE, DWORD64); - DWORD (*SymGetOptions) (); - bool (*SymGetSymFromAddr64)(HANDLE, DWORD64, DWORD64*, IMAGEHLP_SYMBOL64*); - bool (*SymInitialize) (HANDLE, char const*, bool); - DWORD64 (*SymLoadModule64) (HANDLE, HANDLE, char const*, char const*, void*, DWORD); - DWORD (*SymSetOptions) (DWORD); -}; - -struct psapi_dll final : dll { - virtual ~psapi_dll(); - psapi_dll(); - - static psapi_dll& get(); - - bool (*EnumProcessModules) (HANDLE, HMODULE*, DWORD, DWORD*); - bool (*GetModuleInformation) (HANDLE, HMODULE, MODULEINFO*, DWORD); - DWORD (*GetModuleBaseName) (HANDLE, HMODULE, char**, DWORD); -}; - -#endif - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_WIN32API -#endif // _LIBCPP_STACKTRACE_WIN_DLL diff --git a/libcxx/src/stacktrace/windows/impl.cpp b/libcxx/src/stacktrace/windows/impl.cpp deleted file mode 100644 index e9425271cee97..0000000000000 --- a/libcxx/src/stacktrace/windows/impl.cpp +++ /dev/null @@ -1,179 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include <__config> -#if defined(_LIBCPP_WIN32API) - -// windows.h must be first -# include -// other windows-specific headers -# include -# define PSAPI_VERSION 1 -# include - -# include "stacktrace/windows/dll.h" -# include "stacktrace/windows/impl.h" - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -win_impl::~win_impl() { - auto& dbg = dbghelp_dll::get(); - if (initialized_) { - (*dbg.SymCleanup)(proc_); - initialized_ = false; - } -} - -win_impl::win_impl(base& base) : base_(base) { - std::lock_guard guard(mutex_); - - auto& dbg = dbghelp_dll::get(); - auto& ps = psapi_dll::get(); - - if (!initialized_) { - // Cannot proceed without these DLLs: - if (!dbg) { - return; - } - if (!ps) { - return; - } - proc_ = GetCurrentProcess(); - if (!(exe_ = GetModuleHandle(nullptr))) { - return; - } - if (!(nt_headers_ = (*dbg.ImageNtHeader)(exe_))) { - return; - } - - initialized_ = true; - } - - // The final `true` means we want the runtime to enumerate all this - // process's modules' symbol tables. - initialized_ = (*dbg.SymInitialize)(proc_, nullptr, true); - DWORD symOptions = (*dbg.SymGetOptions)(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - (*dbg.SymSetOptions)(symOptions); -} - -void win_impl::ident_modules() { - if (!initialized_) { - return; - } - - auto& ps = psapi_dll::get(); - DWORD needBytes; - - auto enumMods = (*ps.EnumProcessModules)(proc_, module_handles_, sizeof(module_handles_), LPDWORD(&needBytes)); - if (enumMods) { - module_count_ = needBytes / sizeof(HMODULE); - } else { - module_count_ = 0; - } -} - -void win_impl::symbolize() { - if (!initialized_) { - return; - } - - // Very long symbols longer than this amount will be truncated. - static constexpr size_t kMaxSymName = 256; - - auto& dbg = dbghelp_dll::get(); - - for (auto& entry : base_.__entries_) { - char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; - auto* sym = (IMAGEHLP_SYMBOL64*)space; - sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - sym->MaxNameLength = kMaxSymName; - uint64_t disp{0}; - if ((*dbg.SymGetSymFromAddr64)(proc_, entry.__addr_actual_, &disp, sym)) { - // Copy chars into the destination string which uses the caller-provided allocator. - ((entry_base&)entry).__desc_ = {sym->Name}; - } - } -} - -void win_impl::resolve_lines() { - if (!initialized_) { - return; - } - - auto& dbg = dbghelp_dll::get(); - - for (auto& entry : base_.__entries_) { - DWORD disp{0}; - IMAGEHLP_LINE64 line; - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - if ((*dbg.SymGetLineFromAddr64)(proc_, entry.__addr_actual_, &disp, &line)) { - // Copy chars into the destination string which uses the caller-provided allocator. - entry.__file_ = line.FileName; - entry.__line_ = line.LineNumber; - } - } -} - -/* -Inlining is disabled from here on; -this is to ensure `collect` below doesn't get merged into its caller -and mess around with the top of the stack (making `skip` inaccurate). -*/ -# pragma auto_inline(off) - -_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { - if (!initialized_) { - return; - } - - auto& dbg = dbghelp_dll::get(); - auto thread = GetCurrentThread(); - auto machine = nt_headers_->FileHeader.Machine; - - CONTEXT ccx; - RtlCaptureContext(&ccx); - - STACKFRAME64 frame; - memset(&frame, 0, sizeof(frame)); - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrStack.Mode = AddrModeFlat; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrPC.Offset = ccx.Rip; - frame.AddrStack.Offset = ccx.Rsp; - frame.AddrFrame.Offset = ccx.Rbp; - - while (max_depth && - (*dbg.StackWalk64)( - machine, - proc_, - thread, - &frame, - &ccx, - nullptr, - dbg.SymFunctionTableAccess64, - dbg.SymGetModuleBase64, - nullptr)) { - if (skip) { - --skip; - continue; - } - --max_depth; - auto& entry = base_.__entries_.emplace_back(); - // We don't need to compute the un-slid addr; windbg only needs the actual addresses. - // Assume address is of the instruction after a call instruction, since we can't - // differentiate between a signal, SEH exception handler, or a normal function call. - entry.__addr_actual_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range - } -} - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif diff --git a/libcxx/src/stacktrace/windows/impl.h b/libcxx/src/stacktrace/windows/impl.h deleted file mode 100644 index 6dea1b98b1ad0..0000000000000 --- a/libcxx/src/stacktrace/windows/impl.h +++ /dev/null @@ -1,52 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef _LIBCPP_STACKTRACE_WIN_IMPL_H -#define _LIBCPP_STACKTRACE_WIN_IMPL_H - -#include <__config> -#if defined(_LIBCPP_WIN32API) - -# include -# include -# include - -_LIBCPP_BEGIN_NAMESPACE_STD -namespace __stacktrace { - -struct base; - -struct win_impl { - base& base_; - - static std::mutex mutex_; - static HANDLE proc_; - static HMODULE exe_; - static IMAGE_NT_HEADERS* nt_headers_; - static bool initialized_; - static HMODULE module_handles_[1024]; - static size_t module_count_; // 0 IFF module enumeration failed - - /* - The `dbghelp` APIs are not safe to call concurrently (according to their docs) - so we claim a lock in constructor. - */ - explicit win_impl(base& base); - ~win_impl(); - - void collect(size_t skip, size_t max_depth); - void ident_modules(); - void symbolize(); - void resolve_lines(); -}; - -} // namespace __stacktrace -_LIBCPP_END_NAMESPACE_STD - -#endif // _LIBCPP_WIN32API -#endif // _LIBCPP_STACKTRACE_WIN_IMPL_H diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp b/libcxx/test/libcxx/stacktrace/only_uses_allocator.pass.cpp similarity index 97% rename from libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp rename to libcxx/test/libcxx/stacktrace/only_uses_allocator.pass.cpp index 01d0b365b6bd6..e1ab24594b4b7 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/only_uses_allocator.pass.cpp @@ -7,8 +7,8 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g -O0 // UNSUPPORTED: asan, msan, tsan, hwasan, sanitizer-new-delete +// ADDITIONAL_COMPILE_FLAGS: -O0 -g #include #include @@ -18,7 +18,6 @@ * This file includes tests which ensure any allocations performed by `basic_stacktrace` * are done via the user-provided allocator. We intercept the usual ways to allocate, * counting the number of calls, through and not through the allocator. - * * (This won't work properly with sanitizers, hence the `UNSUPPORTED` above.) */ @@ -92,10 +91,11 @@ struct test_alloc { _LIBCPP_NO_TAIL_CALLS int main(int, char**) { - // Clear these counters in case anything was created/deleted prior to `main`, - // and in case taking a stacktrace involved initialization of something and is - // outside our control. (void)std::stacktrace::current(); + + // Clear these counters in case anything was created/deleted prior to `main`, + // and in case taking a stacktrace involved initialization of objects in bss + // or some other space. new_count = del_count = 0; { diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp index f40037d7e46e6..eeb92bd0724b1 100644 --- a/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp @@ -10,11 +10,10 @@ // ADDITIONAL_COMPILE_FLAGS: -O0 -g0 #include -#include #include +#include int main(int, char**) { - // Get the current trace. // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: auto trace = std::stacktrace::current(); std::cout << trace << std::endl; diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp index 2c7fa9416f31f..f9b6f90ca3919 100644 --- a/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp @@ -10,20 +10,19 @@ // ADDITIONAL_COMPILE_FLAGS: -O0 -g #include -#include #include +#include int main(int, char**) { - // Get the current trace. - // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: - auto trace = std::stacktrace::current(); + uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); std::cout << trace << std::endl; // First entry of this should be `main`. auto entry = trace.at(0); assert(entry); assert(entry.native_handle()); assert(entry.description() == "main" || entry.description() == "_main"); - // assert(entry.source_file().ends_with(".pass.cpp")); - // assert(entry.source_line() == line_number); + assert(entry.source_file().ends_with(".pass.cpp")); // this cpp file, and not t.tmp.exe + assert(entry.source_line() == line_number); return 0; } diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp index 1f6e2bd557460..701183a1a87a0 100644 --- a/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp @@ -14,16 +14,15 @@ #include int main(int, char**) { - // Get the current trace. - // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: - auto trace = std::stacktrace::current(); + uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); std::cerr << trace << '\n'; // First entry of this should be `main`. auto entry = trace.at(0); assert(entry); assert(entry.native_handle()); assert(entry.description() == "main" || entry.description() == "_main"); - // assert(entry.source_file().ends_with(".pass.cpp")); - // assert(entry.source_line() == line_number); + assert(entry.source_file().ends_with(".pass.cpp")); // this cpp file, and not t.tmp.exe + assert(entry.source_line() == line_number); return 0; } diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp index fafb0d618b908..b3e6f41c23a91 100644 --- a/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp @@ -10,11 +10,10 @@ // ADDITIONAL_COMPILE_FLAGS: -O3 -g0 #include -#include #include +#include int main(int, char**) { - // Get the current trace. // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: auto trace = std::stacktrace::current(); std::cout << trace << std::endl; diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp index 3690499ae7c25..be715570db1ec 100644 --- a/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp @@ -10,20 +10,19 @@ // ADDITIONAL_COMPILE_FLAGS: -O3 -g #include -#include #include +#include int main(int, char**) { - // Get the current trace. - // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: - auto trace = std::stacktrace::current(); + uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); std::cout << trace << std::endl; // First entry of this should be `main`. auto entry = trace.at(0); assert(entry); assert(entry.native_handle()); assert(entry.description() == "main" || entry.description() == "_main"); - // assert(entry.source_file().ends_with(".pass.cpp")); - // assert(entry.source_line() == line_number); + assert(entry.source_file().ends_with(".pass.cpp")); // this cpp file, and not t.tmp.exe + assert(entry.source_line() == line_number); return 0; } diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp index 4b32e33110d1a..32f6dfa1d4acb 100644 --- a/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp +++ b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp @@ -10,20 +10,19 @@ // ADDITIONAL_COMPILE_FLAGS: -O3 -g -gsplit-dwarf #include -#include #include +#include int main(int, char**) { - // Get the current trace. - // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: - auto trace = std::stacktrace::current(); + uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); std::cout << trace << std::endl; // First entry of this should be `main`. auto entry = trace.at(0); assert(entry); assert(entry.native_handle()); assert(entry.description() == "main" || entry.description() == "_main"); - // assert(entry.source_file().ends_with(".pass.cpp")); - // assert(entry.source_line() == line_number); + assert(entry.source_file().ends_with(".pass.cpp")); + assert(entry.source_line() == line_number); return 0; } diff --git a/libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp b/libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp new file mode 100644 index 0000000000000..8c46ffc18780a --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g -gdwarf-3 +// FIXME: also requires _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +/* +Note: requires _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME, as well as at least +one such tool installed on the local system and findable with PATH. +You can also run this locally like so: + +``` +BUILDDIR=build +ninja -C "${BUILDDIR}" cxx-test-depends + +# Build and run via lit. Use the default program names and let `env` try to find full paths. +"${BUILDDIR}/bin/llvm-lit" -sv libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp + +# To force use of a particular path for a tool (not relying on PATH), specify these env variables +and run the test program directly (`lit` won't pass these variables). Use `/bin/false` to disable a tool. +Examples: + +LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH=/opt/homebrew/Cellar/binutils/2.45/bin/addr2line \ +LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH=/usr/bin/atos \ +LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH=/opt/homebrew/Cellar/llvm/20.1.7/bin/llvm-symbolizer \ + $BUILDDIR/libcxx/test/libcxx/stacktrace/Output/use_available_progs.pass.cpp.dir/t.tmp.exe +``` +*/ + +#include <__config> +#include <__stacktrace/memory.h> +#include +#include +#include + +#include <__stacktrace/images.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct addr2line; +struct arena; +struct atos; +struct base; +struct llvm_symbolizer; + +template +struct _LIBCPP_EXPORTED_FROM_ABI __executable_name { + static char const* get(); +}; + +template +bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +extern template bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +extern template bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +extern template bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +int func0() { return 1000; } +constexpr static int _FUNC0_LINE = __LINE__ - 1; + +int func1() { return 1001; } +constexpr static int _FUNC1_LINE = __LINE__ - 1; + +int func2() { return 1002; } +constexpr static int _FUNC2_LINE = __LINE__ - 1; + +namespace { + +/** +Produce a fake stacktrace with 3 entries, having (in order of index) addresses of `func0`, `func1`, `func2`. +Only addresses are populated; no symbols / source locations are in these entries. +The addresses are the base addrs of these functions (not instructions' addresses, nor +are they decremented by 1 to get the calling instruction and not return address, as the unwinder would). +These addresses come from the main program's address space (this one, "use_available_progs.pass.cpp") +so populate their `__image_` with a pointer to the main program image, so address adjustment +(for ASLR) works. +*/ +std::stacktrace fake_stacktrace() { + static std::__stacktrace::images imgs; + static auto* main_image = imgs.main_prog_image(); + + std::stacktrace ret; + auto& base = *(std::__stacktrace::base*)(&ret); + auto& e0 = base.__emplace_entry_(); + e0.__addr_ = uintptr_t(&func0); + e0.__image_ = main_image; + auto& e1 = base.__emplace_entry_(); + e1.__addr_ = uintptr_t(&func1); + e1.__image_ = main_image; + auto& e2 = base.__emplace_entry_(); + e2.__addr_ = uintptr_t(&func2); + e2.__image_ = main_image; + return ret; +} + +void check_stacktrace(std::stacktrace& st) { + assert(st.at(0).native_handle() == uintptr_t(&func0)); + assert(st.at(0).description().contains("func0")); // e.g.: _func0, func0, func0(), other variations maybe + assert(st.at(0).source_file().ends_with("use_available_progs.pass.cpp")); + assert(st.at(0).source_line() == _FUNC0_LINE); + + assert(st.at(1).native_handle() == uintptr_t(&func1)); + assert(st.at(1).description().contains("func1")); + assert(st.at(1).source_file().ends_with("use_available_progs.pass.cpp")); + assert(st.at(1).source_line() == _FUNC1_LINE); + + assert(st.at(2).native_handle() == uintptr_t(&func2)); + assert(st.at(2).description().contains("func2")); + assert(st.at(2).source_file().ends_with("use_available_progs.pass.cpp")); + assert(st.at(2).source_line() == _FUNC2_LINE); +} + +template +int try_tool() { + std::cerr << "*** trying tool: " << std::__stacktrace::__executable_name::get() << '\n'; + if (std::__stacktrace::__has_working_executable()) { + auto st = fake_stacktrace(); + auto& base = (std::__stacktrace::base&)st; + std::__stacktrace::stack_bytes stack_bytes; + std::__stacktrace::byte_pool stack_pool = stack_bytes.pool(); + std::__stacktrace::arena arena(stack_pool, st.get_allocator()); + std::__stacktrace::__run_tool(base, arena); + std::cout << st << std::endl; + check_stacktrace(st); + std::cerr << "... succeeded\n"; + return 1; + } else { + std::cerr << "... not found\n"; + } + return 0; +} + +} // namespace + +int main(int, char**) { + /* + If for some reason all tools failed to run, we don't quite want to declare a success, + so this is false until a tool ran (and succeeded). + + If any of these tools exist, but the stacktrace operation failed when using it, + the `assert`s within that test will abort immediately. + + Therefore, we can't assume one's machine (or CI) has any one of these tools; but assume + it will have at least _something_, and ensure that something works. + */ + int something_worked = 0; + something_worked += try_tool(); + something_worked += try_tool(); + something_worked += try_tool(); + assert(something_worked); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp index bd1418220d275..c254c71dcebb8 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.4) Comparisons [stacktrace.basic.cmp] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp index 30a8e36dbd812..d01713d03903f 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.4) Comparisons [stacktrace.basic.cmp] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp index b0c7d6db0b402..094ae3b4406ce 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp @@ -8,6 +8,7 @@ // REQUIRES: std-at-least-c++23, has-unix-headers, libcpp-hardening-mode={{extensive|debug}} // XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing +// ADDITIONAL_COMPILE_FLAGS: -g /* Hardened requirements for the `current` call with given `skip` and `max_depth` amounts: @@ -25,7 +26,7 @@ int main(int, char**) { TEST_LIBCPP_ASSERT_FAILURE( - std::stacktrace::current(1, 0xffffffffffffffff), "sum of skip and max_depth too large; overflows size_type"); + std::stacktrace::current(1, 0xffffffffffffffff), "sum of skip and max_depth overflows size_type"); return 0; } diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp index 642626475eb5f..db5d8960f05f3 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g -Og +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.2) diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp index 401bc97bcb7c1..44b3c182d8f7c 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp @@ -17,6 +17,7 @@ */ #include +#include #include uint32_t test1_line; @@ -36,6 +37,8 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current() { auto st = test2(); + std::cout << st << '\n'; + assert(st.size() >= 3); assert(st[0]); assert(st[0].native_handle()); diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp index ccd7c57acb425..e87564d004010 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp @@ -18,21 +18,25 @@ */ #include +#include #include _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip_depth() { // current stack is: [this function, main, (possibly something else, e.g. `_start` from libc)] // so it's probably 3 functions deep -- but certainly at least 2 deep. - auto st = std::stacktrace::current(); + auto st = std::stacktrace::current(0); + std::cout << st << '\n'; assert(st.size() >= 2); - auto it = st.begin(); - auto entry1 = *(it++); // represents this function - auto entry2 = *(it++); // represents our caller, `main` + auto it = st.begin(); + ++it; + auto entry = *it; // represents our caller, `main` // get current trace again, but skip the 1st - st = std::stacktrace::current(1, 1); + st = std::stacktrace::current(1); + std::cout << st << '\n'; assert(st.size() >= 1); - assert(*st.begin() == entry2); + it = st.begin(); + assert(*it == entry); } _LIBCPP_NO_TAIL_CALLS diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp index 1de9d83acfea6..bdff39b2745d1 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.5) Modifiers [stacktrace.basic.mod] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp index def734fa56697..ac9488eec8a0f 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.6) Non-member functions diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp index 3d49094a525fc..e803815ae2ffc 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.6) Non-member functions diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp index ca68883f6a115..2d74121afe6f4 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.6) Non-member functions @@ -18,10 +19,13 @@ */ #include +#include #include int main(int, char**) { - auto a = std::stacktrace::current(); + auto a = std::stacktrace::current(); + auto astr = std::to_string(a); + std::cerr << astr << '\n'; assert(std::to_string(a[0]).contains("main")); assert(std::to_string(a[0]).contains("to_string.pass")); diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp index f9604c761b17c..91e3b16f46111 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp index 23c3e44a36c26..bddfa3a8e1909 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp index 237bf314fb233..8edc2cd093e16 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp index fa4e646992439..9c520c2cf61e1 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp index f60be2f13f6ad..ea056da9beec0 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp index a199657963ee2..f102422d79020 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp index fbdfaa854caa7..3dd654ce0a676 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp index 69bd00372d109..4258d8b3d178b 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp index 31c1b7449482c..53e01239bc96d 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp index 6fa04e5b7b55a..843c8c8a965fa 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.4.3) Observers [stacktrace.basic.obs] diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp index 40e01faeed8ec..3c22957a68b32 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.3.2) Constructors [stacktrace.entry.cons] diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp index 968c3df3be0cc..9a5f88ddbd510 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.3.2) Constructors [stacktrace.entry.cons] diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp index 2f3376a34e4ce..7bf2a171eb745 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g /* (19.6.3.2) Constructors [stacktrace.entry.cons] diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp index 2493e2bd8b490..c681caa3a36e0 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -O0 -g /* (19.6.3.4) Query [stacktrace.entry.query] diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp index fc2d5efb6a31e..6c6ad520f09bc 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -O0 -g /* (19.6.3.4) Query [stacktrace.entry.query] diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp index e601b74f032f0..6d171238f58b7 100644 --- a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -O0 -g /* (19.6.3.4) Query [stacktrace.entry.query] diff --git a/libcxx/test/std/diagnostics/stacktrace/test_allocs.h b/libcxx/test/std/diagnostics/stacktrace/test_allocs.h index 274b6362d4e36..42138700551e7 100644 --- a/libcxx/test/std/diagnostics/stacktrace/test_allocs.h +++ b/libcxx/test/std/diagnostics/stacktrace/test_allocs.h @@ -40,40 +40,40 @@ struct TestAlloc { using other = Other; }; - static std::shared_ptr> new_arena() { + static std::shared_ptr> new_alloc() { return std::make_shared>(); } - static std::shared_ptr> global_arena() { - static auto ret = new_arena(); + static std::shared_ptr> global_alloc() { + static auto ret = new_alloc(); return ret; } /** Type-erased allocator used for servicing allocate and deallocate. - Two `TestAlloc`'s are equal IFF they contain the same arena pointer. - Always-equal `TestAlloc`'s get a pointer to a shared `global_arena`. */ - std::shared_ptr> arena_; + Two `TestAlloc`'s are equal IFF they contain the same alloc pointer. + Always-equal `TestAlloc`'s get a pointer to a shared `global_alloc`. */ + std::shared_ptr> alloc_; - /** Instances are equal IFF they have the same arena pointer (even if this is "always_equals", - since such instances point to the global arena). */ - bool operator==(auto const& rhs) const noexcept { return arena_.get() == rhs.arena_.get(); } + /** Instances are equal IFF they have the same alloc pointer (even if this is "always_equals", + since such instances point to the global alloc). */ + bool operator==(auto const& rhs) const noexcept { return alloc_.get() == rhs.alloc_.get(); } - /** Construct with a new arena, or, if always-equal, the global arena. */ - TestAlloc() noexcept(_KNoExCtors) : arena_(_KAlwaysEqual ? global_arena() : new_arena()) {} + /** Construct with a new alloc, or, if always-equal, the global alloc. */ + TestAlloc() noexcept(_KNoExCtors) : alloc_(_KAlwaysEqual ? global_alloc() : new_alloc()) {} template - TestAlloc(Other const& rhs) : arena_(rhs.arena_) {} + TestAlloc(Other const& rhs) : alloc_(rhs.alloc_) {} template TestAlloc& operator=(Other const& rhs) { - arena_ = rhs.arena_; + alloc_ = rhs.alloc_; } - std::allocator& arena() { return *(std::allocator*)arena_.get(); } + std::allocator& alloc() { return *(std::allocator*)alloc_.get(); } - T* allocate(size_t n) noexcept(_KNoExAlloc) { return arena().allocate(n); } - auto allocate_at_least(size_t n) noexcept(_KNoExAlloc) { return arena().allocate_at_least(n); } - void deallocate(T* ptr, size_t n) noexcept(_KNoExAlloc) { return arena().deallocate(ptr, n); } + T* allocate(size_t n) noexcept(_KNoExAlloc) { return alloc().allocate(n); } + auto allocate_at_least(size_t n) noexcept(_KNoExAlloc) { return alloc().allocate_at_least(n); } + void deallocate(T* ptr, size_t n) noexcept(_KNoExAlloc) { return alloc().deallocate(ptr, n); } }; // For convenience and readability: From 684d45fae499a6001e5f2328919b0270fafcb0cd Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Thu, 7 Aug 2025 16:10:38 -0400 Subject: [PATCH 18/19] Misc fixes so things build --- libcxx/include/__stacktrace/images.h | 6 +- libcxx/include/__stacktrace/memory.h | 148 ++++++++++-------- libcxx/src/stacktrace/images.cpp | 1 + libcxx/src/stacktrace/impl_generic.cpp | 9 +- libcxx/src/stacktrace/tools/apple_atos.cpp | 2 +- libcxx/src/stacktrace/tools/gnu_addr2line.cpp | 2 +- .../src/stacktrace/tools/llvm_symbolizer.cpp | 5 +- libcxx/src/stacktrace/tools/tools.h | 10 +- .../basic.cons/current_no_args.pass.cpp | 24 ++- 9 files changed, 110 insertions(+), 97 deletions(-) diff --git a/libcxx/include/__stacktrace/images.h b/libcxx/include/__stacktrace/images.h index ce943e673477a..52144d38310f5 100644 --- a/libcxx/include/__stacktrace/images.h +++ b/libcxx/include/__stacktrace/images.h @@ -20,15 +20,13 @@ struct image; struct images; struct image { - constexpr static size_t __max_string_len = 1024; - uintptr_t loaded_at_{}; uintptr_t slide_{}; - fixed_str<__max_string_len> name_{}; + fixed_str name_{}; bool is_main_prog_{}; bool operator<(image const& __rhs) const { return loaded_at_ < __rhs.loaded_at_; } - operator bool() const { return name_[0]; } + operator bool() const { return !name_.empty(); } }; /** diff --git a/libcxx/include/__stacktrace/memory.h b/libcxx/include/__stacktrace/memory.h index ab8725424d7ed..6c8dd3c8eafd5 100644 --- a/libcxx/include/__stacktrace/memory.h +++ b/libcxx/include/__stacktrace/memory.h @@ -38,38 +38,35 @@ A few memory-related utilities: Uses the caller's provided allocator, so none of these involve heap allocations "outside of" the caller-provided allocator. * A `str` class, inheriting from `std::string`, ensuring allocations happen via `arena` - * A `fixed_str` class, not related to the arena, but instead backed by a `char[n]` - array within that same struct, so it doesn't perform any [de]allocations; it only - uses its own character array. A small amount of glue / hacks are done here to allow the rest of the stacktrace-related code to use familiar string etc. operations, while encapsulating away the details of where memory might come from, since we need to be careful about unexpected allocations. */ -// clang-format off - struct byte_pool final { byte* ptr_; function destroy_; byte_pool* link_; byte* end_; - byte_pool(byte* __bytes, - size_t __size, - function __destroy = [] {}, - byte_pool* __link = nullptr) noexcept - : ptr_(__bytes), destroy_(__destroy), link_(__link), end_(__bytes + __size) {} + byte_pool( + byte* __bytes, size_t __size, function __destroy = [] {}, byte_pool* __link = nullptr) noexcept + : ptr_(__bytes), destroy_(__destroy), link_(__link), end_(__bytes + __size) {} byte* operator()(size_t __sz, size_t __align) noexcept { - auto __ptr = uintptr_t(ptr_); // convert curr ptr to integer, to do math - auto __misalign = __ptr % __align; // if current ptr not aligned, - if (__misalign) { __ptr += (__align - __misalign); } // waste a few bytes to ensure alignment - auto __ret = __ptr; // we would return this aligned position - __ptr += __sz; // next object will start here - if (__ptr > uintptr_t(end_)) { return nullptr; } // if this exceeds our space, then fail - ptr_ = (byte*) __ptr; // otherwise update current position - return (byte*) __ret; // returned aligned position as byte ptr + auto __ptr = uintptr_t(ptr_); // convert curr ptr to integer, to do math + auto __misalign = __ptr % __align; // if current ptr not aligned, + if (__misalign) { + __ptr += (__align - __misalign); + } // waste a few bytes to ensure alignment + auto __ret = __ptr; // we would return this aligned position + __ptr += __sz; // next object will start here + if (__ptr > uintptr_t(end_)) { + return nullptr; + } // if this exceeds our space, then fail + ptr_ = (byte*)__ptr; // otherwise update current position + return (byte*)__ret; // returned aligned position as byte ptr } }; @@ -77,21 +74,23 @@ template struct stack_bytes final { byte bytes_[_Sz]; - ~stack_bytes() = default; - stack_bytes() noexcept = default; + ~stack_bytes() = default; + stack_bytes() noexcept = default; stack_bytes(const stack_bytes&) = delete; - stack_bytes(stack_bytes&&) = delete; + stack_bytes(stack_bytes&&) = delete; - byte_pool pool() { return {bytes_, _Sz, []{}, nullptr}; } + byte_pool pool() { + return {bytes_, _Sz, [] {}, nullptr}; + } }; struct arena { - function new_bytes_; // new byte-array factory - function del_bytes_; // byte-array destroyer - byte_pool* curr_pool_; // byte pool currently "in effect" - byte_pool* next_pool_; // allocated (from curr_pool_) but not initialized - size_t allocs_ {}; // number of successful allocations - size_t deallocs_ {}; // incremented on each dealloc; dtor ensures these are equal! + function new_bytes_; // new byte-array factory + function del_bytes_; // byte-array destroyer + byte_pool* curr_pool_; // byte pool currently "in effect" + byte_pool* next_pool_; // allocated (from curr_pool_) but not initialized + size_t allocs_{}; // number of successful allocations + size_t deallocs_{}; // incremented on each dealloc; dtor ensures these are equal! // An arena is scoped to a `basic_stacktrace::current` invocation, so this is usable by only one thread. // Additionally, it's used internally throughout many function calls, so for convenience, store it here. @@ -108,11 +107,14 @@ struct arena { _LIBCPP_ASSERT(active_arena_ptr_ == this, "different arena unexpectively set as the active one"); active_arena_ptr_ = nullptr; _LIBCPP_ASSERT(deallocs_ == allocs_, "destructed arena still has live objects"); - while (curr_pool_) { curr_pool_->destroy_(); curr_pool_ = curr_pool_->link_; } + while (curr_pool_) { + curr_pool_->destroy_(); + curr_pool_ = curr_pool_->link_; + } } arena(auto&& __new_bytes, auto&& __del_bytes, byte_pool& __initial_pool) noexcept - : new_bytes_(__new_bytes), del_bytes_(__del_bytes), curr_pool_(&__initial_pool) { + : new_bytes_(__new_bytes), del_bytes_(__del_bytes), curr_pool_(&__initial_pool) { prep_next_pool(); _LIBCPP_ASSERT(!active_arena_ptr_, "already an active arena"); active_arena_ptr_ = this; @@ -125,10 +127,11 @@ struct arena { template arena(byte_pool& __initial_pool, _UA const& __user_alloc) - : arena( - [&__user_alloc] (size_t __sz) { return as_byte_alloc(__user_alloc).allocate(__sz); }, - [&__user_alloc] (void* __ptr, size_t __sz) { return as_byte_alloc(__user_alloc).deallocate((byte*)__ptr, __sz); }, - __initial_pool) {} + : arena([&__user_alloc](size_t __sz) { return as_byte_alloc(__user_alloc).allocate(__sz); }, + [&__user_alloc](void* __ptr, size_t __sz) { + return as_byte_alloc(__user_alloc).deallocate((byte*)__ptr, __sz); + }, + __initial_pool) {} arena(arena const&) = delete; arena& operator=(arena const&) = delete; @@ -136,13 +139,13 @@ struct arena { void prep_next_pool() noexcept { // Allocate (via current pool) a new byte_pool record, while we have enough space. // When the current pool runs out of space, this one will be ready to use. - next_pool_ = (byte_pool*) (*curr_pool_)(sizeof(byte_pool), alignof(byte_pool)); + next_pool_ = (byte_pool*)(*curr_pool_)(sizeof(byte_pool), alignof(byte_pool)); _LIBCPP_ASSERT(next_pool_, "could not allocate next pool"); } void expand(size_t __atleast) noexcept { constexpr static size_t __k_default_new_pool = 1 << 12; - auto __size = max(__atleast, __k_default_new_pool); + auto __size = max(__atleast, __k_default_new_pool); // "next_pool_" was already allocated, just need to initialize it auto* __bytes = new_bytes_(__size); _LIBCPP_ASSERT(__bytes, "could not allocate more bytes for arena"); @@ -155,12 +158,14 @@ struct arena { std::byte* alloc(size_t __size, size_t __align) noexcept { auto* __ret = (*curr_pool_)(__size, __align); - if (__ret) [[likely]] { goto success; } + if (__ret) [[likely]] { + goto success; + } // Need a new pool to accommodate this request + internal structs expand(__size + __align + sizeof(byte_pool) + alignof(byte_pool)); // upper bound __ret = (*curr_pool_)(__size, __align); _LIBCPP_ASSERT(__ret, "arena failed to allocate"); -success: + success: ++allocs_; return __ret; } @@ -181,31 +186,6 @@ struct alloc { } }; -template -struct fixed_buf { - using value_type = _Tp; - template struct rebind { using other = fixed_buf<_Up, _Sz>; }; - - _Tp __buf_[_Sz]; - size_t __size_; - void deallocate(_Tp*, size_t) {} - _Tp* allocate(size_t) { return __buf_; } -}; - -template -struct fixed_str : std::basic_string, fixed_buf> { - using _Base _LIBCPP_NODEBUG = std::basic_string, fixed_buf>; - using _Base::operator=; - - fixed_buf __fb_; - fixed_str() : _Base(__fb_) { - this->resize(_Sz - 1); - this->resize(0); - } - fixed_str(fixed_str const& __rhs) : fixed_str() { _Base::operator=(__rhs); } - fixed_str& operator=(fixed_str const& __rhs) = default; -}; - struct str : std::basic_string, alloc> { using _Base _LIBCPP_NODEBUG = std::basic_string, alloc>; using _Base::basic_string; @@ -236,7 +216,47 @@ struct str : std::basic_string, alloc> { } }; -// clang-format on +/** A string that contains its own fixed-size, fixed-location buffer. */ +template +struct fixed_str final { + size_t __size_{0}; + char __buf_[_Sz]{0}; + + ~fixed_str() = default; + fixed_str() = default; + + size_t size() const { return __size_; } + bool empty() const { return !size(); } + auto* data(this auto& __self) { return __self.__buf_; } + operator std::string_view() const { return {__buf_, __size_}; } + + fixed_str& operator=(std::string_view __sv) { + strncpy(__buf_, __sv.data(), std::min(_Sz, __sv.size() + 1)); + __size_ = __sv.size(); + __buf_[__size_] = 0; + return *this; + } + + fixed_str(auto const& __rhs) : fixed_str() { *this = __rhs; } + fixed_str& operator=(auto const& __rhs) { return (*this = std::string_view(__rhs)); } + + template + requires requires { _S2 <= _Sz; } + fixed_str& operator=(fixed_str<_S2> const& __rhs) { + return (*this = std::string_view(__rhs)); + } + + template + requires requires { _S2 <= _Sz; } + fixed_str(fixed_str<_S2> const& __rhs) { + *this = std::string_view(__rhs); + } + + fixed_str(fixed_str const& __rhs) { *this = std::string_view(__rhs); } + fixed_str& operator=(fixed_str const& __rhs) { return (*this = std::string_view(__rhs)); } + + friend std::ostream& operator<<(std::ostream& __os, fixed_str const& __f) { return __os << std::string_view(__f); } +}; } // namespace __stacktrace _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/images.cpp b/libcxx/src/stacktrace/images.cpp index 5385fdb898457..fc8769699c8d4 100644 --- a/libcxx/src/stacktrace/images.cpp +++ b/libcxx/src/stacktrace/images.cpp @@ -11,6 +11,7 @@ // #include "__config" + #if defined(__APPLE__) // MacOS-specific: use the `dyld` loader to access info about loaded Mach-O images. # include <__stacktrace/images.h> diff --git a/libcxx/src/stacktrace/impl_generic.cpp b/libcxx/src/stacktrace/impl_generic.cpp index f3a818622b108..3c6e727ea8c3d 100644 --- a/libcxx/src/stacktrace/impl_generic.cpp +++ b/libcxx/src/stacktrace/impl_generic.cpp @@ -101,11 +101,10 @@ _LIBCPP_EXPORTED_FROM_ABI void base::find_symbols(arena& arena) {} _LIBCPP_EXPORTED_FROM_ABI void base::find_source_locs(arena& arena) { # if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME - (void)(false // - || __run_tool(*this, arena) // preferred on MacOS - || __run_tool(*this, arena) // prefer this in other (non-MacOS, non-Windows) - || __run_tool(*this, arena) // a good fallback; dev machines tend to have gcc if not llvm - ); + (void)(false // + || (__has_working_executable() && __run_tool(*this, arena)) // + || (__has_working_executable() && __run_tool(*this, arena)) // + || (__has_working_executable() && __run_tool(*this, arena))); // # endif } diff --git a/libcxx/src/stacktrace/tools/apple_atos.cpp b/libcxx/src/stacktrace/tools/apple_atos.cpp index 11873e28b2f75..7a40a6f0c163f 100644 --- a/libcxx/src/stacktrace/tools/apple_atos.cpp +++ b/libcxx/src/stacktrace/tools/apple_atos.cpp @@ -71,7 +71,7 @@ template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& ar spawner spawner{tool, base}; if (spawner.errno_) { return false; } - fixed_str line; // our read buffer + str line; // our read buffer auto* entry_iter = base.entries_begin(); // position at first entry while (spawner.stream_.good()) { // loop until we get EOF from tool stdout std::getline(spawner.stream_, line); // consume a line from stdout diff --git a/libcxx/src/stacktrace/tools/gnu_addr2line.cpp b/libcxx/src/stacktrace/tools/gnu_addr2line.cpp index b99797ea58fe6..f857636cc4d42 100644 --- a/libcxx/src/stacktrace/tools/gnu_addr2line.cpp +++ b/libcxx/src/stacktrace/tools/gnu_addr2line.cpp @@ -106,7 +106,7 @@ template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, aren spawner spawner{tool, base}; if (spawner.errno_) { return false; } - fixed_str line; // our read buffer + str line ; // our read buffer auto* entry_iter = base.entries_begin(); // position at first entry while (spawner.stream_.good()) { // loop until we get EOF from tool stdout std::string_view view; diff --git a/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp b/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp index 779342e48e6f3..f31c811a233ed 100644 --- a/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp +++ b/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp @@ -38,8 +38,7 @@ bool llvm_symbolizer::build_argv() { while (it != end) { auto& entry = *(entry_base*)(it++); if (entry.__image_ && !entry.__image_->name_.empty()) { - fixed_str image_path = entry.__image_->name_; - push_arg("FILE:%s %p", image_path.data(), (void*)entry.adjusted_addr()); + push_arg("FILE:%s %p", entry.__image_->name_.data(), (void*)entry.adjusted_addr()); } else { push_arg("%p", (void*)entry.adjusted_addr()); } @@ -89,7 +88,7 @@ template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base spawner spawner{tool, base}; if (spawner.errno_) { return false; } - fixed_str line; // our read buffer + str line; // our read buffer auto* entry_iter = base.entries_begin() - 1; // "before first" entry while (spawner.stream_.good()) { // loop until we get EOF from tool stdout std::getline(spawner.stream_, line); // consume a line from stdout diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h index 46e5747990ab1..6a5cc58ea0dbb 100644 --- a/libcxx/src/stacktrace/tools/tools.h +++ b/libcxx/src/stacktrace/tools/tools.h @@ -117,7 +117,7 @@ struct file_actions { optional fa_{}; fd stdout_read_; // read end of subprocess's stdout, IFF redir_stdout used fd stdout_write_; // write end of subprocess's stdout, IFF redir_stdout used - errno_t errno_{}; // set to nonzero if any of these C calls failed + int errno_{}; // set to nonzero if any of these C calls failed bool failed() const { return errno_; } @@ -223,8 +223,8 @@ struct sigchld_enable { struct pid_waiter { pid_t pid_{}; - int status_{}; // value is valid iff wait() completed - errno_t errno_{}; // set to nonzero if any of these C calls failed + int status_{}; // value is valid iff wait() completed + int errno_{}; // set to nonzero if any of these C calls failed bool done_{}; operator pid_t() const { return pid_; } @@ -271,7 +271,7 @@ struct spawner { fd::istream stream_; // istream interface from which we can `getline` sigchld_enable chld_enable_; // temporarily enables SIGCHLD so `waitpid` works pid_waiter pid_{0}; // set during successful `spawn`, can `waitpid` automatically - errno_t errno_{}; // set to nonzero if any of these C calls failed + int errno_{}; // set to nonzero if any of these C calls failed bool failed() const { return errno_; } @@ -346,7 +346,7 @@ template <> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); struct llvm_symbolizer : tool_base { - constexpr static char const* __default_prog_name = "llvm_symbolizer"; + constexpr static char const* __default_prog_name = "llvm-symbolizer"; constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"; llvm_symbolizer(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp index 44b3c182d8f7c..ce5ed4a5c24ba 100644 --- a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// // REQUIRES: std-at-least-c++23 -// ADDITIONAL_COMPILE_FLAGS: -g +// ADDITIONAL_COMPILE_FLAGS: -O0 -g /* (19.6.4.2) @@ -22,6 +22,7 @@ uint32_t test1_line; uint32_t test2_line; +uint32_t main_line; _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) @@ -36,31 +37,26 @@ _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { } _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current() { - auto st = test2(); + main_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto st = test2(); std::cout << st << '\n'; assert(st.size() >= 3); assert(st[0]); assert(st[0].native_handle()); assert(st[0].description().contains("test1")); - assert(st[0].source_file().contains("current_no_args.pass.cpp")); + assert(st[0].source_file().ends_with("current_no_args.pass.cpp")); + assert(st[0].source_line() == test1_line); assert(st[1]); assert(st[1].native_handle()); assert(st[1].description().contains("test2")); - assert(st[1].source_file().contains("current_no_args.pass.cpp")); + assert(st[1].source_file().ends_with("current_no_args.pass.cpp")); + assert(st[1].source_line() == test2_line); assert(st[2]); assert(st[2].native_handle()); assert(st[2].description().contains("test_current")); - assert(st[2].source_file().contains("current_no_args.pass.cpp")); - - // We unfortunately cannot guarantee the following; in CI, and possibly on users' build machines, - // there may not be an up-to-date version of e.g. `addr2line`. - // assert(st[0].source_file().ends_with("current_no_args.pass.cpp")); - // assert(st[0].source_line() == test1_line); - // assert(st[1].source_file().ends_with("current_no_args.pass.cpp")); - // assert(st[1].source_line() == test2_line); - // assert(st[2].source_file().ends_with("current_no_args.pass.cpp")); - // assert(st[2].source_line() == main_line); + assert(st[2].source_file().ends_with("current_no_args.pass.cpp")); + assert(st[2].source_line() == main_line); } _LIBCPP_NO_TAIL_CALLS From 9fc80ded49e4d06d7b83037d37add2cb8e640ffa Mon Sep 17 00:00:00 2001 From: Steve O'Brien Date: Tue, 12 Aug 2025 17:31:27 -0400 Subject: [PATCH 19/19] Testing for whether 'backtrace' exists on CI, for testing --- libcxx/src/CMakeLists.txt | 1 + libcxx/src/stacktrace/libbacktrace.h | 44 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 libcxx/src/stacktrace/libbacktrace.h diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 6b7d97b2b516f..95d024f89a425 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -45,6 +45,7 @@ set(LIBCXX_SOURCES stacktrace/images.cpp stacktrace/impl_generic.cpp stacktrace/impl_windows.cpp + stacktrace/libbacktrace.h stacktrace/tools/apple_atos.cpp stacktrace/tools/gnu_addr2line.cpp stacktrace/tools/llvm_symbolizer.cpp diff --git a/libcxx/src/stacktrace/libbacktrace.h b/libcxx/src/stacktrace/libbacktrace.h new file mode 100644 index 0000000000000..c41b242fdfcc6 --- /dev/null +++ b/libcxx/src/stacktrace/libbacktrace.h @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef __LIBCPP_STACKTRACE_LIBBACKTRACE_H +#define __LIBCPP_STACKTRACE_LIBBACKTRACE_H + +#include <__stacktrace/basic_stacktrace.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { +bool try_libbacktrace(); +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#if !defined(_WIN32) && __has_include() + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +auto z = XXXXXXXXXXXXXXXXXXXXXXXX___BACKTRACE___XXXXXXXXXXXXXXXXXXXXXXXX; + +inline bool try_libbacktrace() { return false; } + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#else + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +inline bool try_libbacktrace() { return false; } + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // !defined(_WIN32) && __has_include() + +#endif // __LIBCPP_STACKTRACE_LIBBACKTRACE_H