Implementation of assert() in kernel space

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Implementation of assert() in kernel space

Post by Colonel Kernel »

this thread is referenced in the FAQ
I want to add some debugging & error-handling facilities to my kernel, and I'm curious about how other people have tackled assert().

For example, let's say some code in a low-level part of the kernel (like the kernel-mode C run-time library) detects a logic error in a debug build and wants to report it via assert(). Here are the choices as I see them (assuming the assertion failed):
  • 1. assert() prints out the file and line number, then panics.
  • 2. assert() tries to capture the current register contents and a kernel stack dump itself, then prints out the file and line number, then panics.
  • 3. assert() prints out the file and line number, then triggers one of the kernel's exception handlers (by executing int 3 maybe), which in turn dumps the machine state that it has already collected, and then panics.
Option 1 is easy to do, but doesn't include machine state in the output. Option 2 seems hard to do (at least to me... maybe I'm missing something?). Option 3 leverages the existing code I have for capturing the context of the current task, but are there any risks associated with this approach?

As an aside, how important is machine state for debugging anyway? My feeling is that it's less important for assert(), since you have file & line numbers available, but it's important for all other cases... If I could probably do without it, I may just go for option 1.
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!
User avatar
Pype.Clicker
Member
Member
Posts: 5964
Joined: Wed Oct 18, 2006 2:31 am
Location: In a galaxy, far, far away
Contact:

Re:Implementation of assert() in kernel space

Post by Pype.Clicker »

register state may help you figuring out the value of some variable. Probably the result of the assert() computation makes more sense, though.

Eg:

Code: Select all

kassert(ptr->next==p2->next,4,ptr,p2,ptr->next,p2->next);
that would output
"assertion failed at file.c:line X, 8004260, 8004280, 0, 8004280"
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Re:Implementation of assert() in kernel space

Post by Colonel Kernel »

That makes sense, and should be easy to do...

I had another thought about making assert trigger a breakpoint exception -- would it be the right way to allow future integration with a remote kernel debugger?
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Re:Implementation of assert() in kernel space

Post by Colonel Kernel »

I did a bit of research... In case anyone is interested, Linux takes the approach I mentioned in its BUG() macro (which is called from at least one version of assert()). It uses the reserved opcode ud2 to defer panicking to the central trap-dispatching mechanism.
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!
dh

Re:Implementation of assert() in kernel space

Post by dh »

i did a VERY basic assert():

Code: Select all

#define assert(check, condition) if (check != condition) kSystem_Halt();
I'll put the full thing once it is tested ;P
proxy

Re:Implementation of assert() in kernel space

Post by proxy »

your assert has a problem (even as simple as it is...) in some cases it can break scoping.. like this:

Code: Select all

   if (foo)
      assert( yabba, dabba );
   else
      something_else( );
this will expand to..

Code: Select all

   if (foo)
      if(yabba != dabba) 
         kSystem_Halt;;
      else
         something_else( );
and thus you have a dangling else problem...

to fix this is simple define your assert liek this:

Code: Select all

#define assert(check, condition) do { if(check != condition) kSystem_Halt(); } while(0)
also.... another issue

you shoudl ALWAYS put params of a macro in parethesis to make sure it expands properly for example..suppose i do:

Code: Select all

assert(a & 3, 0);
this will expand to:

Code: Select all

if(a & 3 != 0) kSystem_Halt();
well != has higher priority than & so this acts like this...

Code: Select all

if(a & (3 != 0)) kSystem_Halt();
no bueno...

best is like this:

Code: Select all

#define assert(check, condition) do { if((check) != (condition)) kSystem_Halt(); } while(0)
if EVEN better :) is like this..

Code: Select all

#define assert(check) do { if(!(check)) kSystem_Halt(); } while(0)
this way you can make any comparison you want (not just != ....)

good luck :)

proxy
dh

Re:Implementation of assert() in kernel space

Post by dh »

oh ya? I'll study that. Thanks.
mystran

Re:Implementation of assert() in kernel space

Post by mystran »

I think it's better to avoid do{...}while() blocks when you can avoid them. You can write a conditional in a macro in a way that it can be used inside anything, as long as you don't need loops. In fact, you can also do sequence of things if you want too. You can't loop without a block, but then again I don't think looping in macros is that good of a thing anyway, and you can always call a function that does loop.

Code: Select all

#define assert(test) \
  ((test) \
    ? (kprint("assertion failed, " __FILE__ ":" __LINE__ ": " #test), panic()) \
    : 0)
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Re:Implementation of assert() in kernel space

Post by Colonel Kernel »

I think the loop in this case is necessary though -- as a safeguard in case kSystem_halt fails for some reason and doesn't actually halt the CPU.
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re:Implementation of assert() in kernel space

Post by Solar »

Erm... do { } while ( 0 ) does not loop!

The assert() implementation recommended by P.J. Plauger (author of the Dinkumware library and member of the C committee) is indeed the trinary operator though.

What follows is a ripaway from how I implemented assert() in the PDCLib, provided as Public Domain.

assert.h:

Code: Select all

#ifndef _KERNEL_ASSERT
#define _KERNEL_ASSERT
#if __STDC_VERSION__ == 199901L
void _Kassert_99( char const * const, char const * const, char const * const );
#else
void _Kassert( char const * const );
#endif
#define __symbol2value( x ) #x
#define __symbol2string( x ) __symbol2value( x )
#endif

/* re-including assert.h results in assert() being redefined */
#undef assert

#ifdef NDEBUG
#define assert( ignore ) ( (void) 0 )
#else
#if __STDC_VERSION__ == 199901L
#define assert( expression ) ( ( expression ) ? (void) 0 \
        : _Kassert( "Assertion failed: " #expression \
                          ", function ", __func__, \
                          ", file " __FILE__ \
                          ", line " __symbol2string( __LINE__ ) \
                          "." ) )
#else
#define assert( expression ) ( ( expression ) ? (void) 0 \
        : _Kassert( "Assertion failed: " #expression \
                          ", file " __FILE__ \
                          ", line " __symbol2string( __LINE__ ) \
                          "." ) )
#endif
#endif
Implement _Kassert() and _Kassert_99() to do whatever you like. The char* contain e.g.:

"Assertion failed: x == 0, file mykernel.c, line 385." for _Kassert(), and

"Assertion failed: x == 0, function "
"mykernel_map_table"
", file mykernel.c, line 285." for _Kassert_99(), respectively.

Only C99 does have __func__ specified, hence the __STDC_VERSION__ check. Above implementation allows you to use this assert() in exactly the way described by the C standard (i.e., you may include assert.h multiple times, the macro is defined to nothing when NDEBUG is set etc.)

Hope this helps.
Every good solution is obvious once you've found it.
proxy

Re:Implementation of assert() in kernel space

Post by proxy »

as solar mentioned the "do { } while(0)" is not ment to be a loop at all, it is ment to avoid scope issues (which i gave an example of...) keep in mind that the compiler (even older ones) is smart enough to know not to emit loop related code for this one. it is a VERY common way to properly implement assert.

proxy
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Re:Implementation of assert() in kernel space

Post by Colonel Kernel »

0... 1... Damn those off-by-one errors. ;)
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!
Post Reply