在Android L.上运行本机库错误:仅支持与位置无关的可执行文件(PIE)
当我在Android L(Nexus 5)上运行本机代码时,出现错误。
错误:只支持与位置无关的可执行文件(PIE)。
我的三星Galaxy S3(Android 4.3)上正确执行相同的代码。
这是我的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
然而,当我用APP_PLATFORM := android-16
replaceAPP_PLATFORM := android-9
(正如我在这里阅读的,PIE支持出现在Jelly Been(API级别16))中,相同的可执行文件在Android L上正常工作。
有没有办法使用APP_PLATFORM := android-9
编译本机代码APP_PLATFORM := android-9
并在Android L上运行它?
如果你只能支持Android 4.1+,只需设置APP_PLATFORM := android-16
就可以了。 在幕后设置APP_PIE := true
。 您的二进制文件将在旧SDK上进行段错误。
如果您还需要支持较低的SDK级别,则需要创build两个二进制文件。 我见过的其他一些答案build议使用不同的APP_PLATFORM来维护两个独立的源代码树,但是您不需要这样做。 有可能使一个Android.mk输出一个PIE和一个非PIE二进制文件。
NDK 10c及更高版本:
确保PIE在默认情况下处于禁用状态,因为手动启用比禁用PIE更容易。 除非您的APP_PLATFORM大于等于16,否则PIE不会被默认启用。 确保你的APP_PLATFORM没有设置(默认为android-3,或者自NDK 15以来的android-14),比android-16低,或者设置APP_PIE := false
。
下面的Android.mk然后创build一个PIE和一个非PIE二进制文件, 但有一个警告(见下文) :
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)
然后,你必须添加一些逻辑来调用你的代码中的正确的二进制文件。
不幸的是,这意味着你将不得不编译可执行模块两次,这可能会很慢。 您还需要指定LOCAL_SRC_FILES和任何库两次,这可能是令人沮丧和难以跟踪。 你可以做的是将主要的可执行文件编译为一个静态库,并且从静态库中除了构build可执行文件之外什么也不做。 静态库不需要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)
这似乎很好地工作,虽然仍然需要一定的样板量。
NDK 10b:
NDK 10b默认启用PIE,不允许你禁用它,除非是可怕的黑客。 真的,只要更新到10C。 我将这个旧的答案留在这里供参考,但我不会推荐给任何人。
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)
Chromium项目发布了一个包装器 ,允许PIE二进制文件在JB之前的Android版本上运行。 请注意,您的PIE可执行文件需要一些额外的标志才能使其工作:
CFLAGS += -fvisibility=default -fPIE LDFLAGS += -rdynamic -fPIE -pie
在我的情况下,我为3个架构提供了大约2MB的二进制文件,并且不想为了继续支持ICS而将6MB的未压缩数据添加到APK中。 run_pie
是非常小的(6-7kB),所以它符合法案。
run_pie
不应该使用PIE标志来构build,它不应该在Android 5.0+上执行(因为当然,非PIE二进制文件是被禁止的)。 不幸的是,它不能被静态构build,因为它需要与-ldl
链接,NDK只提供该库的共享版本。
Java方面可能看起来像这样:
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; }
busybox
是PIE可执行文件,位于应用程序的私有文件目录中。
另请参阅: 此处和此处关于此主题的较早讨论。
编辑JFDee:在我的情况下,当我的PIE可执行文件运行run_pie时,我一直得到错误“dlopen()失败:无法加载库”。 我不得不明确地设置LD_LIBRARY_PATH到可执行文件所在的目录,即当前path。
在这种情况下,“run_pie”调用的修改示例代码行如下所示:
... command = "LD_LIBRARY_PATH=. " + dir + "/run_pie " + command; ...
我构build了两个可执行文件:一个带有APP_PLATFORM := android-9
,另一个带有APP_PLATFORM := android-16
。 要在Java中运行本地代码,我需要这个:
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 }