m3gdb, the Modula-3-enhanced version of gdb

Rodney Bates


Table of Contents

Introduction
Building (m3)gdb
Build Dependencies
Compiling Modula-3 Code for Debugging
Commands in m3gdb
The print Command
The start command
Linespecs
Linespecs using a file name and line number
Linespecs that are qualified references
Linespecs using mangled names
Expressions
Identifiers
Procedures
Types
TEXT values
Modula-3 References
Arrays
Calls
Arithmetic operators
Relational operators
Builtin functions
m3gdb Support of Alternate Modula-3 Implementations
The Info Modula-3 command
m3gdb and Garbage Collection
m3gdb and thread support
Debugging and Runtime Initialization
Module Body execution in CM3
Using the Modula-3 Runtime System to Aid Debugging
Miscellaneous
Nonlocal variable problems in CM3
History and Acknowledgements

Introduction

m3gdb is a modified version of the well-known gdb debugger, with added support for the Modula-3 programming language. Much of the function of m3gdb is the same as gdb, and this article makes no attempt to duplicate information found in existing gdb documentation . Instead, it documents properties of m3gdb that add to or differ from gdb. The reader is assumed to be familiar the gdb documentation, or able to consult it as necessary. Most of the commands, command line options, environment variables, etc. of m3gdb are the same as for gdb. The differences lie in the syntax and semantics of expressions, linespecs, etc. and in the output m3gdb produces, when Modula-3 code is involved.

Throughout, the term "gdb" will be used in statements that apply only to unmodified gdb. The term "m3gdb" will be used in statements that are specific to m3gdb. The term "(m3)gdb" will be used to state properties that m3gdb inherits from gdb, and thus are the same in both debuggers.

gdb is a multi-language debugger. In deriving (m3)gdb from gdb, none of gdb's function is removed. Only new support for Modula-3 is added. Thus, (m3)gdb is also a multi-language debugger, with one additional supported programming language. (m3)gdb further supports debugging mixed-language programs, where modules written in different languages are linked together.

m3gdb supports code compiled by the SRC, PM3, EZM3, and CM3 Modula-3 compilers. A single m3gdb executable dynamically detects and adapts to code compiled by any of these compilers. The SRC, PM3, and EZM3 compilers differ very little, as far as m3gdb is concerned, so the phrase "PM3 et. al." will be used in statements that apply equally to any of these three Modula-3 compilers. More information on the various compilers and other language implementation alternatives and be found in this section.

Building (m3)gdb

Despite its support for all four Modula-3 compilers, the current, maintained version of m3gdb is kept only in the CM3 source repositories. Within the repository it is found at cm3/m3-sys/m3gdb. It is entirely written in C, so a working Modula-3 compiler is not required to build it. However, it is integrated into CM3, and this integration uses m3makefiles and the Modula-3 build system. Thus, the easiest way to build it is, with a working installed CM3, go into subdirectory cm3/scripts and execute ./do-cm3-m3gdb.sh buildship. This will build and install m3gdb in the normal CM3 bin directory, usually /usr/local/cm3/bin, where the installed executable will be named m3gdb.

This will go through the usual configure process, which will build a debugger that both executes on and debugs programs on the machine the build process executes on. Building by this method will always repeat the configuration process, which can be annoyingly time-consuming if you are doing development work on m3gdb and have only modified a source file or two. Once the configure step has been done on a given machine, it is safe to disable it for future recompiles on the same machine. You can do this by uncommenting the line %quick = 1, found near the top of cm3/m3-sys/m3gdb/src/m3makefile. Note that this is quake code, where the "%" is the comment-start character.

To build m3gdb without using a Modula-3 compiler, go in to cm3/m3-sys/m3gdb and execute ./configure and make. This is the usual C build process. The compiled executable will then be found in cm3/m3-sys/m3gdb/gdb/gdb, from whence you can move or copy it as desired.

(m3)gdb has a source file ada-lex.c, that is mechanically generated from ada-lex.l, by flex. Normally, the CM3 distribution will contain an up-to-date ada-lex.c, but if not, you may need to have flex installed to build m3gdb. There are also several *.y files that need bison to regenerate their corresponding *.c files, following the same pattern.

In order to be able to debug variables of Modula-3 types INTEGER and LONGINT, m3gdb needs to be compiled by a C compiler that has an integer size at least as wide as each of these Modula-3 types.

Build Dependencies

(m3)gdb needs the development files for libncurses. In Ubuntu, this is in package libncurses5-dev.

Compiling Modula-3 Code for Debugging

As with all debuggers, (m3)gdb requires that code to be debugged be compiled with certain options that ask the compiler to insert debug information in the object modules and executable. (m3)gdb needs this information to know things like the names, memory locations, and types of variables. Without such information, (m3)gdb can still function, but only at the machine instruction level, or in very limited ways at the source code level. m3gdb recognizes only the stabs or stabs+ debug information format. In CM3, the latter is required for full m3gdb function.

To check whether an object or executable file has the right kind of debug information, execute objdump -G <filename>|head. The file contains the needed debug information iff you see the line "Contents of .stab section:".

By default, the CM3 distribution is set up to compile with debug information, at least on some platforms. There are mechanisms for specifying either on the cm3 command line or in the m3makefile, that it should be produced, but these default to on, and there does not seem to be a way to turn them off.

However, debug information can be turned off or on in another way by omitting or adding the line if debug args += "-gstabs+" end in the quake procedure named m3_backend. This is found in the CM3 configuration file, usually located at /usr/local/cm3/bin/cm3.cfg.

For example, to enable debug information, it should look someting like:

        proc m3_backend (source, object, optimize, debug) is
          local args = [ "-quiet", source, "-o", object, "-fPIC", "-m32", "-fno-reorder-blocks" ]
          if optimize  args += "-O3"  end
          if debug     args += "-gstabs+"  end
          if M3_PROFILING args += "-p" end
          return try_exec (m3back, args)
        end
      

This line has been missing in the distributed configuration file for some targets, at some times in the past few years.

By default, PM3 et. al. produce debug information. If your installation is not giving debug information, add the line option("debuginfo","T") to your m3makefile. To suppress generation of debug information, add the line option("debuginfo","") to your m3makefile.

Commands in m3gdb

The print Command

In C, every expression is also a statement and may return a result and/or have side-effects. These semantics are reflected in gdb, which was first a C debugger. In particular, the print command accepts an argument that is an expression, which it both evaluates, printing the result, and carries out its side-effects on the debugee program. The expression could have no result, if its type is void, in which case, the print command displays (void). The user can execute a C assignment or call by typing it as the argument to the print command. This facility is, in effect, an interpreter for a significant subset of the language, implemented by gdb.

Many of the Modula-3-specific extensions that m3gdb provides are interpretation capabilities for Modula-3. m3gdb follows the C-like pattern for the print command by allowing its argument to be either an expression, an assignment statement, or a call statement. Note that an expression could also be a call on a function procedure or method. If the argument is a Modula-3 expression, the print command evaluates it and prints the result. If the expression contains a function call, there could also be side-effects as well. If the argument is a statement, the command executes the statement and displays (void). Section Expressions describes the expressions m3gdb can evaluate.

The start command

When the main program is written in Modula-3, the m3gdb start command will execute all compiler-generated initializations and all module initialization bodies in the runtime system, which is in library libm3core and always used when there is Modula-3 code. (but see this note). Execution will stop before any of the other module initialization blocks.

For code compiled by PM3 et. al., m3gdb accomplishes this by setting a breakpoint in procedure RunMainBodies, and the stop after the start command will appear as hitting this breakpoint. RunMainBodies is located in library libm3core, and if it is dynamically linked, it may not have been loaded at the time you type the start command, thus requiring you to go through the usual ritual of allowing the breakpoint to be made pending, until the containing library is loaded. Answer y to the question. The breakpoint will be resolved automatically.

For CM3-compiled code, m3gdb accomplishes this by setting a breakpoint in Main.i3, and the stop after the start command will appear as hitting this breakpoint.

Linespecs

(m3)gdb has commands, e.g., the break command, that take an argument called a linespec. A linespec is used to denote an executable place in the code. There is overlap beween an expression and a linespec, because either can refer to a module, interface, procedure, or (anonymous) block. In a linespec, a procedure or block directly denotes its first executable statement, while any of the four can be used as qualifier in path eventually leading to a procedure or block. In an expression, a procedure can be named in a call, actual parameter, or as a procedure constant, an interface or module can similarly be used as a qualifier, leading to a procedure.

However, there are significant differences in (m3)gdb's syntax and semantics of linespecs and expressions. An expression is more general in one respect in that it can denote a variable, formal parameter, type, field, method, or procedure. But an expression needs to have a context where the debugee program is stopped, in order to get, e.g., values of variables, and, more fundamentally, to imply which language the expression is to be interpreted in.

In contrast, a complete linespec can only denote an executable location. While this cannot be a variable, formal, type, field, or method, it must be possible to make sense of it independent of any execution context. This means it could be a specification in the syntax of any of the languages supported by (m3)gdb. It could also have the form <sourceFileName>:<lineNumber>, where <sourceFileName> could contain dots. So (m3)gdb's parsing and analysis of expressions and linespecs is quite different. Nonetheless, m3gdb attempts to make them behave the same, for cases that can occur in either kind of denotation.

Linespecs using a file name and line number

A linespec with the form <sourceFileName>:<lineNumber> denotes the specified line number of the source file. This is no different from other gdb-supported languages. If this line is not an executable place, (m3)gdb will use a nearby line that is.

Linespecs that are qualified references

A linespec can take the form of a list of components, each of which is an identifier or a decimal number, separated by dots, with embedded white space allowed between the tokens. This is a kind of fully-qualified path to a procedure or block. The first component must be an identifier and must denote an interface, module, or procedure. A subsequent identifier denotes a procedure by that name, declared local to the unit denoted by the prefix. A number n denotes the n-th anonymous block nested immediately inside the unit denoted by the prefix. This system allows specification of any procedure or block.

If the debugee program is running but stopped, and thus has an execution context, m3gdb first tries to interpret the first identifier in that context, using the scope rules of Modula-3. If that fails, m3gdb next tries to interpret the first identifier as an interface name, looking in the entire link closure of the program. If that fails, m3gdb finally tries to interpret the first identifier as a module name, again looking in the entire link closure of the program. If the first identifier refers to a module in a not-yet-loaded, dynamically linked library, this will fail, and (m3)gdb will try to find another way to interpret the linespec.

A subsequent identifier that denotes anything other than a procedure is a failed attempt at interpreting the linespec. Likewise, a block number that would denote a nonexistent block is a failure. Although a procedure can be referred to using an interface as prefix, any further components of the linespec are interpreted relative to the body of the procedure, i.e., within the module that exports the procedure. A procedure within a module will be found if it is either explicitly declared in the module or in an exported interface of the module. This means that, when the exporting module has a different name from an interface, a procedure can usually be referenced using either the interface name or the module name as prefix.

The initialization block of a module can be specified as either the module name alone, or with the form <moduleName>.1, i.e., the first (and only, in this case) block inside the module. As a prefix of a longer linespec (i.e., to specify some procedure or block inside the module initialization block), the latter form is required, because, for example, <moduleName>.<procedureName>; denotes a procedure declared local to the module, not to the module's initialization block. See this note about hitting breakpoints specified in this way.

m3gdb is inconsistent about what it does when an interpretation attempt fails. Sometimes it tries the next way of looking up the first identifier as Modula-3 code. This usually happens when the failure occurs within the first two components. Sometimes it abandons trying to interpret the entire linespec as Modula-3 code, but allows gdb to try other ways to interpret it, in other languages. This usually happens when the first identifier can't be found and when the component list is ill-formed. Sometimes it displays an error message and gives up altogether. This happens for bad components after the second. This inconsistency is considered a bug, but it is not obvious what the best semantics are.

Linespecs using mangled names

All the Modula-3 compilers emit mangled linker names for procedures, and such a name can be used in a linespec as an alternative way to identify a procedure. The mangled name for any procedure is similar to the component list linespecs above, with each dot replaced by two underscores. The entire name is a single linker name, so no embedded white space is allowed. The first component is always the module the procedure's body is declared in. The block numbers have no leading zeros. It is possible for a programmer to spoof such a name by an identifier with double underscores, causing confusion to m3gdb.

Expressions

The following list gives Modula-3 code constructs that are supported, to at least some degree, in m3gdb expressions. Where m3gdb semantics are not identical to Modula-3 semantics, subsequent subsections describe the differences. All of these constructs are treated as expressions by m3gdb. but some are statements in Modula-3.

Identifiers

Unqualified Identifiers

m3gdb generally follows Modula-3's rules for looking up an unqualified identifier, with some exceptions.

The debug symbol data emitted by the compilers contains no information about identifiers declared in a CONST or EXCEPTION declaration, so you can't refer to these in an m3gdb expression. (But you can refer to values of enumeration types.) This can further change the semantics of identifier lookup. For example, in true Modula-3 code, there could be a reference to a named constant that is declared in some inner scope. In an m3gdb expression, the same identifier might end up denoting an entirely different declaration by the same name, in some outer scope. Since m3gdb doesn't know the constant exists, it will find the identifier in the outer scope.

When a normal Modula-3 scope lookup of an identifier fails, m3gdb looks for an interface or module by that name, in the entire link closure of the program, excluding any not-yet-loaded dynamically linked libraries. The name of an interface or module, by itself, has no meaning in an m3gdb expression, but it can be followed by a dot and used to name a procedure, variable, or type declared in the interface or module. Identifiers known in a module via exported interfaces can be named in this way, in addition to those declared directly in the module. If there is both an interface and a module by the same name, the interface is searched first, but a procedure named in that way is treated as referring to the procedure body.

If, perversely, there were a module that did not export a same-named interface and both contained different declarations of the same identifier, this lookup order would mean you could not name the meaning declared in the module, unless the program context were somewhere inside the module.

References to nonlocal variables

For PM3 et. al., m3gdb fully supports references to variables that are declared in some scope outer to the referencing context.

For CM3, the debug information for following static links is inadequate. So long as nested procedures are called only as procedure constants, it should work correctly. If a nested procedure is called through a formal parameter, m3gdb might find the wrong instance of a statically containing procedure, when accessing the containing procedure's variables nonlocally. For now, m3gdb warns whenever accessing variables nonlocally. Also, see this note.

This problem does not apply to fully global variables, since they do not require the static link mechanism to address them.

Qualified Identifiers

Dot selections in m3gdb follow most of Modula-3's rules. You can select:

  • A procedure, type, or variable of an interface or module.

  • A field of a record value.

  • A field or method of an object value.

  • One of the named values of an enumeration type.

You can not select a constant or exception of an interface or module. You can not select a method name of an object type.

Procedures

m3gdb displays the value of a procedure in both of two ways: a qualified path as in a linespec, and a source file and line number.

A procedure can be referred to in an m3gdb expression, either as part of a call, to pass the procedure (constant) as an actual parameter, or to assign it to a variable.

m3gdb does not check assignability of two procedure types. This is relevant for assignments and passing parameters. If both are procedure types, it assumes they are assignable. It warns when it makes this liberal assumption. Bad things will almost certainly happen if you abuse this.

Types

A type can be referred to in an m3gdb expression. The primary use is as parameter to builtin functions such as LOOPHOLE, VAL, etc. Only named types work, either builtin or declared in a TYPE declaration. m3gdb does not recognize Modula-3 type constructors.

m3gdb displays the value of a named type as a qualified path, as in a linespec, but ending with a type name.

TEXT values

m3gdb displays values of type TEXT and accepts them in expressions. It supports both the form used in PM3 et. al. and the form used in CM3. If the program was compiled by CM3, it will handle wide TEXT literals and also the type WIDECHAR and its literals.

Normally, m3gdb displays TEXT values in Modula-3 lexical syntax, with double quotes, escape sequences, and, if appropriate, a leading 'W'. However, the /k option in a print command will cause it instead to display the TEXT value's internal data structure. For PM3 et. al., this is a traced reference to an open array of characters. For CM3, this will be one of the several object subtypes of TEXT. m3gdb properly recognizes and displays values of CM3 type TextLiteral.T. Here, it takes the length of the text from the appropriate field, instead of from the declared type, which contains a fixed array whose length is almost always far too long.

Even without specifying the /k option, if you happen to know what the internal representation is, you can apply appropriate operators to it, e.g., dereferencing, field selection, subscripting, or even method calls.

There are some cases where, in order to evaluate/execute a user-typed expression, m3gdb has to actually allocate an object in the heap of the debugee program. Examples are assigning a user-typed TEXT value to a variable or passing it as an actual parameter. This can happen unexpectedly if the expression in the m3gdb command contains the Modula-3 concatenation operator &, which m3gdb evaluates by calling Text.Cat in the debugee process. This will alter the debugee's execution environment it a subtle way. Usually this will not matter, and the garbage collector will eventually collect such objects after they become inaccessible. Such operations will not work if you are only debugging a core file and don't have an executing debugee program.

If you type print "ABC" & "DEF", m3gdb will execute three calls in the target program. Two on Text.FromChars to get the TEXT values allocated and one on Text.Cat to do the concatenation. All three TEXT strings will be allocated in the debugee program's traced heap, but will immediately become garbage, available for collection.

Modula-3 References

m3gdb tolerates application of a redundant dereferencing operator ^ to a value of an object type, with a warning.

m3gdb always treats a heap object as having its allocated type, not the static type of the expression used to refer to it. This allows you to select fields, etc. of the actual object.

A LOOPHOLE applied to a heap object is an exception. This would allow you to access a field or method of a supertype that was hidden by a different but same-named field or method in the allocated type.

Arrays

m3gdb does not support subscripting on non-byte-aligned, bitpacked arrays.

m3gdb does not support array constructors.

m3gdb does not allow assignability of references to non-equal array types. This is relevant for assignments and passing parameters. In Modula-3, some such combinations are assignable. Sorry.

Calls

For ordinal types, m3gdb can't distinguish a VAR parameter from a READONLY parameter, given the information available from any of the compilers. In this case, it assumes VAR, and requires the actual and formal to have identical types. This assumption makes VAR mode work (i.e., the actual can be changed by the called procedure), but means m3gdb's type rule is overly strict for READONLY.

In the latter case, if the actual is assignable but not identical and you really need to pass it, you will have to use a LOOPHOLE on the actual parameter in the call. On the other hand, if you use the LOOPHOLE and the mode is really VAR, bad things could happen.

For all other classes of types, either m3gdb has a way to distinguish VAR from READONLY, or it doesn't matter.

Arithmetic operators

m3gdb's liberal type rules allow the integer binary (i.e., two-operand) arithmetic operations to be performed on any subrange type and any reference type, as well as INTEGER, LONGINT, CARDINAL, LONGCARD, and mixtures thereof.

ABS and unary - can only be applied to an INTEGER, LONGINT, or floating operand. They won't even work on a PACKED or a formal parameter passed by reference. Since it is semantically an identity, m3gdb just ignores unary + without even bothering to type check it.

Relational operators

The relational operators =, #, <, <=, >, and >= do no type checking at all.

Builtin functions

Builtin functions FIRST, LAST, and NUMBER work on ordinal and array types only. They do not work on array values or on floating types or values.

TYPECODE works on non-NIL values that have traced reference types. It does not work on types, nor on the value NIL.

LOOPHOLE works as in Modula-3, except it cannot be used to convert to an open array type.

ADRSIZE, BITSIZE, and BYTESIZE work as in Modula-3, except they cannot be applied to an open array value.

ADR can be applied to any value.

m3gdb Support of Alternate Modula-3 Implementations

m3gdb supports code compiled by the SRC, PM3, EZM3, and CM3 Modula-3 compilers. A single m3gdb executable dynamically detects and adapts to code compiled by any of these compilers.

Programs that link together Modula-3 code produced by a mixture of CM3 and PM3 et. al. compilers are likely to confuse m3gdb's detection of what compiler was used. These compilers have significant differences in their runtime organization, and m3gdb assumes there is not a mixture. Any such problem behaviour can even depend on the order in which dynamically-linked libraries are brought in. In fact, mixing code like this is likely to cause other problems, even without m3gdb's involvement.

There are two different code generators in use by the various compilers. One is derived from the well-known gcc compiler, with modest modifications to support Modula-3. It inherits much of gcc's repertoire of supported targets and its range of optimization options. For PM3 et. al., it is derived from gcc 2.7.2. For CM3, it is derived from gcc 4.3.0. The other code generator is an i386-specific code generator, written in Modula-3 and designed for fast compilation.

There are three different thread implementations for Modula-3 threads. One uses setjmp and longjmp and is implemented entirely within the runtime library libm3core. It subschedules the process thread that is provided by the operating system, among the Modula-3 threads. In recent years, it has been undermined by security-motivated changes in setjmp and longjmp. Only a few platforms have an updated version. The second is much newer and uses the library libpthread. It integrates scheduling of Modula-3 threads with other threads. It also is adapted to multi-core and multi-chip SMP systems. The third uses Windows native threads and is used only on Windows platforms.

There are two different garbage collectors. Both are capable of incremental collection, i.e., running collection activity in a parallel thread to the running program, thus avoiding unexpected pauses in execution. The older, virtual-memory-synchronized collector uses virtual memory to detect heap changes that would otherwise undermine the correctness of the collector. The newer, compiler-assisted collector uses compiler-inserted notifications for the same purpose.

The Info Modula-3 command

m3gdb has a new command "Info Modula-3". This will tell you which compiler and which code generator were used to compile the debugee program and which threads implementation and which garbage collector are in use. In case you have trouble remembering which spelling of the language name to use, it accepts the cartesian product of "Modula" spelled out or abbreviated with a single "M", lowercase/uppercase "M", and with/without the hyphen. A mixture of implementations will confuse it. It may be unable to get some of the information before the runtime system has been initialized.

m3gdb and Garbage Collection

The virtual-memory-synchronized garbage collector works by asking the operating system to artificially hardware-protect certain memory areas from access and to notify it when such and access occurs. The notification is through a segment fault that the garbage collector catches and handles. Unfortunately, m3gdb can't distinguish this artificial segment fault from a normal one, resulting in numerous false stops of the debugee program. m3gdb prevents this by automatically disabling incremental collection when debugging with this collector. This is the equivalent of manually adding @M3novm to the command line arguments, which you can also do redundantly and harmlessly. You can reenable incremental collection by typing the command "print RTCollectorSRC.EnableVM()", after the runtime system has been initialized.

m3gdb and thread support

The libpthread thread implementation uses signal SIG64 internally. By default, (m3)gdb stops when this signal is received, resulting in numerous false stops. When this thread implementation is in use, m3gdb automatically executes the command "handle SIG64 nostop noprint pass", before the debugee program starts to run. This causes m3gdb to silently pass this signal to the program, preventing the false stops. If you want to reverse this, for example, to debug this thread implementation, type the command "handle SIG64 stop print pass".

Debugging and Runtime Initialization

Some debugging commands can fail if executed too early, before certain initializations have happened. Understanding initialization can matter to you if you want to debug module body code or the runtime system itself, or try to execute calls in m3gdb commands before everything has been initialized.

m3gdb uses addresses found in debug information in executable files to address global variables and procedures. On some targets, executable code uses a different mechanism that addresses global variables and procedures indirectly, using pointers that are also stored in global locations. These pointers are initialized by compiler/linker-generated machine code, and this happens during program startup.

You can access a global variable with an m3gdb command any time, although its value may not yet be initialized. However, if you call a global procedure using an m3gdb command, and it or any other procedure it calls, directly or indirectly, accesses a global variable, it may use one of these pointers. If the pointer has not yet been initialized, this will almost certainly cause your program to suffer a segment fault that would never happen if you only executed compiler-generated code.

Other problems of more varied nature can also occur if the module body code, written by the Modula-3 programmer, has not been executed when you use an m3gdb command to call a procedure too early.

Using the m3gdb start command will ensure that the runtime system has been initialized. After that, m3gdb calls on procedures that are in libm3core and in the closure should be safe. To use m3gdb safely to make calls in your own code, you need to be sure execution has proceeded at least through the first line of the body of the module that exports Main.

If you link in a library, all the code of the entire library is loaded into your address space, and all the debug data is available to m3gdb, even for modules that are in the library but not in the IMPORT/EXPORT closure of the main program. This means you can call procedures in such modules and access their global variables from m3gdb commands. However, the compilers take care of initialization only for modules in the closure, so such modules will never be initialized. Similarly, only certain modules in libm3core are initialized as part of the runtime system.

As an example, you can always call RTTypeFP.FromFingerprint, because it is in libm3core. And this could be a useful thing to want to do during a debug session (e.g., when debugging something involving pickles). But RTTypeFP is not initialized as part of the runtime system and is usually not named in any IMPORT or EXPORT in a typical program, and therefore is not in the closure.

So this call, in an m3gdb command, will result in a segment fault while trying allocate a heap object of a type declared in RTTypeFP. Even the needed type definition is present in memory, but the needed pointer to it is not set up. To make this work, you have to put IMPORT RTTypeFP; somewhere in your program and recompile and also postpone the m3gdb call until the necessary initialization has taken place.

Module Body execution in CM3

In CM3-compiled code, if you put a breakpoint at, e.g., Mod.1 rather than using a source code line number, you will see somewhat strange behaviour. The CM3 runtime system invokes module bodies multiple times. The compiler translates them so they do different things on different invocations. The compiler also gives them a parameter named mode, whose value, in part, determines what the module body code will do.

If execution stops at a breakpoint such as Mod.1, and mode has value 0, the programmer-written Modula-3 code of the module body will not be executed during this invocation, and the initialization of the runtime system might not have been done either. This can happen more than once. When mode has value 1, then the Modula-3 code itself is about to be executed.

You can work around this by using a linespec with a source code line number in your break command.

Using the Modula-3 Runtime System to Aid Debugging

There are many things you can do to aid debugging, just by using m3gdb commands to print and set variables and to call procedures in the runtime system.

As an example, RTTypeFP.FromFingerprint could be useful when debugging pickles themselves, or a program that uses them. However, see here for a caveat on how to use it.

Miscellaneous

Nonlocal variable problems in CM3

In CM3, there are problems in displaying variables and formal parameters that are nonlocally accessed somewhere in the Modula-3 code, (in addition to this problem.) If a formal parameter is accessed nonlocally anywhere in the program, accessing it locally in m3gdb by the commands info arg, frame, or backtrace will display an incorrect value, while info loc will give two values for such a parameter, only one of which is correct. The print command appears to be correct in such cases, as far as tested to date.

History and Acknowledgements

m3gdb was originally developed as a modification to gdb, version 4.17, done at DEC's Stanford Research Center, by unknown authors. Over the years, the modifications have been moved to gdb versions 4.0.1.0, 5.3, 6.3, and 6.4, along with many enhancements, by Antony Hosking and Rodney Bates. Any additional information about history or contributors would be welcomed.

This document was originally written in October of 2008 by Rodney M. Bates, rodney.bates@wichita.edu.