调试:附加到在cmd.exe中运行的控制台应用程序进程

32

如何在不通过 F5 启动的 CMD 窗口中运行的控制台应用程序上“附加到进程”?我之所以问这个问题是因为此应用程序需要使用命令行参数,我想要一个真实的体验。

我甚至尝试过附加到 CMD.exe,但没有成功,或者使用 Console.ReadKey() 来设置断点也没有成功。我有些无助了。

这是否可能?


3
如果您还不知道,您可以在 Visual Studio 中设置命令行参数,在按下 F5 启动应用程序时使用。右键单击项目 -> 属性 -> 调试 -> 启动选项 -> 命令行参数。 - Jacob G
@Jacob G,是的,我可以做到,但这样做非常笨拙,如果我想更改参数,就必须深入对话框中查找。 - Chris
2
附加到 cmd.exe 是无用的,因为您的控制台应用程序在其自己的进程中运行,而不是在 cmd.exe 中。 - Lex Li
8个回答

35
你有几个选项:
  • 使用 Visual Studio 中的“调试 -> 命令行参数”选项;
  • 使用“调试 -> 附加到进程”并找到你的进程;它不是 cmd.exe,而是一个可执行文件名为“MyProject.exe”的进程。你可以使用 Process Explorer 或另一个具有“树形视图”支持的任务管理器轻松查找进程ID - 只需寻找由你的cmd.exe启动的进程。
  • 在 Windows 上(截至2022年),将Debugger.Launch()Debugger.Break()放入你的代码中 - 执行此操作后,系统将启动对话框,询问你选择用于调试的哪个 Visual Studio 实例(你可以选择已经打开你的项目的那个)。

太棒了 - 只需将Console.ReadKey()添加为第一行,它就像魔法般地运行了。 - else
3
在使用 Debugger.Break() 运行 .NET 4.6.1 应用程序时,无法连接到 VS2017。相反,这里可以使用 Debugger.Launch()。 - Tadas Šukys

7

如果想要从命令行调试而不是使用VS GUI迷宫:

  • 启动Visual Studio命令提示符

  • 输入vsjitdebugger/?,将会给出如下的命令示例:

c:> vsjitdebugger [AppName] [Args] : 启动指定的可执行文件并连接到调试器

  • 输入tlisttasklist将会给出现有进程的PID。例如:

c:> tasklist | find /i "web"


2

当然可以。您可以尝试以下两种方法:

  1. 启动进程,然后转到“调试” -> “附加”,找到该进程。您可能需要刷新才能看到它。
  2. 如果可能,在代码中添加“Debugger.Break()”语句,这将自动中断(但请务必在生产代码中删除它或用预处理指令包围它)。

2

2020更新:@VladV的回答已经过时。

Debugger.Break()不再起作用。

尝试使用Debugger.Launch()替代它,还要在这行代码之后设置断点,否则VS会开始抱怨。


1

正如其他人所说,您可以在项目内指定启动命令行参数,并在Visual Studio中开始调试。

如果您仍然想要附加到正在运行的应用程序,则需要将调试器附加到MyApp.exe(无论您的应用程序叫什么 - 编译为bin \ debug目录的exe文件),而不是cmd.exe。附加到cmd.exe是附加到命令进程,而不是您的应用程序进程。


0
在项目设置的“调试”部分,有一个文本框用于“命令行参数:”。当VS调试器启动C#程序时,它会将这些参数传递给进程,就像使用这些参数从命令行启动程序一样。
另一种选择是使用命令行调试器。这里有几个选项,但老实说,除非你遇到了一些非常棘手的调试场景,否则它们可能不是你想要使用的替代品。如果你有兴趣了解它们,可以在这个SO答案中找到一个很好的总结:
- MSIL调试器-Mdbg、Dbgclr、Cordbg
你还可以尝试在初始化时早期调用System.Diagnostics.Debugger.Break()的技巧——如果程序正在运行调试器,它会中断,如果程序没有在运行调试器,你应该被问是否要附加一个调试器。你可以根据配置文件或环境变量设置有条件地进行调用,这样只有在真正感兴趣的情况下才会中断(有点侵入性,但不太糟糕)。

0
只需在"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\currentversion\image file execution options"中添加一个注册表项,该项的名称为你的exe文件名,在该项下添加一个带有值"vsjitdebugger.exe"的"debugger"键,你就可以看到一个对话框弹出,要求你选择VS版本以便在exe启动时进行调试。
有关更多信息,请参见MSDN "How to: Launch the Debugger Automatically"。

0

我本以为我会在这里找到更好的解决方案,但似乎我已经拥有的是最好的。 Debugger.Break() 对我根本不起作用。但是前段时间我在GitHub上找到了VisualStudioAttacher类。现在无法找到该库,但我将发布我的稍微修改过的版本。

您将像这样使用它。

class Program {
    static void Main(string[] args) {
        VSAttacher.attachDebugger("SolutionFileContainingThisCode.sln");

        Console.WriteLine("Hello World"); //set a brakepoint here
        //...               
    }

}

这将仅附加到当前打开的 Visual Studio 实例,无需选择调试器。

设置

  1. 创建名为 VSAttacher 的新类库项目,或者任何你喜欢的名称。
  2. 在要调试的项目中添加对 VSAttacher 项目的引用。
  3. VSAttacher 项目中,添加对 envdte 库的引用。
  4. 将以下代码粘贴到 VSAttacher 项目中:

代码:

using System.IO;
using EnvDTE;
using DTEProcess = EnvDTE.Process;
using System;
using System.Collections.Generic;
using Process = System.Diagnostics.Process;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace AppController {
    #region Classes

    /// <summary>Visual Studio attacher.</summary>
    public static class VSAttacher {

        public static Action<object> log = (o) => Console.WriteLine(o);
    
        //Change following variables depending on your version of visual studio
        //public static string VSProcessName = "WDExpress";
        //public static string VSObjectName = "!WDExpress";
        public static string VSProcessName = "devenv";
        public static string VSObjectName = "!VisualStudio";

        /// <summary>
        /// Tries to attach the program to Visual Studio debugger.
        /// Returns true is the attaching was successful, false is debugger attaching failed.
        /// </summary>
        /// <param name="sln">Solution file containing code to be debugged.</param>
        public static bool attachDebugger(string sln) {
            if (System.Diagnostics.Debugger.IsAttached) return true;
            log("Attaching to Visual Studio debugger...");

            var proc = VSAttacher.GetVisualStudioForSolutions(
                new List<string>() { Path.GetFileName(sln) });
            if (proc != null) VSAttacher.AttachVSToProcess(
                    proc, Process.GetCurrentProcess());
            else { 
                try { System.Diagnostics.Debugger.Launch(); }
                catch (Exception e) { }
            } // try and attach the old fashioned way

            if (System.Diagnostics.Debugger.IsAttached) {
                log(@"The builder was attached successfully. Further messages will displayed in ""Debug"" output of ""Output"" window.");
                return true;
            }
            log("Could not attach to visual studio instance.");
            return false;
        }

        #region Public Methods


        #region Imports
        [DllImport("User32")]
        private static extern int ShowWindow(int hwnd, int nCmdShow);

        /// <summary>Returns a pointer to an implementation of <see cref="IBindCtx"/> (a bind context object). This object stores information about a particular moniker-binding operation.</summary>
        /// <param name="reserved">This parameter is reserved and must be 0.</param>
        /// <param name="ppbc">Address of an <see cref="IBindCtx"/>* pointer variable that receives the interface pointer to the new bind context object. When the function is successful, the caller is responsible for calling Release on the bind context. A NULL value for the bind context indicates that an error occurred.</param>
        /// <returns></returns>
        [DllImport("ole32.dll")]
        public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);

        /// <summary>Returns a pointer to the <see cref="IRunningObjectTable"/> interface on the local running object table (ROT).</summary>
        /// <param name="reserved">This parameter is reserved and must be 0.</param>
        /// <param name="prot">The address of an IRunningObjectTable* pointer variable that receives the interface pointer to the local ROT. When the function is successful, the caller is responsible for calling Release on the interface pointer. If an error occurs, *pprot is undefined.</param>
        /// <returns>his function can return the standard return values E_UNEXPECTED and S_OK.</returns>
        [DllImport("ole32.dll")]
        public static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);


        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr SetFocus(IntPtr hWnd);
        #endregion

        public static string GetSolutionForVisualStudio(Process visualStudioProcess) {
            var vsi = getVSInstance(visualStudioProcess.Id);
            try { return vsi?.Solution.FullName;}
            catch (Exception) {} return null;
        }

        public static Process GetAttachedVisualStudio(Process ap) {
            var vsps = getVSProcess();
            foreach (Process vsp in vsps) {
                var vsi = getVSInstance(vsp.Id);
                if (vsi == null) continue;
                try {
                    foreach (Process dp in vsi.Debugger.DebuggedProcesses)
                        if (dp.Id == ap.Id) return dp;
                } catch (Exception) {}
            }
            return null;
        }

        public static void AttachVSToProcess(Process vsp, Process applicationProcess) {
            var vsi = getVSInstance(vsp.Id);
            if (vsi == null) return;
            //Find the process you want the VS instance to attach to...
            DTEProcess tp = vsi.Debugger.LocalProcesses.Cast<DTEProcess>().FirstOrDefault(process => process.ProcessID == applicationProcess.Id);

            //Attach to the process.
            if (tp != null) {
                tp.Attach();

                ShowWindow((int)vsp.MainWindowHandle, 3);
                SetForegroundWindow(vsp.MainWindowHandle);
            } else {
                throw new InvalidOperationException("Visual Studio process cannot find specified application '" + applicationProcess.Id + "'");
            }
        }

        public static Process GetVisualStudioForSolutions(List<string> sns) {
            foreach (string sn in sns) {
                var vsp = GetVSProc(sn);
                if (vsp != null) return vsp;
            }
            return null;
        }


        public static Process GetVSProc(string name) {
            var vsps = getVSProcess(); var e = false;
            foreach (Process vsp in vsps) {
                _DTE vsi = getVSInstance(vsp.Id);
                if (vsi == null) { e = true; continue; }
                try {
                    string sn = Path.GetFileName(vsi.Solution.FullName);
                    if (string.Compare(sn, name, StringComparison.InvariantCultureIgnoreCase)
                        == 0) return vsp;
                } catch (Exception) { e = true; }
            }
            if (!e) log($@"No running Visual Studio process named ""{VSProcessName}"" were found.");
            return null;
        }

        #endregion

        #region Private Methods

        private static IEnumerable<Process> getVSProcess() {
            Process[] ps = Process.GetProcesses();
            //var vsp = ps.Where(p => p.Id == 11576);
            return ps.Where(o => o.ProcessName.Contains(VSProcessName));
        }

        private static _DTE getVSInstance(int processId) {
            IntPtr numFetched = IntPtr.Zero;
            IMoniker[] m = new IMoniker[1];

            GetRunningObjectTable(0, out var rot);
            rot.EnumRunning(out var ms); ms.Reset();

            var rons = new  List<string>();
            while (ms.Next(1, m, numFetched) == 0) {
                IBindCtx ctx;
                CreateBindCtx(0, out ctx);

                m[0].GetDisplayName(ctx, null, out var ron);
                rons.Add(ron);
                rot.GetObject(m[0], out var rov);

                if (rov is _DTE && ron.StartsWith(VSObjectName)) {
                    int currentProcessId = int.Parse(ron.Split(':')[1]);

                    if (currentProcessId == processId) {
                        return (_DTE)rov;
                    }
                }
            }
            log($@"No Visual Studio _DTE object was found with the name ""{VSObjectName}"" that resides in given process (PID:{processId}).");
            log("The processes exposes following objects:");
            foreach (var ron in rons) log(ron);
            return null;
        }

        #endregion
    }

    #endregion
}

如果您使用 Visual Studio 的 Express 版本,则应更改 VSProcessNameVSObjectName(此仅在 Express 和 Community 版本中进行了测试)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接