Lambda object + c callback sigsegv

If I implement a C callback like this:

register_callback([](/*some args*/){/*some stuff*/}); 

I get SIGSEGV when it starts, but if I register it as follows:

 auto const f([](/*some args*/){/*some stuff*/}); register_callback(f); 

Then it works great. Of particular interest (for me) is the stack trace created by the sanitizer:

 ASAN:SIGSEGV ================================================================= ==22904==ERROR: AddressSanitizer: SEGV on unknown address 0x7f1582c54701 (pc 0x7f1582c54701 sp 0x7f1582c544a8 bp 0x7f1582c54510 T2) #0 0x7f1582c54700 ([stack:22906]+0x7fc700) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV ??:0 ?? 

It looks as if the function pointer was pointing to the stack. Does lambda push on stack to push code on stack? Since I am not committing anything, the location of the function pointer is a mystery to me. What's happening? Optimization flags were not used. I am not looking for workarounds.

EDIT: Apparently, “+” was the key to a working example. I do not know why this is necessary. Remove the "+" and the compilation example, but SIGSEGV will clang-3.5 with both clang-3.5 and gcc-4.9 .

 #include <curl/curl.h> #include <ostream> #include <iostream> int main() { auto const curl(curl_easy_init()); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, "cnn.com"); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* auto const f([](char* const ptr, size_t const size, size_t const nmemb, void* const data) { *static_cast<::std::ostream*>(data) << ptr; return size * nmemb; } ); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +f); */ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char* const ptr, size_t const size, size_t const nmemb, void* const data) { *static_cast<::std::ostream*>(data) << ptr; return size * nmemb; } ); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &::std::cout); curl_easy_perform(curl); curl_easy_cleanup(curl); } return 0; } 
+5
source share
2 answers

curl_easy_setopt defined as (in curl/easy.h ):

 CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); 

This means that the third argument to param must be of a type that can be passed as a variator C. Unfortunately, while curl_easy_setopt expects a function pointer, passing class objects (and lambdas are class objects) is "conditionally supported using semantics defined by implementation "( [expr.call] / 7), so the compiler accepts it, but then curl_easy_setopt tries to interpret the lambda object as a function pointer with catastrophic results.

The object you are actually passing in is a carefree lambda, which means it is an empty class object of size 1 byte (all the most derived objects must be at least one byte in size). The compiler will push this argument up to an integer number of words (4 bytes on 32-bit, 8 bytes on 64-bit) and either pass 0 or leave this register / stack slot unallocated, which means garbage passes (since lambda does not actually use your memory footprint when called).

+5
source

I just wrote a similar lambda using libcurl and got a crash, after a thorough check, I got the following code working as charm.

The magic is to add the initial + to the unqualified lambda expression, which will cause the conversion to a simple pointer to a C function.

 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, /* NOTE: Leader '+' trigger conversion from non-captured Lambda Object to plain C pointer */ +[](void *buffer, size_t size, size_t nmemb, void *userp) -> size_t { // invoke the member function via userdata return size * nmemb; }); 

As far as I understand, curl_easy_setopt() wants a void* , not an explicit function type, so the compiler just gives the address of the OBJECT lambda object; if we do a function pointer function on a lambda object, the compiler will return a function pointer on the lambda object.

+1
source

Source: https://habr.com/ru/post/1211413/


All Articles