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

AndroidAndroid Ndk

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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)

Solution 2 - Android

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;
...

Solution 3 - Android

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
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMaksim DmitrievView Question on Stackoverflow
Solution 1 - AndroidSimo KinnunenView Answer on Stackoverflow
Solution 2 - AndroidKevin CernekeeView Answer on Stackoverflow
Solution 3 - AndroidMaksim DmitrievView Answer on Stackoverflow