Allowing the dynamic linker to work against an executable functions

Today I wanted to get a plugin to work against the snapmanager.cgi executable's server.

The idea is pretty simple, the process loads the plugins and one of them (so far) wants to connect against the generate_content() signal, only that signal is part of the manager_cgi class which is not part of a .so library. Because of that, it doesn't expose its functions by default.

This is a quite interesting problem because it makes sense that the executable functions would not be visible to the dynamic linker. It should not be required. Actually, this is the first time I need such a feature, meaning that in all previous cases I always had the server and plugins in .so files.

So, now I needed to get just one function visible, but I could not see how to get the linker to do that. I first tried an attribute which g++ supports but that did not seem to have an effect.

__attribute__((visibility("default"))) void entry_point()
{
   ...
}

Here we see that the symbol entry_point() is asked to be made visible as per the default scheme. I think that my executable already had those symbols visible by default because it appeared in the symbol table as expected. It required some runtime linking. But it did not make it any more visible to the dl library.

The real solution was to add a command line option to the linker. The option to add is the -rdynamic which says to make the symbols publicly available for dynamic linking, exactly what we need here.

Since we are using cmake, it was a matter of adding one instruction as follow:

set_target_properties(
    ${PROJECT_NAME}
    PROPERTIES
        LINK_FLAGS -rdynamic
)

The instruction goes after the add_executable(${PROJECT_NAME} ...). It has to be after, otherwise, the set_target_properties() will tell you that it doesn't know that target.

The list of properties that can be modified by the function is pretty large already in the latest cmake. The LINK_FLAGS works in my current version which is 3.5.1.

I made sure that the executable was recompiled and relinked with the latest version of cmake I had and it worked like a charm.

One problem I ran into with this technique is a linkage issue. When the watchdog plugin was loaded from snapmanager.cgi, the dynamic linker was happy, the new SNAP_LISTEN() initialization worked like a charm.

However, when I tried to run the snapmanagerdaemon service, it would fail. This is because the service would try to call the manager_cgi::instance() function and that function is not available when in snapmanagerdaemon (since in that case the snapmanager.cgi is not loaded at all.) So I had to find out a way to prevent the linkage problem.

Looking at how others would resolve such a problem, it was pretty clear that with our RTLD_LAZY flag in the dlopen() call, that function should not cause problems. However, when initializing that very plugin, the function was required and thus the linkage failed at that point. So I needed to avoid the call. First I found a Stackoverflow answer that said you could use a dynamic_cast<>(). I tried that:

snap_manager::manager_cgi * cgi(dynamic_cast<snap_manager::manager_cgi *>(f_snap));
if(cgi != nullptr)
{
    SNAP_LISTEN(...);
}

However, I still had a linkage error. What I did not understand for a while, though, is that now I had a different type of linkage error. When you do a dynamic_cast<>(), you need to have access to the typeinfo of the class:

$ c++filt _ZTIN12snap_manager11manager_cgiE
typeinfo for snap_manager::manager_cgi

This means a pointer to a variable which is something that can't be handled at a later time (i.e. the RTLD_LAZY flag does not prevent that linkage from happening immediately on load.) So instead of getting a terminated() call at the SNAP_LISTEN() call, I was getting an error on the dlopen(). Something I did not expect. Member functions, static member functions, global functions are all going to be loaded the first time they are required. However, virtual functions and variables have to be resolved at the time the dlopen() occurs. You should never have a problem with a virtual function, however, variables are certainly somewhat easy to get in your way. For example, you could write an inline static function that returns an instance pointer to your class:

static A * get_instance()
{
    return g_instance;
}
...
static A * g_instance;

In this case, the static function gets inlined and thus you now have a direct reference to that g_instance static variable which won't exist if you don't also link against that specific module that defines g_instance. (In case you still needed a good argument for not using inline functions unless absolutely necessary like in a template.)

So in my case I resolved the problem by creating a virtual function which I named server_type(). That function returns "manager" in the main implementation and "manager_cgi" in the snapmanager.cgi implementation. That way I can distinguish the server and know whether that offensive SNAP_LISTEN() can be called or not. The resulting code is:

std::string const type(f_snap->server_type());
if(type == "manager_cgi")
{
    SNAP_LISTEN(...);
}

This way the SNAP_LISTEN() only happens when the corresponding snapmanager.cgi class is available and that means the snapmanagerdaemon works as expected again.

Syndicate content

Snap! Websites
An Open Source CMS System in C++

Contact Us Directly