New debugger features for userlibraries

Posted by – February 20, 2009

A number of new functions were added to the DebuggerModule.h file in the SDK with 4.30 and since there is no documentation for them except the header file itself, i am going to explain them here briefly. They are mostly about localizing the debugger messages for the libraries but there are also some useful functions to validate the function parameters. I am using C to explain things even though most userlibraries are probably created using Tailbite, but C is what this stuff was written in/for and not everything can be directly translated to PB code (like the variable arguments functions). If you use TailBite you’ll have to use some tricks to make it all work. Note that all Message and Key parameters take ascii strings as input, even in unicode mode.

Error reporting:

In addition to the PB_DEBUGGER_SendError() which was there before, there is now also PB_DEBUGGER_SendWarning(). It works just like the SendError one, but issues a warning and not an error. Warnings are handled depending on the level that the user chooses. They can be ignored, just added to the log or handled like an error (program stop etc). The intention is to report anything that is probably wrong, but could also be valid input as a warning so the user is not constantly annoyed by errors that are none. The PB libraries currently report the following things as warnings:

  • Missing files for LoadImage(), LoadSprite(), etc. This often indicates that the user just mistyped the filename, but it is also perfectly valid to use these functions to try to load a nonexisting file and handle the failure properly.
  • Passing something that isn’t a PB procedure to a function that expects a callback. This helps catch cases like CreateThread(@Function, 0) where the ‘()’ is simply forgotten which is a very common mistake. It is however perfectly legal to pass a pointer to an external (maybe dll) function or label here, this is why this is not an error. A 0-pointer or a procedure pointer with the wrong prototype are however handled as errors, because those clearly are not valid input.
  • If OnError functions are used with enabled debugger, or with disabled OnError lines support.
  • On Linux: Gtk warnings/errors. Those mostly do not cause a program to fail, that is why they are just warnings. The debugger catches these since 4.30 because this way you can get a PB linenumber for the warnings which was not possible before.

There is the SendDebuggerWarning macro to keep the same style as the SendDebuggerError() before.

Parameter validation:

PB_DEBUGGER_CheckLabel() takes a pointer and returns true if the given pointer represents a label in the code. This does not detect labels in direct Asm, so there should be only a warning if this check fails.

PB_DEBUGGER_CheckProcedure() can check if a pointer is a procedure in the code and if so, wether the procedure matches a given prototype. This is a vararg function and takes the following arguments:

  • The pointer to check
  • The expected return type. The type is specified using the values of the PB type constants (#PB_Long, #PB_String etc). They are defined for C in the PureLibrary.h header.
  • The expected number of arguments to the procedure.
  • The type of each argument, the same way as the return type.

The possible returnvalues are listed in the DebuggerModule.h and indicated wether the function was found and if the prototype matches. Note that the automatic promoting of parameters into account. For example a word is always expanded to a long on the stack on x86, so passing PB_Word as expected parameter will also match a procedure that has a long at this parameter position (as the stack layout is the same). As mentioned above, a mismatching prototype or 0-pointer should be seen as an error, but a nonzero pointer that does not point to a PB procedure may still be valid, so better just issue a warning there.

PB_DEBUGGER_FileExists() was there before, and simply checks if the given file exists. The input to this has to be a unicode string in unicode mode unlike the other functions.

To make the list complete, there are also the PB_DEBUGGER_FullScreen, PB_DEBUGGER_Unicode and PB_DEBUGGER_Thread variables to indicate the settings/state of the program. There is also the threadlocal structure PB_DebuggerGlobals which currently only tells you if the program is in a StartDrawing() block. To get this info, use PB_Object_GetThreadMemory() from the Object.h with the PB_DEBUGGER_Globals variable to get the actual pointer to the threadlocal structure. All this stuff was there also before 4.30

Localisation of debugger messages: Using the common error messages

There is a list of very common error message for checking input parameters. These can be used within your library even without providing your own translation files. You can see the possible messages in the Debugger.Catalog file in the PureBasic folder in the [Common] section. The functions to access these are:

char *PB_DEBUGGER_GetCommonLanguage(char *Key, ...);
void  PB_DEBUGGER_SendCommonError(char *Key, ...);
void  PB_DEBUGGER_SendCommonWarning(char *Key, ...);

As you can see, they are vararg functions. They work in fact just like printf(). Most common messages have placeholders for strings/numbers and you have to pass arguments to fill them. The GetCommonLanguage() just returns the translated language string, the other two directly send a warning/error. There are again the SendCommonError, SendCommonWarning and GetCommonLanguage macros for better readability.

Localisation of debugger messages: Using custom messages

You can also provide your own messages and your own Catalog files. This involves the following steps:

First you have to include the information about your language data and the default strings with your debugger functions in the form of a PB_Language structure. Here is an example:

static PB_Language LanguageTable =
{
  "Libraries.catalog",
  "PB_Libraries",
  "OnError",
  0,
  {
    "DebuggerPresent", "The OnError library may not catch all errors with enabled debugger.",
    "LinesDisabled",   "The OnError lines support is not enabled.",
    "NoHandler",       "This function can only be called inside an eror handler.",
    "", "",
  }
};

These are the fields:

  • Default Filename for your catalog file (without path). This filename is checked first, but if it cannot be loaded, all Catalog files are searched for the correct data. So this is more of a hint.
  • The value of the “Application” key in the [LanguageInfo] group inside the Catalog file. This is what truely identifies your Catalog, so it has to be unique.
  • The name of the group inside the Catalog file in which the following Keys are located. Note that the group names of all libraries share the same namespace, so group names have to be unique too. Either use your library name here (as the PB libs do), or prefix your names with something unique. Multiple Libraries can share the same Catalog file (as the PB libs do), but one PB_Language structure has to correspond to one group. If two libraries refere to the same group, their values will overwrite each other.
  • The ‘Initialized’ field must be set to 0 initially. The debugger uses this to track wether it already loaded this language data or not.
  • Following is the list of Key-Value pairs with the default language. The list is terminated by two empty strings.

Now you can use this defined language (even without any actual Catalog files). Like with the common language, there are 3 functions to get the translations:

M_PBFUNCTION(char *) PB_DEBUGGER_GetLanguage(PB_Language *data, char *Key);
M_PBFUNCTION(void)   PB_DEBUGGER_SendTranslatedError(PB_Language *data, char *Key);
M_PBFUNCTION(void)   PB_DEBUGGER_SendTranslatedWarning(PB_Language *data, char *Key);

These are not varargs, and don’t support the printf-like syntax. The reason is that we access them mostly through macros, and support for varargs macros is not very good among C compilers. The first argument is a pointer to the PB_Language structure and the second one is the language key to look for. If you use C and name your PB_Language structure ‘LanguageTable’ as in the example above, then you can use the GetLanguage, SendTranslatedError and SendTranslatedWarning macros to leave out the pointer argument for simplicity.

If all this works, you can start creating translations in Catalog files. The format is the simple preference format. Just look at the Libraries.Catalog for an example. The [LanguageInfo] group and its “Application” and “Language” keys are mandatory, the other fields in [LanguageInfo] are for information only and not used by the debugger. Place your catalogs in the PB folder where the other ones are.

The actual language that will be used is determined by the compiler or ide, not the debugger. You can set it for the commandline compiler with the /LANGUAGE switch. The ide sets the language to the one it uses itself.

Leave a Reply