diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h --- a/lldb/include/lldb/Symbol/Function.h +++ b/lldb/include/lldb/Symbol/Function.h @@ -436,9 +436,13 @@ /// /// \param[in] range /// The section offset based address for this function. + /// + /// \param[in] trampoline_target + /// The symbol name this function should trampoline to. 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); + Type *func_type, const AddressRange &range, + llvm::StringRef trampoline_target = {}); /// Destructor. ~Function() override; @@ -550,6 +554,14 @@ /// A type object pointer. Type *GetType(); + ConstString GetTrampolineTargetName() const { + return m_trampoline_target; + } + + bool IsTrampoline() const { + return !m_trampoline_target.IsEmpty(); + } + /// Get const accessor for the type that describes the function return value /// type, and parameter types. /// @@ -650,6 +662,9 @@ /// information. Mangled m_mangled; + /// The symbol name this function should trampoline to. + ConstString m_trampoline_target; + /// 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 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -250,6 +250,12 @@ 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/ThreadPlanRunToAddress.h b/lldb/include/lldb/Target/ThreadPlanRunToAddress.h --- a/lldb/include/lldb/Target/ThreadPlanRunToAddress.h +++ b/lldb/include/lldb/Target/ThreadPlanRunToAddress.h @@ -45,6 +45,12 @@ bool MischiefManaged() override; + /// Finds all the addresses the symbol name resolves to, and make a thread + /// plan that runs to them. + static lldb::ThreadPlanSP + MakeThreadPlanRunToAddressFromSymbol(Thread &thread, ConstString symbol_name, + bool stop_others); + protected: bool DoPlanExplainsStop(Event *event_ptr) override; diff --git a/lldb/include/lldb/Target/ThreadPlanStepThrough.h b/lldb/include/lldb/Target/ThreadPlanStepThrough.h --- a/lldb/include/lldb/Target/ThreadPlanStepThrough.h +++ b/lldb/include/lldb/Target/ThreadPlanStepThrough.h @@ -44,6 +44,8 @@ bool abort_other_plans, bool stop_others, Status &status); + lldb::ThreadPlanSP LookForFunctionWithTrampolineTarget(); + void ClearBackstopBreakpoint(); lldb::ThreadPlanSP m_sub_plan_sp; diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -776,7 +776,12 @@ if (!sc_list.GetContextAtIndex(i, sc)) break; + bool is_trampoline = + Target::GetGlobalProperties().GetEnableTrampolineSupport() && + sc.function && sc.function->IsTrampoline(); + bool keep_it = + !is_trampoline && NameMatchesLookupInfo(sc.GetFunctionName(), sc.GetLanguage()); if (keep_it) ++i; diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp --- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp +++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp @@ -882,89 +882,10 @@ ConstString trampoline_name = current_symbol->GetMangled().GetName(Mangled::ePreferMangled); - if (trampoline_name) { - const ModuleList &images = target_sp->GetImages(); - - SymbolContextList code_symbols; - images.FindSymbolsWithNameAndType(trampoline_name, eSymbolTypeCode, - code_symbols); - size_t num_code_symbols = code_symbols.GetSize(); - - if (num_code_symbols > 0) { - for (uint32_t i = 0; i < num_code_symbols; i++) { - SymbolContext context; - AddressRange addr_range; - if (code_symbols.GetContextAtIndex(i, context)) { - context.GetAddressRange(eSymbolContextEverything, 0, false, - addr_range); - addresses.push_back(addr_range.GetBaseAddress()); - if (log) { - addr_t load_addr = - addr_range.GetBaseAddress().GetLoadAddress(target_sp.get()); - - LLDB_LOGF(log, - "Found a trampoline target symbol at 0x%" PRIx64 ".", - load_addr); - } - } - } - } - - SymbolContextList reexported_symbols; - images.FindSymbolsWithNameAndType( - trampoline_name, eSymbolTypeReExported, reexported_symbols); - size_t num_reexported_symbols = reexported_symbols.GetSize(); - if (num_reexported_symbols > 0) { - for (uint32_t i = 0; i < num_reexported_symbols; i++) { - SymbolContext context; - if (reexported_symbols.GetContextAtIndex(i, context)) { - if (context.symbol) { - Symbol *actual_symbol = - context.symbol->ResolveReExportedSymbol(*target_sp.get()); - if (actual_symbol) { - const Address actual_symbol_addr = - actual_symbol->GetAddress(); - if (actual_symbol_addr.IsValid()) { - addresses.push_back(actual_symbol_addr); - if (log) { - lldb::addr_t load_addr = - actual_symbol_addr.GetLoadAddress(target_sp.get()); - LLDB_LOGF( - log, - "Found a re-exported symbol: %s at 0x%" PRIx64 ".", - actual_symbol->GetName().GetCString(), load_addr); - } - } - } - } - } - } - } - - SymbolContextList indirect_symbols; - images.FindSymbolsWithNameAndType(trampoline_name, eSymbolTypeResolver, - indirect_symbols); - size_t num_indirect_symbols = indirect_symbols.GetSize(); - if (num_indirect_symbols > 0) { - for (uint32_t i = 0; i < num_indirect_symbols; i++) { - SymbolContext context; - AddressRange addr_range; - if (indirect_symbols.GetContextAtIndex(i, context)) { - context.GetAddressRange(eSymbolContextEverything, 0, false, - addr_range); - addresses.push_back(addr_range.GetBaseAddress()); - if (log) { - addr_t load_addr = - addr_range.GetBaseAddress().GetLoadAddress(target_sp.get()); - - LLDB_LOGF(log, - "Found an indirect target symbol at 0x%" PRIx64 ".", - load_addr); - } - } - } - } - } + if (trampoline_name) + thread_plan_sp = + ThreadPlanRunToAddress::MakeThreadPlanRunToAddressFromSymbol( + thread, trampoline_name, stop_others); } else if (current_symbol->GetType() == eSymbolTypeReExported) { // I am not sure we could ever end up stopped AT a re-exported symbol. // But just in case: diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -2440,12 +2440,15 @@ assert(func_type == nullptr || func_type != DIE_IS_BEING_PARSED); + const char *trampoline_target = die.GetTrampolineTargetName(); + 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 + trampoline_target); if (func_sp.get() != nullptr) { if (frame_base.IsValid()) diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.h @@ -28,6 +28,8 @@ // Accessing information about a DIE const char *GetMangledName() const; + const char *GetTrampolineTargetName() const; + const char *GetPubname() const; using DWARFBaseDIE::GetName; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp @@ -203,6 +203,14 @@ return nullptr; } + +const char *DWARFDIE::GetTrampolineTargetName() const { + if (IsValid()) + return m_die->GetTrampolineTargetName(m_cu); + else + return nullptr; +} + const char *DWARFDIE::GetPubname() const { if (IsValid()) return m_die->GetPubname(m_cu); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.h @@ -99,6 +99,8 @@ const char *GetMangledName(const DWARFUnit *cu, bool substitute_name_allowed = true) const; + const char *GetTrampolineTargetName(const DWARFUnit *cu) const; + const char *GetPubname(const DWARFUnit *cu) const; bool GetDIENamesAndRanges( diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDebugInfoEntry.cpp @@ -685,6 +685,10 @@ return name; } +const char * +DWARFDebugInfoEntry::GetTrampolineTargetName(const DWARFUnit *cu) const { + return GetAttributeValueAsString(cu, DW_AT_trampoline, nullptr, true); +} // GetPubname // // Get value the name for a DIE as it should appear for a .debug_pubnames or diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp --- a/lldb/source/Symbol/Function.cpp +++ b/lldb/source/Symbol/Function.cpp @@ -231,10 +231,12 @@ // 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) + const AddressRange &range, + llvm::StringRef trampoline_target) : 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_trampoline_target(trampoline_target), + m_block(func_uid), m_range(range), m_frame_base(), m_flags(), + m_prologue_byte_size(0) { m_block.SetParentScope(this); assert(comp_unit != nullptr); } diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -4770,6 +4770,12 @@ 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 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -182,6 +182,9 @@ 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/ThreadPlanRunToAddress.cpp b/lldb/source/Target/ThreadPlanRunToAddress.cpp --- a/lldb/source/Target/ThreadPlanRunToAddress.cpp +++ b/lldb/source/Target/ThreadPlanRunToAddress.cpp @@ -201,3 +201,100 @@ } return found_it; } + +ThreadPlanSP ThreadPlanRunToAddress::MakeThreadPlanRunToAddressFromSymbol( + Thread &thread, ConstString symbol_name, bool stop_others) { + TargetSP target_sp(thread.CalculateTarget()); + if (!target_sp) + return {}; + + Log *log = GetLog(LLDBLog::Step); + std::vector
addresses; + const ModuleList &images = target_sp->GetImages(); + + SymbolContextList code_symbols; + images.FindSymbolsWithNameAndType(symbol_name, eSymbolTypeCode, code_symbols); + const size_t num_code_symbols = code_symbols.GetSize(); + + if (num_code_symbols > 0) { + SymbolContext context; + AddressRange addr_range; + for (uint32_t i = 0; i < num_code_symbols; i++) { + context.Clear(true); + if (code_symbols.GetContextAtIndex(i, context)) { + addr_range.Clear(); + context.GetAddressRange(eSymbolContextEverything, 0, false, addr_range); + addresses.push_back(addr_range.GetBaseAddress()); + LLDB_LOG(log, "Found a trampoline target symbol at {0:x}.", + addr_range.GetBaseAddress().GetLoadAddress(target_sp.get())); + } + } + } + + SymbolContextList reexported_symbols; + images.FindSymbolsWithNameAndType(symbol_name, eSymbolTypeReExported, + reexported_symbols); + size_t num_reexported_symbols = reexported_symbols.GetSize(); + if (num_reexported_symbols > 0) { + for (uint32_t i = 0; i < num_reexported_symbols; i++) { + SymbolContext context; + if (reexported_symbols.GetContextAtIndex(i, context)) { + if (context.symbol) { + Symbol *actual_symbol = + context.symbol->ResolveReExportedSymbol(*target_sp.get()); + if (actual_symbol) { + const Address actual_symbol_addr = actual_symbol->GetAddress(); + if (actual_symbol_addr.IsValid()) { + addresses.push_back(actual_symbol_addr); + actual_symbol_addr.GetLoadAddress(target_sp.get()); + LLDB_LOGF(log, "Found a re-exported symbol: %s at 0x%" PRIx64 ".", + actual_symbol->GetName().GetCString(), + actual_symbol_addr.GetLoadAddress(target_sp.get())); + } + } + } + } + } + } + + SymbolContextList indirect_symbols; + images.FindSymbolsWithNameAndType(symbol_name, eSymbolTypeResolver, + indirect_symbols); + size_t num_indirect_symbols = indirect_symbols.GetSize(); + if (num_indirect_symbols > 0) { + for (uint32_t i = 0; i < num_indirect_symbols; i++) { + SymbolContext context; + AddressRange addr_range; + if (indirect_symbols.GetContextAtIndex(i, context)) { + context.GetAddressRange(eSymbolContextEverything, 0, false, addr_range); + addresses.push_back(addr_range.GetBaseAddress()); + LLDB_LOGF(log, "Found an indirect target symbol at 0x%" PRIx64 ".", + addr_range.GetBaseAddress().GetLoadAddress(target_sp.get())); + } + } + } + + if (addresses.empty()) + return {}; + + // First check whether any of the addresses point to Indirect symbols, + // and if they do, resolve them: + std::vector load_addrs; + for (Address address : addresses) { + Symbol *symbol = address.CalculateSymbolContextSymbol(); + if (symbol && symbol->IsIndirect()) { + Status error; + Address symbol_address = symbol->GetAddress(); + addr_t resolved_addr = + thread.GetProcess()->ResolveIndirectFunction(&symbol_address, error); + if (error.Success()) { + load_addrs.push_back(resolved_addr); + } + } else { + load_addrs.push_back(address.GetLoadAddress(target_sp.get())); + } + } + + return std::make_shared(thread, load_addrs, + stop_others); +} diff --git a/lldb/source/Target/ThreadPlanShouldStopHere.cpp b/lldb/source/Target/ThreadPlanShouldStopHere.cpp --- 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/RegisterContext.h" #include "lldb/Target/Thread.h" @@ -81,10 +82,16 @@ // 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->IsTrampoline()) should_stop_here = false; } diff --git a/lldb/source/Target/ThreadPlanStepThrough.cpp b/lldb/source/Target/ThreadPlanStepThrough.cpp --- a/lldb/source/Target/ThreadPlanStepThrough.cpp +++ b/lldb/source/Target/ThreadPlanStepThrough.cpp @@ -8,11 +8,13 @@ #include "lldb/Target/ThreadPlanStepThrough.h" #include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Symbol/Function.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/Target.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Stream.h" @@ -33,6 +35,10 @@ 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 @@ -95,6 +101,9 @@ } } + if (!m_sub_plan_sp) + m_sub_plan_sp = LookForFunctionWithTrampolineTarget(); + Log *log = GetLog(LLDBLog::Step); if (log) { lldb::addr_t current_address = GetThread().GetRegisterContext()->GetPC(0); @@ -111,6 +120,32 @@ } } +ThreadPlanSP ThreadPlanStepThrough::LookForFunctionWithTrampolineTarget() { + Thread &thread = GetThread(); + TargetSP target_sp(thread.CalculateTarget()); + if (!target_sp) + return {}; + + StackFrameSP current_frame = thread.GetStackFrameAtIndex(0); + if (!current_frame) + return {}; + + const SymbolContext ¤t_context = + current_frame->GetSymbolContext(lldb::eSymbolContextFunction); + Function *current_function = current_context.function; + if (!current_function) + return {}; + + ConstString trampoline_name = + ConstString(current_function->GetTrampolineTargetName()); + if (trampoline_name.IsEmpty()) + return {}; + + return ThreadPlanRunToAddress::MakeThreadPlanRunToAddressFromSymbol( + thread, trampoline_name, m_stop_others); + ; +} + void ThreadPlanStepThrough::GetDescription(Stream *s, lldb::DescriptionLevel level) { if (level == lldb::eDescriptionLevelBrief) diff --git a/lldb/test/API/lang/c/trampoline_stepping/Makefile b/lldb/test/API/lang/c/trampoline_stepping/Makefile new file mode 100644 --- /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 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py @@ -0,0 +1,80 @@ +"""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_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 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/main.c @@ -0,0 +1,41 @@ +void foo(void) {} + +__attribute__((__trampoline__("foo"))) +void bar(void) { + foo(); +} + +__attribute__((__trampoline__("bar"))) +void baz(void) { + bar(); +} + +__attribute__((__trampoline__("bar"))) +void doesnt_call_trampoline(void) { + int a = 2; + int b = 3; + int c = a + b; +} + + +void direct_trampoline_call(void) { + bar(); // Break here for direct + bar(); +} + +void chained_trampoline_call(void) { + baz(); // Break here for chained + baz(); +} + +void unused_target(void) { + doesnt_call_trampoline(); // Break here for unused +} + +int main(void) { + direct_trampoline_call(); + chained_trampoline_call(); + unused_target(); + return 0; +} +