Weak connection with "necessary"

I am having trouble using a library containing weak symbols and flags --as-needed

.

Example

(The Jack library is used here )

$ cat <<EOF >myjack.c
#include <jack/weakjack.h>
#include <jack/jack.h>
int main() {
  if (jack_client_opent)
     jack_client_open("foobar", JackNoStartServer, 0, 0);
  else return 1;
  return 0;
}
EOF

$ gcc -o myjack myjack.c  -Wl,--no-as-needed -ljack

$ ./myjack && echo "ok" || echo "K.O."
ok

$ ldd myjack | grep jack
    libjack.so.0 => /usr/lib/x86_64-linux-gnu/libjack.so.0 (0x00007f16f615f000)

$ gcc -o myjack myjack.c  -Wl,--as-needed -ljack

$ ./myjack && echo "ok" || echo "K.O."
K.O.

$ ldd myjack | grep jack

$

      

(The example code has been modified to not segfault anymore, since segfault is not my problem)

Problem

The problem seems to be this:

  • Jack declares all symbols to be weak (if I enable <jack/weakjack.h>

    ). everything is fine with me; I want my characters to remain weak. especially my program loosely communicates with socket on OSX ( -weak_framework Jackmp

    ), which requires enabling<jack/weakjack.h>

  • When linking to the --as-needed

    linker, it will exclude any library that does not reference at least one non-weak symbol. from the manpage:

- as needed, the DT_NEEDED tag should only be released for a library that, at that point in the link, satisfies a weak undefined symbol reference from a regular object file

  • some OS (eg Ubuntu-16.04LTS) have a default --as-needed

    .

Now I think it --as-needed

is a good linker feature to get rid of a lot of really unnecessary runtime dependencies.

However, I don't understand why a weak addiction is considered no addiction at all. For me, a weak dependency is to enable additional features. I want these features to be enabled if possible, and deciding if this is possible should be a runtime decision. With its current behavior, it becomes a compilation decision. (If I wanted to, I would simply disable the relevant code through preprocessor magic).

One solution is obviously to just add --no-as-needed

to the linker flags. I don't want this: I want to get rid of overlinking if my distribution (or whoever compiles my binary) thinks this is the right thing to do.

So, I can include as-needed

after the link in my known weak library:

  gcc -o myjack myjack.c  -Wl,--no-as-needed -ljack -Wl,--as-needed ...

      

but that also seems to be wrong, since then all libraries after my forced library are suddenly forced --as-needed

(maybe this is not what my distribution or someone compiles my binary, thinks this is the right thing to do). It also seems to add a lot of coolness to the build chain, simply because some library only exports weak symbols. I don't want to manually track all the libraries that do this.

I could also, of course, just not include <jack/weakjack.h>

. The reason it is included is because the application also runs on OSX, where I want to optionally depend on the JACK structure (which is why I link to -weak_framework Jackmp

) and save my program in the absence of that structure.

I really don't want to clutter my application code due to the subtle differences between linkers across platforms. This is probably the main problem I am having with all of this: why should I add platform-specific code to my application to accommodate various linker quirks - I would probably be fine with adding custom code for example. not including weakjack.h

if the compiler has no equivalent for -weak_library

or -weak_framework

; but it currently seems like the closest I can get is something like #ifdef __APPLE__

, which makes me shudder in this context).

So, I really liked some option for enforcing libraries that only have weak characters that need to be aligned.

Is there such a thing?

+3


source to share


1 answer


I am having problems using a library containing weak symbols and the -as-required linker flag.

No, it is not.

Find out where yours is libjack.so

, for example

$ locate libjack
/usr/lib/x86_64-linux-gnu/libjack.so
/usr/lib/x86_64-linux-gnu/libjack.so.0
/usr/lib/x86_64-linux-gnu/libjack.so.0.1.0
...

      

Then use nm

to view JACK API character types in libjack.so

:

$ nm -C -D /usr/lib/x86_64-linux-gnu/libjack.so | grep jack_
000000000000e7e0 T jack_acquire_real_time_scheduling
000000000000d530 T jack_activate
000000000002ccf0 T jack_client_close
000000000000e820 T jack_client_create_thread
....
....
000000000000f340 T jack_uuid_empty
000000000000f320 T jack_uuid_parse
000000000000f2e0 T jack_uuid_to_index
000000000000f330 T jack_uuid_unparse

      

You will find that they are all of type T

(= regular global symbol in a text section:) man nm

. There are some weak symbols in the library:

$ nm -C -D /usr/lib/x86_64-linux-gnu/libjack.so | egrep ' (w|W) '
                 w __cxa_finalize
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
0000000000025410 W std::ctype<char>::do_widen(char) const
0000000000014c10 W void std::vector<unsigned short, std::allocator<unsigned short> >::_M_emplace_back_aux<unsigned short const&>(unsigned short const&)
0000000000014b10 W std::pair<std::_Rb_tree_iterator<unsigned short>, bool> std::_Rb_tree<unsigned short, unsigned short, std::_Identity<unsigned short>, std::less<unsigned short>, std::allocator<unsigned short> >::_M_insert_unique<unsigned short>(unsigned short&&)
0000000000014ad0 W std::_Rb_tree<unsigned short, unsigned short, std::_Identity<unsigned short>, std::less<unsigned short>, std::allocator<unsigned short> >::_M_erase(std::_Rb_tree_node<unsigned short>*)

      

But none of them are in the JACK API. Nothing you can do to restore libjack.so

yours will change that. The correct way to characterize your problem is:

I am unable to link the library with the --as-needed

linker flag with the program in which I decided to loosen all my links to this library

The JACK API's defining symbol references are libjack.so

all strong. You have written a program that directs the compiler to emit characters in your object code that are undefined weak references to the JACK API, and you find that, with the necessary linkage, these weak references cannot be forced to bind libjack.so

to provide their missing definitions.

it seems that the problem is this:

Connector

declares all characters weak (if enabled).

when linking with -as-needed, the linker will exclude any library that does not reference at least one non-weak symbol.

some OSs (e.g. Ubuntu-16.04LTS) have -as-needed by default.

The last two points are correct. A split between distributions linking shared libraries as needed and by default, which didn't come back in Debian Wheezy 2013, which moved on as needed . The Debian distro has since followed suit, while the RedHat / Fedora clan has been stuck with the quo ante status.

The first point is confusion. libjack.so

as we noted, exports highly specific JACK APIs, which you cannot change by writing and compiling new code. If you included <jack/weakjack.h>

in one of your source files, then you are declaring all JACK API symbols in your code, and the compiler will provide an object file containing only weak references to the JACK API. <jack/weakjack.h>

simply defines macros that have this effect.

It would be surprising if the old and basic Linux library, for example, libjack

ruined its adaptation to an equally split. I suspect you have missed some of the little fingerprint aroundjack/weakjack.h

:

Detailed description

One of the challenges facing the developers is to use the new features introduced in new versions of [JACK], while supporting older versions of the system. Usually, if an application uses a new function in a library / API, it cannot work on earlier versions of the library / API that do not support this feature. Such applications will either not launch or an attempt has been made to use this feature. This problem can be solved by using loosely coupled symbols.

...

A concrete example will help. Suppose someone is using the JACK version of the client, we'll call it "Jill". Jill was linked to a version of JACK that contains a newer part of the API (say jack_set_latency_callback ()) and would like to use if available.

When Jill is launched on a system that has a suitable "new" version of JACK, this function will be available completely normally. But if Jill is running on a system with an older version of JACK, the feature is not available.

With normal symbol bindings, this would create a startup error when someone tries to start Jill with an "old" version of JACK. However, functions added to JACK after version 0.116.2 are declared "loose" , which means that their absence does not cause an error during program startup. Instead, Jill can check if the jack_set_latency_callback character is null or not. If its value is null, it means that the JACK installed on this computer is too old to support this feature. If it is not empty, then Jill can use it just like any other function in the API. For example:

if (jack_set_latency_callback) {
   jack_set_latency_callback (jill_client, jill_latency_callback, arg);
}

      

However, there are clients who might want to use this approach for parts of the JACK API that predates 0.116.2. For example, they might want to see even though really old core API parts like jack_client_open () exist at runtime.

Such clients must include <jack / weakjack.h> before any other JACK header . This will cause the entire JACK API to be loosely coupled so that any and all functions can be checked for existence at runtime. It is important to understand that very few clients need to do this - if you use this feature, you must have a clear reason for this.

[in italics]

This makes it clear that a program such as yours is taking the exceptional step of including jack/weakjack.h

in order to loosen its references, but you can expect the entire JACK API to work successfully only if it checks the definiteness of each JACK API before referencing it and handles the case where it is undefined. Your program doesn't match. This does:

myjack1.c



#include <jack/weakjack.h>
#include <jack/jack.h>
#include <stdio.h>

int main() {
    if (jack_client_open) { 
        jack_client_open("foobar", JackNoStartServer, 0, 0);
    } else {
        puts("`jack_client_open` is not available");
    }
    return 0;
}

      

and does this:

myjack2.c

#include <jack/weakjack.h>
#include <jack/jack.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
    jack_client_t * (*jack_client_open_fp)
        (const char *, jack_options_t,jack_status_t *,...) = jack_client_open;

    if (!jack_client_open_fp) {
        void * dsoh = dlopen("libjack.so",RTLD_LAZY);
        if (!dsoh) {
            fputs("`libjack` is not available\n",stderr);
            exit(EXIT_FAILURE);
        }
        *(void**)(&jack_client_open_fp) = dlsym(dsoh,"jack_client_open");
        if (!jack_client_open_fp) {
            fputs("`jack_client_open` is not available\n",stderr);
            exit(EXIT_FAILURE);
        }
    }
    jack_client_open_fp("foobar", JackNoStartServer, 0, 0);
    exit(EXIT_SUCCESS);
}

      

which will outline the usual approach to a discovery API - apt for a program designed to be installed and run on a system that may not provide at all libjack

. So you've built it without referencing libjack

how:

gcc -o myjack2 myjack2.c -ldl

      

and on Ubuntu 17.04 - which provides libjack

- it can work like:

$ ./myjack2 
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock

      

So the T & Cs library is in good order with regard to the required link. Which seems to leave you in a position to be independently unhappy about how it's done as needed rather than otherwise, which will allow you to loosen all your references to the JACK API and still get libjack

your loose references to its API Symbols: -

I don't understand why weak addiction is considered no addiction at all. For me, the weakest dependency is to enable additional features. I want these functions to be if possible, possible, and the decision on whether this is possible should be runtime. With its current behavior, it becomes a compile-time decision.

Your opinion that a weak symbolic link creates a weak dependency on the library that defines the symbol has no basis for the GNU linker. A program depends on a library if linking requires the symbol definition that the library provides; otherwise it does not depend on this library: there are no weak or strong dependencies. (Darwin Mach-O's ruler maintains a kinship distinction)

There are weak characters, not standard and ordinary, which is strong. The {weak | strong} is shorthand for {weak \ strong} symbol, because the same symbol can refer to multiple linker input files, sometimes or always weakly, and sometimes or always strong.

A strong character must have exactly one defining reference in the anchor.

The weak symbol is such that:

  • The compiler is not required to find a definition for it: it can remain undefined in the output file

  • A component is not required to fix multiple weak definitions of the same symbol in different input files. If only one defining link within a link is strong, then that strong definition is chosen, and all weak ones are ignored. I dropped the defining links in links are weak, then the linker will pick one randomly.

It follows from the first part of this that undefined weak character reference does not lead to interdependency at all. No definition is required, and the fact that no definition is required is the result of a decision by the programmer (for example #include <jack/weak_jack.h>

) or perhaps the compiler. It is unreasonable to expect the linker, if it aims to link only the shared libraries that are needed, then must link the libraries to provide symbol definitions for which you or the compiler told it not to need definitions.

If the linker were supposed to behave as in your case, it would be the linktime solution to freeze and include the API, which by enabling jack/weak_jack.h

you have indicated that you want to be reserved entirely for runtime detection.

Linking your problematic program to a -no-as-needed

successful one as a way of suffocating a bug in the program. The mistake is that by enabling jack/weak_jack.h

you commit to discover the entire API at all times, but you don't, and instead use the API for granted. Hence segfault with required connection. Linking with -no-as-needed

simply cancels the include effect jack/weak_jack.h

. Including it says your program doesn't need any of the API definitions: -no-as-needed

says whatever they are, you get it anyway.

In light of the fact that all versions of the JACK API version 0.116.2 are weak without use jack/weak_jack.h

, I think you just don't have any use for this header unless you really plan on a program that will do something worthwhile on the host from which missing libjack

. if you are planning this, then you have no alternative to discovering at runtime all the JACK APIs you use regardless of the binding conventions, as you cannot bind libjack

anyway.

If not, then just link libjack

and, if you just call jack_client_open

, your program on any host will dynamically link all API definitions, regardless of whether they are on that host, because your reference to jack_client_open

(in the absence of <jack/weak_jack.h>

) will require libjack

, regardless of matters to the linker who made the link or not. If you want to be compatible versus API versions, then you need to implement runtime detection as described in any API document that is documented with an attribute JACK_WEAK_EXPORT

 - as opposed to JACK_OPTIONAL_WEAK_EXPORT, or JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT

: the latter denote core APIs that can only be relaxed via <jack/weak_jack.h>

.

+5


source







All Articles