AirBuild
Ingénierie

Le premier App Store, réinventé avec le développement par IA.

En 2008, l’iPhone 3G et le premier App Store ont fait découvrir les apps mobiles à de nombreuses personnes. Nous avons créé AirBuild3G pour donner à ce téléphone une nouvelle fonction. Sur un iPhone 3G ou 3GS, vous pouvez décrire une nouvelle app, travailler sur son code source avec un modèle de programmation, puis la compiler et l’exécuter sur le même téléphone. Le modèle s’exécute sur le service de votre choix. Les fichiers de votre projet et les outils de développement restent sur le téléphone.

AirBuild3G est créé avec Xcode 27 sur un Mac. Les apps que vous créez avec cet outil utilisent un compilateur distinct, installé sur le téléphone. Pour faire fonctionner les deux, nous avons dû intervenir dans quatre domaines : la préparation de l’appareil, l’édition de liens, HTTPS et la chaîne d’outils du téléphone. Les sections suivantes retracent ce travail jusqu’à votre première app.

Préparer le matériel d’origine

Son petit écran et son bouton principal physique rendent l’iPhone 3G immédiatement reconnaissable. Créer de nouveau pour cet appareil implique de respecter ses proportions et ses limites d’origine, jusque dans les commandes qui donnaient à ses apps leur aspect familier. AirBuild3G utilise les commandes UIKit disponibles sous iOS 4, notamment les tableaux de réglages groupés et les actions de la barre de navigation.

AirBuild3G fonctionne sur l’iPhone 3G et 3GS. Le 3G nécessite des binaires armv6 ; le 3GS utilise armv7. Le programme interne détermine le paquet d’environnement que chaque téléphone peut installer. Notre appareil armv6 de référence est un iPhone 3G sous iOS 4.2.1 avec un programme interne personnalisé.

  1. Préparerrestaurer / activer
  2. ConnecterSSH + réseau
  3. Installerapp + paquet d’environnement
  4. EnvironnementRéglages → Environnement
La configuration se déroule en quatre étapes : préparez le téléphone, connectez-vous à celui-ci, installez AirBuild3G et le paquet d’environnement, puis touchez Installer dans Réglages → Environnement.

Commencez avec un téléphone qui démarre sur son écran d’accueil et accepte les connexions SSH depuis votre Mac. Il lui faut une connexion réseau et un gestionnaire de paquets fonctionnels pour recevoir et installer les paquets Debian. Son modèle matériel et la version de son programme interne déterminent les paquets à envoyer, tandis que son adresse réseau détermine leur destination. Il vous faut également un câble USB fiable et une source d’alimentation.

Certains téléphones plus anciens nécessitent des étapes supplémentaires pour l’activation et l’accès au système. Pour le matériel pris en charge, utilisez Legacy iOS Kit et suivez les instructions correspondant au modèle et au programme interne de votre téléphone. Sauvegardez le téléphone avant de commencer. AirBuild3G n’active pas lui-même le téléphone.

AirBuild3G a également besoin de Substrate pour sa prise en charge de TLS. Sur un 3G, le build doit inclure armv6 : un paquet armv7 uniquement ne peut pas y fonctionner, quelle que soit sa version. Un téléphone préparé avec Legacy iOS Kit peut déjà disposer d’un build compatible. Si Substrate fonctionne, conservez-le tel quel.

Réglages → Environnement suit la même règle. Cette option ajoute les outils de développement sans modifier le gestionnaire de paquets, le service SSH ni les autres composants de base qui fonctionnent déjà. Une mise à niveau générale de ces composants peut installer des binaires que l’ancien système ne peut pas exécuter.

Créer avec Xcode 27

Le compilateur de Xcode 27 produit encore du code armv6 et armv7 et accepte iOS 4.0 comme cible de déploiement. Xcode ne fournit que le SDK d’iOS 27, nous compilons donc à partir des en-têtes d’un ancien SDK. Passez le chemin du SDK dans LEGACY_SDK sur la ligne de commande make, ou exportez-le. Les scripts qui créent les paquets d’environnement le lisent également, et une valeur définie uniquement dans le Makefile ne leur parvient jamais.

L’éditeur de liens est plus strict. Il refuse armv6 comme cible d’édition de liens, toute cible de déploiement antérieure à iOS 4.3 et signale que les bibliothèques de l’ancien SDK ont été créées pour une plateforme inconnue. Pour ces bibliothèques, nous effectuons plutôt l’édition de liens avec des stubs générés et conservons l’ancien SDK pour les en-têtes.

  1. Compiléclang
    arch
    armv6
    iOS min.
    4.0
    code
    e92d40f0 …
  2. Liéréétiqueter, puis ld
    arch
    armv6armv7
    iOS min.
    4.05.0
    code
    e92d40f0 …
  3. Distribuérétablir l’étiquette, corriger
    arch
    armv7armv6
    iOS min.
    5.04.0
    code
    e92d40f0 …
    make verify
Clang produit les instructions armv6. Avant et après l’édition de liens, le build ne réécrit que les métadonnées, puis vérifie le résultat.

Le build compile les deux tranches pour iOS 4.0, effectue leur édition de liens pour iOS 5.0, puis ramène leur version minimale à 4.0. Pour armv6, il réétiquette également chaque fichier objet en armv7 afin que l’éditeur de liens l’accepte, puis réétiquette la tranche liée en armv6. Le réétiquetage modifie les métadonnées, pas les instructions. Le code compilé et les stubs de symboles ajoutés par l’éditeur de liens doivent déjà fonctionner sur armv6.

tools/relabel_arch.py
MH_MAGIC = 0xFEEDFACE
SUBTYPES = {"armv6": 6, "armv7": 9}

def main():
    …
    path, want = sys.argv[1], SUBTYPES[sys.argv[2]]

    with open(path, "rb") as f:
        buf = bytearray(f.read())

    if struct.unpack_from("<I", buf, 0)[0] != MH_MAGIC:
        …

    struct.pack_into("<i", buf, 8, want)
    with open(path, "wb") as f:
        f.write(buf)
  1. Le cpusubtype de Mach-O pour chaque architecture : 6 correspond à armv6, 9 à armv7.
  2. Tout le réétiquetage tient dans quatre octets au décalage 8, dans le champ cpusubtype de l’en-tête. Les instructions restent intactes.
Le build exécute ce script sur chaque objet armv6 avant l’édition de liens, puis sur la tranche liée.

Le build conserve -no_pie, car iOS ne disposait pas d’ASLR avant 4.3. Il désactive également la fusion des catégories Objective-C, qui supprimerait sinon le bit Thumb des méthodes fusionnées et ferait planter AirBuild3G sur un 3GS. make verify vérifie les architectures, les commandes de chargement, les instructions armv6 et le bit Thumb des méthodes armv7. Une édition de liens réussie ne prouve rien de tout cela.

Exécutez make pour créer AirBuild3G et son utilitaire privilégié. make payload télécharge et vérifie chaque paquet épinglé dans un cache local, tandis que make package crée hors ligne le paquet d’AirBuild3G et les deux paquets d’environnement à partir de ce cache. La mise en paquet nécessite aussi LEGACY_SDK, car chaque paquet d’environnement contient une archive du SDK destinée au téléphone.

make install-device DEVICE=root@<ip> installe AirBuild3G et le paquet d’environnement iOS 4, actualise l’écran d’accueil et redémarre l’app. Pour un 3GS sous iOS 5 ou version ultérieure, passez plutôt ios6 comme troisième argument à tools/deploy_device.sh. L’app s’exécute sous l’utilisateur mobile, et un petit utilitaire placé à côté exécute tout ce qui nécessite root. Le paquet d’environnement se trouve maintenant sur le téléphone ; Réglages → Environnement installe les outils de développement qu’il contient.

Se connecter à un service de modèles

Dans Réglages, vous saisissez trois valeurs sous Point de terminaison : URL de base, clé API et modèle. Les requêtes partent directement du téléphone vers ce point de terminaison. Nous n’exécutons aucun serveur intermédiaire et il n’y a ni compte ni enregistrement de l’appareil. Votre service de modèles exécute le modèle, facture son utilisation et vous indique les valeurs à saisir. Si nous vous avons offert votre téléphone, il se connecte plutôt à air.build.

La version d’origine d’iOS 4 ne prend en charge que TLS 1.0 par l’intermédiaire de SecureTransport, tandis qu’un point de terminaison actuel peut exiger TLS 1.2. Nous ajoutons TLS 1.2 avec TLSFix, une extension Substrate pour CFNetwork.

Le téléphone doit encore vérifier l’identité du serveur. Son ancien magasin de confiance ne parvient pas toujours à construire la chaîne de certificats utilisée par un point de terminaison actuel, et l’ajout de TLS 1.2 ne met pas ce magasin à jour. Lorsqu’une connexion échoue, traitez le chiffrement et la confiance accordée au certificat comme deux problèmes distincts.

AirBuild3G valide les certificats par défaut. Si une requête échoue, vérifiez d’abord l’URL de base, la clé API, le modèle et la chaîne de certificats. Le réglage Faire confiance à tous les certificats constitue une exception explicite. Son activation supprime une vérification de l’identité du serveur et peut exposer votre clé API à toute personne capable d’usurper le point de terminaison. Laissez-le désactivé, sauf si le point de terminaison se trouve sur votre propre réseau. AirBuild3G lit ce réglage à chaque évaluation de confiance, sa désactivation s’applique donc dès la requête suivante.

src/ABLTrustAll.m
static OSStatus ABLSecTrustEvaluate(SecTrustRef trust, SecTrustResultType *result) {
	…
	OSStatus status = ABLOriginalSecTrustEvaluate(trust, result);
	if (!ABLTrustAnyCertificate()) {
		return status;
	}
	if (result != NULL) {
		*result = kSecTrustResultUnspecified;
	}
	return errSecSuccess;
}
  1. L’évaluation du téléphone s’exécute en premier.
  2. Faire confiance à tous les certificats est désactivé par défaut, cette réponse est donc renvoyée sans modification. Le réglage est lu dans NSUserDefaults à chaque appel.
Le hook sur SecTrustEvaluate renvoie la réponse du téléphone, sauf si Faire confiance à tous les certificats est activé.

La diffusion d’une réponse doit aussi respecter les limites du téléphone. AirBuild3G reçoit et décode le flux sur un thread en arrière-plan, puis ne transmet à l’interface que le texte terminé. La transcription s’actualise à intervalles réguliers au lieu d’être remesurée et de défiler à chaque token, ce qui limite le travail de mise en page pendant l’arrivée de la réponse. Une réponse confirme que la connexion fonctionne. La compilation du code écrit par le modèle nécessite toujours l’environnement sur le téléphone.

Installer un compilateur que le téléphone peut exécuter

Réglages → Environnement installe le compilateur, les en-têtes du SDK, les bibliothèques d’édition de liens et les outils de signature du paquet d’environnement. Le paquet iOS 4 contient GCC pour armv6 ; celui pour iOS 5 et les versions ultérieures contient clang pour armv7. Chaque téléphone installe uniquement celui qui correspond à son programme interne.

Chaque paquet est épinglé par son adresse de téléchargement et sa somme de contrôle. L’installation utilise ces fichiers précis au lieu de demander les dernières versions à un dépôt. Les paquets déjà installés dans la version épinglée sont ignorés, une deuxième exécution d’Environnement se termine donc immédiatement. Les paquets installés sont ensuite bloqués afin que Cydia cesse de proposer des mises à niveau qui casseraient la chaîne d’outils.

Pour l’iPhone 3G, l’environnement utilise le build 2008 d’iphone-gcc 4.2. Le build 2009 réserve un gigaoctet aux en-têtes précompilés, ce qui en place une partie hors de l’espace d’adressage d’un téléphone armv6. Ses moteurs de compilation plantent avant de compiler quoi que ce soit. Les deux builds s’identifient comme Apple build 5555, mais seul celui de 2008 tient dans cet espace d’adressage.

limite armv60x30000000

2008 iphone-gcc · chaque segment sous 6 MBfonctionne

tableau de 512 MB · binaire de testfonctionne

tableau de 768 MB · binaire de testSIGBUS

2009 iphone-gcc · zone d’en-têtes précompilés de 1 GBSIGBUS

Le compilateur de 2008 reste sous la limite armv6 ; le build 2009 échoue avant main, car sa zone réservée la dépasse. Les tableaux de test de 512 MB et 768 MB se placent de part et d’autre de la limite.

Les en-têtes du SDK doivent aussi être modifiés : ce GCC ne peut pas analyser leurs déclarations de blocs. Avant de mettre en paquet les en-têtes d’iOS 4, nous supprimons ces déclarations, ainsi que toutes celles qui dépendent d’un type supprimé. Le paquet clang conserve les en-têtes d’Apple sans modification, car clang prend en charge les blocs. Chaque paquet fournit également un outil de signature capable de signer ce que son compilateur produit.

Le téléphone conserve ses frameworks système dans le cache partagé de dyld, les répertoires des frameworks ne contiennent donc aucun fichier de bibliothèque que l’éditeur de liens puisse lire. Nous fournissons des stubs générés qui contiennent les symboles exportés et les vrais noms des bibliothèques. L’éditeur de liens résout chaque référence à l’aide d’un stub et enregistre la bibliothèque dont elle provient ; au lancement, dyld la lie à l’implémentation réelle dans le cache partagé. Comme les stubs contiennent les vrais noms des symboles, l’omission d’un framework fait échouer l’édition de liens au lieu de faire planter l’app au lancement.

L’environnement définit également les chemins d’inclusion, fait pointer gcc et cc vers le compilateur installé et exporte les indicateurs d’édition de liens qui intègrent l’environnement d’exécution Objective-C et CoreFoundation. Le Makefile fourni lit ces réglages, un nouveau projet ne nécessite donc aucune configuration propre de la chaîne d’outils. Dès lors, vos apps se créent directement sur le téléphone.

template/Makefile
build/%.o: src/%.m | build
	@echo "  compile $<"
	@gcc -c $(IOS_CFLAGS) $< -o $@

# Compiling and linking take different flags, so they are separate steps.
# A missing -framework is a link error here, which is the point: link stubs
# with real symbol names are what make it one instead of a crash at launch.
build/$(NAME): $(OBJECTS)
	@echo "  link    $@"
	@gcc $(OBJECTS) -o $@ $(IOS_LDFLAGS) $(addprefix -framework ,$(FRAMEWORKS))
	@# Unsigned is SIGKILL at launch, not a warning.
	@ldid -S $@
  1. IOS_CFLAGS et IOS_LDFLAGS proviennent de /etc/profile.d/airbuild.sh, que l’environnement installe, aucun projet n’indique donc le chemin d’un SDK ou d’un compilateur.
  2. Le binaire est également signé sur le téléphone. Sans signature, il est interrompu au lancement.
Le Makefile à partir duquel un nouveau projet commence. Le téléphone compile, effectue l’édition de liens et signe uniquement avec la chaîne d’outils qu’il a installée.

Créer et exécuter votre première app

Un nouveau projet part du modèle UIKit fourni. AirBuild3G le copie dans le répertoire du projet et place ses fichiers dans le contexte du modèle. Le modèle part d’un projet qui compile déjà, votre première requête peut donc décrire l’app au lieu de demander un Makefile.

Pour une première app, un bouton qui incrémente un nombre à l’écran est un bon choix. Elle est assez petite pour être lue en entier, mais fait appel à la modification, à la compilation, à la mise en paquet et à l’exécution sur le téléphone. Dans votre requête, indiquez iOS 4 comme cible et demandez au modèle de créer et d’installer l’app. Le code de son interface doit se limiter aux API mises en œuvre par l’iOS du téléphone. La présence d’une déclaration dans les en-têtes ne signifie pas que l’API existe lors de l’exécution.

Le modèle dispose de quatre outils. Execute exécute des commandes dans le répertoire du projet. EditFile écrit un fichier complet. PatchFile remplace un passage exact. CheckBuild exécute make avec les réglages de l’environnement chargés et renvoie la fin de sa sortie. Les résultats des outils sont renvoyés au modèle, qui peut poursuivre le travail ou corriger une modification qui a échoué. L’inférence reste entièrement sur votre service de modèles.

À distanceModèlevotre point de terminaison

appel d’outil

résultat de l’outil

Sur le téléphoneOutilsExecute EditFile PatchFile CheckBuild

La boucle se répète jusqu’à ce que le modèle réponde sans appel d’outil, que vous touchiez Arrêter ou que 16 tours aient été exécutés.

Un tour d’outil. Le modèle s’exécute à distance et ses outils s’exécutent sur le téléphone ; chaque résultat est intégré à la requête suivante lorsque la boucle continue.

Une fois la nouvelle app installée, ouvrez-la sur le téléphone. Pour le compteur, le fait de toucher le bouton et de voir le nombre changer vérifie précisément ce que vous avez demandé. Les requêtes suivantes peuvent modifier la mise en page ou ajouter une autre interaction, et le modèle poursuit son travail à partir des fichiers existants et des résultats d’outils précédents.

AirBuild3G conserve chaque projet et sa conversation sur le téléphone, avec une sauvegarde après chaque tour d’outil. Il mémorise également le projet ouvert, car quitter l’app ou manquer de mémoire peut mettre fin à son processus. Lorsque vous rouvrez AirBuild3G, il revient à ce projet avec sa conversation et ses fichiers. Votre message suivant poursuit la conversation sauvegardée, et le code source est toujours là pour la prochaine modification et le prochain build.

Sur cet écran, même ce compteur relie les apps que vous utilisiez autrefois à celles que vous pouvez maintenant créer.

Du nouveau sur un écran familier

L’iPhone 3G est arrivé en 2008, au moment de l’ouverture de l’App Store. Une nouvelle app pouvait le transformer en instrument, en carnet ou en jeu à emporter partout. Le bouton principal vous ramenait à une grille d’icônes, chacune invitant à essayer autre chose.

Sur ce téléphone aujourd’hui, on mesure le temps écoulé depuis 2008. L’écran ne s’est pas agrandi. Le processeur n’est pas devenu plus rapide. L’interface d’origine exige toujours des apps qu’elles utilisent avec soin quelques commandes et un espace réduit. En travaillant dans ces limites, vous remarquez des détails faciles à négliger sur un appareil plus récent : la réaction d’un bouton, la fluidité du défilement d’une conversation ou l’apparition de l’écran suivant au bon moment.

AirBuild3G donne une nouvelle fonction à cet écran familier. Un modèle actuel vous aide à écrire une app, et un compilateur datant de l’époque du téléphone la crée. Les anciennes icônes peuvent rester à leur place.

À leurs côtés, il reste une place pour une app qui n’existait pas auparavant.

AirBuild3G sur GitHub

Avec toute l’affection de l’équipe AirBuild