在Android L上运行本机库错误:仅支持位置独立可执行文件(PIE)

Mak*_*iev 52 android android-ndk

当我在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
Run Code Online (Sandbox Code Playgroud)

但是,当我替换APP_PLATFORM := android-9APP_PLATFORM := android-16(正如我在这里阅读,PIE支持出现在Jelly Been(API级别16)),相同的可执行文件在Android L上正常工作.

有没有办法APP_PLATFORM := android-9在Android L上使用并运行它来编译本机代码?

Sim*_*nen 49

如果您只能支持Android 4.1+,那么只需设置APP_PLATFORM := android-16即可.它背后的场景APP_PIE := true.您的二进制文件将在较旧的SDK上进行分段.

如果您还需要支持较低的SDK级别,则需要创建两个二进制文件.我见过的其他一些答案建议使用不同的APP_PLATFORMs维护两个独立的源树,但是您不需要这样做.可以使单个Android.mk输出PIE和非PIE二进制文件.

NDK 10c及更高版本:

确保默认情况下禁用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)
Run Code Online (Sandbox Code Playgroud)

然后,您必须添加某种逻辑来调用代码中的正确二进制文件.

不幸的是,这意味着你必须两次编译可执行模块,这可能很慢.您还需要两次指定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)
Run Code Online (Sandbox Code Playgroud)

虽然仍然需要一定量的样板,但这似乎工作得很好.

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)
Run Code Online (Sandbox Code Playgroud)


小智 14

Chromium项目发布了一个包装器,允许PIE二进制文件在JB之前的Android版本上运行.请注意,您的PIE可执行文件需要一些额外的标志才能使其工作:

CFLAGS += -fvisibility=default -fPIE
LDFLAGS += -rdynamic -fPIE -pie
Run Code Online (Sandbox Code Playgroud)

在我的情况下,我为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;
}
Run Code Online (Sandbox Code Playgroud)

哪里busybox是PIE可执行文件并存在于应用程序的私有文件目录中.

另请参阅:此处此处的此主题的早期讨论.

编辑JFDee:在我的情况下,当我使用PIE可执行文件运行run_pie时,我不断收到错误"dlopen()失败:无法加载库".我必须将LD_LIBRARY_PATH显式设置为可执行文件所在的目录,即当前路径.

在这种情况下,"run_pie"调用的修改示例代码行将如下所示:

...
    command = "LD_LIBRARY_PATH=. " + dir + "/run_pie " + command;
...
Run Code Online (Sandbox Code Playgroud)


Mak*_*iev 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
}
Run Code Online (Sandbox Code Playgroud)

  • 你能解释为什么会这样吗?android-16是解决这个问题的方法吗? (3认同)
  • 你有没有在Nexus 5上验证这个? (2认同)