Novell Home

AppNote: Writing Alternate Debugger Command Parsers for NetWare

Novell Cool Solutions: AppNote
By Russell Bateman

Digg This - Slashdot This

Posted: 27 Jul 2004
 

Russell Bateman
Senior Software Engineer
Server-library Development
rbateman@novell.com

This How-to AppNote discusses how to register a command parser from a NetWare-loadable Module (NLM).

Contents

Introduction
Registration
Deregistration
Command Parser
Debug Command Help
Conclusion and Sample Code
Topics NLM development, kernel and low-level code
Products NetWare 5, NetWare 6, NetWare 6.5
Audience Developers
Level Advanced

Introduction

Adding debugger commands to help you debug your special application or library is a little understood process for NetWare. This article will present the details of doing it.

Underlying this capability are hooks in the OS that allow NLMs to register a parsing function to be called whenever a command is entered in the debugger.

Most of the interfaces we will discuss in this article come from the LibC header, netware.h. After reading this article, you will want to peruse the little section dedicated to the particulars of writing alternate debug commands in that header. Just search for "debugging interfaces" in that file.

This sort of programming on NetWare is purely kernel-bound. You cannot register a debugger parsing function from an NLM running in a protected-address space because the functions to register it are not callable there and, even if they were, the OS would abend if it tried to pass execution from the kernel along to the registered function.

Both LibC and CLib add their own debugger commands. These are usually chosen on the basis of how difficult it is to debug something. For example, LibC recently acquired a 'pid' command to aid in our work to create actual UNIX-like processes and fork.

In this article, we're going to implement a simple "hello world" command to be used from the NetWare System Debugger. It's really a very simple task, but one which you can make as elaborate as you like to suit your own purposes. All the source code is appended to the end of this article.

Registration

Registration is achieved by calling RegisterDebugCommandParser. First, however, a special resource tag must be allocated so that the NetWare Operating System can track it. The label we give to this resource tag will show up in places like monitor.nlm, but it doesn't matter what we call it.

rtag_t	rtag;

rtag = AllocateResourceTag(getnlmhandle(),
                  "Debug Command Parser", DebugCommandSignature);

The NLM handle comes from the first argument of your start-up code. If you link LibC's prelude code, this would be the first argument to _NonAppStart, whose employ is discussed in the articles on How to Write Start-up Code for NLMs and How to Write NetWare-loadable Modules (NLMs) as Dynamic Libraries, if you link one, or the third argument to DllMain if you link that function instead. For those NLMs more properly termed applications because they have a main, the easiest way to get this is to call getnlmhandle. In fact, this latter can usually be called in any situation including the other two mentioned. This is how we'll get it since our sample code isn't a driver or a library, but a little application created to run just long enough to implement the command. The code can be easily extracted from this application and stuffed inside a serious driver, library or application as we wish.

err = RegisterDebugCommandParser(DebugCommandParser, rtag);

We'll discuss what DebugCommandParser is in a moment. Note that this is a considerably simpler process than registering a system console command handler as illustrated in another article I wrote recently.

Deregistration

Deregistration is simple, just call UnRegisterDebugCommandParser with DebugCommandParser (passed also in the registration process). The resource tag can be ignored since it was only needed by the registration process and NetWare doesn't require us to deallocate resource tags.

Command Parser

Now that we have taken care of the tiny amount of bookkeeping that goes into setting up a new command parser, let's examine how parsing is done. Here's the function.

int DebugCommandParser
(
   scr_t      scr,
   const char *commandline,
   xframe_t   *frame
)
{
#pragma unused(frame)
   if ( !commandline[0]
      || commandline[0] != 'd'
      || commandline[1] != 'o'
      || commandline[2] != 'p'
      || commandline[3] != 'r'
      || commandline[4] != 'i'
      || commandline[5] != 'n'
      || commandline[6] != 't'
      || commandline[7] != ' ')
      return NEXT_ALT_DEBUG_PARSER;

   OutputToScreen(scr, "Our debugger extension: %s\n",
                                             &commandline[8]);
   return COMMAND_HANDLED;

Admittedly, our sample command doesn't do much. This is because to show a real one like LibC implements would lead more to a discussion on how an internal data structure worked than on writing the debugger parser. You can easily imagine, however, how you can parse a command line into argc/argv, then use your own copy of getopt if its going to be too complicated to parse it by hand. Or you can pass the address of a data structure—obtained by inspection while using some other debugger command—of your own particular data structure. Given that you have access, though the variable frame here, to register values, you can use those too without them being specified on the command line.

The values to return from your parser function are these:

Debug command parser return values
NEXT_DEBUG_PARSER Call next registered debug command parser.
NEXT_ALT_DEBUG_PARSER     Call next registered debug command parser.
COMMAND_HANDLED This was our command and we've handled it; don't let anyone else have a crack at it.
INTERNAL_DEBUGGER Let the NetWare System Debugger handle the command (including to handle it by saying it does not recognize it).

In summary, if you enter the debugger and type

# doprint "Hello world!"

you will see displayed:

Our debugger extension: "Hello world!"

Debugger Command Help

Unlike system console commands, we do not need to display help for our debugger command, that will be taken care of by the debugger's .h command which draws on a special file placed on the path c:\nwserver\nls\4, the path to the English messages for NLMs on the DOS partition. If we create the file and put it there, then help will be available through this standard mechanism. If we do not, then there is no other consequence than that it won't be available.

Here is our help file...

;================================================================
; D e b u g g e r   C o m m a n d   A r t i c l e   H e l p
;================================================================
subjects:
{
   Debugger_Command_Article
}

subject:Debugger_Command_Article
{
   head { Debugger Command Article }
   commands { doprint }
}

command:doprint
{
   args { <string> }

   <string> to print after prompt "Our debugger extension: "
}
The name of this file must end in .dht. It is a 7-bit ASCII text file deliminated by <cr><lf> and must have the same name as the NLM registering and implementing it for the debugger to pick it up. The subject name, however, is open although it should not be as strange as the one we have here. If we were implementing a more serious set of commands, then we would probably have a little better name, but what we chose here will stand out when we type
# helps
with no arguments to list the available subjects. If we type
# help Debugger_Command_Article

then we get the help for our subject. The subject lists the commands associated with it and you get help for all of them at the same time. Our debugger extension has only one command, doprint, so we only get help for it. At some future point, this will probably be refined to allow for giving help on one command out of the subject instead of all commands in the case where there are several (as LibC, CLib and most other components that extend the debugger).

To add another command to this file's syntax, I need only add an item to the "commands" list (see under the "subject:" syntax) and then an additional command entry similar to the "command:doprint" above.

This help system is brand new to the NetWare System Debugger for NetWare CSP11. Many components still have their own, specific helps (as when you type "libc help" in the debugger), but soon everyone will adopt this new system.

Conclusion and Sample Code

This is how, then, you can add debugger commands to the NetWare console command line. There are many caveats, however. You cannot make calls from this sort of code to functions in CLib or LibC that require context since no context is available nor can be had. You must not call any function that blocks. You cannot easily perform any I/O since the NetWare System Debugger halts the NetWare kernel and executes only the thread on which the debugger runs.

Here is the source code to the entire exercise in this article (except for the helps whose text is complete as shown above):

#include <stdio.h>
#include <screen.h>
#include <stdlib.h>
#include <library.h>
#include <netware.h>
#include <semaphore.h>
#include <nks/vm.h>


int	DebugCommandParser( scr_t , const char *, xframe_t * );
void	appexit( void *sem );


void appexit
(
   void	*sem
)
{
   (void) NXVmUnregisterExitHandler(appexit, sem);
   sem_post(sem);
}

int main( void )
{
   int      err;
   sem_t	   parkingSemaphore;
   rtag_t   rtag;

   if (!(rtag = AllocateResourceTag(getnlmhandle(),
                          "Debug Command Parser", DebugCommandSignature)))
   {

      printf("Failed to allocate resource tag (highly unlikely)...\n");
      return ENOMEM;
   }

   (void) NXVmRegisterExitHandler(appexit, &parkingSemaphore);

   if (err = sem_init(&parkingSemaphore, 0, 0))
      return err;

   if (err = RegisterDebugCommandParser(DebugCommandParser, rtag))
   {
      printf("Failed to register debugger parser...\n");
   }
   else
   {
      printf("Successfully registered debugger parser: "
                              "hop in and try it out!\n");
      sem_wait(&parkingSemaphore);
      UnRegisterDebugCommandParser(DebugCommandParser);
   }

   sem_destroy(&parkingSemaphore);

   return err;
}

int DebugCommandParser
(
   scr_t      scr,
   const char *commandline,
   xframe_t   *frame
)
{
#pragma unused(frame)
   if ( !commandline[0]
      || commandline[0] != 'd'
      || commandline[1] != 'o'
      || commandline[2] != 'p'
      || commandline[3] != 'r'
      || commandline[4] != 'i'
      || commandline[5] != 'n'
      || commandline[6] != 't'
      || commandline[7] != ' ')
      return NEXT_ALT_DEBUG_PARSER;

   OutputToScreen(scr, "Our debugger extension: %s\n", &commandline[8]);

   return COMMAND_HANDLED;
}


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell