Debuggers are not as essential in programming Modula-3 as they are in C or C++. Nevertheless, it is always good to have a robust debugger around. There is a debugger available for Modula-3 called m3gdb. You must use m3gdb to debug code generated by CM Modula-3. It's a version of gdb that's been modified to understand Modula-3 types, declarations, and expressions. These modifications are quite new, and hence the current release of m3gdb is unsupported. We hope to provide better debugging support in future releases. On Win32 platforms, Modula-3 seems to interoperate with the Microsoft Visual C++ Debugger(tm).
m3gdb is documented on its own manual page.
The rest of this section provides some hints for using m3gdb.
When it's first started, you must explicitly tell m3gdb that it's working on a Modula-3 program. To do that type
(m3gdb) set language m3
Similarly, if you need to access C procedures or variables or do some kinds of low-level debugging, type
(m3gdb) set language c
Modula-3 procedures are mapped as closely as possible into C procedures. Two differences exist: ``large'' results and nested procedures.
First, procedures that return structured values (i.e. records, arrays or sets) take an extra parameter. The last parameter is a pointer to the memory that will receive the returned result. This parameter was necessary because some C compilers return structured results by momentarily copying them into global memory. The global memory scheme works fine until it's preempted by the Modula-3 thread scheduler.
Second, nested procedures are passed an extra parameter, the ``static link''. The exact details of how that parameter are passed are system dependent.
I don't know how to call a nested procedure from the debugger's command line.
When a nested procedure is passed as a parameter, the address of the corresponding C procedure and its extra parameter are packaged into a small closure record. The address of this record is actually passed. Any call through a formal procedure parameter first checks to see whether the parameter is a closure or not and then makes the appropriate call. Likewise, assignments of formal procedure parameters to variables perform runtime checks for closures.
<*EXTERNAL*> procedures have no extra parameters. except if they return large results??
m3gdb provides two commands to assist debugging multi-threaded programs. The command threads lists the threads that exist in the program, with the first thread being the most recently active one. The command switch n switches the current context to the thread identified as n by the threads command. If you use the switch command, you must use it again to switch back to the thread that was interrupted before you can continue the execution of the program (i.e. the first thread listed by the threads command). If the switch command is interrupted, the state of m3gdb is essentially random and it will most likely crash. The current language must be m3 for the threads and switch commands to work.
There is no mechanism to run a single thread while keeping all others stopped.
Some platforms (eg DS3100 and SPARC) use a VM-synchronized garbage collector. On those platforms will find it simplest to run it with the @M3novm switch. For example, start your program with
(m3gdb) run MyProgram @M3novm
If you do not use the @M3novm flag, you must set m3gdb to ignore VM faults generated on the collector's behalf, by typing
(m3gdb) handle 11 noprint pass
But then, without @M3novm, you might not be able to examine the heap when the program stops, because the heap may be protected. You can turn off all current heap protection by telling m3gdb
(m3gdb) call RTCollectorSRC.FinishVM()
If you also want the collector not to use VM protection in the future (i.e., if you wish you'd typed @M3novm to start with), you can type
(m3gdb) call RTCollectorSRC.DisableVM()
If your program is not run from the debugger, and it dumps core, the runtime automatically calls the equivalent of RTCollectorSRC.FinishVM() to let you examine the heap in the core file.