To LUGNET HomepageTo LUGNET News HomepageTo LUGNET Guide Homepage
 Help on Searching
 
Post new message to lugnet.robotics.rcxOpen lugnet.robotics.rcx in your NNTP NewsreaderTo LUGNET News Traffic PageSign In (Members)
 Robotics / RCX / 2316
2315  |  2317
Subject: 
Design by Contract (long post)
Newsgroups: 
lugnet.robotics.rcx
Date: 
Thu, 8 Jan 2004 07:03:04 GMT
Viewed: 
3033 times
  
Hi, people.

The reason for the flood of posts is that I’ve been trying to get authenticated on LUGNET for the last week :( But as you can see, I’m authenticated now :)

On to the topic of this post (sorry for such a long post):

I have created an assert() macro which works under BrickOS. For those unfamiliar with assert(), it is a standard C macro whose purpose is to help find bugs. It is integral to the ‘design by contract’ style of programming pioneered by Bertrand Meyer.

Briefly, assert() expects a boolean expression as an argument. If the argument evaluates true, nothing happens. If it evaluates false, that is considered to be a program error, and the program terminates, identifying which assert() statement failed.

How would you use it ? Well I noticed that the BrickOS code almost never checks the malloc return value. If malloc() is unable to allocate the requested memory, it should return a null pointer. So you might write

assert (myptr = malloc (sizeof (mystruct));

Now, if the memory can’t be allocated, the program will still crash, but it will do it noisily, rather than silently, and you have some chance of tracking down what failed. This is a facility that finds bugs for you automatically !

Using assertions serves a documentation purpose, enforces design assumptions and is a useful tool for writers of modules to protect themselves from poorly behaved clients. There are plenty of books and magazine articles that can tell you more.

Cost

‘But what about the overhead ?’ I hear you saying. The assert() macro is controlled by a preprocessor constant: If NDEBUG is defined (no debugging) assert() statements vanish from the code. When assert() is enabled, Each time it is used it costs an expression evaluation, and a function call.

Description: I modified the helloworld demo program to show how assert is used and implemented:
#include <conio.h>
#include <unistd.h>

//#include "assert.h"

//#define NDEBUG

void assert_fail (void);
__asm__ ("
.text
.global _cputw
        _assert_fail:
         mov.w @r7, r0
         jsr   @_cputw
        loop:
         bra loop
");

#ifdef NDEBUG
#define assert(x) ((void)0)
#else
#define assert(x) if (x) {} else { assert_fail(); }
#endif


int main(int argc, char **argv) {
  cputs("hello");
  sleep(1);
  cputs("world");
  sleep(1);

  assert (1==0);

  cputs ("Here");
  sleep(1);

  return 0;
}
“Here” should not be displayed. Normally, the macro definition would be in a file called assert.h, and the assert_fail function would be in any convenient module.

How does it work ? When NDEBUG is defined, the macro does nothing. When not defined, assert calls assert_fail() if its condition evaluates false. assert_fail() displays its function return address and halts. The function return address is the point in the code where the macro was inserted.

What use is the function return address ? If you know the program load address, and have the linker map files handy, you can work out which assert() was the one that failed.

The program load address is tricky, because it might be different each time the program is loaded. I added the following code to program_run() to display the program load address:
static void program_run(unsigned nr) {
  if(program_valid(nr)) {
    program_t *prog=programs+nr;

// *IM ADDED*
#ifdef DISPLAY_START_ADDR                // help track down assert()
    cputs ("Addr");     msleep (500);
    cputw (prog->text); msleep (1000);
#endif
// *IM ADDED END*
When I run the test program on my RCX, it prints out:
“Addr” B68C
“Hello”
“World”
B6B6
And then stops. So the assert statement was B6B6-B68C = 0x2A from the start of my program. How do I relate that to the source code ? Use the linker map file:

The Gnu linker, ld will create a map file if you ask it to – edit the makefile in the demo directory, so that it includes a ‘dmap’ target:
PROGRAMS= assert_test.lx assert_test.dmap
When the program is make’d, the assert_test.dmap file that I got includes the following lines:
0000ae2a ? _mm_start
0000b000 t _assert_fail
0000b000 t .text
0000b000 T ___text
0000b006 t loop
0000b008 t .bf
0000b008 T _main
0000b03c t .ef
As best as I can work out 0xb000 is a virtual address that is simply a place holder for the ultimate load address (which varies at run time). You can see in this listing that main() occupies addresses 0x0008 .. 0x003c from the start of the program. As we saw above, 0x2A is in this range, so the assert() that fired was in main.

You may have several assert()s in the same function. If you generate an assembler listing file, you get a mixed C-assembly listing with op-codes and addresses. You can use the listing file to track the assert() down to the exact line of code. To get the listing file, I edited the following line in makefile.common in the main BrickOS directory:
COPT  =-O2 -fno-builtin -fomit-frame-pointer -g -Wa,-alh=$*.lst
I agree that its all a bit of a hassle to go to – my hex calculator is getting more of a work out than it has in a long time. But if you are committed to assert() (I am), then its a small price to pay for something that will find your bugs for you.

Enjoy !

Iain



Message has 1 Reply:
  Re: Design by Contract (long post)
 
(...) Hi Iain! (...) Cool! (...) I have a question about this: If NDEBUG is defined, shouldn't it still execute x? That is, shouldn't the code in assert.h (or wherever) be: #ifdef NDEBUG #define assert(x) (x) #else #define assert(x) if (x) else (...) (20 years ago, 8-Jan-04, to lugnet.robotics.rcx, FTX)

7 Messages in This Thread:


Entire Thread on One Page:
Nested:  All | Brief | Compact | Dots
Linear:  All | Brief | Compact
    

Custom Search

©2005 LUGNET. All rights reserved. - hosted by steinbruch.info GbR