diff --git a/loc/lci/OpenFolderSchema.json.lci b/loc/lci/OpenFolderSchema.json.lci index e3d3ac8b4..10049e79b 100644 --- a/loc/lci/OpenFolderSchema.json.lci +++ b/loc/lci/OpenFolderSchema.json.lci @@ -388,6 +388,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MICore/JsonLaunchOptions.cs b/src/MICore/JsonLaunchOptions.cs index 6277a5633..6f872157e 100644 --- a/src/MICore/JsonLaunchOptions.cs +++ b/src/MICore/JsonLaunchOptions.cs @@ -122,6 +122,12 @@ public abstract partial class BaseOptions /// [JsonProperty("unknownBreakpointHandling", DefaultValueHandling = DefaultValueHandling.Ignore)] public UnknownBreakpointHandling? UnknownBreakpointHandling { get; set; } + + /// + /// Controls GDB's debuginfod behavior. + /// + [JsonProperty("debuginfod", DefaultValueHandling = DefaultValueHandling.Ignore)] + public DebuginfodSettings Debuginfod { get; set; } } internal class VisualizerFileConverter : JsonConverter @@ -315,6 +321,21 @@ public enum UnknownBreakpointHandling Stop } + public partial class DebuginfodSettings + { + /// + /// If true (default), GDB's debuginfod support is enabled. + /// + [JsonProperty("enabled")] + public bool? Enabled { get; set; } + + /// + /// The timeout in seconds for debuginfod server requests. Default is 30. Set to 0 for no override. + /// + [JsonProperty("timeout", DefaultValueHandling = DefaultValueHandling.Ignore)] + public int? Timeout { get; set; } + } + public partial class LaunchOptions : BaseOptions { #region Public Properties for Serialization diff --git a/src/MICore/LaunchOptions.cs b/src/MICore/LaunchOptions.cs index dbe934817..c53bc89d0 100644 --- a/src/MICore/LaunchOptions.cs +++ b/src/MICore/LaunchOptions.cs @@ -137,6 +137,8 @@ static internal PipeLaunchOptions CreateFromJson(JObject parsedOptions) quoteArgs = platformSpecificTransportOptions.QuoteArgs ?? quoteArgs; } + Json.LaunchOptions.BaseOptions baseOptions = Json.LaunchOptions.LaunchOptionHelpers.GetLaunchOrAttachOptions(parsedOptions); + PipeLaunchOptions pipeOptions = new PipeLaunchOptions( pipePath: pipeProgram, pipeArguments: EnsurePipeArguments(pipeArgs, debuggerPath, gdbPathDefault, quoteArgs), @@ -145,7 +147,6 @@ static internal PipeLaunchOptions CreateFromJson(JObject parsedOptions) pipeEnvironment: GetEnvironmentEntries(pipeEnv) ); - Json.LaunchOptions.BaseOptions baseOptions = Json.LaunchOptions.LaunchOptionHelpers.GetLaunchOrAttachOptions(parsedOptions); pipeOptions.InitializeCommonOptions(baseOptions); if (baseOptions is Json.LaunchOptions.LaunchOptions) { @@ -300,6 +301,12 @@ public EnvironmentEntry(Json.LaunchOptions.Environment jsonEntry) this.Value = jsonEntry.Value; } + public EnvironmentEntry(string name, string value) + { + this.Name = name; + this.Value = value; + } + /// /// [Required] Name of the environment variable /// @@ -805,6 +812,12 @@ public UnixShellPortLaunchOptions(string startRemoteDebuggerCommand, this.InitializeCommonOptions(baseLaunchOptions); this.BaseOptions = baseLaunchOptions; } + + string prefix = GetDebuginfodEnvironmentPrefix(); + if (!string.IsNullOrEmpty(prefix)) + { + this.StartRemoteDebuggerCommand = prefix + this.StartRemoteDebuggerCommand; + } } } @@ -1211,6 +1224,81 @@ public UnknownBreakpointHandling UnknownBreakpointHandling } } + private bool _enableDebuginfod = true; + + /// + /// If true (default), GDB's debuginfod support is enabled. + /// + public bool EnableDebuginfod + { + get { return _enableDebuginfod; } + set + { + VerifyCanModifyProperty(nameof(EnableDebuginfod)); + _enableDebuginfod = value; + } + } + + private int _debuginfodTimeout = 30; + + /// + /// The timeout in seconds for debuginfod requests. Default is 30. Set to 0 for no override. + /// + public int DebuginfodTimeout + { + get { return _debuginfodTimeout; } + set + { + VerifyCanModifyProperty(nameof(DebuginfodTimeout)); + _debuginfodTimeout = value; + } + } + + /// + /// Returns environment entries to configure debuginfod on the GDB process. + /// + public List GetDebuginfodEnvironmentEntries() + { + var entries = new List(); + if (DebuggerMIMode != MIMode.Gdb) + return entries; + + if (EnableDebuginfod) + { + if (DebuginfodTimeout > 0) + { + string timeoutStr = DebuginfodTimeout.ToString(System.Globalization.CultureInfo.InvariantCulture); + entries.Add(new EnvironmentEntry("DEBUGINFOD_TIMEOUT", timeoutStr)); + entries.Add(new EnvironmentEntry("DEBUGINFOD_MAXTIME", timeoutStr)); + } + } + else + { + entries.Add(new EnvironmentEntry("DEBUGINFOD_URLS", "")); + } + + return entries; + } + + /// + /// Returns an 'env' command prefix for debuginfod settings, for use in shell-based remote commands. + /// Returns empty string if no env vars are needed. + /// + public string GetDebuginfodEnvironmentPrefix() + { + var entries = GetDebuginfodEnvironmentEntries(); + if (entries.Count == 0) + return string.Empty; + + var sb = new StringBuilder(); + sb.Append("env "); + foreach (var entry in entries) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}={1} ", entry.Name, entry.Value); + } + return sb.ToString(); + } + public string GetOptionsString() { try @@ -1824,6 +1912,9 @@ protected void InitializeCommonOptions(Json.LaunchOptions.BaseOptions options) } this.UnknownBreakpointHandling = options.UnknownBreakpointHandling ?? UnknownBreakpointHandling.Throw; + this.EnableDebuginfod = options.Debuginfod?.Enabled ?? true; + int debuginfodTimeout = options.Debuginfod?.Timeout ?? 30; + this.DebuginfodTimeout = debuginfodTimeout >= 0 ? debuginfodTimeout : 30; } protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions source) @@ -1855,6 +1946,8 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc this.ShowDisplayString = source.ShowDisplayString; this.WaitDynamicLibLoad = source.WaitDynamicLibLoad; + this.EnableDebuginfod = source.EnableDebuginfod; + this.DebuginfodTimeout = source.DebuginfodTimeout >= 0 ? source.DebuginfodTimeout : 30; this.SetupCommands = LaunchCommand.CreateCollection(source.SetupCommands); this.PostRemoteConnectCommands = LaunchCommand.CreateCollection(source.PostRemoteConnectCommands); diff --git a/src/MICore/LaunchOptions.xsd b/src/MICore/LaunchOptions.xsd index db1f833a9..5449b012e 100644 --- a/src/MICore/LaunchOptions.xsd +++ b/src/MICore/LaunchOptions.xsd @@ -207,6 +207,29 @@ + + + + If true (default), GDB's debuginfod support is enabled, allowing automatic downloading of debug symbols. + Set to false to disable debuginfod, which can prevent GDB from hanging when debuginfod servers are unavailable. + + + + + + + The timeout in seconds for debuginfod server requests. Default is 30. Only applies when EnableDebuginfod is true. + A value of 0 means no timeout override is applied (GDB/libdebuginfod defaults are used). + This sets the DEBUGINFOD_TIMEOUT and DEBUGINFOD_MAXTIME environment variables on the GDB process. + Applies to local, runInTerminal, and SSH attach transports. This setting is not applied for pipeTransport; set DEBUGINFOD_TIMEOUT and DEBUGINFOD_MAXTIME in the debugger's environment manually. + + + + + + + + diff --git a/src/MICore/LaunchOptions.xsd.types.designer.cs b/src/MICore/LaunchOptions.xsd.types.designer.cs index 0c0c83941..ab1b8c954 100644 --- a/src/MICore/LaunchOptions.xsd.types.designer.cs +++ b/src/MICore/LaunchOptions.xsd.types.designer.cs @@ -99,12 +99,24 @@ public partial class AndroidLaunchOptions { [System.ComponentModel.DefaultValueAttribute(true)] public bool WaitDynamicLibLoad; + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(true)] + public bool EnableDebuginfod; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(30)] + public int DebuginfodTimeout; + public AndroidLaunchOptions() { this.Attach = false; this.SourceRoots = ""; this.JVMPort = 65534; this.JVMHost = "localhost"; this.WaitDynamicLibLoad = true; + this.EnableDebuginfod = true; + this.DebuginfodTimeout = 30; } } @@ -454,8 +466,20 @@ public partial class BaseLaunchOptions { [System.ComponentModel.DefaultValueAttribute(true)] public bool WaitDynamicLibLoad; + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(true)] + public bool EnableDebuginfod; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(30)] + public int DebuginfodTimeout; + public BaseLaunchOptions() { this.WaitDynamicLibLoad = true; + this.EnableDebuginfod = true; + this.DebuginfodTimeout = 30; } } @@ -536,8 +560,20 @@ public partial class IOSLaunchOptions { [System.ComponentModel.DefaultValueAttribute(true)] public bool WaitDynamicLibLoad; + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(true)] + public bool EnableDebuginfod; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(30)] + public int DebuginfodTimeout; + public IOSLaunchOptions() { this.WaitDynamicLibLoad = true; + this.EnableDebuginfod = true; + this.DebuginfodTimeout = 30; } } diff --git a/src/MICore/Transports/LocalTransport.cs b/src/MICore/Transports/LocalTransport.cs index 71e6f0aa3..85c3e56bd 100755 --- a/src/MICore/Transports/LocalTransport.cs +++ b/src/MICore/Transports/LocalTransport.cs @@ -40,6 +40,11 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, proc.StartInfo.SetEnvironmentVariable("PATH", path); } + foreach (var entry in options.GetDebuginfodEnvironmentEntries()) + { + proc.StartInfo.SetEnvironmentVariable(entry.Name, entry.Value); + } + // Allow to execute custom commands before launching debugger. // For ex., instructing GDB not to break for certain signals if (options.DebuggerMIMode == MIMode.Gdb && !string.IsNullOrWhiteSpace(options.WorkingDirectory)) diff --git a/src/MICore/Transports/RunInTerminalTransport.cs b/src/MICore/Transports/RunInTerminalTransport.cs index 4418f105d..a9fbb8fc5 100644 --- a/src/MICore/Transports/RunInTerminalTransport.cs +++ b/src/MICore/Transports/RunInTerminalTransport.cs @@ -163,7 +163,8 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti } // Do not pass the launchOptions Environment entries as those are used for the debuggee only. - RunInTerminalLauncher launcher = new RunInTerminalLauncher(windowtitle, new List(0).AsReadOnly()); + var debuggerEnv = options.GetDebuginfodEnvironmentEntries().AsReadOnly(); + RunInTerminalLauncher launcher = new RunInTerminalLauncher(windowtitle, debuggerEnv); launcher.Launch( cmdArgs, diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index 942a366dd..b5e8d1bd3 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -624,7 +624,10 @@ private async Task> GetInitializeCommands() if (_launchOptions.DebuggerMIMode == MIMode.Gdb) { commands.Add(new LaunchCommand("-interpreter-exec console \"set pagination off\"")); - commands.Add(new LaunchCommand("set debuginfod enabled on", ignoreFailures:true)); + if (_launchOptions.EnableDebuginfod) + { + commands.Add(new LaunchCommand("set debuginfod enabled on", ignoreFailures: true)); + } } // When user specifies loading directives then the debugger cannot auto load symbols, the MIEngine must intervene at each solib-load event and make a determination diff --git a/src/MIDebugPackage/OpenFolderSchema.json b/src/MIDebugPackage/OpenFolderSchema.json index e8431f00f..0039be008 100644 --- a/src/MIDebugPackage/OpenFolderSchema.json +++ b/src/MIDebugPackage/OpenFolderSchema.json @@ -193,6 +193,24 @@ "stop" ], "description": "Controls how breakpoints set externally (usually via raw GDB commands) are handled when hit.\nAllowed values are \"throw\", which acts as if an exception was thrown by the application, and \"stop\", which only pauses the debug session. The default value is \"throw\"." + }, + "debuginfod": { + "type": "object", + "description": "Controls GDB's debuginfod behavior for automatic downloading of debug symbols.", + "default": { "enabled": true, "timeout": 30 }, + "properties": { + "enabled": { + "type": "boolean", + "description": "If true (default), GDB's debuginfod support is enabled. Set to false to disable debuginfod, which can prevent GDB from hanging when debuginfod servers are unavailable.", + "default": true + }, + "timeout": { + "type": "integer", + "description": "The timeout in seconds for debuginfod server requests. Default is 30. Set to 0 for no timeout override (GDB defaults apply). Applies to local, runInTerminal, and SSH attach transports. This setting is not applied for pipeTransport; set DEBUGINFOD_TIMEOUT and DEBUGINFOD_MAXTIME in the debugger's environment manually.", + "default": 30, + "minimum": 0 + } + } } }, "definitions": { diff --git a/test/CppTests/OpenDebug/CrossPlatCpp/LaunchCommand.cs b/test/CppTests/OpenDebug/CrossPlatCpp/LaunchCommand.cs index 06e37fb21..5da394a34 100644 --- a/test/CppTests/OpenDebug/CrossPlatCpp/LaunchCommand.cs +++ b/test/CppTests/OpenDebug/CrossPlatCpp/LaunchCommand.cs @@ -40,6 +40,33 @@ public sealed class CppLaunchCommandArgs : LaunchCommandArgs [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool ShowDisplayString; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public DebuginfodArgs debuginfod; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public SetupCommandArg[] setupCommands; + } + + public sealed class DebuginfodArgs + { + [JsonProperty("enabled", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? Enabled { get; set; } + + [JsonProperty("timeout", DefaultValueHandling = DefaultValueHandling.Ignore)] + public int? Timeout { get; set; } + } + + public sealed class SetupCommandArg + { + [JsonProperty("text")] + public string Text { get; set; } + + [JsonProperty("description", DefaultValueHandling = DefaultValueHandling.Ignore)] + public string Description { get; set; } + + [JsonProperty("ignoreFailures", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? IgnoreFailures { get; set; } } #endregion diff --git a/test/CppTests/Tests/DebuggeeMonikers.cs b/test/CppTests/Tests/DebuggeeMonikers.cs index 6738f4c99..c49a53408 100644 --- a/test/CppTests/Tests/DebuggeeMonikers.cs +++ b/test/CppTests/Tests/DebuggeeMonikers.cs @@ -60,5 +60,10 @@ internal static class Natvis { public const int Default = 1; } + + internal static class Debuginfod + { + public const int Default = 1; + } } } diff --git a/test/CppTests/Tests/DebuginfodTests.cs b/test/CppTests/Tests/DebuginfodTests.cs new file mode 100644 index 000000000..b14dae27b --- /dev/null +++ b/test/CppTests/Tests/DebuginfodTests.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using DebuggerTesting; +using DebuggerTesting.Compilation; +using DebuggerTesting.OpenDebug; +using DebuggerTesting.OpenDebug.Commands; +using DebuggerTesting.OpenDebug.CrossPlatCpp; +using DebuggerTesting.OpenDebug.Events; +using DebuggerTesting.OpenDebug.Extensions; +using DebuggerTesting.Ordering; +using DebuggerTesting.Settings; +using Xunit; +using Xunit.Abstractions; + +namespace CppTests.Tests +{ + /// + /// Tests that validate debuginfod settings don't cause GDB to hang when + /// debuginfod servers are unreachable. + /// + [TestCaseOrderer(DependencyTestOrderer.TypeName, DependencyTestOrderer.AssemblyName)] + public class DebuginfodTests : TestBase + { + #region Constructor + + public DebuginfodTests(ITestOutputHelper outputHelper) : base(outputHelper) + { + } + + #endregion + + private const string DebuggeeName = "debuginfod"; + private const string SourceFileName = "debuginfod_test.cpp"; + + // RFC 5737 TEST-NET-1: guaranteed non-routable, connections will be dropped + private const string UnreachableDebuginfodUrl = "http://192.0.2.1:8002"; + + #region Tests + + [Theory] + [RequiresTestSettings] + public void CompileDebuginfodDebuggee(ITestSettings settings) + { + this.TestPurpose("Create and compile the debuginfod test debuggee"); + this.WriteSettings(settings); + + IDebuggee debuggee = Debuggee.Create(this, settings.CompilerSettings, DebuggeeName, DebuggeeMonikers.Debuginfod.Default); + debuggee.AddSourceFiles(SourceFileName); + debuggee.Compile(); + } + + /// + /// Tests that disabling debuginfod prevents hangs when stepping into library code + /// with DEBUGINFOD_URLS pointing to an unreachable server. + /// + [Theory] + [DependsOnTest(nameof(CompileDebuginfodDebuggee))] + [RequiresTestSettings] + [UnsupportedDebugger(SupportedDebugger.VsDbg | SupportedDebugger.Lldb, SupportedArchitecture.x64 | SupportedArchitecture.x86)] + public void DebuginfodDisabledDoesNotHang(ITestSettings settings) + { + this.TestPurpose("Verify that disabling debuginfod prevents GDB from hanging when stepping into library code."); + this.WriteSettings(settings); + + IDebuggee debuggee = Debuggee.Open(this, settings.CompilerSettings, DebuggeeName, DebuggeeMonikers.Debuginfod.Default); + + string originalUrl = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS"); + string originalVerbose = Environment.GetEnvironmentVariable("DEBUGINFOD_VERBOSE"); + try + { + Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", UnreachableDebuginfodUrl); + Environment.SetEnvironmentVariable("DEBUGINFOD_VERBOSE", "1"); + + Stopwatch sw = Stopwatch.StartNew(); + + using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings)) + { + this.Comment("Launch with debuginfod disabled"); + LaunchCommand launch = new LaunchCommand(settings.DebuggerSettings, debuggee.OutputPath); + launch.Args.debuginfod = new DebuginfodArgs { Enabled = false }; + runner.RunCommand(launch); + + this.Comment("Set breakpoint at regex_search call and run to it"); + runner.SetBreakpoints(debuggee.Breakpoints(SourceFileName, 13)); + runner.Expects.StoppedEvent(StoppedReason.Breakpoint).AfterConfigurationDone(); + + this.Comment("Step into std::regex_search to trigger debuginfod lookup"); + runner.Expects.StoppedEvent(StoppedReason.Step).AfterStepIn(); + + sw.Stop(); + this.Comment($"Step into library completed in {sw.ElapsedMilliseconds}ms"); + + Assert.True(sw.ElapsedMilliseconds < 10000, + $"Step into library took {sw.ElapsedMilliseconds}ms with debuginfod disabled. " + + "Expected < 10s. Debuginfod may not be properly disabled."); + + runner.Expects.ExitedEvent().TerminatedEvent().AfterContinue(); + runner.DisconnectAndVerify(); + } + + string engineLogPath = Path.Combine(PathSettings.TempPath, + $"EngineLog-{nameof(DebuginfodDisabledDoesNotHang)}-{settings.DebuggerSettings.DebuggeeArchitecture}-{settings.DebuggerSettings.DebuggerType}.log"); + if (File.Exists(engineLogPath)) + { + string logContent = File.ReadAllText(engineLogPath); + this.Comment("Verifying debuginfod was NOT enabled in GDB"); + Assert.DoesNotContain("debuginfod enabled", logContent); + } + } + finally + { + Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalUrl); + Environment.SetEnvironmentVariable("DEBUGINFOD_VERBOSE", originalVerbose); + } + } + + /// + /// Tests that a short debuginfod timeout prevents hangs when stepping into library code + /// with DEBUGINFOD_URLS pointing to an unreachable server. + /// + [Theory] + [DependsOnTest(nameof(CompileDebuginfodDebuggee))] + [RequiresTestSettings] + [UnsupportedDebugger(SupportedDebugger.VsDbg | SupportedDebugger.Lldb | SupportedDebugger.Gdb_MinGW | SupportedDebugger.Gdb_Cygwin, SupportedArchitecture.x64 | SupportedArchitecture.x86)] + public void DebuginfodTimeoutPreventsHang(ITestSettings settings) + { + this.TestPurpose("Verify that debuginfod timeout prevents GDB from hanging when stepping into library code."); + this.WriteSettings(settings); + + IDebuggee debuggee = Debuggee.Open(this, settings.CompilerSettings, DebuggeeName, DebuggeeMonikers.Debuginfod.Default); + + string originalUrl = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS"); + string originalVerbose = Environment.GetEnvironmentVariable("DEBUGINFOD_VERBOSE"); + try + { + Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", UnreachableDebuginfodUrl); + Environment.SetEnvironmentVariable("DEBUGINFOD_VERBOSE", "1"); + + Stopwatch sw = Stopwatch.StartNew(); + + using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings)) + { + this.Comment("Launch with debuginfod enabled but short timeout (5s)"); + LaunchCommand launch = new LaunchCommand(settings.DebuggerSettings, debuggee.OutputPath); + launch.Args.debuginfod = new DebuginfodArgs { Enabled = true, Timeout = 5 }; + launch.Args.setupCommands = new SetupCommandArg[] + { + new SetupCommandArg { Text = "set debuginfod verbose 1", IgnoreFailures = true } + }; + runner.RunCommand(launch); + + this.Comment("Set breakpoint at regex_search call and run to it"); + runner.SetBreakpoints(debuggee.Breakpoints(SourceFileName, 13)); + runner.Expects.StoppedEvent(StoppedReason.Breakpoint).AfterConfigurationDone(); + + this.Comment("Step into std::regex_search to trigger debuginfod lookup"); + runner.Expects.StoppedEvent(StoppedReason.Step).AfterStepIn(); + + sw.Stop(); + this.Comment($"Step into library completed in {sw.ElapsedMilliseconds}ms"); + + Assert.True(sw.ElapsedMilliseconds < 30000, + $"Step into library took {sw.ElapsedMilliseconds}ms with debuginfod timeout=5s. " + + "Expected < 30s. The timeout may not be applied correctly."); + + runner.Expects.ExitedEvent().TerminatedEvent().AfterContinue(); + runner.DisconnectAndVerify(); + } + + string engineLogPath = Path.Combine(PathSettings.TempPath, + $"EngineLog-{nameof(DebuginfodTimeoutPreventsHang)}-{settings.DebuggerSettings.DebuggeeArchitecture}-{settings.DebuggerSettings.DebuggerType}.log"); + if (File.Exists(engineLogPath)) + { + string logContent = File.ReadAllText(engineLogPath); + this.Comment("Verifying debuginfod was enabled in GDB"); + Assert.Contains("debuginfod enabled", logContent); + } + } + finally + { + Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalUrl); + Environment.SetEnvironmentVariable("DEBUGINFOD_VERBOSE", originalVerbose); + } + } + + #endregion + } +} diff --git a/test/CppTests/debuggees/debuginfod/src/debuginfod_test.cpp b/test/CppTests/debuggees/debuginfod/src/debuginfod_test.cpp new file mode 100644 index 000000000..396682c92 --- /dev/null +++ b/test/CppTests/debuggees/debuginfod/src/debuginfod_test.cpp @@ -0,0 +1,29 @@ +// Test program that calls into library functions where debuginfod would +// attempt to download debug symbols/source. When stepping through regex +// or libc internals, GDB triggers debuginfod lookups if enabled. + +#include +#include +#include + +int do_regex_match(const std::string& input, const std::string& pattern) +{ + std::regex re(pattern); + std::smatch match; + if (std::regex_search(input, match, re)) + { + std::cout << "Match: " << match[0] << std::endl; + return 1; // breakpoint line + } + return 0; +} + +int main() +{ + std::string text = "Hello debuginfod test 12345"; + std::string pattern = R"(\d+)"; + + int result = do_regex_match(text, pattern); // step into here + std::cout << "Result: " << result << std::endl; + return result; +}