Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Linking static library into shared object

3,603 views
Skip to first unread message

Urs Thuermann

unread,
Feb 17, 2011, 5:54:50 PM2/17/11
to
I want to link a static library of object files, compiled without
-fPIC, into a shared library. The reason is that I have no source
code for the static library so I cannot recompile but I want to load
it using dlopen(3). So my plan was to write a small wrapper C file
and link this together with the static library into an shared object.

This worked on 32-bit Intel x86 but not on x86_64 aka amd64. The
problem seems to be referneces to external symbols from object files
compiled without -fPIC.

This simplified code shows the problem. On x86 I get

$ uname -sm
Linux i686
$ cat foo.c
int foo(int a)
{
return bar(a);
}
$ cat bar.c
int bar(int a)
{
return a;
}
$ make foo.o bar.o
cc -c -o foo.o foo.c
cc -c -o bar.o bar.c
$ cc -shared -o z.so foo.o bar.o
$

I have also loaded this shared object using dlopen() and it worked as
expected.

But on amd64 I get

$ uname -sm
Linux x86_64
$ make foo.o bar.o
cc -c -o foo.o foo.c
cc -c -o bar.o bar.c
$ cc -shared -o z.so foo.o bar.o
/usr/bin/ld: foo.o: relocation R_X86_64_PC32 against symbol `bar' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: ld returned 1 exit status
$

When I use -fPIC the compiler and linker produce a shared object that
actually works, i.e. I can load it with dlopen() and it works. My
problem is that I don't have source so I cannot recompile with -fPIC.

$ rm *.o
$ uname -sm
Linux x86_64
$ make foo.o bar.o CFLAGS=-fPIC
cc -fPIC -c -o foo.o foo.c
cc -fPIC -c -o bar.o bar.c
$ cc -shared -o z.so foo.o bar.o
$

Why isn't it possible to use absolute PC32 adressing in shared
objects? I know that would make relocation necessary and cause the
affected pages to be not shared because COW. But I wouldn't mind
that. In x86 this works that way.

Or is there any other way to achieve what I described above?


urs

Andrew Haley

unread,
Feb 18, 2011, 7:08:32 AM2/18/11
to
Urs Thuermann <u...@isnogud.escape.de> wrote:
> I want to link a static library of object files, compiled without
> -fPIC, into a shared library.
>

No. Non-PIC only works that way on x86 as a concession to old
libraries; x86_64 shared libraries are all PIC.

Andrew.

Urs Thuermann

unread,
Feb 21, 2011, 6:17:01 AM2/21/11
to
Andrew Haley <andr...@littlepinkcloud.invalid> writes:

> No. Non-PIC only works that way on x86 as a concession to old
> libraries; x86_64 shared libraries are all PIC.

Can you explain a bit more? Why is this a concession to old libs? I
thought PIC and non-PIC shared libs just come naturally from the way
mmap() with MAP_PRIVATE works. My impression was that dlopen() simply
mmap()s the shared object with MAP_PRIVATE, then does linking
(eventually causing relocation) and that's it. If the shared lib is
non-PIC, relocation will cause COW and the affected pages are not
shared. For PIC, no relocation will be done and therefore no COW.

Why doesn't it work the same way on other architectures? Is there
special code to check for and to disallow non-PIC shared libs? And if
so, why? IMHO, a linker warning would be enough to warn about the
fact, that the resulting shared lib is not really fully shared. But
nevertheless it would be useful to be able to do this, e.g. if you
have non-PIC code that you cannot recompile.

urs

Jan Seiffert

unread,
Feb 21, 2011, 11:30:57 AM2/21/11
to
Urs Thuermann schrieb:

> Andrew Haley <andr...@littlepinkcloud.invalid> writes:
>
>> No. Non-PIC only works that way on x86 as a concession to old
>> libraries; x86_64 shared libraries are all PIC.
>
> Can you explain a bit more? Why is this a concession to old libs?

Because x86-32 is a PITA, PIC is complicated there and costs performance.
So in the beginning the general consensus was to generaly _not_ use PIC.

Only later PIC showed clear advantages, so the performance hit was "accepted",
PIC (at least for shared libs) became the default.
But now you had old, legacy, non-PIC libs.

> I thought PIC and non-PIC shared libs just come naturally from the way
> mmap() with MAP_PRIVATE works. My impression was that dlopen() simply
> mmap()s the shared object with MAP_PRIVATE, then does linking
> (eventually causing relocation) and that's it. If the shared lib is
> non-PIC, relocation will cause COW and the affected pages are not
> shared. For PIC, no relocation will be done and therefore no COW.
>
> Why doesn't it work the same way on other architectures? Is there
> special code to check for and to disallow non-PIC shared libs? And if
> so, why?

Because other archs simply do not allow non-PIC code "par ordre de mufti".

This has only a semi-technical reason:
On the bare metal you could use non-PIC code, do full relocations, break the COW
mapping, on nearly all HW, no problem with that. (It could be sometimes more
complicated than PIC, but on the technical level it would work).

But target specific features (ex: IP-Relative addressing, lots of register so
one can be spared for a base pointer, limited constant range in fixed size
instruction encodings so absolut addressing is expensive) make PIC easily
possible/the better option. For everything (libs and executable).

Not using PIC would be stupid.
Which lead to a prohibition of non-PIC code, by simply not defining/implementing
it (the object file format misses an easy way to get an absolut relocation).
It's in the ABI.

x86-64 finally supports IP-Relative addressing (but only a little bit "broken",
thanks to the x86-64 inventor AMD, not all x86-addressing modes work with %rip,
what where they thinking/drinking...).
The decision was that PIC should be clearly favoured and since x86-64 was a
clean slate, to remove non-PIC support on libs.
Unfortunatly the Arch still has both modes, so you can get .o in both flavours.

It's time to give your supplier of external Code a "hint" to always use PIC.
PIC/PIE is on x86-64 basically "for free".

> IMHO, a linker warning would be enough to warn about the
> fact, that the resulting shared lib is not really fully shared. But
> nevertheless it would be useful to be able to do this, e.g. if you
> have non-PIC code that you cannot recompile.
>
> urs

Greetings
Jan

--
Gravity is only an option

Andrew Haley

unread,
Feb 21, 2011, 3:34:09 PM2/21/11
to
Urs Thuermann <u...@isnogud.escape.de> wrote:
> Andrew Haley <andr...@littlepinkcloud.invalid> writes:
>
>> No. Non-PIC only works that way on x86 as a concession to old
>> libraries; x86_64 shared libraries are all PIC.
>
> Can you explain a bit more? Why is this a concession to old libs? I
> thought PIC and non-PIC shared libs just come naturally from the way
> mmap() with MAP_PRIVATE works. My impression was that dlopen() simply
> mmap()s the shared object with MAP_PRIVATE, then does linking
> (eventually causing relocation) and that's it. If the shared lib is
> non-PIC, relocation will cause COW and the affected pages are not
> shared. For PIC, no relocation will be done and therefore no COW.

Right, that's how it works on old x86.

> Why doesn't it work the same way on other architectures? Is there
> special code to check for and to disallow non-PIC shared libs?

The x86-64 system ABI simply has no provision for non-PIC shared
libraries. There is no code to relocate them in ld.so. There is no
copy-on-write.

Non-PIC shared libraries are almost always a performance loss, so
there's no point in supporting them. This is especially true of
x86-64 because it has better support for PIC than x86.

> And if so, why? IMHO, a linker warning would be enough to warn
> about the fact, that the resulting shared lib is not really fully
> shared. But nevertheless it would be useful to be able to do this,
> e.g. if you have non-PIC code that you cannot recompile.

Maybe, but pretty much everywhere outside old x86 people know that
this just isn't going to work. Linking a static library into a shared
library has never been a such a great idea either.

Andrew.

Urs Thuermann

unread,
Feb 22, 2011, 5:48:39 PM2/22/11
to
Jan Seiffert <nomail@invalid> writes:

> This has only a semi-technical reason:
> On the bare metal you could use non-PIC code, do full relocations,
> break the COW mapping, on nearly all HW, no problem with that. (It
> could be sometimes more complicated than PIC, but on the technical
> level it would work).
>
> But target specific features (ex: IP-Relative addressing, lots of
> register so one can be spared for a base pointer, limited constant
> range in fixed size instruction encodings so absolut addressing is
> expensive) make PIC easily possible/the better option. For
> everything (libs and executable).

Ah, thanks for your comprehensive explanation. Things are much
clearer now for me.

> Not using PIC would be stupid.
> Which lead to a prohibition of non-PIC code, by simply not
> defining/implementing it (the object file format misses an easy way
> to get an absolut relocation). It's in the ABI.

> The decision was that PIC should be clearly favoured and since


> x86-64 was a clean slate, to remove non-PIC support on libs.

Hm, then it would be consequent to make PIC the default for gcc.

> Unfortunatly the Arch still has both modes, so you can get .o in
> both flavours.

)-:

> It's time to give your supplier of external Code a "hint" to always
> use PIC. PIC/PIE is on x86-64 basically "for free".

Actually, I haven't yet looked into that code to see whether it's PIC
or non-PIC. Since it's delivered as a libfoo.a for static linking I
assumed it's non-PIC, also because that is the default code generation
of gcc. Since I'd like to use that code with dlopen(), I tried my
simple examples I showed in my first post.

Unfortunately, we are probably not in a position where we could ask
the supplier of that code to provide a PIC version. A nice hint to
always use PIC is the most we can do and then I am not sure if we
would get a new version.

urs

Urs Thuermann

unread,
Feb 22, 2011, 6:07:37 PM2/22/11
to
Andrew Haley <andr...@littlepinkcloud.invalid> writes:

> Maybe, but pretty much everywhere outside old x86 people know that
> this just isn't going to work. Linking a static library into a shared
> library has never been a such a great idea either.

Yes, I have always also thought that this wouldn't work. Then I had a
situation where I wished it would be possible and tried it on x86
which I currently use prevalently. First, I was surprised that it
work but then it felt quite natural, since I have linked non-PIC
object files to shared libs long before.

Copying the trials to x86_64 I found only PIC object can be linked
into shared libs. But if PIC on x86_64 is as easy and inexpensive as
Jan wrote, it should maybe the defaut. Then it would again be
possilbe to link every object file, be it a plain ELF relocatable or
an archive of such files, i.e. static lib, into shared libs.

urs

Andrew Haley

unread,
Feb 23, 2011, 5:39:56 AM2/23/11
to

I understand the argument, but gcc's default isn't going to change
now.

I don't think that PIC on x86_64 is completely zero cost: access to
statically allocated data, for example, costs more.

Like so:

main:
subq $8, %rsp
movq hello@GOTPCREL(%rip), %rsi
movq stderr@GOTPCREL(%rip), %rax
movq (%rax), %rdi
movl $0, %eax
call fprintf@PLT
...

instead of the non-PIC:

main:
subq $8, %rsp
movl $hello, %esi
movq stderr(%rip), %rdi
movl $0, %eax
call fprintf

But it's not a huge deal, and the benefits of PIC mostly outweigh
these isues.

For comparison, the 32-bit PIC code was really ugly:

main:
pushl %ebx
subl $8, %esp
call __i686.get_pc_thunk.bx
addl $_GLOBAL_OFFSET_TABLE_, %ebx
movl hello@GOT(%ebx), %eax
movl %eax, 4(%esp)
movl stderr@GOT(%ebx), %eax
movl (%eax), %eax
movl %eax, (%esp)
call fprintf@PLT
...

__i686.get_pc_thunk.bx:
movl (%esp), %ebx
ret

Andrew.

0 new messages