Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forth as an interactive C program tester

I'm willing to use an interactive language to test some C code from a legacy project. I know a little Forth, but I haven't ever used it in a real world project. I'm looking at pForth right now.

Is it reasonable to use an interactive Forth interpreter to test the behavior of some function in a C program? This C code has lots of structs, pointers to structs, handles and other common structures found in C.

I suppose I'll have to write some glue code to handle the parameter passing and maybe some struct allocation in the Forth side. I want an estimate from someone with experience in this field. Is it worth it?

like image 550
ivarec Avatar asked Jan 14 '23 12:01

ivarec


1 Answers

If you want interactive testing and are targeting embedded platforms, then Forth is definitely a good candidate. You'll always find a Forth implementation that runs on your target platform. Writing one is not even hard either if need be.

Instead of writing glue code specific to your immediate needs, go for a generic purpose Forth to C interface. I use gforth's generic C interface which is very easy to use. For structure handling in Forth, I use an MPE style implementation which is very flexible when it comes to interfacing with C (watch out for proper alignment though, see gforth %align / %allot / nalign).

The definition of generic purpose structure handling words takes about 20 lines of Forth code, same for single linked lists handling or hash tables.

Since you cannot use gforth (POSIX only), write an extension module for your Forth of choice that implements a similar C interface. Just make sure that your Forth and your C interface module uses the same malloc() and free() than the C code you want to test.

With such an interface, you can do everything in Forth by just defining stub words (i.e. map Forth words to C functions and structures).

Here's a sample test session where I call libc's gettimeofday using gforth's C interface.

s" structs.fs" included also structs \ load structure handling code

clear-libs
s" libc" add-lib  \ load libc.so. Not really needed for this particular library

c-library libc    \ stubs for C functions
\c #include <sys/time.h>
c-function gettimeofday gettimeofday a a -- n ( struct timeval *, struct timezone * -- int )
end-c-library

struct timeval          \ stub for struct timeval
    8 field: ->tv_sec   \ sizeof(time_t) == 8 bytes on my 64bits system
    8 field: ->tv_usec
end-struct

timeval buffer: tv

\ now call it (the 0 is for passing NULL for struct timezone *)
tv 0 gettimeofday .  \ Return value on the stack. output : 0
tv ->tv_sec @ .      \ output : 1369841953

Note that tv ->tv_sec is in fact the equivalent of (void *)&tv + offsetof(struct timeval, tv_sec) in C, so it gives you the address of the structure member, so you have to fetch the value with @. Another issue here: since I use a 64 bits Forth where the cell size is 8 bytes, storing/fetching an 8 bytes long is straightforward, but fetching/storing a 4 bytes int will require some special handling. Anyhow, Forth makes this easy: just define special purpose int@ and int! words for that.

As you can see, with a good generic purpose C interface you do not need to write any glue code in C, only the Forth stubs for your C functions and structures are needed, but this is really straightforward (and most of it could be automatically generated from your C headers).

Once you're happy with your interactive tests, you can move on to automated tests:

  • Copy/paste the whole input/output from your interactive test session to a file named testXYZ.log
  • strip the output (keeping only the input) from your session log and write this to a file named testXYZ.fs
  • To run the test, pipe testXYZ.fs to your forth interpreter, capture the output and diff it with testXYZ.log.

Since removing output from an interactive session log can be somewhat tedious, you could also start by writing the test script testXYZ.fs then run it and capture the output testXYZ.log, but I prefer starting from an interactive session log.

Et voilà !

For reference, here's the structure handling code that I used in the above example :

\ *****************************************************************************
\ structures handling
\ *****************************************************************************

\ Simple structure definition words. Structure instances are zero initialized.
\
\ usage :
\ struct foo
\     int: ->refCount
\     int: ->value
\ end-struct
\ struct bar
\            int: ->id
\     foo struct: ->foo
\       16 chars: ->name
\ end-struct
\
\ bar buffer: myBar
\ foo buffer: myFoo
\ 42 myBar ->id !
\ myFoo myBar ->foo !
\ myBar ->name count type
\ 1 myBar ->foo @ ->refCount +! \ accessing members of members could use a helper word

: struct ( "name" -- addr 0 ; named structure header )
    create here 0 , 0
  does>
    @ ;

\ <field-size> FIELD <field-name>
\ Given a field size on the stack, compiles a word <field-name> that adds the
\ field size to the number on the stack.

: field: ( u1 u2 "name" -- u1+u2 ; u -- u+u2 )
    over >r \ save current struct size
    : r> ?dup if
    postpone literal postpone +
    then
    postpone ;
    + \ add field size to struct size
; immediate

: end-struct ( addr u -- ; end of structure definition )
    swap ! ;

: naligned ( addr1 u -- addr2 ; aligns addr1 to alignment u )
    1- tuck + swap invert and ;

\ Typed field helpers
: int: cell naligned cell postpone field: ; immediate
: struct: >r cell naligned r> postpone field: ; immediate
: chars: >r cell naligned r> postpone field: ; immediate
\ with C style alignment
4 constant C_INT_ALIGN
8 constant C_PTR_ALIGN
4 constant C_INT_SIZE
: cint: C_INT_ALIGN naligned C_INT_SIZE postpone field: ; immediate
: cstruct: >r C_PTR_ALIGN naligned r> postpone field: ; immediate
: cchars: >r C_INT_ALIGN naligned r> postpone field: ; immediate

: buffer: ( u -- ; creates a zero-ed buffer of size u )
    create here over erase allot ;
like image 189
wldsvc Avatar answered Jan 18 '23 08:01

wldsvc