Why does this dynamic library load code work with gcc?

Background:

I found myself with the unenviable task of porting a C ++ GNU / Linux application to Windows. One of the tasks of this application is to search for shared libraries by specific paths, and then dynamically load classes from them using the posix dlopen () and dlsym () calls. We have a very good reason for downloading this method, so that I do not become involved here.

Problem:

In order to dynamically detect characters generated by the C ++ compiler using dlsym () or GetProcAddress (), they must be decoupled using the extern "C" link block. For example:

#include <list> #include <string> using std::list; using std::string; extern "C" { list<string> get_list() { list<string> myList; myList.push_back("list object"); return myList; } } 

This code is great for C ++ and compiles and runs on numerous compilers on both Linux and Windows. However, it does not compile with MSVC because "the return type is not valid C". The workaround we came up with is to change the function to return a pointer to a list instead of a list object:

 #include <list> #include <string> using std::list; using std::string; extern "C" { list<string>* get_list() { list<string>* myList = new list<string>(); myList->push_back("ptr to list"); return myList; } } 

I am trying to find the optimal solution for the GNU / Linux bootloader that will either work with new functions as well as with a prototype of an old obsolete function, or at least detect when an obsolete function is encountered and a warning is issued. It would be indecent for our users if the code simply broke off when they tried to use the old library. My initial idea was to set up a SIGSEGV signal handler while calling get_list (I know this is not good - I am open to better ideas). So just to confirm that loading the old library will be segfault, where I thought I started the library using the old function prototype (returning a list object) through the new loading code (which expects a pointer to the list) and to my surprise it just worked. The question is why?

The downloaded code below works with both of the function prototypes listed above. I have confirmed that it works on Fedora 12, RedHat 5.5 and RedHawk 5.1 using gcc versions 4.1.2 and 4.4.4. Compile the libraries using g ++ with -shared and -fPIC, and the executable must be linked to dl (-ldl).

 #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <list> #include <string> using std::list; using std::string; int main(int argc, char **argv) { void *handle; list<string>* (*getList)(void); char *error; handle = dlopen("library path", RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); exit(EXIT_FAILURE); } dlerror(); *(void **) (&getList) = dlsym(handle, "get_list"); if ((error = dlerror()) != NULL) { printf("%s\n", error); exit(EXIT_FAILURE); } list<string>* libList = (*getList)(); for(list<string>::iterator iter = libList->begin(); iter != libList->end(); iter++) { printf("\t%s\n", iter->c_str()); } dlclose(handle); exit(EXIT_SUCCESS); } 
+7
source share
1 answer

According to the interlocutor, this is because you are lucky.

As it turned out, the ABI used for gcc (and most other compilers) for x86 and x64 returns "large" structures (too large to fit in the register), passing an additional "hidden" arg pointer to a function that uses this pointer as space to store the return value, and then returns the pointer itself. Thus, it turns out that a function of the form

 struct foo func(...) 

roughly equivalent

 struct foo *func(..., struct foo *) 

where it is expected that the caller will allocate space for 'foo' (possibly on the stack) and pass a pointer to it.

It so happens that if you have a function that expects it to be called in this way (waiting for the structure to return) and instead call it with a function pointer that returns a pointer, it MAY work if the junk bits it receives are for extra arguments (the contents of a random register left there by the caller) indicate somewhere accessible for writing, the called function happily writes the value returned by it and then returns this pointer, so the called code returns is something that looks like a valid pointer to a structure, which he expects. Thus, the code may work externally, but in fact, it probably knocks out a random bit of memory, which may be important later.

+4
source

All Articles