6

I am trying to create a hook on the system function open(). I've done this along the following lines.

I created a wrapper library with the following:

extern int mocked_open(const char* fn, int flags, va_list args);

int open(const char* fn, int flags, ...)
{
    int r = -1;
    va_list args;

    va_start(args, flags);
    r = mocked_open(fn, flags, args);
    va_end(args);

    return r;
}

I compile this into libwrapper.so, which I load using LD_PRELOAD.

The implementation of mocked_open() is as follows (I use the CPPUtest framework):

int mocked_open(const char* fn, int flags, va_list args)
{
    if (strncmp(fn, test_device_id, 11) == 0)
    {
        return mock().actualCall("open").returnValue().getIntValue();
    }
    else
    {
        int r = -1;
        int (*my_open)(const char*, int, ...);
        void* fptr = dlsym(RTLD_NEXT, "open");
        memcpy(&my_open, &fptr, sizeof(my_open));

        if (flags & O_CREAT)
        {
            r = my_open(fn, flags, va_arg(args, mode_t));
        }
        else
        {
            r = my_open(fn, flags);
        }

        return r;
    }
}

The test_device_id is a simple string ("test_device"), which I hope is not used elsewhere.

During running the tests the executable crashes with a segmentation fault. I've traced this down to the GCC profiling functionality, which wants to open/create a bunch of .gcda files and calls open() for this.

After some debugging with strace (per suggestion below), I found that the line r = my_open(fn, flags, va_arg(args, mode_t)); is indeed the culprit. It is being called recursively, or so it seems: I see a lot of calls to this line, without the function returning. Then a segfault. The file being opened is the corresponding .gcda file (for profiling). In fact, the segfault only occurs with profiling enabled.

Ludo
  • 813
  • 1
  • 9
  • 21
  • I think your problem is variadic handling http://stackoverflow.com/questions/150543/forward-an-invocation-of-a-variadic-function-in-c – user590028 Feb 16 '15 at 13:47
  • @user590028: can you elaborate? I figured there's something wrong with the variadic argument handling (as I wrote), but what...? – Ludo Feb 16 '15 at 14:04
  • 1
    What kind of calls to `open()` does `strace` show is used on your test-cases? – alk Feb 16 '15 at 14:46
  • @alk: I see a lot of calls to `open` for loading libraries on various paths on LD_LIBARY_PATH. These cases seem to be correctly handled by my test for the filename. I don't see a call with my test device, so that's good also. The last lines are `getpid() = 25837 --- SIGSEGV (Segmentation fault) @ 0 (0) --- +++ killed by SIGSEGV +++ Segmentation fault`. These lines are after the tests (when printing a summary). – Ludo Feb 16 '15 at 15:01
  • Adding on to that: with strace and some print statements I verified that the segmentation fault occurs in the first branch of `if (flags & O_CREAT)`. I see a lot of calls to this line, without the function actually returning... The file being written at that moment is `ut_driver.gcda` (the file of the 2nd code snippet is `ut_driver.cpp`). – Ludo Feb 16 '15 at 15:28
  • what is this `return mock().actualCall("open").returnValue().getIntValue();`? is this c++? – Iharob Al Asimi Feb 19 '15 at 17:48
  • @iharob: yes, that is from CPPUTest. The function displayed above is between `extern "C" { ... }` in a .cpp file. – Ludo Feb 20 '15 at 08:59

3 Answers3

5

Try this

typedef int (*OpenFunction)(const char* fn, int flags, ...);

then

OpenFunction function;
void      **pointer;

pointer  = (void **)&function;
*pointer = dlsym(RTLD_NEXT, "open");

this is a complete working example

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>

#include <errno.h>

typedef int (*OpenFunction)(const char* fn, int flags, ...);

int main(int argc, char **argv)
{
    OpenFunction function;
    void       *dl;
    int         fd;
    void      **pointer;

    if (argc < 2)
        return -1;
    pointer  = (void **)&function;
    *pointer = dlsym(RTLD_NEXT, "open");

    fd = function(argv[1], O_RDONLY);
    if (fd != -1)
    {
        printf("file opened succesfully\n");
        close(fd);
    }
    else
    {
        printf("%s: cannot open the file\n", strerror(errno));
    }
    return 0;
}
unwind
  • 391,730
  • 64
  • 469
  • 606
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • I read here (http://stackoverflow.com/questions/10519312/how-does-this-make-sense-void-fptr-dlsymhandle-my-function) that using this kind of cast was not particularly recommended. – Ludo Feb 16 '15 at 13:56
  • 1
    For the record: using your approach for getting the function pointer does not make a difference, I still get the segmentation fault. – Ludo Feb 16 '15 at 14:03
  • @Ludo I updated it in case you don't like the cast, and since you're still getting the segmentation fault, the problem is some other problem, because the example works correctly. – Iharob Al Asimi Feb 16 '15 at 14:07
  • 1
    I agree that your example is fine. I verified it also with O_CREAT and S_IRWXU set... so I think the problem is not in obtaining the function pointer, but somewhere in my handling of the va_list... Or maybe somewhere else completely... – Ludo Feb 16 '15 at 14:19
4

When you compile with gcov profiling enabled, the compiler inserts extra code into your functions, to keep track of which code has been executed. In rough pseudocode, that inserted code will do (among other things):

if (!output_file_has_been_opened) {
    fd = open(output_filename, ...);
    check_ok(fd);
    output_file_has_been_opened = TRUE;
    track_coverage();
}

... so if the output file hasn't yet been successfully opened (as at the start of your program), it will attempt to open it. Unfortunately, in this case that will call your mocked open() function - which has the same inserted code; since the file still hasn't been successfully opened, and since the gcov code isn't aware that there's something unusual going on, it will attempt the open() call again - and this is what's causing the recursion (and eventual segfault, once the stack is exhausted).

psmears
  • 26,070
  • 4
  • 40
  • 48
  • Hmm, I think this explanation must be somewhat incomplete, because typically gcov will only attempt to open the file when it writes its output - eg when a program exits, but also at some other times. Something must be triggering it to write the .gcda file (hence why you're seeing that open happening at all), but it's not clear to me right now what that is... – psmears Feb 19 '15 at 23:59
  • This explanation sounds extremely plausible to me and is consistent with my observations. I see the calls to `open` for opening the gcda file _after_ my tests are completed. I'm not sure how much teardown code CPPUTest has, but as far as I can tell, the calls are pretty much at the end of the program execution. Your explanation however also implies I cannot place a hook on `open()` with profiling enabled... – Ludo Feb 20 '15 at 09:04
  • @Ludo: Thanks! To be honest I still feel there's a piece of the jigsaw that's missing: it's not clear to me what's causing the gcov stuff to write out its file. If we can figure that out, there may be a way to keep the `open()` hook with profiling enabled. Does gdb give a meaningful stack trace for the segmentation fault? In any case, one workaround might be (if your build system allows it) to compile the LD_PRELOAD library without profiling, but the rest of the code with - you won't get gcov data for the test library, but presumably this is less important than for the code under test? – psmears Feb 20 '15 at 11:31
  • I could disable the code coverage for preloaded lib, but the build system makes this really complicated. I don't think it's worth the effort just to get a handful of extra lines covered... I'm trying to get gdb to work, but I can't seem to get the paths right in combination with LD_PRELOAD. – Ludo Feb 23 '15 at 10:38
  • @Ludo: Fair enough re the build system, I've been there! For gdb, maybe try something like: `env LD_PRELOAD=/full/path/to/lib.so gdb -args /path/to/program arg1 arg2` (This will also cause the library to be linked into the gdb process itself, but you may get away with it - if not, I'd suggest putting a long `sleep()` early on in the program then attaching with `gdb -p `.) – psmears Feb 23 '15 at 11:10
0

You are passing va_list incorrectly. Try following hope it helps

        r = my_open(fn, flags, args);

For more info http://c-faq.com/varargs/handoff.html

Pointer
  • 627
  • 2
  • 8
  • 19