Bionic est une implémentation de la bibliothèque standard C , développée par Google pour son système d'exploitation Android . Elle se distingue de la bibliothèque GNU C (glibc) par sa conception adaptée aux appareils disposant de moins de mémoire et de puissance de traitement qu'un système Linux classique . Bionic est une combinaison de code original et de code issu de FreeBSD , NetBSD et OpenBSD , distribué sous licence BSD , contrairement à glibc qui utilise la licence publique générale limitée GNU (LGPL) . Cette différence était cruciale aux débuts d'Android, lorsque la liaison statique était courante. De plus, comme Bionic possède sa propre interface binaire d'application , son remplacement par une autre libc entraînerait des dysfonctionnements pour toutes les applications existantes.
Bionic est une bibliothèque C destinée au noyau Linux et fournit libc , libdl et libm ( la fonctionnalité libpthread est intégrée à libc et non fournie séparément comme sur certains autres systèmes). Cela diffère des bibliothèques C BSD avec lesquelles Bionic partage du code, car ces dernières requièrent un noyau BSD .
Objectifs initiaux
Les objectifs initiaux publiquement énoncés pour Bionic étaient les suivants :
- Licence BSD : Google souhaitait isoler les applications Android de l'effet des licences copyleft pour créer un espace utilisateur et un écosystème d'applications propriétaires , mais :
- Android est basé sur le noyau Linux, qui est soumis à la licence publique générale GNU (GPL) version 2.
- La bibliothèque C standard la plus répandue pour le noyau Linux est la bibliothèque GNU C (glibc), soumise à la licence publique générale limitée GNU (LGPL), également une licence copyleft. Contrairement à la GPL, la LGPL autorise explicitement la liaison dynamique, mais n'autorise pas la liaison statique de logiciels propriétaires sans fournir le code source ou les fichiers objets pouvant être liés.
- La licence BSD permissive est une licence non copyleft compatible dans les deux sens. Un substitut de glibc sous licence BSD pourrait servir de couche d'isolation entre le noyau copyleft et les applications non copyleft ; c'est pourquoi Google l'a choisi pour son système Bionic comme substitut de glibc.
- Taille réduite : Bionic était beaucoup plus petit que la bibliothèque GNU C ; plus important encore, ses besoins en mémoire étaient (et restent) beaucoup plus faibles.
- Vitesse : Bionic a été conçu pour des processeurs fonctionnant à des fréquences d'horloge relativement basses.
Architectures prises en charge
Bionic ne prend en charge que les noyaux Linux, mais est actuellement compatible avec les architectures arm, arm64, riscv64, x86 et x86-64 . La plateforme elle-même exigeait armv7 avec Neon depuis Marshmallow , bien que le kit de développement natif Android (NDK) ait continué à prendre en charge armv5 (appelé armeabi) jusqu'à la version r16. Le NDK prend toujours en charge armv7, mais la version r24 a abandonné la prise en charge des architectures non-Neon. Historiquement, la plateforme offrait une prise en charge partielle de SH-4 , mais aucun appareil n'a jamais été commercialisé et cette prise en charge a depuis été supprimée. Le NDK n'a jamais pris en charge SH-4, et la prise en charge de MIPS et MIPS64 a été supprimée du NDK dans la version r17.
Composants
Certaines parties du code source de la libc, comme stdio , proviennent des BSD (principalement OpenBSD ), tandis que d'autres, comme l' implémentation de pthread , ont été écrites à partir de zéro.
L' implémentation de l'allocateur de mémoire dynamique a évolué au fil du temps. Avant Lollipop, il n'existait qu'un seul allocateur de mémoire natif : dlmalloc de Doug Lea . Lollipop et Marshmallow ont vu l'émergence de deux implémentations : dlmalloc et jemalloc . jemalloc offre des performances nettement supérieures à dlmalloc, mais au prix d'une consommation de mémoire supplémentaire pour la gestion de la mémoire. La plupart des appareils utilisaient jemalloc, tandis que ceux disposant de peu de mémoire continuaient d'utiliser dlmalloc. De Nougat à Android 10 , tous les appareils utilisent jemalloc. Les appareils à faible mémoire bénéficient d'une configuration « allégée » de jemalloc, désactivant le cache de transfert (tcache) afin d'obtenir une consommation de mémoire proche de celle de dlmalloc, tout en conservant une grande partie de sa vitesse. Avec Android 11 , l'allocateur de mémoire de la plupart des appareils a été remplacé par jemalloc.Scudo sacrifie certaines des hautes performances de jemalloc au profit de fonctionnalités de sécurité renforcées. Les appareils à faible mémoire sont toutefois toujours autorisés à utiliser jemalloc.
Certains appareils 64 bits , comme le Nexus 9 , sont en réalité des appareils à faible capacité de mémoire en raison des besoins supplémentaires en espace liés aux pointeurs 64 bits et à l'hébergement de deux zygotes. ( Un zygote est un service système Android qui est le parent de tous les processus d'application Android. )
Le code source de libm provient en grande partie de FreeBSD , mais avec un assembleur optimisé fourni par les différents fournisseurs de SoC .
Le lien dynamique (et libdl) ont été écrits à partir de zéro.
Bionic n'inclut pas libthread_db (utilisée par gdbserver ), contrairement au NDK. La plateforme Android intégrait une version statique de gdbserver, permettant ainsi aux développeurs d'utiliser la dernière version de gdb même sur les anciens appareils. Android ayant abandonné la prise en charge de gdb au profit de lldb, cette fonctionnalité est désormais obsolète.
Android ne dispose pas de bibliothèques distinctes comme libpthread, libresolv ou librt ; toutes les fonctionnalités sont intégrées à libc. Concernant libpthread, aucune optimisation pour le mode monothread n'est effectuée, car les applications fonctionnent dans un environnement multithread avant même l'exécution de la première instruction de code tiers.
La plateforme Android utilise libc++ comme bibliothèque standard C++ (les versions jusqu'à Lollipop incluses utilisaient stlport). Historiquement, le NDK proposait stlport et GNU libstdc++, mais ces bibliothèques ont été supprimées à partir de la version r18 du NDK. Il est important de noter que si du code natif d'une application Android utilise du C++, tout le code C++ doit utiliser la même STL . La STL n'est pas fournie par le système d'exploitation Android et doit être intégrée à chaque application.
Différences par rapport à POSIX
Bien que Bionic vise à implémenter l'intégralité des normes C11 et POSIX , il manque encore (sous Android 15) une dizaine de fonctions POSIX dans la libc . Certaines fonctions POSIX, comme la famille endpwent/getpwent/setpwent, sont également inapplicables à Android, car ce dernier ne dispose pas de base de données passwd . Depuis Oreo , la libm est complète.
Certaines fonctions ne sont pas conformes aux normes POSIX ou C pour des raisons de sécurité, comme printf qui ne prend pas en charge la %nchaîne de format.
De nombreuses extensions GNU parmi les plus utilisées sont implémentées dans Bionic, de même que diverses extensions BSD.
Lien avec le NDK
Le code de la plateforme utilise directement Bionic, mais les développeurs tiers utilisent le kit de développement natif Android (NDK). De nombreux développeurs tiers ciblent encore d'anciennes versions du système d'exploitation, ce qui contribue à la croyance répandue que Bionic manque de nombreuses fonctionnalités. Gingerbread exportait 803 fonctions de la libc, tandis qu'Oreo en exporte 1278 (soit une augmentation de 1,6 fois).
Historiquement, le NDK et la plateforme ont divergé, mais le NDK r11 et les versions ultérieures ont remplacé les forks du NDK par leurs équivalents pour la plateforme actuelle. Ce travail s'est initialement concentré sur les compilateurs GCC et Clang .
Avant la version r14 du NDK, lorsque les en-têtes « unifiés » ont été proposés en option, le NDK utilisait des copies des en-têtes de plateforme pour différents niveaux d'API. De ce fait, les correctifs d'en-tête uniquement (par exemple, les corrections apportées aux définitions de constantes ou de structures) n'étaient pas accessibles à la plupart des utilisateurs du NDK, car ils ciblaient un niveau d'API plus ancien. En revanche, les correctifs de plateforme n'étaient intégrés qu'aux en-têtes de la plateforme actuelle. Lors du développement d'Oreo, les en-têtes de plateforme ont été annotés avec les informations de niveau d'API afin que le même ensemble d'en-têtes puisse être utilisé pour tous les niveaux d'API, seules les fonctions disponibles au niveau d'API ciblé par le développeur étant visibles. Ce sont ces en-têtes dits « unifiés », qui sont la configuration par défaut depuis la version r15 du NDK.
Avant la version r16 du NDK, ce dernier liait une bibliothèque nommée libandroid_support.a au code utilisant libc++ afin de fournir les fonctions requises par libc++ qui n'étaient pas présentes dans les anciennes versions du système d'exploitation. Ce code, différent de celui utilisé par la plateforme, a introduit de nombreux bogues (notamment la gestion incorrecte des arguments positionnels de la famille printf dans tout code utilisant libc++). De la version r16 à la version r25 du NDK, libandroid_support.a existait toujours, mais était compilée directement à partir du code source de la plateforme en vigueur lors de chaque compilation du NDK. Depuis la version r26 du NDK, libandroid_support.a a disparu, car toutes les versions du système d'exploitation encore prises en charge par le NDK intègrent toutes les fonctionnalités nécessaires à libc++.
Fortifier la source
Depuis Android Jelly Bean MR1 (4.2), Bionic prend en charge une fonctionnalité similaire à celle de glibc _FORTIFY_SOURCE[ : les fonctions non sécurisées de manipulation de chaînes de caractères et de mémoire (telles que `std ::vector`, `std::map` et `std map`) effectuent des vérifications de dépassement de tampon. Ces vérifications sont réalisées à la compilation si la taille des tampons peut être déterminée à ce moment-là, sinon à l'exécution. Comme fortify repose sur la prise en charge à l'exécution par libc, sa portabilité vers les anciennes versions d'Android est limitée . La plateforme elle-même est compilée avec fortify activé.strcpy()strcat()memcpy()_FORTIFY_SOURCE
Historiquement, l'un des inconvénients de fortify résidait dans sa forte dépendance à GCC, ce qui rendait sa prise en charge difficile avec d'autres compilateurs, comme Clang. De ce fait, lorsque Android a adopté Clang comme compilateur par défaut , l'implémentation de fortify de Bionic est devenue nettement moins performante. Dans Android Oreo (8.0), fortify a été entièrement repensé pour être compatible avec Clang, offrant ainsi une expérience équivalente à celle de fortify sur GCC. Depuis cette refonte, des vérifications supplémentaires ont été ajoutées à celles de glibc afin de détecter le code manifestement incorrect, même s'il ne provoque pas nécessairement un comportement indéfini. Cette nouvelle implémentation ne nécessitant pas plus de dépendances à libc que la précédente, les améliorations spécifiques à Clang sont disponibles pour les applications ciblant les versions d'Android antérieures à Oreo.
Controverses
Pour la création de Bionic, Google a utilisé des fichiers d'en-tête du noyau Linux sous licence GPLv2 . Afin de se soustraire à la GPL, Google a affirmé avoir nettoyé ces fichiers d'en-tête de toute œuvre protégeable par le droit d'auteur, les réduisant à de simples « faits » non protégeables. Linus Torvalds, le créateur de Linux, a jugé le comportement de Google acceptable, mais l'interprétation de la GPL par Google a été contestée, notamment par Raymond Nimmer, professeur de droit à la faculté de droit de l'université de Houston .