当我在Android L(Nexus 5)上运行本机代码时,我收到错误。
错误:仅支持位置独立可执行文件(PIE)。
在我的Samsung 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-9
替换APP_PLATFORM := android-16
时(正如我读here,PIE支持出现在Jelly Been(API级别16)),相同的可执行文件在Android L上正常工作
有没有办法使用APP_PLATFORM := android-9
编译本机代码并在Android L上运行?
答案 0 :(得分:48)
如果你只能支持Android 4.1+,只需设置APP_PLATFORM := android-16
就可以了。在幕后设置APP_PIE := true
。您的二进制文件将在较旧的SDK上进行分段。
如果您还需要支持较低的SDK级别,则需要创建两个二进制文件。我见过的其他一些答案建议使用不同的APP_PLATFORMs维护两个独立的源树,但是您不需要这样做。可以使单个Android.mk输出PIE和非PIE二进制文件。
NDK 10c及更高版本:
确保默认情况下禁用PIE,因为手动启用PIE比禁用PIE更容易。默认情况下,PIE不会启用,除非您的APP_PLATFORM是> = 16。确保你的APP_PLATFORM没有设置(默认为android-3,或自NDK 15后的android-14),低于android-16,或设置APP_PIE := false
。
以下Android.mk然后创建一个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和任何库,这可能令人沮丧且难以跟踪。你可以做的是将主可执行文件编译为静态库,并从静态库中构建可执行文件。静态库不需要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)
答案 1 :(得分:12)
Chromium项目发布了wrapper,允许PIE二进制文件在JB之前的Android版本上运行。请注意,您的PIE可执行文件需要一些额外的标志才能使其工作:
CFLAGS += -fvisibility=default -fPIE
LDFLAGS += -rdynamic -fPIE -pie
在我的情况下,我为3个架构提供了大约2MB的二进制文件,并且不想向APK添加6MB的未压缩数据以继续支持ICS。 run_pie
非常小(6-7kB),因此符合要求。
run_pie
不应使用PIE标志构建,并且不应在Android 5.0+上执行(因为,当然,非PIE二进制文件被禁止)。不幸的是,它无法静态构建,因为它需要与-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:在我的情况下,我一直收到错误&#34; dlopen()失败:无法加载库&#34;使用我的PIE可执行文件运行run_pie时。我必须将LD_LIBRARY_PATH显式设置为可执行文件所在的目录,即当前路径。
在这种情况下修改的示例代码行&#34; run_pie&#34;电话会是这样的:
...
command = "LD_LIBRARY_PATH=. " + dir + "/run_pie " + command;
...
答案 2 :(得分:7)
我构建了两个可执行文件:一个使用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
}