Stabilité de l'ABI

La stabilité de l'interface binaire d'application (ABI) est une condition préalable des mises à jour propres au framework, car les modules des fournisseurs peuvent dépendre de la couche des bibliothèques partagées du kit de développement (VNDK) qui se trouvent dans la partition système. Dans une version Android, les bibliothèques partagées VNDK nouvellement créées doivent être Compatible avec l'ABI avec les bibliothèques partagées VNDK précédemment publiées, afin que les modules des fournisseurs peuvent fonctionner avec ces bibliothèques sans recompilation et sans erreurs d'exécution. Entre les versions d'Android, les bibliothèques VNDK peuvent être modifiées et il n'y a pas d'ABI garanties.

Pour assurer la compatibilité avec les ABI, Android 9 inclut un vérificateur d'ABI d'en-tête, comme décrit dans les sections suivantes.

À propos du VNDK et de la conformité avec les ABI

Le VNDK est un ensemble restreint de bibliothèques que les modules des fournisseurs peuvent associer qui permettent d'effectuer des mises à jour propres au framework. La conformité de l'ABI fait référence aux la capacité d'une version plus récente d'une bibliothèque partagée à fonctionner comme prévu avec qui est lié dynamiquement (c'est-à-dire qui fonctionne comme une ancienne version du de la bibliothèque.

À propos des symboles exportés

Un symbole exporté (également appelé symbole global) désigne Un symbole qui remplit toutes les conditions suivantes:

  • Exportées par les en-têtes publics d'une bibliothèque partagée.
  • Cet élément figure dans la table .dynsym du fichier .so. correspondant à la bibliothèque partagée.
  • Présente une liaison FAIBLE ou GÉNÉRALE.
  • La visibilité est PAR DÉFAUT ou PROTÉGÉE.
  • L'index de section n'est pas UNDEFINED.
  • Le type est FUNC ou OBJECT.

Les en-têtes publics d'une bibliothèque partagée sont définis comme les en-têtes à la disposition d'autres bibliothèques/binaires via export_include_dirs, export_header_lib_headers export_static_lib_headers, export_shared_lib_headers et Attributs export_generated_headers dans Android.bp les définitions du module correspondant à la bibliothèque partagée.

À propos des types accessibles

Un type accessible est tout type C/C++ intégré ou défini par l'utilisateur accessibles directement ou indirectement par le biais d'un symbole exporté ET issu de l'exportation via des en-têtes publics. Par exemple, libfoo.so possède la fonction Foo, qui est un symbole exporté présent dans .dynsym. La bibliothèque libfoo.so inclut les suivantes:

toto_exported.h toto.private.h
typedef struct foo_private foo_private_t;

typedef struct foo {
  int m1;
  int *m2;
  foo_private_t *mPfoo;
} foo_t;

typedef struct bar {
  foo_t mfoo;
} bar_t;

bool Foo(int id, bar_t *bar_ptr);
typedef struct foo_private {
  int m1;
  float mbar;
} foo_private_t;
Android.bp
cc_library {
  name : libfoo,
  vendor_available: true,
  vndk {
    enabled : true,
  }
  srcs : ["src/*.cpp"],
  export_include_dirs : [
    "exported"
  ],
}
Table .dynsym
Num Value Size Type Bind Vis Ndx Name
1 0 0 FUNC GLOB DEF UND dlerror@libc
2 1ce0 20 FUNC GLOB DEF 12 Foo

Si l'on considère Foo, les types accessibles directement/indirects sont les suivants:

Type Description
bool Type renvoyé : Foo.
int Type du premier paramètre Foo.
bar_t * Type de deuxième paramètre Foo. À l'adresse bar_t *, bar_t est exporté via foo_exported.h.

bar_t contient un membre mfoo, de type foo_t, qui est exportée via foo_exported.h ce qui entraîne l'exportation de types supplémentaires:
  • int : est le type de m1.
  • int * : est le type de m2.
  • foo_private_t * : est le type de mPfoo.

Toutefois, foo_private_t n'est PAS accessible, car il n'est pas accessible. exportés via foo_exported.h. (foo_private_t *) est opaque. Par conséquent, les modifications apportées à foo_private_t sont autorisées.)

Une explication similaire peut être donnée pour les types accessibles via la classe de base. des spécificateurs et des paramètres de modèle.

Assurer la conformité de l'ABI

La conformité de l'ABI doit être garantie pour les bibliothèques marquées vendor_available: true et vndk.enabled: true dans le les fichiers Android.bp correspondants. Exemple :

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}

Pour les types de données accessibles directement ou indirectement par une fonction exportée, le Les modifications suivantes apportées à une bibliothèque sont considérées comme destructives pour l'ABI:

Type de données Description
Structures et classes
  • Modifiez la taille du type de classe ou de structure.
  • Classes de base <ph type="x-smartling-placeholder">
      </ph>
    • Ajoutez ou supprimez des classes de base.
    • Ajoutez ou supprimez des classes de base héritées virtuellement.
    • Modifiez l'ordre des classes de base.
  • Fonctions des membres <ph type="x-smartling-placeholder">
      </ph>
    • Supprimez les fonctions membres*.
    • Ajoutez des arguments à des fonctions membres ou supprimez-en.
    • Modifier les types d'argument ou les types renvoyés pour fonctions*.
    • Modifiez la mise en page du tableau virtuel.
  • Membres des données <ph type="x-smartling-placeholder">
      </ph>
    • Supprimer les membres des données statiques.
    • Ajoutez ou supprimez des membres de données non statiques.
    • Modifiez les types de membres des données.
    • Modifiez les décalages au niveau des membres de données non statiques**.
    • Modifiez const, volatile et/ou restricted qualificatifs de membres de données***.
    • Rétrograder les spécificateurs d'accès des membres de données***.
  • Modifiez les arguments du modèle.
Union
  • Ajoutez ou supprimez des membres de données.
  • Modifiez la taille du type d'union.
  • Modifiez les types de membres des données.
Énumérations
  • modifier le type sous-jacent.
  • Modifiez les noms des énumérateurs.
  • Modifiez les valeurs des énumérateurs.
Symboles globaux
  • Supprimez les symboles exportés par les en-têtes publics.
  • Pour les symboles globaux de type FUNC <ph type="x-smartling-placeholder">
      </ph>
    • Ajoutez ou supprimez des arguments.
    • Modifiez les types d'arguments.
    • Modifiez le type renvoyé.
    • Rétrograder le spécificateur d'accès***.
  • Pour les symboles globaux de type OBJECT <ph type="x-smartling-placeholder">
      </ph>
    • Modifier le type C/C++ correspondant.
    • Rétrograder le spécificateur d'accès***.

* Les fonctions de membres publics et privés doivent toutes deux ne peuvent pas être modifiées ni supprimées, car les fonctions intégrées publiques peuvent faire référence les fonctions de membre privé. Les références de symboles à des fonctions de membres privés peuvent être conservés dans les binaires de l'appelant. Modifier ou supprimer des fonctions de membres privés des bibliothèques partagées peut donner lieu à des binaires incompatibles avec les versions antérieures.

** Les décalages accordés aux membres de données publiques ou privées ne doivent pas être car les fonctions intégrées peuvent faire référence à ces membres de données dans le corps de la fonction. La modification des décalages des membres des données peut entraîner binaires incompatibles avec les versions antérieures.

*** Bien que celles-ci ne modifient pas l'organisation de la mémoire , il existe des différences sémantiques qui pourraient empêcher les bibliothèques fonctionne comme prévu.

Utiliser les outils de conformité de l'ABI

Lorsqu'une bibliothèque VNDK est créée, son ABI est comparée à référence d'ABI correspondante pour la version du VNDK en cours de compilation. Références Les fichiers de vidage ABI se trouvent dans:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based

Par exemple, si vous créez libfoo pour x86 au niveau d'API 27, L'ABI déduite de libfoo est comparée à sa référence à l'emplacement suivant:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

Erreur de faille de l'ABI

En cas de défaillance de l'ABI, le journal de compilation affiche des avertissements indiquant le type d'avertissement au rapport abi-diff. Par exemple, si l'ABI de libbinder a une modification incompatible, le système de compilation génère une erreur avec un message semblable à ce qui suit:

*****************************************************
error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES
Please check compatibility report at:
out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff
******************************************************
---- Please update abi references by running
platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ----

Créer des vérifications d'ABI de la bibliothèque VNDK

Lorsqu'une bibliothèque VNDK est créée:

  1. header-abi-dumper traite les fichiers sources compilés dans créer la bibliothèque VNDK (les fichiers sources de la bibliothèque ainsi que ses fichiers sources) ; (héritées via des dépendances transitives statiques) pour produire Fichiers .sdump correspondant à chaque source.
    création de sdump
    Figure 1. Créer le .sdump fichiers
  2. header-abi-linker traite ensuite .sdump. (à l'aide d'un script de version qui lui est fourni ou de la .so fichier correspondant à la bibliothèque partagée) pour générer un .lsdump qui consigne toutes les informations d'ABI correspondant à la bibliothèque partagée.
    Création de lsdump
    Figure 2. Créer le .lsdump fichier
  3. header-abi-diff compare .lsdump fichier avec un fichier de référence .lsdump pour générer un rapport de différences qui décrit les différences entre les ABI des deux bibliothèques.
    création de diff Abi Diff
    Figure 3. Créer le rapport de différences

en-tête-abi-dumper

L'outil header-abi-dumper analyse un fichier source C/C++ et effectue les vidages l'ABI déduite de ce fichier source dans un fichier intermédiaire. La compilation le système exécute header-abi-dumper sur tous les fichiers sources compilés, tout en une bibliothèque qui inclut les fichiers sources les dépendances.

Entrées
  • Un fichier source C/C++
  • Répertoires "Inclure" exportés
  • Options de compilateur
Sortie Un fichier qui décrit l'ABI du fichier source (par exemple, foo.sdump représente l'ABI de foo.cpp.

Actuellement, les fichiers .sdump sont au format JSON, ce qui n'est pas le cas d'être stable dans les prochaines versions. À ce titre, .sdump le formatage du fichier doit être considéré comme un détail d'implémentation du système de compilation.

Par exemple, libfoo.so contient le fichier source suivant : foo.cpp:

#include <stdio.h>
#include <foo_exported.h>

bool Foo(int id, bar_t *bar_ptr) {
    if (id > 0 && bar_ptr->mfoo.m1 > 0) {
        return true;
    }
    return false;
}

Vous pouvez utiliser header-abi-dumper pour générer une classe intermédiaire Fichier .sdump qui représente l'ABI présentée par le fichier source avec:

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++

Cette commande indique à header-abi-dumper d'analyser foo.cpp avec les indicateurs de compilation qui suivent -- ; et émettre les informations sur l'ABI exportées par les en-têtes publics dans exported. Ce qui suit est foo.sdump généré par header-abi-dumper:

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" : [],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

foo.sdump contient les informations sur l'ABI exportées par le fichier source foo.cpp et les en-têtes publics, par exemple,

  • record_types Faire référence aux structures, unions ou classes définis dans les en-têtes publics. Chaque type d'enregistrement contient des informations sur ses champs, ses le spécificateur d'accès, le fichier d'en-tête dans lequel il est défini et d'autres .
  • pointer_types Faire référence aux types de pointeur directement/indirectement référencés par les enregistrements/fonctions exportés dans les en-têtes publics, ainsi que avec le type vers lequel pointe le pointeur (via le referenced_type dans type_info). Des informations similaires sont enregistrées Fichier .sdump pour les types qualifiés, types C/C++ intégrés, tableau et les types de référence lvalue et rvalue. De telles informations permettent diffing récursive.
  • functions Représente les fonctions exportées par des en-têtes publics. Ils contiennent également des informations sur le nom déformé de la fonction, le type renvoyé, les types de paramètres, le spécificateur d'accès et d'autres attributs.
<ph type="x-smartling-placeholder">

en-tête-abi-linker

L'outil header-abi-linker récupère les fichiers intermédiaires générés par header-abi-dumper en entrée, puis associe ces fichiers:

Entrées
  • Fichiers intermédiaires produits par header-abi-dumper
  • Script de version/Fichier de carte (facultatif)
  • .so fichier de la bibliothèque partagée
  • Répertoires "Inclure" exportés
Sortie Un fichier qui décrit l'ABI d'une bibliothèque partagée (par exemple, libfoo.so.lsdump représente l'ABI de libfoo.

L'outil fusionne les graphiques de type dans tous les fichiers intermédiaires qui lui sont fournis, en tenant compte de la définition unique (des types définis par l'utilisateur d'unités de traduction portant le même nom complet, peuvent être sémantiquement différentes) selon les unités de traduction. L'outil analyse ensuite Un script de version ou la table .dynsym de la bibliothèque partagée (fichier .so) pour créer la liste des symboles exportés.

Par exemple, libfoo est composé des éléments foo.cpp et bar.cpp header-abi-linker pourrait être appelé pour Créez le vidage complet de l'ABI associé de libfoo comme suit:

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current

Exemple de résultat de la commande dans libfoo.so.lsdump:

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 1,
   "is_integral" : true,
   "is_unsigned" : true,
   "linker_set_key" : "_ZTIb",
   "name" : "bool",
   "referenced_type" : "_ZTIb",
   "self_type" : "_ZTIb",
   "size" : 1
  },
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" :
 [
  {
   "name" : "_Z3FooiP3bar"
  },
  {
   "name" : "_Z6FooBadiP3foo"
  }
 ],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "Foo",
   "linker_set_key" : "_Z3FooiP3bar",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3bar"
    }
   ],
   "return_type" : "_ZTIb",
   "source_file" : "exported/foo_exported.h"
  },
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3bar",
   "name" : "bar *",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTIP3bar",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

L'outil header-abi-linker:

  • Associe les fichiers .sdump qui lui sont fournis (foo.sdump et bar.sdump), en filtrant les informations sur l'ABI qui ne sont pas présentes dans les en-têtes résidant dans le répertoire: exported.
  • Analyse libfoo.so et collecte des informations sur les symboles exporté par la bibliothèque via sa table .dynsym.
  • Ajout de _Z3FooiP3bar et _Z6FooBadiP3foo.

libfoo.so.lsdump est le vidage final de l'ABI généré pour libfoo.so

en-tête-abi-diff

L'outil header-abi-diff compare deux fichiers .lsdump. représentant l'ABI de deux bibliothèques et génère un rapport de différences indiquant le entre les deux ABI.

Entrées
  • Fichier .lsdump représentant l'ABI d'un ancien fichier partagé bibliothèque.
  • Fichier .lsdump représentant l'ABI d'une nouvelle bibliothèque partagée.
Sortie Rapport de différences indiquant les différences entre les ABI proposées par les deux les bibliothèques partagées.

Le fichier de différences d'ABI se trouve <ph type="x-smartling-placeholder"></ph> protobuf text format. Ce format est susceptible d'être modifié. dans les prochaines versions.

Par exemple, vous avez deux versions de libfoo: libfoo_old.so et libfoo_new.so Dans libfoo_new.so, dans bar_t, vous remplacez le type de mfoo foo_t à foo_t *. bar_t étant un accessible, il doit être signalé comme une modification destructive de l'ABI header-abi-diff

Pour exécuter header-abi-diff:

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo

Exemple de résultat de la commande dans libfoo.so.abidiff:

lib_name: "libfoo"
arch: "arm64"
record_type_diffs {
  name: "bar"
  type_stack: "Foo-> bar *->bar "
  type_info_diff {
    old_type_info {
      size: 24
      alignment: 8
    }
    new_type_info {
      size: 8
      alignment: 8
    }
  }
  fields_diff {
    old_field {
      referenced_type: "foo"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
    new_field {
      referenced_type: "foo *"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
  }
}

libfoo.so.abidiff contient un rapport sur toutes les ruptures de l'ABI modifications dans libfoo. Message record_type_diffs indique qu'un enregistrement a été modifié et répertorie les modifications incompatibles, ce qui incluent:

  • La taille de l'enregistrement passe de 24 octets à 8 octets.
  • Le type de champ de mfoo passe de foo à foo * (toutes les définitions de type sont supprimées).

Le champ type_stack indique comment header-abi-diff a atteint le type qui a été modifié (bar). Ce champ peut être interprétée comme Foo est une fonction exportée qui reçoit bar * comme paramètre, qui pointe vers bar, qui était exportées et modifiées.

<ph type="x-smartling-placeholder">

Appliquer l'ABI et l'API

Pour appliquer l'ABI et l'API des bibliothèques partagées du VNDK, les références d'ABI doivent être enregistré dans ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/. Pour créer ces références, exécutez la commande suivante:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py

Après avoir créé les références, toute modification apportée au code source qui entraîne Une modification incompatible d'ABI ou d'API dans une bibliothèque VNDK entraîne désormais une erreur de compilation.

Pour mettre à jour les références d'ABI pour des bibliothèques spécifiques, exécutez la commande suivante:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>

Par exemple, pour mettre à jour les références d'ABI libbinder, exécutez la commande suivante:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder