diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 3c84585e1ae3b..1ae927d63f9d4 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -771,6 +771,12 @@ def Artificial : InheritableAttr { let SimpleHandler = 1; } +def TransparentStepping: InheritableAttr { + let Spellings = [Clang<"transparent_stepping">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [TransparentSteppingDocs]; +} + def XRayInstrument : InheritableAttr { let Spellings = [Clang<"xray_always_instrument">, Clang<"xray_never_instrument">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index f19af47749d74..d1f3bd0af3087 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6924,6 +6924,66 @@ As such, this function attribute is currently only supported on X86 targets. }]; } +def TransparentSteppingDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``transparent_stepping`` attribute is intended as a hint for debuggers that this +function itself is not interesting, but it calls a function that might be. So, when +stepping in arrives at a function with this attribute, debuggers should transparently +step-in through it into the functions called by the annotated function (but not by +subsequent calls made by those functions), stopping at the first one its normal rules +for whether to stop says to stop at - or stepping out again if none qualify. Also, when +stepping out arrives at a function with this attribute, the debugger should continue +stepping out to its caller. + +For example: + +.. code-block:: c + + int bar(void) { + return 42; + } + + __attribute__((transparent_stepping)) + int foo(void) { + return bar(); + } + + int caller(void) { + return foo(); + } + +Stepping into ``foo`` should step directly into ``bar`` instead, and stepping out of ``bar`` +should stop in ``caller``. + +Functions with the ``transparent_stepping`` attribute can be chained together: + +.. code-block:: c + + int baz(void) { + return 42; + } + + __attribute__((transparent_stepping)) + int bar(void) { + return baz(); + } + + __attribute__((transparent_stepping)) + int foo(void) { + return bar(); + } + + int caller(void) { + return foo(); + } + +In this example, stepping into ``foo`` should step directly into ``baz``, and stepping out of +``baz`` should stop in ``caller``. + }]; +} + + def ReadOnlyPlacementDocs : Documentation { let Category = DocCatType; let Content = [{This attribute is attached to a structure, class or union declaration. diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 0a1f72f9f07d2..7d6f22d6cf51e 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -67,6 +67,12 @@ static uint32_t getDeclAlignIfRequired(const Decl *D, const ASTContext &Ctx) { return D->hasAttr() ? D->getMaxAlignment() : 0; } +static bool getIsTransparentStepping(const Decl *D) { + if (!D) + return false; + return D->hasAttr(); +} + CGDebugInfo::CGDebugInfo(CodeGenModule &CGM) : CGM(CGM), DebugKind(CGM.getCodeGenOpts().getDebugInfo()), DebugTypeExtRefs(CGM.getCodeGenOpts().DebugTypeExtRefs), @@ -1882,6 +1888,8 @@ llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction( SPFlags |= llvm::DISubprogram::SPFlagLocalToUnit; if (CGM.getLangOpts().Optimize) SPFlags |= llvm::DISubprogram::SPFlagOptimized; + if (getIsTransparentStepping(Method)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; // In this debug mode, emit type info for a class when its constructor type // info is emitted. @@ -3809,6 +3817,8 @@ llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD, if (Stub) { Flags |= getCallSiteRelatedAttrs(); SPFlags |= llvm::DISubprogram::SPFlagDefinition; + if (getIsTransparentStepping(FD)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; return DBuilder.createFunction( DContext, Name, LinkageName, Unit, Line, getOrCreateFunctionType(GD.getDecl(), FnType, Unit), 0, Flags, SPFlags, @@ -3958,6 +3968,8 @@ llvm::DISubprogram *CGDebugInfo::getObjCMethodDeclaration( if (It == TypeCache.end()) return nullptr; auto *InterfaceType = cast(It->second); + if (getIsTransparentStepping(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; llvm::DISubprogram *FD = DBuilder.createFunction( InterfaceType, getObjCMethodName(OMD), StringRef(), InterfaceType->getFile(), LineNo, FnType, LineNo, Flags, SPFlags); @@ -4124,6 +4136,8 @@ void CGDebugInfo::emitFunctionStart(GlobalDecl GD, SourceLocation Loc, SPFlags |= llvm::DISubprogram::SPFlagLocalToUnit; if (CGM.getLangOpts().Optimize) SPFlags |= llvm::DISubprogram::SPFlagOptimized; + if (getIsTransparentStepping(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; llvm::DINode::DIFlags FlagsForDef = Flags | getCallSiteRelatedAttrs(); llvm::DISubprogram::DISPFlags SPFlagsForDef = @@ -4210,6 +4224,9 @@ void CGDebugInfo::EmitFunctionDecl(GlobalDecl GD, SourceLocation Loc, llvm::DINodeArray Annotations = CollectBTFDeclTagAnnotations(D); llvm::DISubroutineType *STy = getOrCreateFunctionType(D, FnType, Unit); + if (getIsTransparentStepping(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; + llvm::DISubprogram *SP = DBuilder.createFunction( FDContext, Name, LinkageName, Unit, LineNo, STy, ScopeLine, Flags, SPFlags, TParamsArray.get(), nullptr, nullptr, Annotations); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 39218c9c287ae..b0b1e86fbe4b0 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6687,6 +6687,12 @@ static void handleSwiftAsyncName(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) SwiftAsyncNameAttr(S.Context, AL, Name)); } +static void handleTransparentStepping(Sema &S, Decl *D, + const ParsedAttr &AL) { + D->addAttr(::new (S.Context) + TransparentSteppingAttr(S.Context, AL)); +} + static void handleSwiftNewType(Sema &S, Decl *D, const ParsedAttr &AL) { // Make sure that there is an identifier as the annotation's single argument. if (!AL.checkExactlyNumArgs(S, 1)) @@ -8948,6 +8954,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_NoDebug: handleNoDebugAttr(S, D, AL); break; + case ParsedAttr::AT_TransparentStepping: + handleTransparentStepping(S, D, AL); + break; case ParsedAttr::AT_CmseNSEntry: handleCmseNSEntryAttr(S, D, AL); break; diff --git a/clang/test/CodeGen/attr-transparent-stepping-method.cpp b/clang/test/CodeGen/attr-transparent-stepping-method.cpp new file mode 100644 index 0000000000000..5eb91b90bd65b --- /dev/null +++ b/clang/test/CodeGen/attr-transparent-stepping-method.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -debug-info-kind=limited -emit-llvm -o - %s | FileCheck %s + +void bar(void) {} + +struct A { +[[clang::transparent_stepping()]] +void foo(void) { + bar(); +} +}; + +int main() { + A().foo(); +} + +// CHECK: DISubprogram(name: "foo"{{.*}} DISPFlagIsTransparentStepping diff --git a/clang/test/CodeGen/attr-transparent-stepping.c b/clang/test/CodeGen/attr-transparent-stepping.c new file mode 100644 index 0000000000000..6ae42dce78123 --- /dev/null +++ b/clang/test/CodeGen/attr-transparent-stepping.c @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -debug-info-kind=limited -emit-llvm -o - %s | FileCheck %s + +void bar(void) {} + +__attribute__((transparent_stepping)) +void foo(void) { + bar(); +} + +// CHECK: DISubprogram(name: "foo"{{.*}} DISPFlagIsTransparentStepping diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index f7c5ce9468841..d55874611cabe 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -185,6 +185,7 @@ // CHECK-NEXT: Target (SubjectMatchRule_function) // CHECK-NEXT: TargetClones (SubjectMatchRule_function) // CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member) +// CHECK-NEXT: TransparentStepping (SubjectMatchRule_function) // CHECK-NEXT: TrivialABI (SubjectMatchRule_record) // CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local) // CHECK-NEXT: UseHandle (SubjectMatchRule_variable_is_parameter) diff --git a/clang/test/Sema/attr-transparent-stepping.c b/clang/test/Sema/attr-transparent-stepping.c new file mode 100644 index 0000000000000..8bcaa823f9542 --- /dev/null +++ b/clang/test/Sema/attr-transparent-stepping.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +__attribute__((transparent_stepping)) +void correct(void) {} + +__attribute__((transparent_stepping(1))) // expected-error {{'transparent_stepping' attribute takes no arguments}} +void wrong_arg(void) {} diff --git a/clang/test/SemaCXX/attr-transparent-stepping-method.cpp b/clang/test/SemaCXX/attr-transparent-stepping-method.cpp new file mode 100644 index 0000000000000..7f2be98a7c584 --- /dev/null +++ b/clang/test/SemaCXX/attr-transparent-stepping-method.cpp @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + + +struct S { +[[clang::transparent_stepping]] +void correct(void) {} + +[[clang::transparent_stepping(1)]] // expected-error {{'transparent_stepping' attribute takes no arguments}} +void one_arg(void) {} +}; + diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h index fe2416aed783d..de9400e7af97c 100644 --- a/lldb/include/lldb/Symbol/Function.h +++ b/lldb/include/lldb/Symbol/Function.h @@ -442,7 +442,7 @@ class Function : public UserID, public SymbolContextScope { Function(CompileUnit *comp_unit, lldb::user_id_t func_uid, lldb::user_id_t func_type_uid, const Mangled &mangled, Type *func_type, const AddressRange &range, - bool can_throw = false); + bool can_throw = false, bool generic_trampoline = false); /// Destructor. ~Function() override; @@ -554,6 +554,10 @@ class Function : public UserID, public SymbolContextScope { /// A type object pointer. Type *GetType(); + bool IsGenericTrampoline() const { + return m_is_generic_trampoline; + } + /// Get const accessor for the type that describes the function return value /// type, and parameter types. /// @@ -659,6 +663,8 @@ class Function : public UserID, public SymbolContextScope { /// information. Mangled m_mangled; + bool m_is_generic_trampoline; + /// All lexical blocks contained in this function. Block m_block; diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 8936402da644f..5e1c6874eec6f 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -289,6 +289,12 @@ class TargetProperties : public Properties { bool GetDebugUtilityExpression() const; + /// Trampoline support includes stepping through trampolines directly to their + /// targets, stepping out of trampolines directly to their callers, and + /// automatically filtering out trampolines as possible breakpoint locations + /// when set by name. + bool GetEnableTrampolineSupport() const; + private: // Callbacks for m_launch_info. void Arg0ValueChangedCallback(); diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index 94bc413d93ff8..73daf1ebcd541 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -904,6 +904,27 @@ class Thread : public std::enable_shared_from_this, bool abort_other_plans, bool stop_other_threads, Status &status); + /// Gets the plan used to step through a function with a generic trampoline. A + /// generic trampoline is one without a function target, which the thread plan + /// will attempt to step through until it finds a place where it makes sense + /// to stop at. + /// \param[in] abort_other_plans + /// \b true if we discard the currently queued plans and replace them with + /// this one. + /// Otherwise this plan will go on the end of the plan stack. + /// + /// \param[in] stop_other_threads + /// \b true if we will stop other threads while we single step this one. + /// + /// \param[out] status + /// A status with an error if queuing failed. + /// + /// \return + /// A shared pointer to the newly queued thread plan, or nullptr if the + /// plan could not be queued. + virtual lldb::ThreadPlanSP QueueThreadPlanForStepThroughGenericTrampoline( + bool abort_other_plans, lldb::RunMode stop_other_threads, Status &status); + /// Gets the plan used to continue from the current PC. /// This is a simple plan, mostly useful as a backstop when you are continuing /// for some particular purpose. diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h index 57fee942441b4..cd5d4a4ecc1f8 100644 --- a/lldb/include/lldb/Target/ThreadPlan.h +++ b/lldb/include/lldb/Target/ThreadPlan.h @@ -302,6 +302,7 @@ class ThreadPlan : public std::enable_shared_from_this, eKindStepInRange, eKindRunToAddress, eKindStepThrough, + eKindStepThroughGenericTrampoline, eKindStepUntil }; diff --git a/lldb/include/lldb/Target/ThreadPlanStepOverRange.h b/lldb/include/lldb/Target/ThreadPlanStepOverRange.h index 8585ac62f09b3..d2bddcb573a2e 100644 --- a/lldb/include/lldb/Target/ThreadPlanStepOverRange.h +++ b/lldb/include/lldb/Target/ThreadPlanStepOverRange.h @@ -30,7 +30,6 @@ class ThreadPlanStepOverRange : public ThreadPlanStepRange, bool ShouldStop(Event *event_ptr) override; protected: - bool DoPlanExplainsStop(Event *event_ptr) override; bool DoWillResume(lldb::StateType resume_state, bool current_plan) override; void SetFlagsToDefault() override { diff --git a/lldb/include/lldb/Target/ThreadPlanStepRange.h b/lldb/include/lldb/Target/ThreadPlanStepRange.h index 2fe8852771000..606135022dfc2 100644 --- a/lldb/include/lldb/Target/ThreadPlanStepRange.h +++ b/lldb/include/lldb/Target/ThreadPlanStepRange.h @@ -41,6 +41,7 @@ class ThreadPlanStepRange : public ThreadPlan { void AddRange(const AddressRange &new_range); protected: + bool DoPlanExplainsStop(Event *event_ptr) override; bool InRange(); lldb::FrameComparison CompareCurrentFrameToStartFrame(); bool InSymbol(); diff --git a/lldb/include/lldb/Target/ThreadPlanStepThroughGenericTrampoline.h b/lldb/include/lldb/Target/ThreadPlanStepThroughGenericTrampoline.h new file mode 100644 index 0000000000000..03ebb83918858 --- /dev/null +++ b/lldb/include/lldb/Target/ThreadPlanStepThroughGenericTrampoline.h @@ -0,0 +1,52 @@ +//===-- ThreadPlanStepInRange.h ---------------------------------*- 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 LLDB_TARGET_THREADPLANSTEPTHROUGHGENERICTRAMPOLINE_H +#define LLDB_TARGET_THREADPLANSTEPTHROUGHGENERICTRAMPOLINE_H + +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanShouldStopHere.h" +#include "lldb/Target/ThreadPlanStepRange.h" + +namespace lldb_private { + +class ThreadPlanStepThroughGenericTrampoline : public ThreadPlanStepRange, + public ThreadPlanShouldStopHere { +public: + ThreadPlanStepThroughGenericTrampoline(Thread &thread, + lldb::RunMode stop_others); + + ~ThreadPlanStepThroughGenericTrampoline() override; + + void GetDescription(Stream *s, lldb::DescriptionLevel level) override; + + bool ShouldStop(Event *event_ptr) override; + bool ValidatePlan(Stream *error) override; + +protected: + void SetFlagsToDefault() override { + GetFlags().Set( + ThreadPlanStepThroughGenericTrampoline::s_default_flag_values); + } + +private: + // Need an appropriate marker for the current stack so we can tell step out + // from step in. + + static uint32_t + s_default_flag_values; // These are the default flag values + // for the ThreadPlanStepThroughGenericTrampoline. + ThreadPlanStepThroughGenericTrampoline( + const ThreadPlanStepThroughGenericTrampoline &) = delete; + const ThreadPlanStepThroughGenericTrampoline & + operator=(const ThreadPlanStepThroughGenericTrampoline &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_THREADPLANSTEPTHROUGHGENERICTRAMPOLINE_H diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp index 2582d7bcd66fa..e885a1798265e 100644 --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -799,7 +799,12 @@ void Module::LookupInfo::Prune(SymbolContextList &sc_list, if (!sc_list.GetContextAtIndex(i, sc)) break; + bool is_trampoline = + Target::GetGlobalProperties().GetEnableTrampolineSupport() && + sc.function && sc.function->IsGenericTrampoline(); + bool keep_it = + !is_trampoline && NameMatchesLookupInfo(sc.GetFunctionName(), sc.GetLanguage()); if (keep_it) ++i; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp index 5c9c69b77443b..81d846ba2a25f 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -2395,12 +2395,16 @@ DWARFASTParserClang::ParseFunctionFromDWARF(CompileUnit &comp_unit, assert(func_type == nullptr || func_type != DIE_IS_BEING_PARSED); + bool is_generic_trampoline = die.IsGenericTrampoline(); + const user_id_t func_user_id = die.GetID(); func_sp = std::make_shared(&comp_unit, func_user_id, // UserID is the DIE offset func_user_id, func_name, func_type, - func_range); // first address range + func_range, // first address range + false, // canThrow + is_generic_trampoline); if (func_sp.get() != nullptr) { if (frame_base.IsValid()) diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwift.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwift.cpp index e2b1375863c5a..bf93346aad510 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwift.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwift.cpp @@ -274,9 +274,11 @@ Function *DWARFASTParserSwift::ParseFunctionFromDWARF( decl_column)); const user_id_t func_user_id = die.GetID(); + bool is_generic_trampoline = die.IsGenericTrampoline(); func_sp.reset(new Function(&comp_unit, func_user_id, func_user_id, - func_name, nullptr, func_range, - can_throw)); // first address range + func_name, nullptr, + func_range, // first address range + can_throw, is_generic_trampoline)); if (func_sp.get() != NULL) { if (frame_base.IsValid()) diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp index e8492079af88a..2b8032eae8032 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp @@ -203,6 +203,13 @@ const char *DWARFDIE::GetMangledName() const { return nullptr; } +bool DWARFDIE::IsGenericTrampoline() const { + if (IsValid()) + return m_die->GetIsGenericTrampoline(m_cu); + else + return false; +} + const char *DWARFDIE::GetPubname() const { if (IsValid()) return m_die->GetPubname(m_cu); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h index 7ce9550a081e9..db353071546f4 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h @@ -28,6 +28,8 @@ class DWARFDIE : public DWARFBaseDIE { // Accessing information about a DIE const char *GetMangledName() const; + bool IsGenericTrampoline() const; + const char *GetPubname() const; const char *GetQualifiedName(std::string &storage) const; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp index 5a584e15c397d..769ca30e458ea 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp @@ -679,6 +679,12 @@ DWARFDebugInfoEntry::GetMangledName(const DWARFUnit *cu, return name; } +bool +DWARFDebugInfoEntry::GetIsGenericTrampoline(const DWARFUnit *cu) const { + DWARFFormValue form_value; + return GetAttributeValue(cu, DW_AT_trampoline, form_value, nullptr, true) != 0; +} + // GetPubname // // Get value the name for a DIE as it should appear for a .debug_pubnames or diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h index 63d6aa79240b3..3fc9a90d7a129 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h @@ -97,6 +97,8 @@ class DWARFDebugInfoEntry { const char *GetMangledName(const DWARFUnit *cu, bool substitute_name_allowed = true) const; + bool GetIsGenericTrampoline(const DWARFUnit *cu) const; + const char *GetPubname(const DWARFUnit *cu) const; const char *GetQualifiedName(DWARFUnit *cu, std::string &storage) const; diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp index b60e6634fc430..5223f0d122f0b 100644 --- a/lldb/source/Symbol/Function.cpp +++ b/lldb/source/Symbol/Function.cpp @@ -233,10 +233,11 @@ Function *IndirectCallEdge::GetCallee(ModuleList &images, // Function::Function(CompileUnit *comp_unit, lldb::user_id_t func_uid, lldb::user_id_t type_uid, const Mangled &mangled, Type *type, - const AddressRange &range, bool canThrow) + const AddressRange &range, bool canThrow, bool is_generic_trampoline) : UserID(func_uid), m_comp_unit(comp_unit), m_type_uid(type_uid), - m_type(type), m_mangled(mangled), m_block(func_uid), m_range(range), - m_frame_base(), m_flags(), m_prologue_byte_size(0) { + m_type(type), m_mangled(mangled), + m_is_generic_trampoline(is_generic_trampoline), m_block(func_uid), + m_range(range), m_frame_base(), m_flags(), m_prologue_byte_size(0) { m_block.SetParentScope(this); if (canThrow) m_flags.Set(flagsFunctionCanThrow); diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index 8129cf7c421d7..b921118b2e4f3 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -65,6 +65,7 @@ add_lldb_library(lldbTarget ThreadPlanStepOverRange.cpp ThreadPlanStepRange.cpp ThreadPlanStepThrough.cpp + ThreadPlanStepThroughGenericTrampoline.cpp ThreadPlanStepUntil.cpp ThreadPlanTracer.cpp ThreadPlanStack.cpp diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index edc00a9c8e190..f884a9414fd0f 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -5305,6 +5305,12 @@ bool TargetProperties::GetDebugUtilityExpression() const { nullptr, idx, g_target_properties[idx].default_uint_value != 0); } +bool TargetProperties::GetEnableTrampolineSupport() const { + const uint32_t idx = ePropertyEnableTrampolineSupport; + return m_collection_sp->GetPropertyAtIndexAsBoolean( + nullptr, idx, g_target_properties[idx].default_uint_value != 0); +} + void TargetProperties::SetDebugUtilityExpression(bool debug) { const uint32_t idx = ePropertyDebugUtilityExpression; m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, debug); diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td index 2bdc218fd8181..de93957633f7b 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -218,6 +218,9 @@ let Definition = "target" in { def DebugUtilityExpression: Property<"debug-utility-expression", "Boolean">, DefaultFalse, Desc<"Enable debugging of LLDB-internal utility expressions.">; + def EnableTrampolineSupport: Property<"enable-trampoline-support", "Boolean">, + Global, DefaultTrue, + Desc<"Enable trampoline support in LLDB. Trampoline support includes stepping through trampolines directly to their targets, stepping out of trampolines directly to their callers, and automatically filtering out trampolines as possible breakpoint locations when set by name.">; } let Definition = "process_experimental" in { diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 428da24f0fcbb..6de3453f41567 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -41,6 +41,7 @@ #include "lldb/Target/ThreadPlanStepOverBreakpoint.h" #include "lldb/Target/ThreadPlanStepOverRange.h" #include "lldb/Target/ThreadPlanStepThrough.h" +#include "lldb/Target/ThreadPlanStepThroughGenericTrampoline.h" #include "lldb/Target/ThreadPlanStepUntil.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Target/UnwindLLDB.h" @@ -1419,6 +1420,17 @@ ThreadPlanSP Thread::QueueThreadPlanForStepThrough(StackID &return_stack_id, return thread_plan_sp; } +ThreadPlanSP Thread::QueueThreadPlanForStepThroughGenericTrampoline( + bool abort_other_plans, lldb::RunMode stop_other_threads, Status &status) { + ThreadPlanSP thread_plan_sp( + new ThreadPlanStepThroughGenericTrampoline(*this, stop_other_threads)); + + if (!thread_plan_sp || !thread_plan_sp->ValidatePlan(nullptr)) + return ThreadPlanSP(); + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + ThreadPlanSP Thread::QueueThreadPlanForRunToAddress(bool abort_other_plans, Address &target_addr, bool stop_other_threads, diff --git a/lldb/source/Target/ThreadPlanShouldStopHere.cpp b/lldb/source/Target/ThreadPlanShouldStopHere.cpp index a470e969037bd..d59178689f9c1 100644 --- a/lldb/source/Target/ThreadPlanShouldStopHere.cpp +++ b/lldb/source/Target/ThreadPlanShouldStopHere.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "lldb/Target/ThreadPlanShouldStopHere.h" +#include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" @@ -96,10 +97,16 @@ bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback( // independently. If this ever // becomes expensive (this one isn't) we can try to have this set a state // that the StepFromHere can use. - if (frame) { - SymbolContext sc; - sc = frame->GetSymbolContext(eSymbolContextLineEntry); - if (sc.line_entry.line == 0) + SymbolContext sc; + sc = frame->GetSymbolContext(eSymbolContextLineEntry); + + if (sc.line_entry.line == 0) + should_stop_here = false; + + // If we're in a trampoline, don't stop by default. + if (Target::GetGlobalProperties().GetEnableTrampolineSupport()) { + sc = frame->GetSymbolContext(lldb::eSymbolContextFunction); + if (sc.function && sc.function->IsGenericTrampoline()) should_stop_here = false; } diff --git a/lldb/source/Target/ThreadPlanStepInRange.cpp b/lldb/source/Target/ThreadPlanStepInRange.cpp index 3e1cd4910949f..d679d9587dc53 100644 --- a/lldb/source/Target/ThreadPlanStepInRange.cpp +++ b/lldb/source/Target/ThreadPlanStepInRange.cpp @@ -221,6 +221,17 @@ bool ThreadPlanStepInRange::ShouldStop(Event *event_ptr) { // We may have set the plan up above in the FrameIsOlder section: + if (!m_sub_plan_sp) + m_sub_plan_sp = thread.QueueThreadPlanForStepThroughGenericTrampoline( + false, m_stop_others, m_status); + if (log) { + if (m_sub_plan_sp) + LLDB_LOGF(log, "Found a generic step through plan: %s", + m_sub_plan_sp->GetName()); + else + LLDB_LOGF(log, "No generic step through plan found."); + } + if (!m_sub_plan_sp) m_sub_plan_sp = thread.QueueThreadPlanForStepThrough( m_stack_id, false, stop_others, m_status); diff --git a/lldb/source/Target/ThreadPlanStepOverRange.cpp b/lldb/source/Target/ThreadPlanStepOverRange.cpp index b1cb070e0a3d0..0db2fe855854d 100644 --- a/lldb/source/Target/ThreadPlanStepOverRange.cpp +++ b/lldb/source/Target/ThreadPlanStepOverRange.cpp @@ -334,37 +334,6 @@ bool ThreadPlanStepOverRange::ShouldStop(Event *event_ptr) { return false; } -bool ThreadPlanStepOverRange::DoPlanExplainsStop(Event *event_ptr) { - // For crashes, breakpoint hits, signals, etc, let the base plan (or some - // plan above us) handle the stop. That way the user can see the stop, step - // around, and then when they are done, continue and have their step - // complete. The exception is if we've hit our "run to next branch" - // breakpoint. Note, unlike the step in range plan, we don't mark ourselves - // complete if we hit an unexplained breakpoint/crash. - - Log *log = GetLog(LLDBLog::Step); - StopInfoSP stop_info_sp = GetPrivateStopInfo(); - bool return_value; - - if (stop_info_sp) { - StopReason reason = stop_info_sp->GetStopReason(); - - if (reason == eStopReasonTrace) { - return_value = true; - } else if (reason == eStopReasonBreakpoint) { - return_value = NextRangeBreakpointExplainsStop(stop_info_sp); - } else { - if (log) - log->PutCString("ThreadPlanStepInRange got asked if it explains the " - "stop for some reason other than step."); - return_value = false; - } - } else - return_value = true; - - return return_value; -} - bool ThreadPlanStepOverRange::DoWillResume(lldb::StateType resume_state, bool current_plan) { if (resume_state != eStateSuspended && m_first_resume) { diff --git a/lldb/source/Target/ThreadPlanStepRange.cpp b/lldb/source/Target/ThreadPlanStepRange.cpp index e2323bb31577c..409590db90b1a 100644 --- a/lldb/source/Target/ThreadPlanStepRange.cpp +++ b/lldb/source/Target/ThreadPlanStepRange.cpp @@ -494,3 +494,35 @@ bool ThreadPlanStepRange::IsPlanStale() { } return false; } + + +bool ThreadPlanStepRange::DoPlanExplainsStop(Event *event_ptr) { + // For crashes, breakpoint hits, signals, etc, let the base plan (or some + // plan above us) handle the stop. That way the user can see the stop, step + // around, and then when they are done, continue and have their step + // complete. The exception is if we've hit our "run to next branch" + // breakpoint. Note, unlike the step in range plan, we don't mark ourselves + // complete if we hit an unexplained breakpoint/crash. + + Log *log = GetLog(LLDBLog::Step); + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + bool return_value; + + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + + if (reason == eStopReasonTrace) { + return_value = true; + } else if (reason == eStopReasonBreakpoint) { + return_value = NextRangeBreakpointExplainsStop(stop_info_sp); + } else { + if (log) + log->PutCString("ThreadPlanStepRange got asked if it explains the " + "stop for some reason other than step."); + return_value = false; + } + } else + return_value = true; + + return return_value; +} diff --git a/lldb/source/Target/ThreadPlanStepThrough.cpp b/lldb/source/Target/ThreadPlanStepThrough.cpp index e195abf7e6526..0856aa506e569 100644 --- a/lldb/source/Target/ThreadPlanStepThrough.cpp +++ b/lldb/source/Target/ThreadPlanStepThrough.cpp @@ -33,6 +33,10 @@ ThreadPlanStepThrough::ThreadPlanStepThrough(Thread &thread, m_start_address(0), m_backstop_bkpt_id(LLDB_INVALID_BREAK_ID), m_backstop_addr(LLDB_INVALID_ADDRESS), m_return_stack_id(m_stack_id), m_stop_others(stop_others) { + // If trampoline support is disabled, there's nothing for us to do. + if (!Target::GetGlobalProperties().GetEnableTrampolineSupport()) + return; + LookForPlanToStepThroughFromCurrentPC(); // If we don't get a valid step through plan, don't bother to set up a diff --git a/lldb/source/Target/ThreadPlanStepThroughGenericTrampoline.cpp b/lldb/source/Target/ThreadPlanStepThroughGenericTrampoline.cpp new file mode 100644 index 0000000000000..769eee476967d --- /dev/null +++ b/lldb/source/Target/ThreadPlanStepThroughGenericTrampoline.cpp @@ -0,0 +1,137 @@ +//===-- ThreadPlanStepThroughGenericTrampoline.cpp +//-----------------------------------------===// +// +// 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 "lldb/Target/ThreadPlanStepThroughGenericTrampoline.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +uint32_t ThreadPlanStepThroughGenericTrampoline::s_default_flag_values = + ThreadPlanShouldStopHere::eStepInAvoidNoDebug; + +ThreadPlanStepThroughGenericTrampoline::ThreadPlanStepThroughGenericTrampoline( + Thread &thread, lldb::RunMode stop_others) + : ThreadPlanStepRange(ThreadPlan::eKindStepThroughGenericTrampoline, + "Step through generic trampoline", thread, {}, {}, + stop_others), + ThreadPlanShouldStopHere(this) { + + SetFlagsToDefault(); + auto frame = GetThread().GetFrameWithStackID(m_stack_id); + if (!frame) + return; + SymbolContext sc = frame->GetSymbolContext(eSymbolContextFunction); + + if (!sc.function) + return; + AddRange(sc.function->GetAddressRange()); +} + +ThreadPlanStepThroughGenericTrampoline:: + ~ThreadPlanStepThroughGenericTrampoline() = default; + +void ThreadPlanStepThroughGenericTrampoline::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + + auto PrintFailureIfAny = [&]() { + if (m_status.Success()) + return; + s->Printf(" failed (%s)", m_status.AsCString()); + }; + + if (level == lldb::eDescriptionLevelBrief) { + s->Printf("step through generic trampoline"); + PrintFailureIfAny(); + return; + } + + auto frame = GetThread().GetFrameWithStackID(m_stack_id); + if (!frame) { + s->Printf(""); + return; + } + + SymbolContext sc = frame->GetSymbolContext(eSymbolContextFunction); + if (!sc.function) { + s->Printf(""); + return; + } + + s->Printf("Stepping through generic trampoline %s", + sc.function->GetName().AsCString()); + + lldb::StackFrameSP curr_frame = GetThread().GetStackFrameAtIndex(0); + if (!curr_frame) + return; + + SymbolContext curr_frame_sc = + curr_frame->GetSymbolContext(eSymbolContextFunction); + if (!curr_frame_sc.function) + return; + s->Printf(", current function: %s", + curr_frame_sc.function->GetName().GetCString()); + + PrintFailureIfAny(); + + s->PutChar('.'); +} + +bool ThreadPlanStepThroughGenericTrampoline::ShouldStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Step); + + if (log) { + StreamString s; + DumpAddress(s.AsRawOstream(), GetThread().GetRegisterContext()->GetPC(), + GetTarget().GetArchitecture().GetAddressByteSize()); + LLDB_LOGF(log, "ThreadPlanStepThroughGenericTrampoline reached %s.", + s.GetData()); + } + + if (IsPlanComplete()) + return true; + + m_no_more_plans = false; + + Thread &thread = GetThread(); + lldb::StackFrameSP curr_frame = thread.GetStackFrameAtIndex(0); + if (!curr_frame) + return false; + + SymbolContext sc = curr_frame->GetSymbolContext(eSymbolContextFunction); + + if (sc.function && sc.function->IsGenericTrampoline() && + SetNextBranchBreakpoint()) { + // While whatever frame we're in is a generic trampoline, + // continue stepping to the next branch, until we + // end up in a function which isn't a trampoline. + return false; + } + + m_no_more_plans = true; + SetPlanComplete(); + return true; +} + +bool ThreadPlanStepThroughGenericTrampoline::ValidatePlan(Stream *error) { + // If trampoline support is disabled, there's nothing for us to do. + if (!Target::GetGlobalProperties().GetEnableTrampolineSupport()) + return false; + + auto frame = GetThread().GetFrameWithStackID(m_stack_id); + if (!frame) + return false; + + SymbolContext sc = frame->GetSymbolContext(eSymbolContextFunction); + return sc.function && sc.function->IsGenericTrampoline(); +} diff --git a/lldb/test/API/lang/c/trampoline_stepping/Makefile b/lldb/test/API/lang/c/trampoline_stepping/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py b/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py new file mode 100644 index 0000000000000..4531a727c4900 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py @@ -0,0 +1,104 @@ +"""Test that stepping in/out of trampolines works as expected. +""" + + + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestTrampoline(TestBase): + def setup(self, bkpt_str): + self.build() + + _, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, bkpt_str, lldb.SBFileSpec('main.c')) + return thread + + def test_direct_call(self): + thread = self.setup('Break here for direct') + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn('direct_trampoline_call', name) + + # Check that stepping in will take us directly to the trampoline target. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn('foo', name) + + # Check that stepping out takes us back to the trampoline caller. + thread.StepOut() + name = thread.frames[0].GetFunctionName() + self.assertIn('direct_trampoline_call', name) + + # Check that stepping over the end of the trampoline target + # takes us back to the trampoline caller. + thread.StepInto() + thread.StepOver() + name = thread.frames[0].GetFunctionName() + self.assertIn('direct_trampoline_call', name) + + + def test_chained_call(self): + thread = self.setup('Break here for chained') + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn('chained_trampoline_call', name) + + # Check that stepping in will take us directly to the trampoline target. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn('foo', name) + + # Check that stepping out takes us back to the trampoline caller. + thread.StepOut() + name = thread.frames[0].GetFunctionName() + self.assertIn('chained_trampoline_call', name) + + # Check that stepping over the end of the trampoline target + # takes us back to the trampoline caller. + thread.StepInto() + thread.StepOver() + name = thread.frames[0].GetFunctionName() + self.assertIn('chained_trampoline_call', name) + + def test_trampoline_after_nodebug(self): + thread = self.setup('Break here for nodebug then trampoline') + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn('trampoline_after_nodebug', name) + + # Check that stepping in will take us directly to the trampoline target. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn('foo', name) + + # Check that stepping out takes us back to the trampoline caller. + thread.StepOut() + name = thread.frames[0].GetFunctionName() + self.assertIn('trampoline_after_nodebug', name) + + # Check that stepping over the end of the trampoline target + # takes us back to the trampoline caller. + thread.StepInto() + thread.StepOver() + name = thread.frames[0].GetFunctionName() + self.assertIn('trampoline_after_nodebug', name) + + def test_unused_target(self): + thread = self.setup('Break here for unused') + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn('unused_target', name) + + # Check that stepping into a trampoline that doesn't call its target + # jumps back to its caller. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn('unused_target', name) + diff --git a/lldb/test/API/lang/c/trampoline_stepping/main.c b/lldb/test/API/lang/c/trampoline_stepping/main.c new file mode 100644 index 0000000000000..cb98be00ca1f6 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/main.c @@ -0,0 +1,52 @@ +void foo(void) {} + +__attribute__((transparent_stepping)) +void bar(void) { + foo(); +} + +__attribute__((transparent_stepping)) +void baz(void) { + bar(); +} + +__attribute__((nodebug)) +void nodebug(void) {} + +__attribute__((transparent_stepping)) +void nodebug_then_trampoline(void) { + nodebug(); + baz(); +} + +__attribute__((transparent_stepping)) +void doesnt_call_trampoline(void) {} + +void direct_trampoline_call(void) { + bar(); // Break here for direct + bar(); +} + +void chained_trampoline_call(void) { + baz(); // Break here for chained + baz(); +} + +void trampoline_after_nodebug(void) { + nodebug_then_trampoline(); // Break here for nodebug then trampoline + nodebug_then_trampoline(); +} + +void unused_target(void) { + doesnt_call_trampoline(); // Break here for unused +} + + +int main(void) { + direct_trampoline_call(); + chained_trampoline_call(); + trampoline_after_nodebug(); + unused_target(); + return 0; +} + diff --git a/llvm/include/llvm/IR/DebugInfoFlags.def b/llvm/include/llvm/IR/DebugInfoFlags.def index df375b6c68e81..6e9be38e7272e 100644 --- a/llvm/include/llvm/IR/DebugInfoFlags.def +++ b/llvm/include/llvm/IR/DebugInfoFlags.def @@ -91,11 +91,12 @@ HANDLE_DISP_FLAG((1u << 8), MainSubprogram) // for defaulted functions HANDLE_DISP_FLAG((1u << 9), Deleted) HANDLE_DISP_FLAG((1u << 11), ObjCDirect) +HANDLE_DISP_FLAG((1u << 12), IsTransparentStepping) #ifdef DISP_FLAG_LARGEST_NEEDED // Intended to be used with ADT/BitmaskEnum.h. // NOTE: Always must be equal to largest flag, check this when adding new flags. -HANDLE_DISP_FLAG((1 << 11), Largest) +HANDLE_DISP_FLAG((1 << 12), Largest) #undef DISP_FLAG_LARGEST_NEEDED #endif diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h index b8f10fa31c7b0..e4392bc286697 100644 --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -2025,6 +2025,9 @@ class DISubprogram : public DILocalScope { bool isElemental() const { return getSPFlags() & SPFlagElemental; } bool isRecursive() const { return getSPFlags() & SPFlagRecursive; } bool isObjCDirect() const { return getSPFlags() & SPFlagObjCDirect; } + bool getIsTransparentStepping() const { + return getSPFlags() & SPFlagIsTransparentStepping; + } /// Check if this is deleted member function. /// diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp index 3c9736d382951..4ee848e1713f8 100644 --- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp @@ -1362,6 +1362,9 @@ void DwarfUnit::applySubprogramAttributes(const DISubprogram *SP, DIE &SPDie, if (!SP->getTargetFuncName().empty()) addString(SPDie, dwarf::DW_AT_trampoline, SP->getTargetFuncName()); + if (SP->getIsTransparentStepping()) + addFlag(SPDie, dwarf::DW_AT_trampoline); + if (DD->getDwarfVersion() >= 5 && SP->isDeleted()) addFlag(SPDie, dwarf::DW_AT_deleted); } diff --git a/llvm/test/Assembler/disubprogram-transparent-stepping.ll b/llvm/test/Assembler/disubprogram-transparent-stepping.ll new file mode 100644 index 0000000000000..51550c5eb1bc2 --- /dev/null +++ b/llvm/test/Assembler/disubprogram-transparent-stepping.ll @@ -0,0 +1,39 @@ +; This test verifies that the DISPFlagIsTransparentStepping attribute in a DISubprogram +; is assembled/disassembled correctly. +; +; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s +; +; CHECK: !DISubprogram(name: "baz",{{.*}} DISPFlagIsTransparentStepping +; +; ModuleID = 't.c' +source_filename = "t.c" +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-macosx13.0.0" + +; Function Attrs: noinline nounwind optnone ssp uwtable(sync) +define void @baz() #0 !dbg !10 { +entry: + ret void, !dbg !14 +} + +attributes #0 = { noinline nounwind optnone ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } + +!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6} +!llvm.dbg.cu = !{!7} +!llvm.ident = !{!9} + +!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 2]} +!1 = !{i32 7, !"Dwarf Version", i32 4} +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = !{i32 1, !"wchar_size", i32 4} +!4 = !{i32 8, !"PIC Level", i32 2} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{i32 7, !"frame-pointer", i32 1} +!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !8, producer: "clang version 17.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!8 = !DIFile(filename: "t.c", directory: "/") +!9 = !{!"clang version 17.0.0"} +!10 = distinct !DISubprogram(name: "baz", scope: !8, file: !8, line: 3, type: !11, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagIsTransparentStepping, unit: !7, retainedNodes: !13) +!11 = !DISubroutineType(types: !12) +!12 = !{null} +!13 = !{} +!14 = !DILocation(line: 4, column: 1, scope: !10) diff --git a/llvm/test/DebugInfo/AArch64/disubprogram-transparent-stepping.ll b/llvm/test/DebugInfo/AArch64/disubprogram-transparent-stepping.ll new file mode 100644 index 0000000000000..0e93af5b4c728 --- /dev/null +++ b/llvm/test/DebugInfo/AArch64/disubprogram-transparent-stepping.ll @@ -0,0 +1,42 @@ +; This test verifies that the proper DWARF debug info is emitted +; for a trampoline function with no target. +; +; RUN: llc -filetype=obj %s -o - | llvm-dwarfdump - | FileCheck %s +; +; CHECK: DW_TAG_subprogram +; CHECK: DW_AT_name ("baz") +; CHECK: DW_AT_trampoline (true) +; +; ModuleID = 't.c' +source_filename = "t.c" +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-macosx13.0.0" + +; Function Attrs: noinline nounwind optnone ssp uwtable(sync) +define void @baz() #0 !dbg !10 { +entry: + ret void, !dbg !14 +} + +attributes #0 = { noinline nounwind optnone ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } + +!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6} +!llvm.dbg.cu = !{!7} +!llvm.ident = !{!9} + +!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 2]} +!1 = !{i32 7, !"Dwarf Version", i32 4} +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = !{i32 1, !"wchar_size", i32 4} +!4 = !{i32 8, !"PIC Level", i32 2} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{i32 7, !"frame-pointer", i32 1} +!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !8, producer: "clang version 17.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!8 = !DIFile(filename: "t.c", directory: "/") +!9 = !{!"clang version 17.0.0"} +!10 = distinct !DISubprogram(name: "baz", scope: !8, file: !8, line: 3, type: !11, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagIsTransparentStepping, unit: !7, retainedNodes: !13) +!11 = !DISubroutineType(types: !12) +!12 = !{null} +!13 = !{} +!14 = !DILocation(line: 4, column: 1, scope: !10) + diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp index 23d7db2394119..20b9cb3dda008 100644 --- a/llvm/unittests/IR/MetadataTest.cpp +++ b/llvm/unittests/IR/MetadataTest.cpp @@ -2319,6 +2319,7 @@ TEST_F(DISubprogramTest, get) { assert(!IsLocalToUnit && IsDefinition && !IsOptimized && "bools and SPFlags have to match"); SPFlags |= DISubprogram::SPFlagDefinition; + SPFlags |= DISubprogram::SPFlagIsTransparentStepping; auto *N = DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, ScopeLine, @@ -2402,12 +2403,17 @@ TEST_F(DISubprogramTest, get) { Flags, SPFlags ^ DISubprogram::SPFlagDefinition, Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes, Annotations, TargetFuncName)); - EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, - Type, ScopeLine + 1, ContainingType, - VirtualIndex, ThisAdjustment, Flags, SPFlags, - Unit, TemplateParams, Declaration, - RetainedNodes, ThrownTypes, Annotations, - TargetFuncName)); + EXPECT_NE(N, DISubprogram::get( + Context, Scope, Name, LinkageName, File, Line, Type, + ScopeLine, ContainingType, VirtualIndex, ThisAdjustment, + Flags, SPFlags ^ DISubprogram::SPFlagIsTransparentStepping, + Unit, TemplateParams, Declaration, RetainedNodes, + ThrownTypes, Annotations, TargetFuncName)); + EXPECT_NE(N, DISubprogram::get( + Context, Scope, Name, LinkageName, File, Line, Type, + ScopeLine + 1, ContainingType, VirtualIndex, ThisAdjustment, + Flags, SPFlags, Unit, TemplateParams, Declaration, + RetainedNodes, ThrownTypes, Annotations, TargetFuncName)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, ScopeLine, getCompositeType(), VirtualIndex, ThisAdjustment, Flags, SPFlags,