[Xcb] [ANNOUNCE] xcb-util 0.3.9

Jeremy Huddleston jeremyhu at freedesktop.org
Wed Jun 6 14:10:47 PDT 2012

On Jun 6, 2012, at 4:04 AM, Josh Triplett <josh at joshtriplett.org> wrote:

> On Tue, Jun 05, 2012 at 10:03:44PM -0700, Jeremy Huddleston wrote:
>> On Jun 5, 2012, at 6:35 PM, Josh Triplett <josh at joshtriplett.org> wrote:
>>> I agree with your statement that from a functional standpoint this holds
>>> true: the linker doesn't seem to enforce the minor version rule, so you
>>> can build against a newer library and run with an older one, or vice
>>> versa, as long as the major version matches.  The linker will complain
>>> if you use a symbol that it can't resolve, though.
>> As it should (well unless the symbol is weak and can be checked for at
>> runtime).
> True, though for most symbols that doesn't make sense, since you'd have
> to write things like "if (!xcb_useful_function) aieee();". :)

Well, if aieee() is really needed, then you wouldn't check for it, you'd
 just use it and bail out with the dynamic linker complaining that it
couldn't resolve.

If you can avoid using it, you would do something like:

if (strlcpy) {
} else {

> Better to use symbol versioning or similar to let the dynamic linker
> tell you at the start of your program that a symbol doesn't exist.  

Why?  If you can do something in the case that it doesn't exist, that
should be an option.

> Or,
> if you really have written your program so that you can cope with the
> absence of some functionality, consider either using dlopen/dlsym to
> make that explicit or otherwise having a way to easily verify the
> functionality you need without having to test every symbol for NULLness.

No, that's a horrible solution.  The code snippet above (for strlcpy)
makes it easily accessible for developers.  Doing something with dlsym
is ugly in comparison (and IMO would just cause developers to NOT use
the new functionality):

size_t (*strlcpy_maybe)(char *, const char *, size_t);
strlcpy_maybe = dlsym(RTLD_DEFAULT, "dlsym");
if (strlcpy_maybe) {
} else {

>>> In particular, the minor version serves as a hint to the programmer that
>>> if they link against libABC.so.1.1, they might or might not successfully
>>> run against libABC.so.1.0, depending on what symbols they used.
>> IMO, that should be annotated in header files in a way that allows those
>> symbols to be weak linked and checked for at runtime (and thus go down an
>> alternative codepath if unavailable).
> Not unless that gets wrapped in some kind of interface that avoids the
> need to check all used symbols against NULL before using them; I'd
> prefer to make that the dynamic linker's job.

Yes, the dynamic linker will bail on you if you try to actually *use* the
symbol, but you still need to check it.  I'm not sure what kind of interface
you want.  This seems rather straightforward to me:

if (strlcpy) {
} else

>>> Removing or changing symbols
>>> breaks that assumption; adding symbols doesn't.
>>> Libraries typically introduce symbol versioning to handle more complex
>>> cases that the linker can't catch on its own, namely a *change* to an
>>> existing symbol, such as by adding a new parameter to a function, or
>>> more subtly by changing the layout of a data structure used by a
>>> function.  In that scenario, the linker will see no problem, but the
>>> program will break at runtime, precisely as if you cast a function
>>> pointer to a different type with different parameters and tried to call
>>> it (because, effectively, you did).  Symbol versioning lets you maintain
>>> ABI (though often not API) compatibility in this case, by having old
>>> programs use a compatibility symbol that knows how to handle the old
>>> calling signature.
>> Yeah, we essentially have that same mechanism in place, usually for
>> cancelable/non-cancelable variants, legacy vs UNIX-conforming variants,
>> changes to 'struct stat', ...
> Any notable differences between ELF symbol versioning and OS X dylib
> symbol versioning?

There is quite a bit of difference, but the high level idea is the same.
First of all, we don't actually use versions.  We use variants.  For

$ nm -arch i386 /usr/lib/system/libsystem_c.dylib | grep nftw
000aa2e4 T _nftw
0000b7a0 T _nftw$INODE64$UNIX2003
000b207c T _nftw$UNIX2003

_nftw is the legacy function which is not SUSv3 compliant and uses the older struct stat.
_nftw$UNIX2003 is SUSv3 compliant, and it uses the older struct stat.
_nftw$INODE64$UNIX2003 is SUSv3 compliant, and it uses the newer struct stat.

The developer determines which version they want at build time.  If they
want their binary to run on Tiger (which didn't have the new struct stat
or compliant function), then the output asm will use nftw.  If they are
building for the current OS (and don't explicitly request the older struct
or legacy behavior), then the compiler will output asm that uses
_nftw$INODE64$UNIX2003.  This is all managed by the C-pre-processor and
header file goop.

With ELF, it's my understanding that the versioning is handled by the
linker.  At link time, the linker will see that the input object uses
funC and that it's provided by libC, and that libC has that symbol
versioned as 1.2, so the linker then rewrites funcC to funcC at LIBC_1.2.

Symbol versioning is very useful for dealing with the flat namespace
problem.  For example, consider an application that links against libA
and libB.  libA links against libC.1 and libB links against libC.2. 
Both libC.1 and libC.2 provide different versions of funC().  In a flat
namespace without versioning, this situation would not work. funC at LIBC
would collide.  ELF solves this by versioning the symbols in the global
symbol list.  On OS X, we use a 2-level namespace, so versioning isn't

More information about the Xcb mailing list