web-dev-qa-db-fra.com

Exécution d’une bibliothèque native sur Android L. erreur: seuls les exécutables indépendants de la position (PIE) sont pris en charge

Lorsque je lance le code natif sur Android L (Nexus 5), je reçois le message d'erreur.

erreur: seuls les exécutables indépendants de la position (PIE) sont pris en charge.

Le même code est exécuté correctement sur mon Samsung Galaxy S3 (Android 4.3).

Voici mon 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

Cependant, quand je remplace APP_PLATFORM := Android-9 avec APP_PLATFORM := Android-16 _ (Comme je l'ai lu ici , le support PIE est apparu dans Jelly Been (API niveau 16)), le même fichier exécutable fonctionne correctement sur Android L.

Est-il possible de compiler du code natif en utilisant APP_PLATFORM := Android-9 et exécutez-le sur Android L?

51
Maksim Dmitriev

J'ai construit deux fichiers exécutables: un avec APP_PLATFORM := Android-9 et l'autre avec APP_PLATFORM := Android-16. Pour exécuter le code natif dans Java il me faut ceci:

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
}
7
Maksim Dmitriev

Si vous ne pouvez vivre qu'avec le support Android 4.1+, définissez simplement APP_PLATFORM := Android-16 et vous serez prêt à partir. Dans les coulisses, il met APP_PIE := true. Votre fichier binaire segfault sur les SDK plus anciens.

Si vous devez également prendre en charge des niveaux de SDK inférieurs, vous devez créer deux fichiers binaires. Certaines réponses que j'ai déjà vues recommandent de conserver deux arborescences sources distinctes avec des APP_PLATFORM différentes, mais vous n'avez pas besoin de le faire. Il est possible de créer une seule sortie Android.mk à la fois en binaire PIE et non binaire.

NDK 10c et plus tard:

Assurez-vous que PIE est désactivé par défaut car l'activer manuellement est plus facile que de le désactiver. PIE n'est activé par défaut que si votre APP_PLATFORM est> = 16. Assurez-vous que votre APP_PLATFORM n'est pas défini (par défaut sur Android-3 ou Android-14 depuis NDK 15), inférieur à Android-16 ou défini APP_PIE := false.

Le fichier Android.mk suivant crée ensuite un fichier PIE et un fichier binaire non-PIE, avec toutefois une mise en garde (voir ci-dessous):

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)

Vous devrez ensuite ajouter une sorte de logique pour appeler le binaire correct dans votre code.

Malheureusement, cela signifie que vous devrez compiler le module exécutable deux fois, ce qui peut être lent. Vous devez également spécifier deux fois LOCAL_SRC_FILES et toutes les bibliothèques, ce qui peut être frustrant et difficile à suivre. Ce que vous pouvez faire est de compiler l’exécutable principal en tant que bibliothèque statique et de créer des exécutables à partir de cette bibliothèque statique. Les bibliothèques statiques ne nécessitent pas de 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)

Cela semble fonctionner assez bien, même si une certaine quantité de passe-partout est encore nécessaire.

NDK 10b:

NDK 10b active PIE par défaut et ne vous permet pas de le désactiver, sauf avec des attaques terribles. Vraiment, il suffit de mettre à jour à 10c. Je laisse ici ma vieille réponse pour référence, mais je ne la recommanderais à personne.

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)
48
Simo Kinnunen

Le projet Chromium a publié un wrapper qui permet aux fichiers binaires PIE de s'exécuter sur des versions antérieures à JB Android. Notez que votre exécutable PIE nécessite quelques indicateurs supplémentaires pour que cela fonctionne:

CFLAGS += -fvisibility=default -fPIE
LDFLAGS += -rdynamic -fPIE -pie

Dans mon cas, je livrais des fichiers binaires de 2 Mo environ pour 3 architectures et je ne voulais pas ajouter 6 Mo de données non compressées à l'APK pour continuer à prendre en charge ICS. run_pie est extrêmement petit (6-7 ko) et convient donc parfaitement.

run_pie devrait pas être construit avec les drapeaux PIE, et il devrait ne pas être exécuté sur Android 5.0+ (car, bien entendu, les fichiers binaires non-PIE sont interdits). Il ne peut malheureusement pas être construit de manière statique car il doit être lié à -ldl et NDK fournit uniquement une version partagée de cette bibliothèque.

Le côté Java pourrait ressembler à ceci:

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 est un exécutable PIE et réside dans le répertoire de fichiers privé de l'application.

Voir aussi: discussions précédentes sur ce sujet ici et ici .

Edit JFDee: Dans mon cas, l’erreur "dlopen () échouait: impossible de charger la bibliothèque" lors de l’exécution de run_pie avec mon exécutable PIE. Je devais explicitement définir LD_LIBRARY_PATH sur le répertoire dans lequel se trouvait l’exécutable, c’est-à-dire le chemin actuel.

Dans ce cas, la ligne de code exemple modifiée de l'appel "run_pie" ressemblerait à ceci:

...
    command = "LD_LIBRARY_PATH=. " + dir + "/run_pie " + command;
...
14
Kevin Cernekee