Channels ▼
RSS

Embedded Systems

Designing a Real-Time Debugger

Source Code Accompanies This Article. Download It Now.


NOV92: DESIGNING A REAL-TIME DEBUGGER

DESIGNING A REAL-TIME DEBUGGER

The best of both worlds

David Potter

David is the president of Concurrent Sciences, developer of the Soft-Scope debugger. He can be contacted at 530 S. Asbury, Moscow, ID 83843.


Theoretically, designing preemptive multitasking programming tools for single-tasking operating environments is an academic exercise. However, commercial operating systems like Intel's iRMX for Windows make it possible to write real-time, deterministic applications for environments such as single-tasking DOS or cooperative-multitasking Microsoft Windows. As Table 1 illustrates, Windows and iRMX for Windows are each complex operating systems in their own right, with their respective strengths and weaknesses complementing each other.

Table 1: Windows vs. iRMX.

  Microsoft Windows             iRMX Operating System
  -------------------------------------------------------------------------

  Graphical user interface (+)  Line-oriented interface (-)
  Runs standard Windows and
   Dos applications (+)         Runs only iRMX applications (-)
  No-preemptive, cooperative
   multitasking (-)             Preemptive, priority-based multitasking (+)
  Not real-time, not
   deterministic (-)            Real-time and deterministic (+)

But designing programming tools--particularly a debugger--for this "mixed" world is more complex than building tools for a single environment because the tools must draw on the best of both worlds. This article describes the design and implementation of a debugger we wrote for the iRMX for Windows environment. Building the debugger posed numerous challenges. For one thing, the debugger required a graphical, windowed interface and it had to cooperate with other Windows and DOS applications, while understanding and taking advantage of iRMX operating-system features. What we learned in the process should be valuable to anyone writing iRMX for Windows applications.

Partitioning the Debugger

Our first step was to partition debugger functions into two groups: those performed under Windows and those performed under iRMX; see Table 2. To take advantage of the graphical interface, all user interaction needed to be on the Windows side, while task control and status had to be on the iRMX side. We wrote the Windows component (the "debugger") using the Microsoft Windows SDK; the iRMX component (the "kernel") was written using Intel's iRMX development tools.

Table 2: Partitioning the debugger workload.

  Under Microsoft Windows     Under iRMX Operating System
  -----------------------------------------------------------------

  User interface              Loading and running iRMX applications
  Program symbolics decoding  Task control (breakpoints, stepping,
                              switching tasks)
  Memory and source display   Task status (at breakpoint, GP fault,
                              running, and so on)
                              Information about iRMX system objects
                              (mailboxes, tasks, and so on)

Designing this debugger was more like writing a cross-debugger than a native debugger, since the host environment (Microsoft Windows) and the target environment (Intel's iRMX) are so different. In a typical cross-debugger, the host and target environments are totally separate, run on separate CPUs in different enclosures, and communicate through a serial connection. In fact, the functional partitioning for this debugger and much of the source code came directly from Soft-Scope III/CSiMON, our existing cross-debugger, which is PC-hosted and communicates serially to a target-resident monitor, CSiMON (normally embedded in EPROM).

In the case of the iRMX for Windows debugger, the host and target systems run on the same hardware and, instead of communicating serially, they communicate via iRMX mailboxes between the Windows-resident debugger and the iRMX-resident kernel; see Figure 1. When the debugger needs information about the state of a given task or its current register set and execution location, for instance, it sends a request to the command mailbox, where the kernel waits. The kernel then satisfies the request, sending the results back through a response mailbox. Similarly, the debugger sends a command to the kernel to start and stop execution of the iRMX task being debugged, and information about the success or failure of the operation returns through the response mailbox.

To accomplish this, we used Intel's Real-Time Extension (RTE) library (a set of iRMX functions that operate via a software interrupt), switching context to the iRMX environment, performing the operation, then returning to the caller. The RTE library gave us a way of sending and receiving iRMX messages, reading and writing iRMX memory (not directly addressable by a Windows or DOS program), and cataloging and looking up system objects (such as mailboxes) in an iRMX object directory.

Under Windows, for instance, if the debugger must send a message through an iRMX mailbox to a real-time task running concurrently on the iRMX side, we can use the rqsendmessage system call. The request begins under Windows (or DOS) and, through the RTE library, is sent via a software interrupt into iRMX. For example, when the debugger requires the current execution point of the task being debugged, it formats a request for the contents of the CS and IP registers. It sends this request to the debugger kernel waiting on the other side at its command mailbox. The debugger then waits at another mailbox for a response to its request.

This sounds easy enough, but it turns out that a message sent to an iRMX mailbox must be in the form of an iRMX object, normally an iRMX memory segment. Furthermore, filling the iRMX memory segment with the necessary data requires the use of the rqereadsegment RTE library function, which was created specifically to be called from Windows. Because the memory spaces of Windows and iRMX are totally different, a segment: offset reference made from Windows is incompatible with a segment:offset reference from the iRMX side, and vice versa. Also, the iRMX memory segment sent was explicitly created. This makes sending and receiving a message a three-step process:

  • 1. Create an iRMX segment with rqcreatesegment.
  • 2. Write the message into the iRMX segment with rqewritesegment.
  • 3. Send the message using rqsendmessage.
These functions can be performed from Windows using RTE calls. Example 1 shows the contents of cmdbuff being copied to the iRMX cmd_seg segment and sent to the iRMX sskernel_cmdmbx mailbox. To receive and read the return message, the process is reversed:

Example 1: Contents of cmdbuff being copied to the iRMX cmd_seg segment and sent to the iRMX sskernel_cmdmbx mailbox.

      .       .       .

  cmd_seg = rqcreatesegment (strlen (cmd), &status);
  rqewritesegment (cmdbuff, cmd_seg, 0, strlen (cmd), &status);
  rqsendmessage (sskernel_cmdmbx, cmd_seg, 0, &status);

      .
      .
      .

  • 1. Receive the message using rqreceivemessage.
  • 2. Read the message from iRMX segment into Windows-addressable memory.
  • 3. Delete the iRMX segment with rqdeletesegment.
Example 2 shows the segment resp_seg being received from the sskernel_respmbx mailbox and the contents of the segment being written to resp_buff.

Example 2: resp_seg being received from the sskernel_respmbx mailbox, and the contents of the segment being written to resp_buff.

         .          .          .

  resp_seg = rqreceivemessage (sskernel_respmbx, 0, &dummbx, &status);
  rqereadsegment (resp_seg, 0, resp_buff, count, &status);
  rqdeletesegment (resp_seg, &status);

         .
         .
         .

Multiple, Real-time Tasks

Once the debugger is communicating with the kernel, the next problem concerns the multiple, real-time tasks to be debugged on the iRMX side. How do we keep track of and control these iRMX tasks, which march to the beat of a completely different drummer than the debugger?

Remember those carnival performers who place spinning plates on top of a series of poles and keep them all spinning by frantically running from one to the next? If they concentrated on only one plate, the others would topple off their supports and clutter the stage with broken dishes.

We faced a similar situation. While it is normally the job of the operating system to transparently switch from one task to another , in this case the debugger needed to move quickly and easily from task to task. Furthermore, it had to single-step through the code of one task while other tasks operated concurrently in the background. Thus, the operation of one task could be isolated from that of another, and the system-critical tasks could function while debugging other tasks in the application. Also important was the capability to simultaneously set breakpoints in multiple tasks, so that one task doesn't get too far ahead when the user stops to examine another. Finally, the debugger needed to display all the tasks currently at break, so the user could select any given task and make that the execution environment.

Because the debugger focuses on a single task at a time, its basic operation is, on the Windows side, similar to that of a single-tasking debugger. The Windows side involved considerations such as making task information available and providing an interface so the user could switch from one task to another, but our biggest challenge was in the kernel on the iRMX side. We needed to allow several tasks to be simultaneously at their breakpoints, and had to be able to switch from one task to another when the debugger commanded. Here, we definitely had to run under iRMX in order to take over any task encountering a breakpoint, save its current register state, and communicate through a set of internal mailboxes managed by the kernel. Any task at break would dutifully wait at a mailbox for a command from the debugger, and respond with information about its register set, point of execution, and task state. It is the kernel's job to manage this so that, for example, if three tasks are all at break, the kernel can channel communication to and from each of the tasks on demand from the debugger. Suppose the debugger had a request for the register contents of task #2. The order of events would be as follows:

  • 1. Request for register contents of task #2 is sent from the debugger to the kernel.
  • 2. Kernel receives the message, determines the proper mailbox for task #2, and passes the request along.
  • 3. In task #2's context, the register request is received, and a response is sent back to the debugger.
Figure 2 shows what this might look like. On the Windows side, the debugger must be aware of any changes in the tasks under debug, but its only knowledge of them is through the kernel. What would happen if, for example, a task that the debugger showed at break was deleted or suspended on the iRMX side?

Our solution was to have the debugger send commands to the kernel requesting current information on the user tasks and jobs currently under investigation (and displayed in the Soft-Scope Tasks window). The kernel responds with a list of tasks currently at break, including where they are broken and how they got there.

How Not to Receive an iRMX Message

The iRMX receive-message call (rqreceivemessage) has a parameter that allows the caller to wait for a message for a specified length of time. We discovered, however, that this parameter must be used with care. This timed-wait capability is important for a real-time application, since you can request a message even before it arrives and let the operating system wake you either when the message arrives or when the requested time has elapsed, whichever comes first. This way, synchronization with a cooperating task is simple and straightforward. And, even under Windows, this works fine, as long as nothing important must be done on the Windows side while you're waiting. Remember that, while Windows is a multitasking operating system, iRMX thinks of it as a single task, so a request to be put to sleep for a specified time is tantamount to freezing all of Windows for the duration. Other iRMX tasks can run while you're waiting, but Windows is dead! This is especially problematic if a Windows process needs to complete before a task on the iRMX side can send the response we're waiting for.

This is exactly the situation we were in when the debugger initiated the loading of an iRMX application. A separate Windows application was spawned (via WinExec) which created a window for the iRMX application to use and then communicated to the iRMX operating system to load the application. After the application load completed, we expected a message to be sent back to the debugger via an iRMX mailbox. Immediately after the WinExec, we tried a rqreceivemessage with a reasonable time-out and waited for a response. No matter how long the time-out was, we never got our message back. Worse than that, Windows was dead for the entire duration of the time-out!

The problem was that immediately after starting the Windows application, our rqreceivemessage put the debugger and Windows to sleep, waiting for completion. Deadlock. We never received the message, no matter how long we waited!

The solution was to allow not only iRMX tasks to run while the debugger was waiting (this is actually quite easy, since the debugger and Windows are running at the lowest possible priority in the iRMX operating system), but to let Windows run as well. We accomplished this by:

  • 1. Not using the timed-wait with rqreceivemessage(), so control immediately returned.
  • 2. Waiting under Windows for a specified time, say one second.
  • 3. Going back to #1 to check again, continuing for as many seconds as desired.
The iRMX sleep function allows an iRMX task to go to sleep for a certain amount of time while other iRMX tasks run, but Windows doesn't have the exact analog. And calling the iRMX rqsleep function after checking the mailbox only re-created the problem we were trying to avoid. Windows does provide a Yield function, which yields to other Windows applications, but not a way to wait for a specified amount of time.

A function can be created, however, which does wait for at least the specified time by using Yield in conjunction with GetCurrentTime. The newly created function calls Yield repeatedly until the requested amount of time has elapsed. The time elapsed since the last call to Yield can be calculated by comparing successive results from GetCurrentTime. When the requested amount of time has elapsed, then the function returns. This has the desired effect of allowing all other Windows or iRMX tasks to run while we wait for the event. In Example 3, the win_sleep function provides a sleep function which allows other Windows applications to run, and returns control after the requested number of seconds have passed.

Example 3: win_sleep provides a sleep function which allows other Windows applications to run and returns control after the requested number of seconds have passed.

  void win_sleep(
    signed int requested)      /* Number of seconds to sleep.        */
  {
    auto signed long sleep;    /* Units of 1/1000th of a sec.        */

    d_time ();                 /* Uses GetCurrentTime for elapsed
                                  secs.                              */
    sleep = 1000 * requested;  /* Seconds * 1000 = msecs.            */

    while (sleep > 0) {
      Yield()
      sleep - = d_time();      /* Subtract elapsed secs since last
                                  time.                              */
    }
  }

Conclusion

Not all iRMX for Windows applications will be as complex as this one, but there will probably be many similarities between our experience in designing the Soft-Scope debugger and the design and implementation of other iRMX for Windows applications. To utilize the real-time features of the iRMX system and combine them with the Microsoft Windows interface, partition your application into a Windows component and an iRMX component. Then design a method of communication and synchronization. In our case, we used iRMX mailboxes, but other methods are possible, including the use of dynamic data exchange (DDE) which is supported by the iRMX for Windows operating system.




_DESIGNING A REAL-TIME DEBUGGER_
by David Potter

[EXAMPLE 1]

    .
    .
    .

cmd_seg = rqcreatesegment (strlen (cmd), &status);
rqewritesegment (cmdbuff, cmd_seg, 0, strlen(cmd), &status);
rqsendmessage (sskernel_cmdmbx, cmd_seg, 0, &status);

    .
    .
    .


[EXAMPLE 2]


    .
    .
    .

resp_seg = rqreceivemessage(sskernel_respmbx, 0, &dummbx, &status);
rqereadsegment (resp_seg, 0, resp_buff, count, &status);
rqdeletesegment (resp_seg, &status);

    .
    .
    .


[EXAMPLE 3]


void  win_sleep(
    signed int requested)     /* Number of seconds to sleep.           */
{
    auto signed long sleep;   /* Units of 1/1000th of a second.        */

    d_time ();                /* Uses GetCurrentTime for elasped secs  */
    sleep = 1000 * requested; /* Seconds * 1000 = msecs.               */

    while (sleep > 0) {
        Yield();
        sleep - = d_time();   /* Subtract elapsed sec since last time. */
    }
}


Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video