Search code examples
cocamlshared-memoryxlib

MIT-SHM bindings for OCaml


I’m trying to extend OCaml-Xlib with bindings for the MIT-SHM extension. It’s the first time I’m trying to interface C with OCaml and I’ve never written anything in C, so I guess I’m doing something stupid somewhere.

I first added the XShmQueryExtension function. I added the following to the Xlib.ml file

external xShmQueryExtension: dpy:display -> bool = "ml_xShmQueryExtension"

the following to the wrap_xlib.c file

CAMLprim value
ml_xShmQueryExtension( value dpy )
{
  int ans = XShmQueryExtension( Display_val(dpy) );
  return Val_bool(ans);
}

I changed the Makefile to link with Xext, and it works: when I call the xShmQueryExtension function from OCaml I get true.

Now I’m trying to write a function creating a shared xImage, initializing the shared memory and attaching it to the X server. I added the following to the Xlib.ml file:

type xShmSegmentInfo

external xShmCreateImageAndAttach:
  dpy:display -> visual:visual -> depth:int -> fmt:ximage_format
  -> width:uint -> height:uint -> xShmSegmentInfo * xImage
    = "ml_xShmCreateImageAndAttach_bytecode"
      "ml_xShmCreateImageAndAttach"

and the following to the wrap_xlib.c file:

#define Val_XShmSegmentInfo(d) ((value)(d))
#define XShmSegmentInfo_val(v) ((XShmSegmentInfo *)(v))

CAMLprim value
ml_xShmCreateImageAndAttach( value dpy, value visual, value depth, value format,
                             value width, value height)
{
    CAMLparam5(dpy, visual, depth, format, width);
    CAMLxparam1(height);
    CAMLlocal1(ret);

    XShmSegmentInfo *shminfo = malloc(sizeof(XShmSegmentInfo));

    XImage *ximage = XShmCreateImage(
        Display_val(dpy),
        Visual_val(visual),
        Int_val(depth),
        XImage_format_val(format),
        NULL,
        shminfo,
        UInt_val(width),
        UInt_val(height)
    );
    shminfo->shmid = shmget (IPC_PRIVATE,
      ximage->bytes_per_line * ximage->height, IPC_CREAT|0777);
    shminfo->shmaddr = ximage->data = (char *) shmat (shminfo->shmid, 0, 0);
    if (shminfo->shmaddr == -1)
      fprintf(stderr,"Error");
    shminfo->readOnly = False;
    XShmAttach (Display_val(dpy), shminfo);

    ret = caml_alloc(2, 0);
    Store_field(ret, 0, Val_XShmSegmentInfo(shminfo) );
    Store_field(ret, 1, Val_XImage(ximage) );
    CAMLreturn(ret);
}

CAMLprim value
ml_xShmCreateImageAndAttach_bytecode( value * argv, int argn )
{
    return ml_xShmCreateImageAndAttach(argv[0], argv[1], argv[2], argv[3],
                                       argv[4], argv[5]);
}

Now I’m calling this function in my OCaml program:

let disp = xOpenDisplay ""
let screen = xDefaultScreen disp
let (shminfo, image) = xShmCreateImageAndAttach disp
  (xDefaultVisual disp screen)
  (xDefaultDepth disp screen) ZPixmap 640 174

This is a toplevel call in my OCaml program, and I’m never using the variables shminfo and image again (this is just to test that the function work). This call does not fail, but my program segfault a little while after (the rest of my program constantly dump the screen with xGetImage and do stuff with the pixels, and I get a segfault in some xGetPixel which has nothing to do with the call to xShmCreateImageAndAttach above). I noticed that if I remove the line shminfo->shmaddr = ximage->data = (char *) shmat (shminfo->shmid, 0, 0); I don’t get the segfault anymore (but of course this won’t do what I want).

I assume that this has to do with the garbage collector somehow but I don’t know how to fix it. On the OCaml doc, there is a warning about casting pointers obtained with malloc to the value type, but I don’t really understand what it means and I don’t know if it’s relevant.

Edit:

I replaced the two lines following shmat by the following:

fprintf(stderr,"%i\n",(int)shminfo->shmaddr);
fflush(stderr);

and I get something like 1009700864, so the call to shmat seems to be working.

Here is the backtrace given by gdb:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7acdde8 in ?? () from /usr/lib/libX11.so.6
(gdb) backtrace
#0  0x00007ffff7acdde8 in ?? () from /usr/lib/libX11.so.6
#1  0x000000000044070c in ml_XGetPixel ()
#2  0x00000000004165b9 in camlInit__rvb_at_1023 () at init.ml:43
#3  0x0000000000415743 in camlParse__find_guy_1046 () at parse.ml:58
#4  0x000000000041610c in camlParse__pre_parse_1044 () at parse.ml:95
#5  0x0000000000415565 in camlGame__entry () at game.ml:26
#6  0x00000000004141f9 in caml_program ()
#7  0x000000000045c03e in caml_start_program ()
#8  0x000000000044afa5 in caml_main ()
#9  0x000000000044afe0 in main ()

Solution

  • The warning is relevant if X is going to call free() on the shminfo pointer that you're casting to the value type. The problem is that OCaml assumes that values can be freely copied and handled later by GC. This isn't true for your pointer value, so there will potentially be dangling copies of the pointer. Also, the space can get reused as part of OCaml's heap, and then you have real trouble.

    It doesn't seem to me that X will do this, and since you don't call free() in your code, I don't think this is the problem. But it could be--I don't know how X works.

    It might be good to call fflush(stderr) after your call to fprintf(). It probably won't change anything, but I've found my tracing messages tend to get buffered up and never appear when the program crashes.

    It would also be good to know what the segfaulting address looks like. Is it near 0? Or a big address in the middle of the heap somewhere?

    Sorry I can't pinpoint your error. I don't see anything you're doing wrong after 4 or 5 readings of the code, assuming Display_val and the rest are working correctly. But this is tricky to get right.