Running a native library on Android L. error: only position independent executables (PIE) are supported
Asked Answered
A

3

55

When I run native code on Android L (Nexus 5), I get the error.

error: only position independent executables (PIE) are supported.

The same code is executed correctly on my Samsung Galaxy S3 (Android 4.3).

Here is my Application.mk

APP_PROJECT_PATH := $(call my-dir)/..
APP_ABI := armeabi
NDK_TOOLCHAIN_VERSION := 4.7
APP_PLATFORM := android-9
APP_GNUSTL_FORCE_CPP_FEATURES := exceptions rtti

However when I replace APP_PLATFORM := android-9 with APP_PLATFORM := android-16 (As I read here, PIE support appeared in Jelly Been (API level 16)), the same executable file works fine on Android L.

Is there a way to compile native code using APP_PLATFORM := android-9 and run it on Android L?

Ambitious answered 18/7, 2014 at 6:54 Comment(0)
A
7

I built two executable files: one with APP_PLATFORM := android-9 and the other with APP_PLATFORM := android-16. To run the native code in Java I need this:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
    // Run the file which was created using APP_PLATFORM := android-16
} else {
    // Run the file which was created using APP_PLATFORM := android-9
}
Ambitious answered 22/7, 2014 at 6:18 Comment(3)
Can you provide some explanation on why this works? Is the android-16 the way to fix this?Gentilism
Did you verify this on Nexus 5?Rugg
I have Got this error /home/hfi/Downloads/android-ndk-r10d/platforms/android-16/arch-arm/usr/lib/crtbegin_dynamic.o:crtbrand.c:function _start: error: undefined reference to 'main' collect2: error: ld returned 1 exit status library.mak:106: recipe for target 'libavutil/libavutil-54.so' failed make: *** [libavutil/libavutil-54.so] Error 1Reganregard
V
51

If you can live with only supporting Android 4.1+, just set APP_PLATFORM := android-16 and you'll be good to go. Behind the scenes it sets APP_PIE := true. Your binary will segfault on older SDKs.

If you also need to support lower SDK levels, you'll need to create two binaries. Some other answers I've seen have recommended maintaining two separate source trees with different APP_PLATFORMs, but you don't need to do that. It's possible to make a single Android.mk output both a PIE and a non-PIE binary.

NDK 10c and later:

Make sure that PIE is disabled by default since enabling it manually is easier than disabling it. PIE doesn't get enabled by default unless your APP_PLATFORM is >=16. Make sure that your APP_PLATFORM is either not set (defaulting to android-3, or android-14 since NDK 15), lower than android-16, or set APP_PIE := false.

The following Android.mk then creates a PIE and a non-PIE binary, but has a caveat (see below):

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# Enable PIE manually. Will get reset on $(CLEAR_VARS). This
# is what enabling PIE translates to behind the scenes.
LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie

LOCAL_MODULE := mymod

LOCAL_SRC_FILES := \
    mymod.c

include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)

LOCAL_MODULE := mymod-nopie

LOCAL_SRC_FILES := \
    mymod.c

include $(BUILD_EXECUTABLE)

You'll then have to add some sort of logic to invoke the correct binary in your code.

Unfortunately, this means you'll have to compile the executable module twice, which can be slow. You also need to specify LOCAL_SRC_FILES and any libraries twice, which can be frustrating and difficult to keep track of. What you can do is to compile the main executable as a static library, and build executables from nothing but that static library. Static libraries do not require PIE.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := mymod-common

LOCAL_SRC_FILES := \
  mymod.c

include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)

# Enable PIE manually. Will get reset on $(CLEAR_VARS). This
# is what enabling PIE translates to behind the scenes.
LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie

LOCAL_MODULE := mymod

LOCAL_STATIC_LIBRARIES := mymod-common

include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)

LOCAL_MODULE := mymod-nopie

LOCAL_STATIC_LIBRARIES := mymod-common

include $(BUILD_EXECUTABLE)

This seems to work quite nicely, although a certain amount of boilerplate is still required.

NDK 10b:

NDK 10b enables PIE by default and doesn't let you disable it, except with terrible hacks. Really, just update to 10c. I'm leaving my old answer here for reference but I wouldn't recommend it to anyone.

LOCAL_PATH := $(call my-dir)

# Forcefully disable PIE globally. This makes it possible to
# build some binaries without PIE by adding the necessary flags
# manually. These will not get reset by $(CLEAR_VARS). PIE is
# force-enabled on NDK 10b so we'll need this even if APP_PIE
# is set to false.
TARGET_PIE := false
NDK_APP_PIE := false

include $(CLEAR_VARS)

# Enable PIE manually. Will get reset on $(CLEAR_VARS). This
# is what enabling PIE translates to behind the scenes.
LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie

LOCAL_MODULE := mymod

LOCAL_SRC_FILES := \
    mymod.c

include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)

LOCAL_MODULE := mymod-nopie

LOCAL_SRC_FILES := \
    mymod.c

include $(BUILD_EXECUTABLE)
Valued answered 17/10, 2014 at 10:17 Comment(8)
Can you explain the make file ? its hard to understandAeroscope
Do you mean that you're not familiar with Android.mk files, or that you're comfortable working with Android.mk files but this is especially hard to understand?Valued
Actually my Android.mk file has already some commands ...I just don't know If I have to add that at the beginning of the make file or at the end of the make file..I am just worried depending upon the place I add ...it'd affect.Aeroscope
If you use my recommended version, you should put your existing settings in the mymod-common section (and they should work as-is). Unless you're doing something special, you shouldn't need to touch anything after the BUILD_STATIC_LIBRARY line (except to change the module names).Valued
@SimoKinnunen: I'm getting this same PIE error when trying to execute commands in a console (Terminal IDE). No idea why... Do you know how I solve this problem? Using CY 12.0 (Android 5.0.2) in a Galaxy Tab Pro 8.4.Campney
So what exactly is the cause of this? Your solution is from a developer's standpoint. What about the end user?Copra
@chx101 The cause is a security measure that wasn't available earlier, and has now become mandatory after a few releases that supported both. As an end user you can't really do anything about it, the binaries need to be recompiled. If you have a PIE binary you may be able to run it on non-PIE versions by using the wrapper posted by Kevin in a separate answer.Valued
@SimoKinnunen Thanks. I looked at it and now I understand.Copra
T
14

The Chromium project released a wrapper that allows PIE binaries to run on pre-JB Android releases. Note that your PIE executable requires a few extra flags to make this work:

CFLAGS += -fvisibility=default -fPIE
LDFLAGS += -rdynamic -fPIE -pie

In my case, I was shipping ~2MB binaries for 3 architectures and did not want to add 6MB of uncompressed data to the APK just to continue supporting ICS. run_pie is extremely tiny (6-7kB) so it fit the bill.

run_pie should not be built with the PIE flags, and it should not be executed on Android 5.0+ (because, of course, non-PIE binaries are banned). Unfortunately it cannot be built statically because it needs to be linked with -ldl and NDK only provides a shared version of that library.

The Java side could look something like:

String dir = mContext.getFilesDir().getPath();
String command = dir + "/busybox netstat";
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
    command = dir + "/run_pie " + command;
}

where busybox is a PIE executable and lives in the app's private files directory.

See also: earlier discussions of this topic here and here.

Edit JFDee: In my case, I kept getting the error "dlopen() failed: Cannot load library" when running run_pie with my PIE executable. I had to explicitly set LD_LIBRARY_PATH to the directory the executable resided in, i.e. the current path.

In that case the amended example code line of the "run_pie" call would look like this:

...
    command = "LD_LIBRARY_PATH=. " + dir + "/run_pie " + command;
...
Tribune answered 29/12, 2014 at 17:45 Comment(0)
A
7

I built two executable files: one with APP_PLATFORM := android-9 and the other with APP_PLATFORM := android-16. To run the native code in Java I need this:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
    // Run the file which was created using APP_PLATFORM := android-16
} else {
    // Run the file which was created using APP_PLATFORM := android-9
}
Ambitious answered 22/7, 2014 at 6:18 Comment(3)
Can you provide some explanation on why this works? Is the android-16 the way to fix this?Gentilism
Did you verify this on Nexus 5?Rugg
I have Got this error /home/hfi/Downloads/android-ndk-r10d/platforms/android-16/arch-arm/usr/lib/crtbegin_dynamic.o:crtbrand.c:function _start: error: undefined reference to 'main' collect2: error: ld returned 1 exit status library.mak:106: recipe for target 'libavutil/libavutil-54.so' failed make: *** [libavutil/libavutil-54.so] Error 1Reganregard

© 2022 - 2024 — McMap. All rights reserved.