When I run native code on Android L (Nexus 5), I get the error.
<error: only position independent executables (PIE) are supported.
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
}
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;
...
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)