2008 brachten das iPhone 3G und der erste App Store vielen Menschen mobile Apps näher. Wir haben AirBuild3G entwickelt, um diesem Telefon eine neue Aufgabe zu geben. Auf einem iPhone 3G oder 3GS kannst du eine neue App beschreiben, ihren Quellcode mit einem Coding-Modell bearbeiten und sie auf demselben Telefon kompilieren und ausführen. Das Modell läuft bei einem Dienst deiner Wahl. Deine Projektdateien und die Entwicklungswerkzeuge bleiben auf dem Telefon.
AirBuild3G selbst wird mit Xcode 27 auf einem Mac entwickelt. Die Apps, die du damit erstellst, verwenden einen separaten Compiler, der auf dem Telefon installiert ist. Damit beides funktioniert, waren Änderungen in vier Bereichen nötig: Vorbereitung des Geräts, Linken, HTTPS und die Toolchain auf dem Telefon. Die folgenden Abschnitte begleiten diese Arbeit bis zu deiner ersten App.
Die ursprüngliche Hardware vorbereiten
Das kleine Display und die physische Home-Taste machen das iPhone 3G sofort erkennbar. Wieder dafür zu entwickeln heißt, seine ursprünglichen Proportionen und Grenzen zu respektieren, bis hin zu den Bedienelementen, die seinen Apps ihr vertrautes Gefühl gaben. AirBuild3G selbst verwendet die unter iOS 4 verfügbaren UIKit-Bedienelemente, darunter gruppierte Einstellungstabellen und Aktionen in der Navigationsleiste.
AirBuild3G läuft auf dem iPhone 3G und 3GS. Das 3G benötigt armv6-Binärdateien, das 3GS verwendet armv7. Die Firmware bestimmt, welches Umgebungspaket das jeweilige Telefon installieren kann. Unser armv6-Referenzgerät ist ein iPhone 3G mit iOS 4.2.1 und angepasster Firmware.
- Vorbereitenwiederherstellen / aktivieren
- VerbindenSSH + Netzwerk
- InstallierenApp + Umgebungspaket
- UmgebungEinstellungen → Umgebung
Beginne mit einem Telefon, das bis zum Home-Bildschirm startet und SSH-Verbindungen von deinem Mac akzeptiert. Es braucht eine funktionierende Netzwerkverbindung und Paketverwaltung, um die Debian-Pakete zu empfangen und zu installieren. Hardwaremodell und Firmwareversion bestimmen, welche Pakete gesendet werden, die Netzwerkadresse bestimmt das Ziel. Außerdem brauchst du ein zuverlässiges USB-Kabel und eine Stromquelle.
Bei einigen älteren Telefonen sind zusätzliche Schritte für die Aktivierung und den Systemzugriff nötig. Verwende für unterstützte Hardware das Legacy iOS Kit und folge den Anweisungen für das Modell und die Firmware deines Telefons. Sichere das Telefon, bevor du beginnst. AirBuild3G selbst aktiviert das Telefon nicht.
AirBuild3G benötigt außerdem Substrate für die TLS-Unterstützung. Auf einem 3G muss der Build armv6 enthalten: Ein reines armv7-Paket kann dort unabhängig von seiner Version nicht ausgeführt werden. Auf einem mit Legacy iOS Kit vorbereiteten Telefon ist möglicherweise bereits ein kompatibler Build installiert. Wenn Substrate funktioniert, lass es unverändert.
Für Einstellungen → Umgebung gilt dieselbe Regel. Die Funktion fügt Entwicklungswerkzeuge hinzu und lässt eine funktionierende Paketverwaltung, den SSH-Dienst und andere Basiskomponenten unverändert. Wenn du diese Komponenten pauschal aktualisierst, können Binärdateien installiert werden, die das alte System nicht ausführen kann.
Entwickeln mit Xcode 27
Der Compiler von Xcode 27 erzeugt weiterhin armv6- und armv7-Code und akzeptiert iOS 4.0 als Bereitstellungsziel. Xcode selbst enthält nur das iOS 27 SDK, deshalb kompilieren wir mit den Headern eines älteren SDK. Übergib den Pfad des SDK in der make-Befehlszeile als LEGACY_SDK oder exportiere ihn. Auch die Skripte zum Erstellen der Umgebungspakete lesen diesen Wert. Ein Wert, der nur im Makefile gesetzt ist, erreicht sie nie.
Der Linker ist strenger. Er lehnt armv6 als Linkziel und jedes Bereitstellungsziel vor iOS 4.3 ab. Außerdem meldet er die Bibliotheken des älteren SDK als für eine unbekannte Plattform erstellt. Für diese Bibliotheken linken wir stattdessen gegen generierte Stubs und verwenden das ältere SDK weiterhin für die Header.
- Kompiliertclang
- Architektur
- armv6
- min. iOS
- 4.0
- Code
- e92d40f0 …
- Gelinktneu kennzeichnen, dann ld
- Architektur
armv6armv7- min. iOS
4.05.0- Code
- e92d40f0 …
- Ausgeliefertzurückkennzeichnen, patchen
- Architektur
armv7armv6- min. iOS
5.04.0- Code
- e92d40f0 …
Der Build kompiliert beide Slices für iOS 4.0, linkt sie für iOS 5.0 und setzt ihre Mindestversion anschließend wieder auf 4.0. Für armv6 kennzeichnet er außerdem jede Objektdatei als armv7, damit der Linker sie annimmt, und kennzeichnet den gelinkten Slice danach wieder als armv6. Die neue Kennzeichnung ändert Metadaten, keine Anweisungen. Der kompilierte Code und die vom Linker hinzugefügten Symbol-Stubs müssen bereits auf armv6 laufen.
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)- Der cpusubtype von Mach-O für jede Architektur: 6 steht für armv6, 9 für armv7.
- Die gesamte neue Kennzeichnung: vier Byte an Offset 8, dem cpusubtype-Feld des Headers. Die Anweisungen bleiben unverändert.
Der Build behält -no_pie bei, weil iOS vor 4.3 kein ASLR hatte. Außerdem deaktiviert er das Zusammenführen von Objective-C-Kategorien. Andernfalls würde dabei das Thumb-Bit aus zusammengeführten Methoden entfernt und AirBuild3G auf einem 3GS abstürzen. make verify prüft die Architekturen, die Load Commands, die armv6-Anweisungen und das Thumb-Bit von armv7-Methoden. Ein erfolgreicher Link beweist nichts davon.
Führe make aus, um AirBuild3G und sein privilegiertes Hilfsprogramm zu erstellen. make payload lädt jedes festgelegte Paket in einen lokalen Cache und überprüft es. make package erstellt daraus offline das Paket von AirBuild3G und beide Umgebungspakete. Auch für die Paketierung ist LEGACY_SDK nötig, weil jedes Umgebungspaket ein SDK-Archiv für das Telefon enthält.
make install-device DEVICE=root@<ip> installiert AirBuild3G und das Umgebungspaket für iOS 4, aktualisiert den Home-Bildschirm und startet die App neu. Übergib für ein 3GS mit iOS 5 oder neuer stattdessen ios6 als drittes Argument an tools/deploy_device.sh. Die App läuft als Benutzer mobile. Ein kleines Hilfsprogramm daneben führt alles aus, was root benötigt. Das Umgebungspaket befindet sich jetzt auf dem Telefon. Unter Einstellungen → Umgebung werden die darin enthaltenen Entwicklungswerkzeuge installiert.
Mit einem Modelldienst verbinden
In den Einstellungen gibst du unter Endpunkt drei Werte ein: Basis-URL, API-Schlüssel und Modell. Anfragen gehen direkt vom Telefon an diesen Endpunkt. Dazwischen betreiben wir keinen Server. Es gibt auch kein Konto und keine Geräteregistrierung. Dein Modelldienst führt das Modell aus, rechnet die Nutzung ab und teilt dir mit, was du eingeben musst. Wenn du dein Telefon von uns geschenkt bekommen hast, verbindet es sich stattdessen mit air.build.
Ein unverändertes iOS 4 unterstützt über SecureTransport nur TLS 1.0, ein aktueller Endpunkt kann aber TLS 1.2 voraussetzen. Wir ergänzen TLS 1.2 mit TLSFix, einer Substrate-Anpassung für CFNetwork.
Das Telefon muss weiterhin die Identität des Servers prüfen. Sein alter Vertrauensspeicher kann die von einem aktuellen Endpunkt verwendete Zertifikatskette nicht immer bilden. TLS 1.2 zu ergänzen aktualisiert diesen Speicher nicht. Wenn eine Verbindung fehlschlägt, behandle Verschlüsselung und Zertifikatsvertrauen als getrennte Probleme.
AirBuild3G prüft Zertifikate standardmäßig. Wenn eine Anfrage fehlschlägt, prüfe zuerst Basis-URL, API-Schlüssel, Modell und Zertifikatskette. Die Einstellung Beliebigem Zertifikat vertrauen ist eine ausdrückliche Ausnahme. Wenn du sie aktivierst, entfällt die Identitätsprüfung des Servers. Dadurch könnte dein API-Schlüssel für jeden sichtbar werden, der sich als Endpunkt ausgeben kann. Lass sie deaktiviert, solange sich der Endpunkt nicht in deinem eigenen Netzwerk befindet. AirBuild3G liest die Einstellung bei jeder Vertrauensprüfung, deshalb gilt das Deaktivieren schon für die nächste Anfrage.
static OSStatus ABLSecTrustEvaluate(SecTrustRef trust, SecTrustResultType *result) {
…
OSStatus status = ABLOriginalSecTrustEvaluate(trust, result);
if (!ABLTrustAnyCertificate()) {
return status;
}
if (result != NULL) {
*result = kSecTrustResultUnspecified;
}
return errSecSuccess;
}- Zuerst wird die eigene Prüfung des Telefons ausgeführt.
- Beliebigem Zertifikat vertrauen ist standardmäßig deaktiviert, deshalb wird dieses Ergebnis unverändert zurückgegeben. Die Einstellung wird bei jedem Aufruf aus
NSUserDefaultsgelesen.
Auch das Streaming einer Antwort muss zu den Möglichkeiten des Telefons passen. AirBuild3G empfängt und dekodiert den Stream in einem Hintergrundthread und übergibt nur fertigen Text an die Oberfläche. Das Transkript wird in Abständen aktualisiert, statt bei jedem Token neu vermessen und gescrollt zu werden. So bleibt der Layoutaufwand gering, während die Antwort eintrifft. Eine Antwort zeigt, dass die Verbindung funktioniert. Um den vom Modell geschriebenen Code zu kompilieren, wird weiterhin die Umgebung auf dem Telefon benötigt.
Einen Compiler installieren, den das Telefon ausführen kann
Einstellungen → Umgebung installiert den Compiler, die SDK-Header, Linkbibliotheken und Signierwerkzeuge aus dem Umgebungspaket. Das Paket für iOS 4 enthält GCC für armv6, das Paket für iOS 5 und neuer enthält clang für armv7. Jedes Telefon installiert nur das Paket, das zu seiner Firmware passt.
Jedes Paket ist über Downloadadresse und Prüfsumme festgelegt. Bei der Installation werden genau diese Dateien verwendet, statt ein Repository nach den neuesten Versionen zu fragen. Bereits in der festgelegten Version installierte Pakete werden übersprungen, deshalb ist ein zweiter Durchlauf von Umgebung sofort abgeschlossen. Anschließend werden die installierten Pakete zurückgehalten, damit Cydia keine Upgrades mehr anbietet, die die Toolchain beschädigen.
Für das iPhone 3G verwendet die Umgebung den Build von iphone-gcc 4.2 aus dem Jahr 2008. Der Build von 2009 reserviert ein Gigabyte für vorkompilierte Header. Dadurch liegt ein Teil davon außerhalb des Adressraums eines armv6-Telefons. Seine Compiler-Backends stürzen ab, bevor sie etwas kompilieren. Beide Builds weisen sich als Apple Build 5555 aus, aber nur der von 2008 passt in diesen Adressraum.
0x300000002008 iphone-gcc · jedes Segment unter 6 MBläuft
512 MB großes Array · Testbinärdateiläuft
768 MB großes Array · TestbinärdateiSIGBUS
2009 iphone-gcc · 1 GB großer Bereich für vorkompilierte HeaderSIGBUS
Auch die SDK-Header müssen geändert werden: Dieser GCC kann ihre Blockdeklarationen nicht analysieren. Vor dem Paketieren der Header für iOS 4 entfernen wir diese Deklarationen und alle Deklarationen, die von einem entfernten Typ abhängen. Im clang-Paket bleiben die Header von Apple unverändert, weil clang Blocks unterstützt. Jedes Paket enthält außerdem ein Signierwerkzeug, das die Ausgabe seines Compilers signieren kann.
Das Telefon bewahrt seine System-Frameworks im dyld Shared Cache auf. Deshalb enthalten die Framework-Verzeichnisse keine Bibliotheksdateien, die ein Linker lesen könnte. Wir stellen generierte Stubs mit exportierten Symbolen und echten Bibliotheksnamen bereit. Der Linker löst jede Referenz über einen Stub auf und zeichnet auf, aus welcher Bibliothek sie stammt. Beim Start bindet dyld sie an die echte Implementierung im Shared Cache. Da die Stubs echte Symbolnamen enthalten, lässt ein fehlendes Framework den Linkvorgang scheitern, statt die App beim Start abstürzen zu lassen.
Die Umgebung legt außerdem die Include-Pfade fest, verweist gcc und cc auf den installierten Compiler und exportiert Linker-Flags, die die Objective-C-Laufzeit und CoreFoundation einbinden. Das mitgelieferte Makefile liest diese Einstellungen, deshalb muss für ein neues Projekt keine eigene Toolchain eingerichtet werden. Von da an werden deine Apps direkt auf dem Telefon erstellt.
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 $@IOS_CFLAGSundIOS_LDFLAGSstammen aus/etc/profile.d/airbuild.sh, das von der Umgebung installiert wird. Deshalb muss kein Projekt den Pfad zu einem SDK oder Compiler angeben.- Die Binärdatei wird ebenfalls auf dem Telefon signiert. Ohne Signatur wird sie beim Start beendet.
Deine erste App erstellen und ausführen
Ein neues Projekt beginnt mit der mitgelieferten UIKit-Vorlage. AirBuild3G kopiert sie in das Projektverzeichnis und fügt ihre Dateien dem Kontext des Modells hinzu. Das Modell beginnt mit einem Projekt, das sich bereits erstellen lässt. Deshalb kann deine erste Anfrage die App beschreiben, statt nach einem Makefile zu fragen.
Eine gute erste App ist eine Taste, die eine Zahl auf dem Display erhöht. Sie ist klein genug, um sie vollständig zu lesen, deckt aber trotzdem Bearbeitung, Kompilierung, Paketierung und Ausführung auf dem Telefon ab. Nenne in deiner Anfrage iOS 4 als Ziel und bitte das Modell, die App zu erstellen und zu installieren. Der Code ihrer Oberfläche darf nur APIs verwenden, die in der iOS-Version des Telefons implementiert sind. Eine Deklaration in den Headern bedeutet nicht, dass die API zur Laufzeit vorhanden ist.
Das Modell verfügt über vier Werkzeuge. Execute führt Befehle im Projektverzeichnis aus. EditFile schreibt eine vollständige Datei. PatchFile ersetzt eine genaue Textstelle. CheckBuild führt make mit den geladenen Einstellungen der Umgebung aus und gibt das Ende der Ausgabe zurück. Die Ergebnisse der Werkzeuge gehen zurück an das Modell. Es kann weiterarbeiten oder eine fehlgeschlagene Änderung überarbeiten. Die Inferenz bleibt dabei durchgehend bei deinem Modelldienst.
Werkzeugaufruf
Werkzeugergebnis
Execute EditFile PatchFile CheckBuildDie Schleife wird wiederholt, bis das Modell ohne Werkzeugaufruf antwortet, du auf Stopp tippst oder 16 Runden durchlaufen wurden.
Sobald die neue App installiert ist, öffne sie auf dem Telefon. Beim Zähler prüfst du genau das angeforderte Verhalten, indem du auf die Taste tippst und beobachtest, wie sich die Zahl ändert. Spätere Anfragen können das Layout ändern oder eine weitere Interaktion hinzufügen. Das Modell baut dabei auf den vorhandenen Dateien und früheren Werkzeugergebnissen auf.
AirBuild3G speichert jedes Projekt und die zugehörige Unterhaltung nach jeder Werkzeugrunde auf dem Telefon. Die App merkt sich auch, welches Projekt geöffnet ist, weil ihr Prozess beim Verlassen der App oder bei wenig Speicher beendet werden kann. Wenn du AirBuild3G wieder öffnest, kehrt die App mit der Unterhaltung und den Dateien zu diesem Projekt zurück. Deine nächste Nachricht setzt die gespeicherte Unterhaltung fort, und der Quellcode ist für die nächste Änderung und den nächsten Build weiterhin vorhanden.
Auf diesem Bildschirm verbindet sogar dieser Zähler die Apps, die du früher verwendet hast, mit denen, die du jetzt selbst erstellen kannst.
Etwas Neues auf einem vertrauten Bildschirm
Das iPhone 3G erschien 2008, kurz nachdem der App Store geöffnet hatte. Eine neue App konnte es in ein Instrument, ein Notizbuch oder ein Spiel verwandeln, das du überallhin mitnahmst. Die Home-Taste führte zurück zu einem Raster aus Symbolen. Jedes lud dazu ein, etwas anderes auszuprobieren.
Wenn du das Telefon heute benutzt, spürst du, wie lange 2008 zurückliegt. Das Display ist nicht gewachsen. Der Prozessor ist nicht schneller geworden. Die ursprüngliche Oberfläche verlangt von Apps weiterhin, mit wenigen Bedienelementen und wenig Platz sorgfältig umzugehen. Innerhalb dieser Grenzen fallen Details auf, die auf einem neueren Gerät leicht übersehen werden: ob eine Taste reagiert, ob eine Unterhaltung flüssig scrollt und ob der nächste Bildschirm im richtigen Moment erscheint.
AirBuild3G gibt diesem vertrauten Bildschirm eine weitere Aufgabe. Ein aktuelles Modell hilft dir, eine App zu schreiben, und ein Compiler aus der Zeit des Telefons erstellt sie. Die alten Symbole können bleiben, wo sie sind.
Daneben ist Platz für eine App, die es vorher nicht gab.