Racket SIGSEGV during FFI call

39 views
Skip to first unread message

Christopher Howard

unread,
Jun 26, 2019, 12:34:54 AM6/26/19
to racket...@googlegroups.com
Hi, I have a project going to make Racket bindings to the libhackrf C library installed on my Debian 9 system. I have successfully made and used bindings to around a dozen procedures in the library. However, when I get to the first really important one, my call is crashing with a SIGSEGV. I have this procedure defined:

(define-hackrf hackrf_start_rx (_fun (_cpointer 'hackrf_device)
                                     (_fun _pointer -> _int)
                                     _pointer -> _int))

This procedure takes a pointer to a device, a callback function, and another pointer (which I won't go into but is supposed to be null usually). Internally, hackrf_start_rx is a C procedure which generates a pthread which repeatedly calls the callback function while receiving data, while the original thread returns 0.

christopher@nightshade:~/Repos/hackrf-rkt$ racket
Welcome to Racket v6.7.
> ,load hackrf.rkt
> ,enter hackrf.rkt
"hackrf.rkt"> transf-cb
#<procedure:transf-cb>
"hackrf.rkt"> (hackrf-init)
"hackrf.rkt"> (define h (hackrf-open))
"hackrf.rkt"> h
#<cpointer:hackrf_device>
"hackrf.rkt"> (sensible-defaults h)
"hackrf.rkt"> (define cb (transf-cb))
"hackrf.rkt"> cb
#<procedure:...f-rkt/hackrf.rkt:257:2>
"hackrf.rkt"> (trace-call 'hackrf_start_rx hackrf_start_rx h cb #f)
>(hackrf_start_rx
  #<cpointer:hackrf_device>
  #<procedure:...f-rkt/hackrf.rkt:257:2>
  #f)
<0
0
"hackrf.rkt"> SIGSEGV MAPERR si_code 1 fault on addr (nil)
Aborted

I can't figure out how to debug this further because I don't know how to get Racket to display the C backtrace, and I don't know how to get Racket to run successfully in gdb. I'm thinking either (1) I'm somehow passing in my callback function wrong, or (2) something about Racket is incompatible with C library functions that use pthreads. You can see that hackrf_start_rx is returning 0, so the SIGSEGV MAPERR must be occurring in the still running thread, but I'm not sure what I can conclude from that.

For the whole code see

git checkout cf81011be6509da00b779881a5d388af399a4d2e

-- 
Christopher Howard p: +1 (907) 374-0257 w: https://librehacker.com xmpp: creat...@member.fsf.org otr: E9685B53 01F038DD D29281C9 30FDA71E BD0095D4 gnupg: 23FD5CC5 (keys.gnupg.net) radio: KL1TL featured: http://www.foi.org

Ryan Culpepper

unread,
Jun 26, 2019, 7:53:55 AM6/26/19
to Christopher Howard, racket...@googlegroups.com
On 6/26/19 6:34 AM, Christopher Howard wrote:
> Hi, I have a project going to make Racket bindings to the libhackrf C
> library installed on my Debian 9 system. I have successfully made and
> used bindings to around a dozen procedures in the library. However, when
> I get to the first really important one, my call is crashing with a
> SIGSEGV. I have this procedure defined:
>
> (define-hackrf hackrf_start_rx (_fun (_cpointer 'hackrf_device)
>                                      (_fun _pointer -> _int)
>                                      _pointer -> _int))
>
> This procedure takes a pointer to a device, a callback function, and
> another pointer (which I won't go into but is supposed to be null
> usually). Internally, hackrf_start_rx is a C procedure which generates a
> pthread which repeatedly calls the callback function while receiving
> data, while the original thread returns 0.

It's not safe to call Racket procedures from other OS-level threads, but
you can tell the Racket FFI to work around that restriction by using the
`#:async-apply` argument to the inner `_fun` type.

If the callback function is simple and doesn't rely on being run in any
particular thread (for example, it doesn't use any parameters like the
current output port), then you can try changing the inner function type
to the following:

(_fun #:async-apply (lambda (p) (p)) _pointer -> _int)

If that doesn't work, I think you'll have to do something more
complicated with Racket threads. Maybe someone knows a simple example?

Also, you might want to look at the `#:keep` argument. Racket's GC
doesn't know about references held by foreign libraries, so you need to
make sure that callback function doesn't get collected. (In your
example, you made `cb` a top-level definition, which means that value
won't be GC'd as long as the definition's namespace is still around.)

Ryan

Neil Van Dyke

unread,
Jun 26, 2019, 8:21:25 AM6/26/19
to Christopher Howard, racket...@googlegroups.com
If FFI for this particular library gets to be a headache, a backup
option might be to separate the Linux processes: make a small C program
that links in libhackrf, and provides some kind of channel (e.g., Unix
domain socket) for a separate Racket process to talk with it.  Over the
channel, you can use s-expressions, Unix-ish line protocol, protobufs,
or something else.  You'll have to check whether the communication
overhead of this is viable, but maybe it helps, in the balance, if
Racket is on its own core, and GC is not potentially interfering with a
time-sensitive thread of libhackrf.

FWIW, separate from the FFI difficulty, consider how much trust you have
in the perfection of all the additional memory-unsafe C code you'd be
pulling in via FFI, and how much you'd want to try to crash/corruption
of that (C, pthreads, separate VM, GC, large amounts of unfamiliar
complicated code...).  That could be great educational experience,
especially in a hobby project, but probably not something you want to
risk in production if you don't have to.

Reply all
Reply to author
Forward
0 new messages