web-dev-qa-db-ja.com

デバッグ:cmd.exe内で実行されているコンソールアプリのプロセスにアタッチ

F5で起動せずにCMDウィンドウから実行しているコンソールアプリケーションの「プロセスにアタッチ...」私が尋ねる理由は、アプリケーションがコマンドライン引数を受け取り、本物の経験をしたいからです。

私は_CMD.exe_に接続することさえしましたが、運がないか、またはConsole.ReadKey()を使用してブレークポイントを設定しても運がありません。ここでちょっと困ってます。

これは可能ですか?

27
Chris

いくつかのオプションがあります:

  • Visual Studioで[デバッグ->コマンドライン引数]オプションを使用します。
  • 「デバッグ->プロセスにアタッチ」を使用して、プロセスを見つけます。これはcmd.exeではなく、「MyProject.exe」のような実行可能ファイル名のプロセスです。 Process Explorer または「ツリービュー」をサポートする別のタスクマネージャーを使用して、プロセスIDを簡単に見つけることができます。cmd.exeによって開始されたプロセスを探すだけです。
  • Debugger.Break()をコードに追加します。このメソッドが実行されると、システムは、デバッグに使用するVisual Studioのインスタンスを選択するように求めるダイアログを起動します(プロジェクトが既に開いているインスタンスを選択できます)。
30
VladV

VS GUI迷路を使用するのではなく、コマンドラインからデバッグするには:

  • Visual Studioコマンドプロンプトを起動する

  • vsjitdebugger /?と入力すると、次のようなコマンドの例が表示されます。

c:> vsjitdebugger [AppName] [Args]:指定した実行可能ファイルを起動してデバッガーにアタッチします

  • tlistまたはtasklistと入力すると、既存のプロセスに接続するためのPIDが表示されます。例:

c:>タスクリスト|/i "web"を見つける

5
Kwame

確かに可能です。次の2つのいずれかを試してください。

  1. プロセスを開始してから、Debug-> Attachに移動してプロセスを見つけます。表示するには更新が必要な場合があります。
  2. 可能であれば、コードに "Debugger.Break()"ステートメントを追加します。これは自動的に機能しなくなります(ただし、削除するか、プリプロセッサディレクティブで囲んで、製品コードに組み込まれないようにしてください)。
2
roufamatic

他の人が言ったように、プロジェクト内からstratupコマンドライン引数を指定して、Visual Studio内でデバッグを開始できます。

実行中のアプリケーションにアタッチしたい場合は、デバッガーをcmd.exeではなく、MyApp.exe(アプリケーションが何であれ、bin\debugディレクトリにコンパイルされるexe)にアタッチする必要があります。 cmd.exeにアタッチすると、アプリケーションのプロセスではなく、コマンドプロセスにアタッチされます。

1
adrianbanks

プロジェクト設定の「デバッグ」セクションには、「コマンドライン引数:」のテキストボックスがあります。 VSデバッガーがC#プログラムを開始すると、それらの引数を使用してコマンドラインからプログラムが開始されたかのように、それらの引数がプロセスに渡されます。

別の方法としては、コマンドラインデバッガーを使用します。ここにはいくつかのオプションがありますが、正直なところ、いくつかの非常に複雑なデバッグシナリオに陥らない限り、VSの代わりに使用することはおそらくありません。それらをチェックアウトすることに興味がある場合は、このSO答えに良い要約があります:

初期化の早い段階でSystem.Diagnostics.Debugger.Break()を呼び出す手法を試すこともできます。プログラムがデバッガーの下で実行されている場合、プログラムは壊れます。デバッガーの下で実行されていない場合は、付けたい構成ファイルまたは環境変数の設定に応じて条件付きで呼び出しを行うことができるため、本当に興味がある場合にのみ中断が発生します(やや煩わしいが、悪くない)。

0
Michael Burr

"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\currentversion\image file execution options"にexe名のレジストリエントリを追加し、その下に "vsjitdebugger.exe"という値の "debugger"キーを追加すると、ダイアログポップが表示されますexeの起動時にデバッグするVSバージョンを選択するように要求します。

詳細については、MSDNの「 方法:デバッガーを自動的に起動する 」を参照してください。

0
kaige

私はここでいくつかのより良い解決策を見つけると思いましたが、それは私がすでに持っているものが最高のようです。 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バージョンとコミュニティバージョンでのみテストされています)。

0
Pawcio