StoneThrow StoneThrow - 3 months ago 20
Linux Question

g++ Cygwin/Linux or version discrepancy

Can someone explain the discrepancy in how two instances of g++ handle compilation of the following code to shared libraries?

Foo.h


#ifndef Foo_h
#define Foo_h

void Foo();

#endif // Foo_h


Foo.cpp


#include "Foo.h"
#include <iostream>

void Foo()
{
std::cout << "Greetings from Foo()!" << std::endl;
}


Bar.h


#ifndef Bar_h
#define Bar_h

void Bar();

#endif // Bar_h


Bar.cpp


#include "Bar.h"
#include "Foo.h"
#include <iostream>

void Bar()
{
Foo();
std::cout << "Greetings from Bar()!" << std::endl;
}


On a true Linux box:

>g++ --version
g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

>g++ -fpic -c Foo.cpp
>g++ -fpic -c Bar.cpp
>g++ -shared -o libFoo.so Foo.o
>g++ -shared -o libBar.so Bar.o
>


On Cygwin:

>g++ --version
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>g++ -fpic -c Foo.cpp
>g++ -fpic -c Bar.cpp
>g++ -shared -o libFoo.so Foo.o
>g++ -shared -o libBar.so Bar.o
Bar.o:Bar.cpp:(.text+0x9): undefined reference to `Foo()'
Bar.o:Bar.cpp:(.text+0x9): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `Foo()'
collect2: error: ld returned 1 exit status


I don't really have enough *nix-savvy to know how to install different/matching versions of g++ on either box to see if that is the cause of the problem (on one of the boxes I wouldn't have the privilege to do so, anyway).

I'd always thought that object files, and by extension, libraries - either static or shared - were allowed to have unresolved symbols, and that it's only when linking an executable that all symbols needed to be resolved. That concept has pretty much held true over several years of development experience, too, so I'm baffled by the error generated on Cygwin. I'm very curious to know what's happening here. Thank you.

UPDATE

An answerer below provided the following suggestion which works:
g++ -shared -o libBar.so Bar.o libFoo.so


Look at the resulting contents of libBar.so:

>nm --demangle libBar.so | grep Foo
00000004e4b791c4 I __imp__Z3Foov
00000004e4b7903c I _head_libFoo_so
00000004e4b71750 T Foo()
00000004e4b793ec I libFoo_so_iname


Per my understanding, this means that
Foo()
is binary-included in libBar.so, i.e. the compiled binary content of
Foo()
is present in libBar.so.

This is a bit different than the picture I'd had in my head based on the behavior on the true Linux box. I thought each
.so
would be "standalone" binary code, just like a
.o
file, or a
.a
file that was comprised of only one object file.

I guess what I'm having trouble wrapping my head around is that the Cygwin (or g++ 5.4) behavior is saying that a library cannot have unresolved symbols - this feels contrary to what a lot of prior experience has ingrained in me. I get that an executable cannot have unresolveds, but a library ought to be able to have unresolveds, right? After all, you can't execute a library - it has no
main()
. I know for sure that static libraries can have unresolveds, and I thought difference between shared and static libraries is whether their code is link-time added to the executable binary, or whether their code is runtime looked-up by the executable.

Appreciate further clarity the community can shed here. Thank you.

Answer

There are no shared objects on Windows. There are DLLs. DLLs behave differently from Un*x shared objects. In particular, they are not allowed to have undefined symbols. The PE (Portable Executable) format used in Windows for both DLLs and executables just doesn't have a way to express them. Google "pe dll" "undefined symbols", there's a lot of info around.

Cygwin tries very hard to hide peculiarities of Windows from the programmer but there's only so much it can do.

When you link libBar.so against libFoo.so, code from libFoo.so is not physically included in libBar.do. That would defeat the purpose of DLLs to be loaded at run time. Instead, the linking process creates stubs for all functions imported from other DLLs. These are not real functions. You can make sure this is indeed the case by looking for strings:

% strings libFoo.so | grep "Greetings from"
Greetings from Foo
% strings libBar.so | grep "Greetings from"
Greetings from Bar
Comments