2

I am writing a debugger in C++ and upon exit I need it to cleanup any breakpoints set in the debuggee.

I have made a struct called BREAKPOINT_INFO that contains the information needed to cleanup each breakpoint. Information such as a handle to the process(hProcess), the virtual memory address of the breakpoint(lpBreakPoint), and the opcode of the original instruction that was replaced(instr).

I then have a CleanUp member function that uses the data members described above to restore the original instruction so the debuggee doesn't crash.

typedef struct
{
    HANDLE hProcess;

    PCHAR  lpBreakPoint;

    CHAR   instr;

    void CleanUp()
    {
        DWORD dwError = 0;
        std::ostringstream oss;
        LPSTR szErrorRest = (LPSTR)"Error restoring original instruction: ";

        if(!WriteProcessMemory(hProcess, lpBreakPoint, &instr, sizeof(CHAR), NULL))
        {
            dwError = GetLastError();

            oss << szErrorRest << dwError;

            szErrorRest = oss.str().c_str();

            MessageBox(NULL, szErrorRest, "ERROR", MB_OK|MB_ICONERROR);
        }
    }
}BREAKPOINT_INFO, *PBREAKPOINT_INFO;

However, when I try to pass my member function for each BREAKPOINT_INFO structure, I get a compilation error:

C:\...\...\main.cpp|39|error: cannot convert 'BREAKPOINT_INFO::CleanUp' from type 'void (BREAKPOINT_INFO::)()' to type 'void (__attribute__((__cdecl__)) *)()'|

And even after trying to cast to that type it fails to compile. Is it possible to pass a member function to atexit? And if so, how would I go about doing so? Thank you for your time.

EggHead
  • 61
  • 5
  • `atexit()` takes a regular `void (*function)(void)` — free-standing pointer to a (non-member) function that takes no arguments and returns no value. Don't try to pass anything else. – Jonathan Leffler Apr 29 '17 at 00:49
  • 1
    Use destructors, if you can. – Pubby Apr 29 '17 at 00:50
  • Its best not to use atexit() in c++ imo, but something like scope_exit in main(). – user1095108 Apr 29 '17 at 00:51
  • @JonathanLeffler I have to be able to pass the information needed to cleanup each break point. I would like for this information to be in a nice and neat `struct`/`class`. Otherwise I will have to use globals which is not preferable. – EggHead Apr 29 '17 at 00:53
  • 1
    You will have to do it a different way — you can't pass any information to a function that is passed to `atexit()`. Full stop. You need to make a function that finds the relevant information from a global variable, or something similarly gruesome. But why isn't your data being destroyed by the destructor, anyway? – Jonathan Leffler Apr 29 '17 at 00:56
  • @jonathan you're wrong, he can cify a capturing lambda and pass that to atexit(). – user1095108 Apr 29 '17 at 00:59
  • @user1095108 : write it up as an answer. Is a lambda a member function? – Jonathan Leffler Apr 29 '17 at 01:01
  • @user1095108 I tried the following to no avail. `std::atexit( [breakpoint_info] (void) { breakpoint_info.CleanUp(); } );` I still get a conversion error. `C:\...\...\main.cpp|39|error: cannot convert 'main()::' to 'void (__attribute__((__cdecl__)) *)()' for argument '1' to 'int atexit(void (__attribute__((__cdecl__)) *)())'|` – EggHead Apr 29 '17 at 01:28
  • 1
    No you cannot pass a capturing lambda to `atexit`. – Passer By Apr 29 '17 at 01:37
  • That depends on the compiler. Visual Studio, for example, only allows non-capturing lambdas to be implicitly convertible to free-form function pointers. IIRC, there are compilers that can do it for capturing lambdas, but I forget which ones. – Remy Lebeau Apr 29 '17 at 01:51
  • 1
    Regarding your objection to using globals, you might want to note that `atexit` itself is implemented using a global. :-) – Harry Johnston Apr 29 '17 at 02:27
  • @HarryJohnston Thank you for the information. The objection to using globals had more to do with making the code messy and a slight bit more unmanageable. Not necessarily implementation. – EggHead Apr 29 '17 at 03:06

2 Answers2

2

No you cannot pass anything other than a function pointer to atexit, as specified by its declaration.

int atexit (void (*func)(void)) noexcept

A member function is not a free function.
As you can see in your error message, it has type void (BREAKPOINT_INFO::)() which is not the same as void ().

A pointer to member function in fact, takes an extra first parameter to specify which instance of the class is invoking the member function.

auto member_function_pointer = BREAKPOINT_INFO::CleanUp;
BREAKPOINT_INFO some_info;
member_function_pointer(&some_info);  //is equivalent to...
some_info.CleanUp();

And no, you cannot pass a capturing lambda as a function pointer. A non capturing lambda has an public non-virtual non-explicit const conversion function to pointer to function, which may be the source of misconception that a function pointer can point to other things.

What you should do instead, as mentioned in the comments, is to use a destructor to do cleanup work. This pattern is known as RAII, and is by far one of the best.


As a sidenote, you don't need to do crazy typedef on struct declarations in C++, what you are writing looks a lot like C with methods.

Community
  • 1
  • 1
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Could you clarify how you might use a destructor in this scenario? [According to this answer on Quora](https://www.quora.com/Are-dynamic-objects-destroyed-after-the-main-function-stops-in-C++), the destructors for dynamic objects are not automatically run when the program exits. – Harry Johnston Apr 29 '17 at 23:10
  • ... were you thinking of something like a singleton containing a list of dynamic objects? I guess that's an improvement over a C-style global and atexit(). – Harry Johnston Apr 29 '17 at 23:13
2

I did not originally intend to write an answer, since, even though correct and true, it will invite - votes, but nevertheless, here it goes.

You most certainly CAN pass a capturing lambda object to atexit(), if you cify it first, here's an example:

int a = 10;

std::atexit(cify<void(*)()>([a](){std::cout << a << std::endl;}));

but, to solve your problem, I would rather use some kind of scope exit scheme.

If you capture a pointer or reference to an instance of some class, you will then be able to access its members.

user1095108
  • 14,119
  • 9
  • 58
  • 116
  • Can you explain how this works? In particular, if you capture different instances of the same lambda multiple times (with different captured values) how does your `cify` disambiguate them? – Harry Johnston Apr 29 '17 at 22:33
  • In my defense I had no idea what cify stands for, its not the most obvious abbreviation especially without a captial C :P. Also I'd argue you did not actually pass the lambda, you passed in a class with a static function calling a lambda, which is syntax sugar around manually writing the function. – Passer By Apr 30 '17 at 05:39
  • @HarryJohnston you have the extra int template parameter to disambiguate, the solution is not perfect, but sometimes it's useful, especially if with temporary lambda objects. Otherwise, only the latest lambda instance will be captured and if there is an older instance, it will be destroyed. – user1095108 Apr 30 '17 at 12:03
  • @PasserBy you pass a non-capturing lambda object converted to a function pointer, that will call the capturing lambda object, that has been copied/moved into a static variable. – user1095108 Apr 30 '17 at 12:07
  • OK, I think I see how this would apply to the OPs scenario. They'd still need a static variable to hold a list of structs, but that variable would no longer need to be global to the entire module, it could be local to the function that manipulates the list. (Yes?) Personally, I'd probably replace the struct with a full-blown class that keeps track of its instances itself, but the OP doesn't seem to want to go that way. (Come to think of it, I'm puzzled that the app doesn't *already* keep a list of the structs.) – Harry Johnston Apr 30 '17 at 21:26
  • @HarryJohnston Yes, a global variable is not necessary and I did write I'd rather use a scope_exit approach in his use case. – user1095108 Apr 30 '17 at 21:48