This repository provides a command-line interface (CLI) version of Mosh Hamadani's C# debugging tutorial, originally demonstrated using Visual Studio's GUI. Here, we use mdbg.exe
and other CLI tools to debug C# code.
- Windows operating system
- .NET Framework (comes pre-installed on most Windows systems)
- Chocolatey or winget (for installing NuGet)
- NuGet (for installing Mdbg)
-
Install NuGet using Chocolatey:
choco install nuget.commandline
Or using winget:
winget install Microsoft.NuGet
-
cd
into your directory of choice and install Mdbg using NuGet:nuget install Mdbg
-
Add Mdbg to your system PATH:
set "PATH=%PATH%;C:\path\to\Mdbg\tools"
Replace
C:\path\to\Mdbg\tools
with the actual path where Mdbg was installed.
A cmd batch file (compile.bat
) is provided that allows you to compile the C# code using different methods. The syntax is:
compile.bat <debug/release> <vs2019/windows/dotnet> <csc/msbuild/devenv/dotnet> [clean] [run]
Arguments:
- First argument:
debug
orrelease
(build type) - Second argument:
vs2019
,windows
, ordotnet
(compiler source) - Third argument:
csc
,msbuild
,devenv
, ordotnet
(compiler) - Optional
clean
: Cleans the output directories before building - Optional
run
: Runs the compiled program (usesmdbg.exe
for debug builds)
Examples:
- To compile in debug mode using the Windows-provided
csc.exe
:compile.bat debug windows csc
- To clean, compile in release mode using VS2019's MSBuild, and run the program:
compile.bat release vs2019 msbuild clean run
- To compile and run using .NET Core/5+:
compile.bat debug dotnet run
Note: The order of the first three arguments doesn't matter, except for dotnet
, which must be specified as the second argument.
The repository includes the following key files:
Program.cs
: The main C# source code fileProgram.csproj
: The C# project file for use with MSBuild and Visual StudioProgram.sln
: The solution file for use with Visual Studiocompile.bat
: The batch script for compiling and running the program
-
Compile the code in debug mode:
compile.bat debug windows csc
-
Start mdbg:
mdbg build\Program.exe
-
Set a breakpoint at the beginning of the Main method:
b Program.cs:7
-
Run the program:
run
-
Step through the code:
n
to step overs
to step intoo
to step outg
to continue execution
-
Inspect variables:
p variableName
-
View the call stack:
where
-
Exit mdbg:
q
First, let's compile and run the program to see the initial output:
compile.bat run
at once, or seperately:
compile.bat
and then
path\to\Program.exe
Expected output:
Compilation completed.
6
5
4
We expected to see the three smallest numbers (1, 2, 3), but we got the three largest instead. This indicates a bug in our program.
Let's step through the code and identify the issue. Compile the program in debug mode using our cmd batch:
compile.bat debug run
at once, or do it step by step:
compile.bat debug
and use mdbg.exe
directly:
mdbg path\to\Program.exe
You should see output similar to this:
MDbg (Managed debugger) v0.0.0.0 started.
Copyright (C) Microsoft Corporation. All rights reserved.
For information about commands type "help";
to exit program type "quit".
run build\Program.exe
STOP: Breakpoint Hit
located at line 9 in Program.cs
Note: The "Breakpoint Hit" message is due to an auto-break at the entry point. When mdbg Program.exe
is run, the debugger often halts execution at the program's entry point (usually the Main method) by default. This isn't a user-set breakpoint but an automatic one to give the user control over execution before any code runs.
In this case, we've stopped at line 9 in Program.cs
, which is the opening curly brace {
of the Main method. This means we're paused at the very beginning of the method, before any of its code has executed. The next step will take us into the first executable line of the Main method.
When debugging with mdbg.exe
, two primary commands for moving through your code are s
(step) and n
(next). While they might seem similar, they behave quite differently:
-
s
(step):The
s
orstep
command:- Moves execution into the next function on the current line, or moves to the next line if there is no function to step into.
- Allows you to follow the execution path into method calls, constructors, and other function-like code.
For example, when using
s
repeatedly from the start of theMain
method:[p#:0, t#:0] mdbg> s located at line 10 in Program.cs [p#:0, t#:0] mdbg> s IP: 0 @ System.Collections.Generic.List`1..ctor - MAPPING_EXACT [p#:0, t#:0] mdbg> s IP: 0 @ System.Collections.Generic.List`1..ctor - MAPPING_APPROXIMATE [p#:0, t#:0] mdbg> s IP: 0 @ System.Object..ctor - MAPPING_EXACT ... [p#:0, t#:0] mdbg> s IP: 17 @ System.Collections.Generic.List`1..ctor - MAPPING_EXACT [p#:0, t#:0] mdbg> s located at line 10 in Program.cs
In this example:
- We first step into line 10, which initializes a new
List<int>
. - The subsequent steps take us through the constructor of
List<int>
and its base classes. - The lines starting with
IP:
show the Instruction Pointer (IP) within the method being executed. These represent low-level steps in the construction of theList<int>
object. - After many
s
commands, we finally return to line 10 in ourProgram.cs
.
The
MAPPING_EXACT
andMAPPING_APPROXIMATE
indicate how precisely the debugger can map the current instruction to a line in the source code. -
n
(next):The
n
ornext
command:- Runs code and moves to the next line, even if the current line includes multiple function calls.
- Executes an entire line of code as a single unit, regardless of how many method calls or operations it contains.
Using
n
from the same starting point:[p#:0, t#:0] mdbg> n located at line 10 in Program.cs
With a single
n
command, we move to the next line, skipping over all the internal steps of creating theList<int>
.
Key Differences:
-
Granularity:
s
provides a much finer granularity of control, allowing you to see every step of execution, including within library methods.n
gives a higher-level view, focusing on your code. -
Speed of Debugging: Using
n
allows you to move through your code more quickly, whiles
can be slower but provides more detailed information. -
Use Cases:
- Use
s
when you want to investigate the internals of a method call or when you suspect a problem within a called method. - Use
n
when you're confident in the workings of called methods and want to focus on the flow of your own code.
- Use
By understanding and effectively using both s
and n
, you can navigate your code efficiently during debugging, choosing the appropriate level of detail for your current debugging needs.
Observing Variable Initialization
After stepping to line 10 and then to line 11, we can observe how the numbers
variable changes:
[p#:0, t#:0] mdbg> s
located at line 10 in Program.cs
[p#:0, t#:0] mdbg> p numbers
numbers=<null>
[p#:0, t#:0] mdbg> n
located at line 11 in Program.cs
[p#:0, t#:0] mdbg> p numbers
numbers=System.Collections.Generic.List`1<System.Int32>
[0] = 1
[1] = 2
[2] = 3
[3] = 4
[4] = 5
[5] = 6
Initially, when we're at line 10, numbers
is <null>
because the variable has been declared but not yet initialized. The List<int>
object hasn't been created and assigned to numbers
at this point.
After executing line 10 with the n
command, we move to line 11. Now, numbers
has been initialized with a new List<int>
containing the values 1 through 6. This demonstrates how the debugger allows us to observe the step-by-step process of variable initialization and assignment.
Now, let's navigate to the GetSmallest
method and examine its behavior:
-
Continue stepping through the code using
n
until you reach theGetSmallest
method call:[p#:0, t#:0] mdbg> s located at line 10 in Program.cs [p#:0, t#:0] mdbg> n located at line 11 in Program.cs [p#:0, t#:0] mdbg> s located at line 18 in Program.cs [p#:0, t#:0] mdbg> n located at line 19 in Program.cs [p#:0, t#:0] mdbg> n located at line 21 in Program.cs [p#:0, t#:0] mdbg> n located at line 22 in Program.cs [p#:0, t#:0] mdbg> n located at line 23 in Program.cs
-
Step into the
GetSmallest
method usings
:[p#:0, t#:0] mdbg> s located at line 32 in Program.cs
-
Inside
GetSmallest
, step through each line, examining the variables:[p#:0, t#:0] mdbg> n located at line 34 in Program.cs [p#:0, t#:0] mdbg> n located at line 35 in Program.cs [p#:0, t#:0] mdbg> n located at line 35 in Program.cs [p#:0, t#:0] mdbg> n located at line 36 in Program.cs [p#:0, t#:0] mdbg> n located at line 37 in Program.cs [p#:0, t#:0] mdbg> n located at line 38 in Program.cs [p#:0, t#:0] mdbg> p min min=1
-
Pay close attention to the comparison logic:
[p#:0, t#:0] mdbg> n located at line 39 in Program.cs [p#:0, t#:0] mdbg> p min min=2
Notice that instead of finding the smallest number, it's updating min
to a larger number. This is where we identify the bug in our comparison logic. The condition list[i] > min
is incorrect for finding the smallest number; it should be list[i] < min
.
This step-by-step examination allows us to pinpoint exactly where and how the logic is failing, demonstrating the power of debugging in identifying subtle bugs in our code.
The bug is in the comparison inside the GetSmallest method. Let's fix it:
-
Exit the debugger:
q
-
Open Program.cs in a text editor and locate the GetSmallest method.
-
Change the comparison from:
if (list[i] > min)
to:
if (list[i] < min)
-
Save the file and recompile:
compile.bat debug
-
Run the debugger again and step through to verify the fix:
mdbg !run path\to\Program.exe !go !quit
Feel free to contribute to this project by submitting pull requests or opening issues for any bugs or suggestions.
This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike (CC BY-NC-SA) License. See the LICENSE file for details.
- Mosh Hamadani for the original Visual Studio debugging tutorial
- Microsoft for providing mdbg.exe and other .NET debugging tools