This page is likely outdated (last edited on 12 Mar 2014). Visit the new documentation for updated content.

Guide:Debugger

DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED

Table of contents

DEPRECATED

The Hard Debugger is no longer support in newer versions of Mono, it is only supported by older versions of Mono. This documentation is here for the sake of users that still use the old debugger.

Introduction

This is a guide to the Mono Hard Debugger. Most developers will want to use instead the Soft Debugger support in Mono and MonoDevelop 2.2 as it supports debugging more kinds of applications and it is in general more reliable than the hard debugger.

The principal difference between the hard debugger (MDB) and the soft debugger is that the hard debugger can debug both managed and unmanaged applications, while the soft debugger is limited to pure managed applications. But the soft debugger is a more reliable debugger due to its architecture.

It provides a reusable library that can be used to add debugger functionality to different frontends. The debugger package includes a console debugger named “mdb”, and MonoDevelop will also provide a GUI interface for it soon.

Using the Hard Debugger with MonoDevelop

To use the Debugger with MonoDevelop, you should get at least Mono 2.4, MonoDevelop 2.0, or preferably MonoDevelop 2.2 and the debugger packages.

Details about the integrated debugger can be found on the MonoDevelop Release Notes.

Using the Command Line Debugger

Preparing your Program

To use the debugging facilities in Mono, you should compile your program with debugging information. This is achieved by passing the -debug option to the command line compiler.

gmcs -debug hello.cs

The debugger can debug both 1.x and 2.0 applications.

Debugging Symbols

When using mcs -debug, the compiler generates a .mdb file which contains the debug symbols that the debugger uses to map a method to a position in a source file, and to get line numbers in stack traces.

If your program has been compiled inside Visual Studio or with csc, the debugging symbols are stored in a .pdb file. You can use the program pdb2mdb to convert the pdb to an mdb file that the debugger understands.

So if you have the files hello.exe and hello.pdb, you can use:

pdb2mdb hello.exe

To generate hello.exe.mdb.

Starting the Debugger

Start the command line version of the debugger like this:

mdb Application.exe

to debug a managed application or

mdb nativeapplication

to debug a native application.

Alternatively, you can set the executable after starting the debugger:

 $ mdb
 (mdb) file Application.exe
 Executable file: Application.exe.
 (mdb)

By default, the debugger will not start the application. You can either use the run or start command:

 $ mdb Application.exe
 (mdb) run

or you can also use the -run or -start argument on the command line:

 $ mdb -run Application.exe

The difference between these two commands is that start will start the application and run until the first line of the application’s main() method while run won’t stop in main().

Command line arguments

You can specify the command line arguments passed to the debuggee in three ways:

  1. The “run” command:

    (mdb) run arg1 arg2 …

  2. The “set args” command

    (mdb) set args arg1 arg2 …

  3. On the command line when starting the debugger:

    $ mdb -run -args Application.exe arg1 arg2 …

Commands

Quitting and restarting

To quit a debugging session, just type

(mdb) quit

To kill a debugging session, type

(mdb) kill

This will just kill program being debugged, but not the debugger. After that, you can use run to restart the target:

(mdb) run

This’ll restart your debugging session and stop at the applications’s entry point. Breakpoints are preserved across sessions.

Displaying processes and threads

At the moment, the debugger can only debug one application at once - but since we’re following fork()’s, this application can have more than one process.

You can find out which processes are currently being debugged with the show processes (short: show procs) command:

(mdb) show procs
(*) Process #1 (13158:/work/rohan/INSTALL/lib/xsp/1.0/xsp.exe --root /work/rohan/INSTALL/lib ...)

Each process can have one or more threads:

(mdb) show threads
Process #1 (13158:/work/rohan/INSTALL/lib/xsp/1.0/xsp.exe --root /work/rohan/INSTALL/lib ...):
(*) Thread @1 (13158:2aaaab718f60) Stopped
    Daemon thread @2 (13161:0) Running
    Daemon thread @3 (13162:40224960) Running
    Thread @4 (13220:40475960) Stopped

The (*) marks the current process / thread - this is the thread on which all stepping commands operate (unless you override with the -thread argument, see below). You can view or change the current thread with the thread command:

(mdb) thread
Thread @1 (13158:2aaaab718f60) Stopped
(mdb) frame
#0: 0x40018b32 in Mono.XSP.Server.Main(System.String[])+0x13b2 at ./xsp/src/server.cs:476
 476                            if (!nonstop) {
(mdb) thread 4
Thread @4 (13220:40475960) Stopped
(mdb) thread
Thread @4 (13220:40475960) Stopped
(mdb) frame
#0: 0x40019830 in Mono.WebServer.ApplicationServer.RunServer()+0x18 at ./xsp/src/Mono.WebServer/ApplicationServer.cs:314
 314                    started = true;

All commands which access the target also take an optional -thread argument to specify on which thread they should operate:

(mdb) frame
#0: 0x40018b32 in Mono.XSP.Server.Main(System.String[])+0x13b2 at ./xsp/src/server.cs:476
 476                            if (!nonstop) {
(mdb) frame -thread 4
#0: 0x40019830 in Mono.WebServer.ApplicationServer.RunServer()+0x18 at ./xsp/src/Mono.WebServer/ApplicationServer.cs:314
 314                    started = true;

If you look at the example above, you’ll notice that some threads are marked as Deamon thread - these are threads which are created and used internally by the Mono runtime. The debugger doesn’t prevent you from debugging these, but you should know what you’re doing.

Running and single stepping

The debugger has several commands to single step the target - unless you use the -thread argument, they all operate on the current thread (see #Displaying processes and threads for details):

continue, c

Resume execution of the selected thread until it exists, receives a signal or hits a breakpoint.

background, bg

Resume execution of the selected thread in the background, ie. immediately give control back to the user, without waiting until the target stops.

stop

Stop execution of the selected thread by sending it a stop signal.

step, s

Step one source line.

next, n

Step one source line, but step over method calls.

finish

Run until the end of the current method. This command has two modes of operation:

  • Without any arguments, step until the end of the current method. This only works if the current method has source code. Note that if the current method recursively calls itself, this will continue running until the last iteration has exited.
  • With the optional -native argument, continue stepping until the stack pointer reaches a value whic his higher than at the beginning of this operation. You can use this to finish out of a method without source code or to finish just the current iteration of a recursive method. Note that when called within a method (not at the start), this may stop several times.

stepi, i

Step one machine instruction.

(mdb) frame
#0: 0x401ba443 in X.Main()+0x3 at Foo.cs:218
218         Simple ();
(mdb) stepi
Process @3 stopped at #0: 0x401ba5a0 in X.Simple() at Foo.cs:111.
111     public static void Simple ()
X.Simple():
0x401ba5a0  push   %ebp

This command will not enter JIT trampolines unless the -native argument is given:

(mdb) frame
#0: 0x401ba443 in X.Main()+0x3 at Foo.cs:218
218         Simple ();
(mdb) stepi -native
Process @3 stopped at #0: 0x0830f878.
0x0830f878  push   $0x82b4398

nexti, t

Step one machine instruction, but step over method calls.

return

Make the current stack frame return. This command is dangerous since it just pops off the stack frame and doesn’t care about return values, out parameters etc.

Note than when you’re inside a method which has been called by mdb (via the call or print command, see below), return will abort this invocation - in this case, return is safe to use.

Of course, the debugger may stop earlier if the target receives a signal (or exists).

Stack frames and backtraces

When the target stopped, you normally want to see the current stack frame or get a backtrace:

backtrace, bt
Prints a backtrace.

Process @3 stopped at #0: 0x401ba5cd in X.Simple()+0x2d at Foo.cs:113.
113         int a = 5;
(mdb) bt
(*) #0: 0x401ba5cd in X.Simple()+0x2d at Foo.cs:113
#1: 0x401ba448 in X.Main()+0x8 at Foo.cs:219

Takes an optional -max command specifying the maximum number of frames to print:

(mdb) bt -max 8

frame, f
Show the current stack frame or the frame specified by the optional -frame argument:

Process @3 stopped at #0: 0x401ba5cd in X.Simple()+0x2d at Foo.cs:113.
113         int a = 5;
(mdb) frame -frame 1
#1: 0x401ba448 in X.Main()+0x8 at Foo.cs:219
219         BoxedValueType ();

up, down
Walks one frame up or down in the current backtrace. Prints the new stack frame.

Process @3 stopped at #0: 0x401ba5cd in X.Simple()+0x2d at Foo.cs:113.
113         int a = 5;
(mdb) up
#1: 0x401ba448 in X.Main()+0x8 at Foo.cs:219
219         BoxedValueType ();
(mdb) frame
#1: 0x401ba448 in X.Main()+0x8 at Foo.cs:219
219         BoxedValueType ();

Printing expressions

To print variables or evaluate arbitrary expressions, use the print command. You can also use the ptype command to print the type of an expression.

print, p
Evaluate and print an expression.

(mdb) print a
(System.Int32) a

print /default, p /default
Evaluate and print the context of an expression. Unlike print, this will not show the ToString() representation of the object, but will instead show the values in a class or struct.

(mdb) print /default a

ptype
Print the type of an expression

(mdb) ptype a
System.Int32

See #Expressions for details.

Displaying expressions

To display the value of an expressn every time the target stops (so that you can easily see how it changes during the execution), use the display command, which takes the expression as parameter.

Each display has an associated number, which can be used to refer to the display itself when it is removed with the undisplay command (which takes the display index as parameter).

It can happen that an expression cannot be evaluated in the current stack frame or thread (maybe the variables used are out of scope). In this case, the first time that mdb cannot evaluate the expression the debugger prints a warning, and then the display is implicitly disabled, so that the warning is no longer shown.

If, however, the expression becomes valid again, it is automatically shown. This way the user can keep several displays active, and not worry about the fact that they go out of scope, because the screen will not be cluttered with the invalid ones.

To see all the active displays, one can use the show display command. Note that this command shows all the displays, also the ones that have been internally disabled.

Breakpoints

Breakpoints are inserted with the break command. Without arguments, this command inserts a breakpoint at the current source line.

Process @3 stopped at #0: 0x401b7443 in X.Main()+0x3 at Foo.cs:218.
218         Simple ();
(mdb) b
Inserted breakpoint 1 at Foo.cs:218

After inserting the breakpoint, the debugger tells you the breakpoint number which can be used to enable, disable or remove the breakpoint:

(mdb) show breakpoints
Breakpoints:
 Id   Type  En  ThreadGroup  What
  1  break   y       global  /work/asgard/debugger/test/Foo.cs:218
(mdb) disable 1
(mdb) show breakpoints
Breakpoints:
 Id   Type  En  ThreadGroup  What
  1  break   n       global  /work/asgard/debugger/test/Foo.cs:218
(mdb) enable 2
(mdb) show breakpoints
Breakpoints:
 Id   Type  En  ThreadGroup  What
  1  break   y       global  /work/asgard/debugger/test/Foo.cs:218
(mdb) delete 1
(mdb) show breakpoints
No breakpoints or catchpoints.

However, most of the time, you don’t want to insert a breakpoint at the current location, but somewhere else. Let’s have a look at the following example:

using System;

class X
{
    public static void Foo (int a)
    {
        Console.WriteLine ("Foo: {0}", a);
    }
 
    public static void Foo (string b)
    {
        Console.WriteLine ("Foo with a string: {0}", b);
    }
 
    public void Test (long a)
    {
        Console.WriteLine ("Test: {0}", a);
    }
 
    public static void Hello (int a)
    {
        Console.WriteLine ("Hello: {0}", a);
    }
 
    public static void Main ()
    {
        int a = 5;
        string hello = "Boston";
        X x = new X ();
 
        // You are stopped here.
        Foo (a);
        Foo (hello);
        Hello (a);
        x.Test (9);
    }
}

The syntax to insert a breakpoint is very similar to the one to invoke a method in the target:

Process @3 stopped at #0: 0x401b749a in X.Main()+0x5a at /home/martin/work/Test.cs:32.
32      Foo (a);
(mdb) b Hello
Inserted breakpoint 2 at X.Hello(System.Int32)
(mdb) b X.Hello
Inserted breakpoint 3 at X.Hello(System.Int32)
(mdb) b x.Test
Inserted breakpoint 4 at X.Test(System.Int64)

Note how similar it is to invoking a method; you cannot use the method name alone to insert a breakpoint on an instance method if you’re in static context. You can, however, use a fully qualified name to insert the breakpoint:

(mdb) b Test
ERROR: Cannot use instance member `Test' or current class in static context.
(mdb) b X.Test
Breakpoint 1 at X.Test(System.Int64)

Of course, this only applies if you’re in static context - just like you’d do things in C#:

Process @3 stopped at #0: 0x40bc082e in X.Test(System.Int64)+0xe at Test.cs:17.
17      Console.WriteLine ("Test: {0}", a);
(mdb) b Test
Inserted breakpoint 2 at X.Test(System.Int64)

If the method is ambiguous, overload resolution is performed using types:

Process @3 stopped at #0: 0x401b749a in X.Main()+0x5a at Test.cs:32.
32      Foo (a);
(mdb) b Foo(System.Int32)
Inserted breakpoint 2 at X.Foo(System.Int32)
(mdb) b Foo(System.String)
Inserted breakpoint 3 at X.Foo(System.String)

If you just use a method name, the debugger presents you a list and you can just pick a method from that list:

Process @3 stopped at #0: 0x401b749a in X.Main()+0x5a at Test.cs:32.
32      Foo (a);
(mdb) b Foo
More than one method matches your query:
1  X.Foo(System.Int32)
2  X.Foo(System.String)

(mdb) b -id 1
Inserted breakpoint 2 at X.Foo(System.Int32)
(mdb) b -id 2
Inserted breakpoint 3 at X.Foo(System.String)

You can also specify a source file and line number to insert a breakpoint:

Process @3 stopped at #0: 0x401b749a in X.Main()+0x5a at Test.cs:32.
32      Foo (a);
(mdb) list Hello
18
19  public static void Hello (int a)
20  {
21      Console.WriteLine ("Hello: {0}", a);
22  }
23
24  public static void Main ()
25  {
26      int a = 5;
27      string hello = "Boston";
(mdb) b 21
Inserted breakpoint 2 at /home/martin/work/Test.cs:21
(mdb) b /home/martin/work/Test.cs:26
Inserted breakpoint 3 at /home/martin/work/Test.cs:26

Catching Exceptions

To have the debugger stop when a particular exception is raised, you can use the catch command.

The catch command takes an argument, the type of the exception to catch. This will catch exceptions of that type or any derived types.

For example you can use:

(mdb) catch System.InvalidCastException

to catch exceptions of type InvalidCastException.

If you want to catch all exceptions, you can use catch Exception

Whenever the target stopped because of an exception, you may use print catch to display the exception:

 (mdb) run
 Unhandled Exception: TestException: Boston
   at X.Main () [0x00000] in /work/gondor/debugger/test/C.cs:18
 Thread @1 caught unhandled exception at #0: 0xb7845278 in X.Main()+0x38 at
 /work/gondor/debugger/test/C.cs:19.
   19    }
 (mdb) p catch
 (TestException) { "TestException: Boston
   at X.Main () [0x00000] in /work/gondor/debugger/test/C.cs:18 " }
 (mdb) ptype catch
 class TestException : System.Exception
 {
    string Hello;
    .ctor (string);
 }
 (mdb) p catch.Hello
 (string) "Boston"

Displaying a method’s source code

You can also view a method’s source code by using the list command. The syntax is identical to break:

Process @3 stopped at #0: 0x401b749a in X.Main()+0x5a at Test.cs:32.
32      Foo (a);
(mdb) list
30      // You are stopped here.
31      Foo (a);
32      Foo (hello);
33      Hello (a);
34      x.Test (9);
35  }
36 }
(mdb) list Hello
18
19  public static void Hello (int a)
20  {
21      Console.WriteLine ("Hello: {0}", a);
22  }
23
24  public static void Main ()
25  {
26      int a = 5;
27      string hello = "Boston";

This command takes an optional -lines argument to specify the number of source lines to display:

Process @3 stopped at #0: 0x401b749a in X.Main()+0x5a at Test.cs:32.
32      Foo (a);
(mdb) list -lines 5
30      // You are stopped here.
31      Foo (a);
32      Foo (hello);
33      Hello (a);
34      x.Test (9);

Disassembling

dis[assemble] [-count N] [-method]

Disassembles code at a given location

To disassembly the current instruction or the current method, use the dis command:

(mdb) Process @3 stopped at #0: 0x401b7443 in X.Main()+0x3 at Foo.cs:218.
218         Simple ();
(mdb) dis
0x401b7443  call   X.Simple()
(mdb) dis -method
X.Main():
0x401b7440  push   %ebp
0x401b7441  mov    %esp,%ebp
0x401b7443  call   X.Simple()
0x401b7448  call   X.BoxedValueType()
0x401b744d  call   X.BoxedReferenceType()
0x401b7452  call   X.SimpleArray()
0x401b7457  call   X.MultiValueTypeArray()
0x401b745c  call   X.StringArray()
0x401b7461  call   X.MultiStringArray()
0x401b7466  call   X.StructType()
0x401b746b  call   X.ClassType()
0x401b7470  call   X.InheritedClassType()
0x401b7475  call   X.ComplexStructType()
0x401b747a  call   X.FunctionStructType()
0x401b747f  leave
0x401b7480  ret

Like the examine command, dis takes an optional pointer expression as argument (see #Pointer expressions for details):

(mdb) dis %rip
0x400179d6      mov    %r15,%rdi
(mdb) dis 0x400179d6
0x400179d6      mov    %r15,%rdi
(mdb)

Exploring the Program State

show locals

Shows the local variables active in the current scope

(mdb) show locals
ok = (System.Boolean) false
(mdb)

show params

Shows the parameter to the current method

(mdb) show params
args = (System.String[]) [  ]
(mdb)

show location variable

Displays where the given local variable is stored

(mdb) show location ok
ok is a variable of type System.Boolean stored at %ebx

show registers, show regs

Shows the contents of the CPU registers

(mdb) show registers
EAX=412c6308  EBX=00000000  ECX=00000000  EDX=080523b8  ESI=00021f00    EDI=0002ad50
EBP=412c6240  ESP=412c6220  EIP=40ebd7ed  EFLAGS=PF ZF IF ID
(mdb)

The flags are decoded in EFLAGS

show modules

Shows all the loaded modules in the current program

(mdb) show modules
  Id step?  sym? Name
   0    y     n  /lib/libdl.so.2
   1    y     n  /lib/tls/librt.so.1
   2    y     y  mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   3    y     n  /opt/gnome/lib/libglib-2.0.so.0
   4    y     y  /mono/lib/libmono.so.0
   5    y     n  /lib/tls/libpthread.so.0
   6    y     n  /opt/gnome/lib/libgmodule-2.0.so.0
   7    y     n  /lib/libnsl.so.1
   8    y     n  /opt/gnome/lib/libgthread-2.0.so.0
   9    y     y  System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
  10    y     n  /lib/tls/libc.so.6
  11    y     y  /mono/lib/mono/1.0/mono-debugger-mini-wrapper
  12    y     n  /lib/ld-linux.so.2
  13    y     n  /lib/tls/libm.so.6
  14    y     y  mcs, Version=1.1.11.0, Culture=neutral, PublicKeyToken=0907d8af90186095
(mdb)

show sources module_id

Shows the sources for a given module.

You can use this to get a list of the source files for a given module, for instance, with the above example, you could use:

(mdb) show sources 14
Sources for module mcs, Version=1.1.11.0, Culture=neutral,  PublicKeyToken=0907d8af90186095:
 142  /home/cvs/mcs/mcs/cs-parser.cs
 143  /home/cvs/mcs/mcs/AssemblyInfo.cs
 144  /home/cvs/mcs/mcs/anonymous.cs
 145  /home/cvs/mcs/mcs/assign.cs
 146  /home/cvs/mcs/mcs/attribute.cs
 147  /home/cvs/mcs/mcs/driver.cs
 148  /home/cvs/mcs/mcs/cs-tokenizer.cs
 149  /home/cvs/mcs/mcs/cfold.cs
 150  /home/cvs/mcs/mcs/class.cs
 ...
(mdb)

Examining Memory

examine, x [/[items][format][size]] expression

Memory dump

The examine (short: x) command takes a pointer expression as argument (see #Pointer expressions for details). You can for instance use a processor register, an absolute address or the address of a variable:

(mdb) p %ebp
0x40bbe880
(mdb) x %ebp
0x40bbe880   88
(mdb) x /16 %ebp
0x40bbe880   88 e8 bb 40 48 74 1b 40 - b0 e8 bb 40 a3 74 1b 40
(mdb) p %eip
0x401b7619
(mdb) x /16 0x401b7619
0x401b7619   56 e8 b9 5c 16 c8 83 c4 - 04 ff 75 ec ff 75 e8 e8
(mdb) x /16 &f
0x40bbe864   6e db 36 3f 07 00 00 00 - 00 00 00 00 48 74 1b 40

When trying to take the address of a variable, keep in mind that some variables may be stored in a processor register, so they don’t have an address:

(mdb) p a
5
(mdb) ptype a
System.Int32
(mdb) x &a
Cannot take address of expression `a'

By default, the examine command prints one byte of memory in hexadecimal.

This can be changed specifying various options in a single parameter that must precede the expression, in gdb style: “/[items][format][size]”.

The items section is an integer specifying the number of items to print:

(mdb) x /64 %esp
0x40bbe888   b0 e8 bb 40 a3 74 1b 40 - b0 e8 bb 40 e0 d7 05 40
0x40bbe898   48 db 2a 08 8b 6b 01 00 - e0 e8 bb 40 c8 d7 05 40
0x40bbe8a8   d8 fe 16 08 f8 f3 19 40 - e0 e8 bb 40 84 d9 05 40
0x40bbe8b8   00 00 00 00 30 e9 bb 40 - 00 00 00 00 40 74 1b 40

If you want to see more data, just hit return:

(mdb) x /32 %esp
0x40bbe888   b0 e8 bb 40 a3 74 1b 40 - b0 e8 bb 40 e0 d7 05 40
0x40bbe898   48 db 2a 08 8b 6b 01 00 - e0 e8 bb 40 c8 d7 05 40
(mdb)
0x40bbe8a8   d8 fe 16 08 f8 f3 19 40 - e0 e8 bb 40 84 d9 05 40
0x40bbe8b8   00 00 00 00 30 e9 bb 40 - 00 00 00 00 40 74 1b 40
(mdb)
0x40bbe8c8   20 32 13 42 98 48 30 08 - 40 74 1b 40 88 74 1b 40
0x40bbe8d8   60 d6 2a 08 f8 f3 19 40 - 00 e9 bb 40 c8 97 0b 40
(mdb)
0x40bbe8e8   48 db 2a 08 00 00 00 00 - 30 e9 bb 40 00 00 00 00
0x40bbe8f8   40 1e 1e 08 f8 f3 19 40 - 40 e9 bb 40 34 a7 0b 40

The format specifier is a single character (similar to gdb): o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), c(ASCII char).

Also the size specifier is a single character (again, similar to gdb): b(byte), h(halfword), w(word), g(giant, 8 bytes), a(address, 4 or 8 bytes).

So, to inspect a double, one would use “/fg”, for a single “/fw”, for an IntPtr “/a” (the ‘x’ is the default), and so on…

Expressions

The debugger has a built-in expression evaluator which uses a C#-like language. You can not only view simple expressions like examining a variable, but also do more complex things like accessing properties, indexers or even invoking methods in the target.

Simple expressions

Literals work just like in C#:

(mdb) print 8
8
(mdb) print "Hello World"
"Hello World"
(mdb) print 512L
512
(mdb) print 3.14159
3.14159
(mdb) print 8.00F
8.00

You can also access variables from the target:

(mdb) print a
(System.Int32) 8
(mdb) print hello
(System.String) "Hello World"
(mdb) print y
(Y) { }

If you’re inside a non-static managed method, there’s also a special variable called this:

(mdb) print this
(X) { a = 5 }

Pointer expressions

Some of the commands require a pointer expression (for instance disassemble, examine).

The most common usage for pointers is taking the address of something, like a variable for instance. You do this with the & (address of) expression.

Note that unlike in C or C#, this expression gives you the address of an object and not the address where a pointer to the object can be found.

Let’s assume you have a variable called x which is stored on the stack at %ebp-0x18. If x is a reference type, %ebp-0x18 just contains a pointer to the actual object while for value types, the object itself can be found at %ebp-0x18. However, when you type print &x in mdb, you don’t need to care about these technical details - it’ll always print the address where the first byte of x’s contents can be found.

This means that you can always just examine &x and mdb will show you the correct region of memory.

(mdb) print &a
Cannot take address of expression `a'
(mdb) print &hello
(void *) 0x401b7440
(mdb) print *pointer_var
(System.Int32) 5

Note that while debugging managed code, variables of type `object’ are also treated like pointers. Normally, you don’t need to care about this since the user interface automatically dereferences the pointer for you, but this can become very useful if you encounter an `object’ which is somehow corrupted: even if the debugger can’t display it as a normal managed `object’, you can still examine it as a pointer and so figure out what when wrong.

Processor registers are also treated as pointers:

(mdb) print %eax
0x401b7440
(mdb) print %ebp
0x40bbe888
(mdb) print %esp
0x40bbe888

Last, but not least, you can also use an address literal as a pointer:

(mdb) x 0x40425010
0x40425010   20 af 54 00 00 00 00 00 - 20 af 54 00 00 00 00 00

Invocation expressions

The debugger can also invoke methods in the target. Let’s have a look at the following example:

using System;
 
class X
{
    public static void Foo (int a)
    {
        Console.WriteLine ("Foo: {0}", a);
    }
 
    public static void Foo (string b)
    {
        Console.WriteLine ("Foo with a string: {0}", b);
    }
 
    public static void Hello (int a)
    {
        Console.WriteLine ("Hello: {0}", a);
    }
 
    public static void Main ()
    {
        int a = 5;
        string hello = "Boston";
 
        // You are stopped here.
        Foo (a);
        Foo (hello);
        Hello (a);
    }
}

Now you’re stopped in Main and want to invoke methods.

(mdb) print Foo (3)
Foo: 3
Method `Foo ()' doesn't return a value.
(mdb) print Foo ("Hello World")
Foo with a string: Hello World
Method `Foo ()' doesn't return a value.
(mdb) print Hello (9)
Hello: 9
Method `Hello ()' doesn't return a value.
(mdb) print Hello (a)
Hello: 9
Method `Hello ()' doesn't return a value.
(mdb) print Foo (hello)
Foo with a string: Boston
Method `Foo ()' doesn't return a value.

Note how the debugger automatically does overload resolution when calling Foo.