Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing functions from being stripped from a static library when linked into a shared library?

I have a static library, Foo, that is used by a shared library, Bar. Bar is the native shared library loaded by my Android app. Foo contains JNI functions that are only called by Java code and not by any C++ code in Bar. Because of this, those JNI functions get stripped out of the static library (Foo) when the shared library (Bar) is built. I'm currently using a slightly hacky method to prevent that from happening.

So, in this case, is there a way to tell the compiler to not strip the JNI (or any) functions out when linking?

like image 892
djcouchycouch Avatar asked Aug 23 '10 16:08

djcouchycouch


3 Answers

They're not getting stripped, they're getting ignored. When the shared library is linked, the linker is only pulling in the object files with functions that are actually used. (This is how static libs are defined to work.)

I believe passing the "--whole-archive" flag to the linker will cause it to pull in all object files from a static library. You can provide it on the gcc link line with "-Wl,-whole-archive". You need to follow it with "-Wl,-no-whole-archive" after specifying your library, or ld will continue the behavior for any other static libraries it encounters, which is likely not the behavior you want. See also the ld(1) man page on a Linux system.

Another way to accomplish the same thing is to output a single massive .o file instead of a .a file.

EDIT: Simple command-line example, using libz on the desktop:

% echo "int main() { return 0; }" > foo.c
% gcc -o foo /usr/lib/libz.a foo.c
% ls -s foo
12 foo*
% gcc -o foo -Wl,-whole-archive /usr/lib/libz.a -Wl,-no-whole-archive foo.c
% ls -s foo
104 foo*

(You have to use "/usr/lib/libz.a" instead of "-lz" here because the latter finds the shared library /usr/lib/libz.so.)

I haven't used the NDK much, but it looks like adding the flags to LOCAL_LDFLAGS might do the trick.

like image 75
fadden Avatar answered Oct 12 '22 13:10

fadden


Let's start with the basic two-libs sample from NDK. Here is its original Android.mk file:

 1 # Copyright (C) 2009 The Android Open Source Project
 2 #
 3 # Licensed under the Apache License, Version 2.0 (the "License");
 4 # you may not use this file except in compliance with the License.
 5 # You may obtain a copy of the License at
 6 #
 7 #      http://www.apache.org/licenses/LICENSE-2.0
 8 #
 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 
16 # the purpose of this sample is to demonstrate how one can
17 # generate two distinct shared libraries and have them both
18 # uploaded in
19 #
20 
21 LOCAL_PATH:= $(call my-dir)
22 
23 # first lib, which will be built statically
24 #
25 include $(CLEAR_VARS)
26
27 LOCAL_MODULE    := libtwolib-first
28 LOCAL_SRC_FILES := first.c
29 
30 include $(BUILD_STATIC_LIBRARY)
31 
32 # second lib, which will depend on and include the first one
33 #
34 include $(CLEAR_VARS)
35 
36 LOCAL_MODULE    := libtwolib-second
37 LOCAL_SRC_FILES := second.c
38 
39 LOCAL_STATIC_LIBRARIES := libtwolib-first
40 
41 include $(BUILD_SHARED_LIBRARY)

Note that the JNI function is located in second.c which is not part of the libtwolib-first static library.

First, let us reproduce your problem. The change is simple:

...
27 LOCAL_MODULE    := libtwolib-first
28 LOCAL_SRC_FILES := first.c second.c
...
36 LOCAL_MODULE    := libtwolib-second
37 LOCAL_SRC_FILES :=

If you run the modified project, you will get the following error:

E/AndroidRuntime( 4213): FATAL EXCEPTION: main
E/AndroidRuntime( 4213): java.lang.UnsatisfiedLinkError: add
E/AndroidRuntime( 4213):        at com.example.twolibs.TwoLibs.add(Native Method)
E/AndroidRuntime( 4213):        at com.example.twolibs.TwoLibs.onCreate(TwoLibs.java:39)
E/AndroidRuntime( 4213):        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
E/AndroidRuntime( 4213):        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
E/AndroidRuntime( 4213):        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
E/AndroidRuntime( 4213):        at android.app.ActivityThread.access$2300(ActivityThread.java:125)
E/AndroidRuntime( 4213):        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
E/AndroidRuntime( 4213):        at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 4213):        at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime( 4213):        at android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime( 4213):        at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 4213):        at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime( 4213):        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871)
E/AndroidRuntime( 4213):        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)
E/AndroidRuntime( 4213):        at dalvik.system.NativeStart.main(Native Method)

You explained this problem precisely: the linker "stripped out" the "unused" Java_com_example_twolibs_TwoLibs_add() entry.

Now let us fix this:

39 LOCAL_STATIC_LIBRARIES := libtwolib-first
39 LOCAL_WHOLE_STATIC_LIBRARIES := libtwolib-first

And again the sample works!

like image 43
Alex Cohn Avatar answered Oct 12 '22 13:10

Alex Cohn


For those using CMakeLists.txt, here's how you do it:

target_link_libraries(
        # The shared library name,
        Bar

        # Since Foo is a static library, only the symbols used in Bar are kept. But
        # we intend to use other symbols from the java side, hence, we instruct the
        # linker to grab the whole library,
        -Wl,-whole-archive
        Foo
        -Wl,-no-whole-archive

        # Other libraries needed, potentially including some static libraries that 
        # will be treated the normal way,
        android
        ${log-lib})
like image 1
Raslanove Avatar answered Oct 12 '22 13:10

Raslanove