Jak radzić sobie z kolizjami symboli między bibliotekami połączonymi statycznie?

84

Jedną z najważniejszych zasad i najlepszych praktyk podczas pisania biblioteki jest umieszczenie wszystkich symboli biblioteki w określonej przestrzeni nazw biblioteki. C ++ ułatwia to dzięki namespacesłowu kluczowemu. W C typowym podejściem jest poprzedzanie identyfikatorów pewnym przedrostkiem specyficznym dla biblioteki.

Zasady standardu C umieścić pewne ograniczenia dotyczące tych (na bezpiecznej kompilacji): AC kompilator może odczytywać tylko 8 pierwszych znaków identyfikatora, więc foobar2k_eggsi foobar2k_spamnie może być interpretowane jako tych samych identyfikatorów ważnie - jednak każdy nowoczesny kompilator pozwala na arbitralne długich identyfikatorów więc w naszych czasach (XXI wiek) nie powinniśmy się tym przejmować.

Ale co, jeśli masz do czynienia z bibliotekami, w których nie możesz zmienić nazw symboli / identyfikatorów? Być może masz tylko statyczny plik binarny i nagłówki lub nie chcesz lub nie możesz samodzielnie dostosowywać i rekompilować.

datenwolf
źródło

Odpowiedzi:

143

Przynajmniej w przypadku bibliotek statycznych można to dość wygodnie obejść.

Rozważ te nagłówki bibliotek foo i bar . Ze względu na ten samouczek podam również pliki źródłowe

przykłady / ex01 / foo.h

przykłady / ex01 / foo.c (to może być nieprzezroczyste / niedostępne)

przykład / ex01 / bar.h

przykłady / ex01 / bar.c (może być nieprzezroczysty / niedostępny)

Chcemy ich użyć w programie foobar

przykład / ex01 / foobar.c

Jeden problem staje się natychmiast widoczny: C nie zna przeciążenia. Mamy więc dwa razy dwie funkcje o identycznej nazwie, ale różnej sygnaturze. Potrzebujemy więc jakiegoś sposobu, aby je rozróżnić. W każdym razie zobaczmy, co na ten temat ma do powiedzenia kompilator:

Okej, to nie była niespodzianka, po prostu powiedziało nam, co już wiedzieliśmy lub przynajmniej podejrzewaliśmy.

Czy możemy więc w jakiś sposób rozwiązać tę kolizję identyfikatorów bez modyfikowania kodu źródłowego lub nagłówków oryginalnych bibliotek? W rzeczywistości możemy.

Najpierw rozwiążmy problemy związane z czasem kompilacji. W tym celu otoczymy nagłówki zawierające kilka #definedyrektyw preprocesora, które poprzedzają wszystkie symbole eksportowane przez bibliotekę. Później robimy to z ładnym, przytulnym nagłówkiem wrapper, ale tylko po to, aby zademonstrować, co się dzieje, robiliśmy to dosłownie w pliku źródłowym foobar.c :

przykład / ex02 / foobar.c

Teraz, jeśli skompilujemy to ...

... najpierw wygląda na to, że sytuacja się pogorszyła. Ale spójrz uważnie: Właściwie etap kompilacji poszedł dobrze. To tylko konsolidator narzeka teraz, że symbole kolidują i informuje nas o lokalizacji (plik źródłowy i wiersz), w którym to się dzieje. Jak widzimy, te symbole nie mają przedrostków.

Rzućmy okiem na tablice symboli za pomocą narzędzia nm :

So now we're challenged with the exercise to prefix those symbols in some opaque binary. Yes, I know in the course of this example we have the sources and could change this there. But for now, just assume you have only those .o files, or a .a (which actually is just a bunch of .o).

objcopy to the rescue

There is one tool particularily interesting for us: objcopy

objcopy works on temporary files, so we can use it as if it were operating in-place. There is one option/operation called --prefix-symbols and you have 3 guesses what it does.

So let's throw this fella onto our stubborn libraries:

nm shows us that this seemed to work:

Lets try linking this whole thing:

And indeed, it worked:

Now I leave it as an exercise to the reader to implement a tool/script that automatically extracts the symbols of a library using nm, writes a wrapper header file of the structure

and applies the symbol prefix to the static library's object files using objcopy.

What about shared libraries?

In principle the same could be done with shared libraries. However shared libraries, the name tells it, are shared among multiple programs, so messing with a shared library in this way is not such a good idea.

You will not get around writing a trampoline wrapper. Even worse you cannot link against the shared library on the object file level, but are forced to do dynamic loading. But this deserves its very own article.

Stay tuned, and happy coding.

datenwolf
źródło
4
Impressive! Didn't expect it to be so easy with objcopy.
Kos
12
Did you just... answer your own question within 1 minute of asking it?
Alex B
18
@Alex B: This is an tutorial article and I followed a path suggested to me at meta.stackoverflow.com how one could place tutorials about (interesting?) questions and their solutions. The question about clashing libraries came up and I thought "hm, I know how to deal with this kind of solution", wrote an article and posted it here in form of Q&A. meta.stackexchange.com/questions/97240/…
datenwolf
4
@datenwolf any idea about solving this problem for iOS libraries. As I found out, objcopy doesn't support iOS libraries :/
Ege Akpinar
6
Blah blah blah objcopy --prefix-symbols ... +1!
Ben Jackson
7

Rules of the C standard put some constraints on those (for safe compilation): A C compiler may look at only the first 8 characters of an identifier, so foobar2k_eggs and foobar2k_spam may be interpreted as the same identifiers validly – however every modern compiler allows for arbitrary long identifiers, so in our times (the 21st century) we should not have to bother about this.

This is not just an extension of modern compilers; the current C standard also requires the compiler to support reasonably long external names. I forget the exact length but it's something like 31 characters now if I remember right.

But what if you're facing some libraries of which you cannot change the symbol names / idenfiers? Maybe you got only a static binary and the headers or don't want to, or are not allowed to adjust and recompile yourself.

Then you're stuck. Complain to the author of the library. I once encountered such a bug where users of my application were unable to build it on Debian due to Debian's libSDL linking libsoundfile, which (at least at the time) polluted the global namespace horribly with variables like dsp (I kid you not!). I complained to Debian, and they fixed their packages and sent the fix upstream, where I assume it was applied, since I never heard of the problem again.

I really think this is the best approach, because it solves the problem for everyone. Any local hack you do will leave the problem in the library for the next unfortunate user to encounter and fight with again.

If you really do need a quick fix, and you have source, you could add a bunch of -Dfoo=crappylib_foo -Dbar=crappylib_bar etc. to the makefile to fix it. If not, use the objcopy solution you found.

R.. GitHub STOP HELPING ICE
źródło
You are right of course, however sometimes you need a dirty hack, like the one I showed above. For example if you're stuck with some legacy library where the vendor went out of business or similar. I specifically wrote this up for static libraries.
datenwolf
3

If you're using GCC, the --allow-multiple-definition linker switch is a handy debugging tool. This hogties the linker into using the first definition (and not whining about it). More about it here.

This has helped me during development when I have the source to a vendor-supplied library available and need to trace into a library function for some reason or other. The switch allows you to compile and link in a local copy of a source file and still link to the unmodified static vendor library. Don't forget to yank the switch back out of the make symbols once the voyage of discovery is complete. Shipping release code with intentional name space collisions is prone to pitfalls including unintentional name space collisions.

JJJSchmidt
źródło