
中文
Debugging hat einen ziemlich schlechten Ruf. Ich meine, wenn der Entwickler ein vollständiges Verständnis des Programms hätte, gäbe es keine Fehler und er würde nicht in erster Linie debuggen, oder?
Denken Sie nicht so.
Es wird immer Fehler in Ihrer Software - oder in einer Software, zu dieser Angelegenheit geben. Kein von Ihrem Produktmanager auferlegter Testabdeckung wird dies beheben. Tatsächlich ist es tatsächlich eine giftige Denkweise, Ihre analytische Fähigkeiten zu behindern, wenn das Debuggen als nur ein Verfahren zur Behebung von etwas gebrochen wird.
Stattdessen sollten Sie Debugging einfach als einen Prozess betrachten, um ein Programm besser zu verstehen . Es ist ein subtiler Unterschied, aber wenn Sie es wirklich glauben, verschwindet jede frühere Plackerei einfach.
Seit Grace Hopper, dem Gründer der COBOL -Sprache, entdeckte der erste Fehler der Welt in einem Relais -Computer die Erzeugung von Fehler in der Softwareentwicklung nie. Wie das Vorwort zum Buch 《Advanced Apple Debugging & Reverse Engineering》 sagt: Entwickler möchten nicht denken, dass es keinen Fehler geben wird, wenn es ein gutes Verständnis dafür gibt, wie Software funktioniert. Daher ist das Debuggen fast eine unvermeidliche Phase im Lebenszyklus der Softwareentwicklung.
Wenn Sie einen unerfahrenen Programmierer fragen, wie das Debuggen definiert werden kann, könnte er sagen: "Debugging ist etwas, das Sie tun, um eine Lösung für Ihr Softwareproblem zu finden." Er hat Recht, aber das ist nur ein winziger Teil eines echten Debuggings.
Hier sind die Schritte eines echten Debuggens:
Unter den obigen Schritten ist der wichtigste Schritt der erste Schritt: Finden Sie das Problem heraus. Anscheinend ist es eine Voraussetzung für andere Schritte.
Untersuchungen zeigen, dass die Zeit, in denen erfahrene Programmierer für das Debuggen ausgeben, um dieselben Defekte zu finden, etwa ein zwanzigster von unerfahrenen Programmierern entspricht. Das bedeutet, dass das Debuggenerlebnis einen enormen Unterschied in der Programmierungseffizienz macht. Wir haben viele Bücher über Softwaredesign, leider haben seltene sie eine Einführung in das Debuggen, sogar die Kurse in der Schule.
Während sich der Debugger im Laufe der Jahre verbessert, wird der Programmierer -Codierungsstil gründlich verändert. Natürlich kann der Debugger das gute Denken nicht ersetzen. Denken kann den hervorragenden Debugger nicht ersetzen. Die perfekteste Kombination ist ein ausgezeichneter Debugger mit gutem Denken.
Das folgende Diagramm sind die neun Debugging -Regeln, die in Buch <Debugging: Die 9 unverzichtbaren Regeln für die Suche nach den schwer fassbarsten Software- und Hardwareproblemen>.

Obwohl als iOS-Programmierer die meiste Zeit in der Arbeit nicht mit der Versammlungssprache zu tun hat, aber zu verstehen, dass die Montage immer noch sehr hilfreich ist, insbesondere wenn ein System Framework oder ein Drittanbieter-Framework ohne den Quellcode debuggen.
Asssembly Sprache ist eine maschinenorientierte Programmiersprache mit niedrigem Niveau, die als Sammlung von Mnemonik für Maschinenanweisungen für verschiedene CPUs betrachtet werden kann. Programmierer können die Montagesprache verwenden, um das Computer -Hardware -System direkt zu steuern. und das in der Montagesprache geschriebene Programm hat viele Vorzüge, wie die schnelle Ausführungsgeschwindigkeit und weniger Speicher.
Bisher werden zwei Hauptarchitekturen auf der Apple -Plattform, X86 und Arm, häufig verwendet. In dem mobilen Gerät mithilfe der ARM -Montagesprache, die hauptsächlich daran liegt, dass der Arm eine RISC -Architektur (Reduced Community Computing) mit geringem Stromverbrauchsvorteil ist. Während die Desktop -Plattform wie Mac OS die X86 -Architektur verwendet wird. Die auf iOS -Simulatoren installierten Apps werden tatsächlich als Mac OS -App im Simulator ausgeführt, was bedeutet, dass Simulator wie ein Container funktioniert. Da unser Fall in den iOS -Simulatoren debuggiert wurde, ist das Hauptforschungsziel X86 Assemblersprache.
X86 Assemblersprache entwickelt sich zu zwei Syntaxzweigen: Intel (origlich verwendet in der X86 -Plattformdokumentation) und AT & T. Intel dominiert die MS-DOS und die Windows-Familie, während AT & T in der Unix-Familie üblich ist. Es gibt einen großen Unterschied in der Syntax zwischen Intel und AT & T, wie Variable, Konstant, Zugang von Registern, indirekter Adressierung und Offset. Obwohl ihr Syntaxunterschied enorm ist, ist das Hardware -System das gleiche, was bedeutet, dass einer von ihnen nahtlos auf den anderen migriert werden kann. Da die AT & T -Montagesprache auf Xcode verwendet wird, werden wir uns auf AT & T im folgenden Teil konzentrieren.
Bitte beachten Sie, dass die Intel -Syntax für die Demontage von Hopper Disassemble und IDA Pro verwendet wird.
Belows sind die Unterschiede zwischen Intel und AT & T:
Das Präfix von Operand: In AT & T -Syntax wird % als Präfix des Registers 'Namen verwendet, und $ wird als Präfix des sofortigen Operanden verwendet, während für beide Register und unmittelbarer Operanden kein Präfix verwendet wird. Der andere Unterschied ist, dass 0x als Präfix für Hexadezimal in AT & T hinzugefügt wird. Das folgende Diagramm zeigt den Unterschied zwischen ihren Präfixen:
| AT & T | Intel |
|---|---|
| MOVQ %Rax, %RBX | MOV RBX, RAX |
| Addq $ 0x10, %RSP | Fügen Sie RSP, 010H hinzu |
In der Intel -Syntax wird
h-Suffix für den hexadezimalen Operanden undb-Suffix für binäre Operanden verwendet.
Operand: In der AT & T -Syntax ist der erste Operand Source Operand, der zweite Operand Is Destinand. In der Intel -Syntax ist jedoch die Reihenfolge des Operanden entgegengesetzt. Ab diesem Zeitpunkt ist die Syntax von AT & T für uns nach unserer Lesegewohnheit komfortabler.
Adressierungsmodus: Im Vergleich zur Intel -Syntax ist der indirekte Adressmodus von AT & T schwer zu lesen. Der Algorithmus der Adressberechnung ist jedoch gleich: address = disp + base + index * scale . base repräsentiert die Basisadresse, disp steht für die Offset -Adresse, index * scale bestimmt die Position eines Elements. scale ist die Größe eines Elements, das nur eine Leistung von zwei sein kann. disp/base/index/scale sind alle optional, der Standardwert des index beträgt 0, während der Standardwert der scale 1 beträgt. Jetzt sehen wir die Anweisung der Adressberechnung: %segreg: disp(base,index,scale) ist für AT & T und segreg: [base+index*scale+disp] IS für Intel. In der Tat gehören bei beiden Anweisungen beide zum Segment -Adressierungsmodus. segreg steht für das Segmentregister, das normalerweise im realen Modus verwendet wird, wenn die Ziffernkapazität der CPU über die Ziffer der Register hinausgeht. Zum Beispiel kann die CPU den 20-Bit-Raum einhalten, das Register hat jedoch nur 16-Bit. Um einen 20-stelligen Raum zu erreichen, muss ein weiterer Adressierungsmodus verwendet werden: segreg:offset . Mit diesem Adressierungsmodus ist die Offset -Adresse segreg * 16 + offset , aber sie ist komplizierter als Flat -Speichermodus. Im Schutzmodus befindet sich die Adressierung im linearen Adressraum, was bedeutet, dass die Segmentbasisadresse ignoriert werden kann.
| AT & T | Intel |
|---|---|
| MOVQ 0xB57751 ( %RIP), %RSI | MOV RSI, QWORD PTR [RIP+0xB57751H] |
| Leaq (%Rax,%RBX, 8),%RDI | Lea RDI, QWord PTR [Rax+RBX*8] |
Wenn der sofortige Operand am Ort des
dispoderscaleliegt, kann$Suffix weggelassen werden. In Intel Syntax müssenbyte ptr,word ptr,dword ptrundqword ptrvor dem Speicheroperanden hinzugefügt werden.
Suffix von Opcode: In AT & T -Syntax verfügen alle Opcodes über ein Suffix, um die Größe anzugeben. Es gibt im Allgemeinen vier Arten von Suffixen: b , w , l und q b repräsentiert 8-Bit-Byte, w bedeutet 16-Bit-Wort, l bedeutet 32-Bit-Doppelwort. 32-stelliges Wort wird auch als langes Wort bezeichnet, das aus den 16-Bit-Tagen stammt. q repräsentiert 64-Bit-Quadword. Das folgende Diagramm zeigt die Syntax der Datenübergangsanweisung (MOV) in AT & T und Intel.
| AT & T | Intel |
|---|---|
| MOVB %AL, %BL | MOV BL, AL |
| MOVW %AX, %BX | MOV BX, AX |
| MOVL %EAX, %EBX | MOV EBX, EAX |
| MOVQ %Rax, %RBX | MOV RBX, RAX |
Wie wir wissen, wird der Speicher verwendet, um Anweisungen und Daten für die CPU zu speichern. Speicher ist im Wesentlichen ein Array von Bytes. Obwohl die Geschwindigkeit des Speicherzugriffs sehr schnell ist, benötigen wir immer noch eine kleinere und schnellere Speichereinheit, um die Anweisung der CPU zu beschleunigen, die Register ist. Während der Anweisungsausführung werden alle Daten in Registern vorübergehend gespeichert. Deshalb wird das Register benannt.
Wenn die Prozessoren von 16 Bit auf 32-Bit wachsen, werden 8 Register ebenfalls auf 32-Bit erweitert. Danach wird E Präfix für erweiterte Register zum ursprünglichen Registernamen hinzugefügt. 32-Bit-Prozessor ist Intel Architecture 32-Bit, das ist Ia32. Heute sind die Hauptprozessoren eine 64-Bit-Intel-Architektur, die von IA32 erweitert und als X86-64 bezeichnet wird. Da IA32 vorbei ist, konzentriert sich dieser Artikel nur auf X86-64. Beachten Sie, dass in X86-64 die Anzahl der Register von 8 auf 16 verlängert wird. Nur wegen dieser Erweiterung kann der Programmstatus in Registern gespeichert werden, jedoch nicht in Stapeln. Somit ist die Häufigkeit des Speicherzugriffs stark verringert.
In x86-64 gibt es 16 64-Bit-Generalregister und 16 schwimmende Zeigerregister. Außerdem hat CPU ein weiteres 64-Bit-Anweisungszeigerregister namens rip . Es wurde entwickelt, um die Adresse der nächsten ausgeführten Anweisung zu speichern. Es gibt auch einige andere Register, die nicht weit verbreitet sind, wir beabsichtigen nicht, in diesem Artikel darüber zu sprechen. Unter den 16 allgemeinen Registern stammen acht von ihnen aus dem IA32: Rax 、 rcx 、 rdx 、 rbx 、 rsi 、 rdi 、 rsp und rbp. Die anderen acht allgemeinen Register sind neu hinzugefügt, da X86-64, die R8 - R15 sind. Die 16 schwebenden Register sind xmm0 - xmm15.
Der aktuelle CPUs stammt von 8088, das Register wird auch von 16-Bit auf 32-Bit und schließlich auf 64-Bit erweitert. Somit kann das Programm weiterhin auf den niedrigen 8-Bit- oder 16-Bit oder 32-Bit der Register zugreifen.
Die folgende Tabelle zeigt die 16 allgemeinen Register von x86-64:

Wenn Sie den Befehl register read in LLDB verwenden, können Sie die Registerdaten des aktuellen Stack -Frame abgeben.
Zum Beispiel können wir den folgenden Befehl verwenden, um alle Daten im Register anzuzeigen:
register read -a or register read --all
General Purpose Registers:
rax = 0x00007ff8b680c8c0
rbx = 0x00007ff8b456fe30
rcx = 0x00007ff8b6804330
rdx = 0x00007ff8b6804330
rdi = 0x00007ff8b456fe30
rsi = 0x000000010cba6309 "initWithTask:delegate:delegateQueue:"
rbp = 0x000070000f1bcc90
rsp = 0x000070000f1bcc18
r8 = 0x00007ff8b680c8c0
r9 = 0x00000000ffff0000
r10 = 0x00e6f00100e6f080
r11 = 0x000000010ca13306 CFNetwork`-[__NSCFURLLocalSessionConnection initWithTask:delegate:delegateQueue:]
r12 = 0x00007ff8b4687c70
r13 = 0x000000010a051800 libobjc.A.dylib`objc_msgSend
r14 = 0x00007ff8b4433bd0
r15 = 0x00007ff8b6804330
rip = 0x000000010ca13306 CFNetwork`-[__NSCFURLLocalSessionConnection initWithTask:delegate:delegateQueue:]
rflags = 0x0000000000000246
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
eax = 0xb680c8c0
ebx = 0xb456fe30
ecx = 0xb6804330
edx = 0xb6804330
edi = 0xb456fe30
esi = 0x0cba6309
ebp = 0x0f1bcc90
esp = 0x0f1bcc18
r8d = 0xb680c8c0
r9d = 0xffff0000
r10d = 0x00e6f080
r11d = 0x0ca13306
r12d = 0xb4687c70
r13d = 0x0a051800
r14d = 0xb4433bd0
r15d = 0xb6804330
ax = 0xc8c0
bx = 0xfe30
cx = 0x4330
dx = 0x4330
di = 0xfe30
si = 0x6309
bp = 0xcc90
sp = 0xcc18
r8w = 0xc8c0
r9w = 0x0000
r10w = 0xf080
r11w = 0x3306
r12w = 0x7c70
r13w = 0x1800
r14w = 0x3bd0
r15w = 0x4330
ah = 0xc8
bh = 0xfe
ch = 0x43
dh = 0x43
al = 0xc0
bl = 0x30
cl = 0x30
dl = 0x30
dil = 0x30
sil = 0x09
bpl = 0x90
spl = 0x18
r8l = 0xc0
r9l = 0x00
r10l = 0x80
r11l = 0x06
r12l = 0x70
r13l = 0x00
r14l = 0xd0
r15l = 0x30
Floating Point Registers:
fctrl = 0x037f
fstat = 0x0000
ftag = 0x00
fop = 0x0000
fioff = 0x00000000
fiseg = 0x0000
fooff = 0x00000000
foseg = 0x0000
mxcsr = 0x00001fa1
mxcsrmask = 0x0000ffff
stmm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xff 0xff}
stmm1 = {0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0xff 0xff}
stmm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
stmm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
stmm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0xbc 0x87 0x0b 0xc0}
stmm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
stmm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x78 0xbb 0x0b 0x40}
stmm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm8 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm9 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm10 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm11 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm12 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm13 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm14 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
ymm15 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm8 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm9 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm10 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm11 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm12 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm13 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm14 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
xmm15 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
Exception State Registers:
trapno = 0x00000003
err = 0x00000000
faultvaddr = 0x000000010bb91000
Wie wir wissen, gibt es 16 schwimmende Zeigerregister in x86-64: xmm0 - xmm15. Tatsächlich gibt es einige andere Details davon. Bei der Ausgabe des Befehls register read -a stellen Sie möglicherweise fest, dass neben der XMM -Registergruppe STMM- und YMM -Register vorhanden sind. Hier ist STMM ein Alias des ST -Registers, und ST ist ein Register der FPU (Float Point Unit) in X86, um Float -Daten zu verarbeiten. Die FPU enthält ein Float -Zeigerregister, das acht 80 -Bit -Float -Zeigerregister hat: ST0 - ST7. Wir können feststellen, dass das STMM-Register 80-Bit aus der Ausgabe ist, was nachweisen kann, dass das STMM-Register das ST-Register ist. XMM ist ein 128-Bit-Register, und das YMM-Register ist 256-Bit, was eine Erweiterung von XMM ist. Tatsächlich ist das XMM-Register das niedrige 128-Bit-YMM-Register. Wie das EAX-Register ist das niedrige 32-Bit-Rax-Register. In Pentium III veröffentlichte Intel einen Befehlssatz namens SSE (Streaming Simd Extensions), bei dem es sich um eine Erweiterung von MMX handelt. Acht neue 128 -Bit -Register (xmm0 - xmm7) werden in SSE hinzugefügt. AVX (Advanced Vector Extensionsions) Anweisungssatz ist eine Erweiterungsarchitektur von SSE. Ebenfalls in AVX wurde das 128-Bit-Register XMM auf das 256-Bit-Register YMM erweitert.

Eine Funktionsaufruf beinhaltet Parameterüberschreitungs- und Steuertransfer von einer Kompilierungseinheit zur anderen. In der Funktionsaufrufverfahren werden Datenübergabe, lokale variable Zuordnung und Freigabe von Stack durchgeführt. Und die Stapel, die einer einzigen Funktion zugeordnet sind, werden als Stack -Frame bezeichnet.
Die Funktion, die die Konvention von OS X X86-64 aufruft, ist mit der im Artikel beschriebenen Konvention dieselbe. Daher können Sie sich darauf verweisen, wenn Sie daran interessiert sind.
Während des LLDB -Debuggens können wir bt -Befehl verwenden, um die Stapelverfolgung des aktuellen Threads wie unten zu drucken:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001054e09d4 TestDemo`-[ViewController viewDidLoad](self=0x00007fd349558950, _cmd="viewDidLoad") at ViewController.m:18
frame #1: 0x00000001064a6931 UIKit`-[UIViewController loadViewIfRequired] + 1344
frame #2: 0x00000001064a6c7d UIKit`-[UIViewController view] + 27
frame #3: 0x00000001063840c0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61
// many other frames are ommitted here
Tatsächlich ist bt -Befehl auf Stapelrahmen funktionsfähig. Der Stack Frame erhalten die Rückgabeadresse und die lokale Variable für Funktionen, die als Kontext einer Funktionsausführung angesehen werden können. Wie wir wissen, wächst der Haufen nach oben, während der Stapel nach unten wächst, was von großnötigen Speicheradressen bis hin zu kleinen Zahlen stammt. Sobald eine Funktion aufgerufen wird, wird für das Funktionsaufruf ein eigenständiger Stack -Frame zugewiesen. Das RBP -Register, das als Frame -Zeiger bezeichnet wird, zeigt immer auf das Ende des neuesten zugewiesenen Stack -Frame (hohe Adresse). Das RSP -Register, das als Stackzeiger bezeichnet wird, zeigt immer auf den neuesten zugewiesenen Stack -Frame (niedrige Adresse). Unten finden Sie eine Tabelle des Frame -Stacks:

Die linke Position ist die Speicheradresse, die den indirekten Adressierungsmodus verwendet. Content ist der Wert der Adresse in Position auf. Gemäß der Struktur des Stapelrahmens in der obigen Tabelle kann die Funktionsaufrufverfahren als mehrere Schritte wie folgt beschrieben werden:
Schritt 2 und 3 gehören tatsächlich zum call Anweisung. Zusätzlich können Schritt 4 und Schritt 5 in der Montageanweisung wie folgt beschrieben werden:
TestDemo`-[ViewController viewDidLoad]:
0x1054e09c0 <+0>: pushq %rbp //step 4
0x1054e09c1 <+1>: movq %rsp, %rbp //step 5
Es ist leicht zu bemerken, dass diese beiden Schritte zusammen mit jeder Funktion aufrufen. Es gibt ein weiteres Detail der obigen Tabelle: Es gibt einen roten Bereich unter dem RSP -Register, der von ABI als rote Zone bezeichnet wird. Es ist reserviert und darf nicht durch Signal- oder Interrupt -Handler geändert werden. Da es während der Funktionsaufruf geändert werden kann, können Blattfunktionen daher die Funktionen, die niemals andere Funktionen aufrufen, diesen Bereich für temporäre Daten verwenden können.
UIKit`-[UIViewController loadViewIfRequired]:
0x1064a63f1 <+0>: pushq %rbp
0x1064a63f2 <+1>: movq %rsp, %rbp
0x1064a63f5 <+4>: pushq %r15
0x1064a63f7 <+6>: pushq %r14
0x1064a63f9 <+8>: pushq %r13
0x1064a63fb <+10>: pushq %r12
0x1064a63fd <+12>: pushq %rbx
Unter den oben genannten Anweisungen gehören die Anweisung von 0x1064a63f5 bis 0x1064a63fd zu Schritt 6. Es gibt eine Art Register, die als Funktion des Funktionsvorkommens eingestuft werden, was bedeutet, dass sie zur Aufruffunktion angehören, aber die aufgerufene Funktion ist erforderlich, um ihre Werte zu erhalten. Aus den folgenden Anweisungen der Montage können RBX, RSP und R12 - R15 alle zu solchen Registern gehören.
0x1064a6c4b <+2138>: addq $0x1f8, %rsp ; imm = 0x1F8
0x1064a6c52 <+2145>: popq %rbx
0x1064a6c53 <+2146>: popq %r12
0x1064a6c55 <+2148>: popq %r13
0x1064a6c57 <+2150>: popq %r14
0x1064a6c59 <+2152>: popq %r15
0x1064a6c5b <+2154>: popq %rbp
0x1064a6c5c <+2155>: retq
0x1064a6c5d <+2156>: callq 0x106d69e9c ; symbol stub for: __stack_chk_fail
Die Anweisung zum Aufrufen einer Funktion wird call , siehe unten:
call function
function im Parameter sind die Prozeduren im Textsegment . Call Anrufbeantragung kann in zwei Schritte aufgeteilt. Der erste Schritt besteht darin, die nächste Anweisungsadresse der call auf Stack zu überschreiten. Hier ist die nächste Adresse tatsächlich die Absenderadresse, nachdem die aufgerufene Funktion beendet ist. Der zweite Schritt ist Sprung zur function . call Anrufanweisung entspricht unter zwei Anweisungen:
push next_instruction
jmp function
Im Folgenden finden Sie das Beispiel für call Anrufanweisung im iOS -Simulator:
0x10915c714 <+68>: callq 0x1093ca502 ; symbol stub for: objc_msgSend
0x105206433 <+66>: callq *0xb3cd47(%rip) ; (void *)0x000000010475e800: objc_msgSend
Der obige Code zeigt zwei Verwendungen der call . In der ersten Verwendung ist der Operand eine Speicheradresse, die eigentlich ein Symbolstum einer Mach-O-Datei ist. Es kann das Symbol einer Funktion durch den dynamischen Linker durchsuchen. In der zweiten Verwendung wird der Operand tatsächlich im indirekten Adressierungsmodus erhalten. Darüber hinaus muss in der AT & T -Syntax * in der Sprung/Anrufbefehl (oder den Sprüngen, die mit dem Programmierer im Zusammenhang mit dem Programmierer im Zusammenhang mit dem Programmierer) als Präfix hinzugefügt werden.
Im Allgemeinen wird die Anweisung ret verwendet, um die Prozedur aus der aufgerufenen Funktion an die Aufruffunktion zurückzugeben. Diese Anweisung steckt die Adresse von der Spitze des Stacks und springt zurück zu dieser Adresse und wird weiter ausgestellt. Im obigen Beispiel springt es zurück zu next_instruction . Vor der Ausführung ret -Anweisung werden die Register zur Aufruffunktion gehören. Dies wird bereits in Schritt 6 des Funktionsaufrufverfahrens erwähnt.
Die meisten Funktionen haben Parameter, die ganzzahlig, schweben, Zeiger usw. sein können. Außerdem haben Funktionen normalerweise einen Rückgabwert, der darauf hinweisen kann, dass das Ausführungsergebnis erfolgreich ist oder fehlgeschlagen ist. In OSX können höchstens 6 Parameter durch Register weitergeleitet werden, die RDI, RSI, RDX, RCX, R8 und R9 sind. Wie wäre es mit einer Funktion mit mehr als 6 Parametern? Natürlich existiert dieser Umstand. In diesem Fall kann Stack verwendet werden, um die verbleibenden Parameter in umgekehrter Reihenfolge zu erhalten. OSX verfügt über acht schwimmende Punktregister, die es ermöglichen, bis zu 8 Float -Parametern zu übergeben.
Über den Rückgabewert einer Funktion wird rax -Register verwendet, um den Ganzzahl -Rückgabewert zu speichern. Wenn der Rückgabewert ein Schwimmer ist, müssen xmm0 - xmm1 Register verwendet werden. Die folgende Tabelle zeigt deutlich die Register -Nutzungskonvention während des Funktionsaufrufs.

preserved across function calls geben an, ob das Register über Funktionsaufrufe hinweg erhalten bleiben muss. Wir können sehen, dass neben RBX-, R12 - R15 -Registern auch RSP- und RBP -Register auch zu Callee -Savist -Registern gehören. Dies liegt daran, dass diese beiden Register die wichtigen Standortzeiger vorbehalten, die auf den Programmstapel hinweisen.
Als nächstes folgen wir ein echtes Beispiel, um die Anweisungen in einem Funktionsaufruf zu demonstrieren. Nehmen Sie das Makro DDLogError in CocoaLumberjack als Beispiel. Wenn dieses Makro aufgerufen wird, Klassenmethode log:level:flag:context:file:function:line:tag:format: wird aufgerufen. Nach dem folgenden Code und Anweisungen geht es um den Anruf von DDLogError und die entsprechenden Anweisungen der Montage:
- (IBAction)test:(id)sender {
DDLogError(@"TestDDLog:%@", sender);
}
0x102c568a3 <+99>: xorl %edx, %edx
0x102c568a5 <+101>: movl $0x1, %eax
0x102c568aa <+106>: movl %eax, %r8d
0x102c568ad <+109>: xorl %eax, %eax
0x102c568af <+111>: movl %eax, %r9d
0x102c568b2 <+114>: leaq 0x2a016(%rip), %rcx ; "/Users/dev-aozhimin/Desktop/TestDDLog/TestDDLog/ViewController.m"
0x102c568b9 <+121>: leaq 0x2a050(%rip), %rsi ; "-[ViewController test:]"
0x102c568c0 <+128>: movl $0x22, %eax
0x102c568c5 <+133>: movl %eax, %edi
0x102c568c7 <+135>: leaq 0x2dce2(%rip), %r10 ; @"eTestDDLog:%@"
0x102c568ce <+142>: movq 0x33adb(%rip), %r11 ; (void *)0x0000000102c8ad18: DDLog
0x102c568d5 <+149>: movq 0x34694(%rip), %rbx ; ddLogLevel
0x102c568dc <+156>: movq -0x30(%rbp), %r14
0x102c568e0 <+160>: movq 0x332f9(%rip), %r15 ; "log:level:flag:context:file:function:line:tag:format:"
0x102c568e7 <+167>: movq %rdi, -0x48(%rbp)
0x102c568eb <+171>: movq %r11, %rdi
0x102c568ee <+174>: movq %rsi, -0x50(%rbp)
0x102c568f2 <+178>: movq %r15, %rsi
0x102c568f5 <+181>: movq %rcx, -0x58(%rbp)
0x102c568f9 <+185>: movq %rbx, %rcx
0x102c568fc <+188>: movq -0x58(%rbp), %r11
0x102c56900 <+192>: movq %r11, (%rsp)
0x102c56904 <+196>: movq -0x50(%rbp), %rbx
0x102c56908 <+200>: movq %rbx, 0x8(%rsp)
0x102c5690d <+205>: movq $0x22, 0x10(%rsp)
0x102c56916 <+214>: movq $0x0, 0x18(%rsp)
0x102c5691f <+223>: movq %r10, 0x20(%rsp)
0x102c56924 <+228>: movq %r14, 0x28(%rsp)
0x102c56929 <+233>: movb $0x0, %al
0x102c5692b <+235>: callq 0x102c7d2be ; symbol stub for: objc_msgSend
Da alle Funktionen von Objective-C in den Aufruf der Funktion objc_msgSend werden, log:level:flag:context:file:function:line:tag:format: Methode schließlich zu den folgenden Codes:
objc_msgSend(DDLog, @selector(log:level:flag:context:file:function:line:tag:format:), asynchronous, level, flag, context, file, function, line, tag, format, sender)
Wir haben bereits erwähnt, dass die meisten 6 Register für das Passieren von Parametern verwendet werden können. Die überschüssigen Parameter können Stack verwenden, um den Durchgang durchzuführen. Da die obige Funktion mehr als 6 Parameter aufweist, würde das Parameterübergang sowohl Register als auch Stapel verwenden. Unter zwei Tabellen beschreiben die Detailverwendung von Registern und Stapel für die Parameterübergabe der DDLogError -Funktionsaufruf.
| Allgemeines Register | Wert | Parameter | Montageanweisungen | Kommentar |
|---|---|---|---|---|
| RDI | Ddlog | selbst | 0x102C568EB <+171>: MOVQ %R11, %RDI | |
| RSI | "Protokoll: Ebene: Flag: Kontext: Datei: Funktion: Zeile: Tag: Format:" | op | 0x102C568f2 <+178>: MOVQ %R15, %RSI | |
| RDX | 0 | asynchron | 0x102c568a3 <+99>: xorl %edx, %edx | XORL ist eine exklusive Operation. Hier wird es verwendet, um das EDX -Register zu löschen |
| RCX | 18446744073709551615 | Ebene | 0x102C568f9 <+185>: MOVQ %RBX, %RCX | (Ddloglevelallall oder nsuIntegermax) |
| R8 | 1 | Flagge | 0x102C568aa <+106>: MOVL %EAX, %R8D | DdlogflagError |
| R9 | 0 | Kontext | 0x102C568AF <+111>: MOVL %EAX, %R9D |
| Stapelrahmenversatz | Wert | Parameter | Montageanweisungen | Kommentar |
|---|---|---|---|---|
| (%RSP) | "/Users/dev-aozhimin/desktop/testddlog/testddlog/viewController.m" | Datei | 0x102C56900 <+192>: MOVQ %R11, ( %RSP) | |
| 0x8 (%RSP) | "-[ViewController-Test:]" | Funktion | 0x102C56908 <+200>: MOVQ %RBX, 0x8 ( %RSP) | |
| 0x10 (%RSP) | 0x22 | Linie | 0x102C5690D <+205>: MOVQ $ 0x22, 0x10 (%RSP) | Die entsprechende Aufruf von ddLogerError ist in Zeile 34 |
| 0x18 (%RSP) | 0x0 | Etikett | 0x102C56916 <+214>: Movq $ 0x0, 0x18 (%RSP) | Null |
| 0x20 (%RSP) | "Testddlog:%@" | Format | 0x102C5691f <+223>: MOVQ %R10, 0x20 ( %RSP) | |
| 0x28 (%RSP) | Absender | Der erste Parameter variabler Parameter | 0x102C56924 <+228>: MOVQ %R14, 0x28 ( %RSP) | Ein Beispiel von Uibutton |
Wenn der Wert des Registers eine Zeichenfolge ist, wie
op-Parameter imrsi-Register, kann die Zeichenfolge direkt in LLDB überpo (char *) $rsi-Befehl gedruckt werden. Andernfalls kannpo $rsiverwendet werden, um einen Wert im Ganzzahlformat zu drucken.
Mit Hilfe der Versammlungssprache können wir uns auf ein auf niedriges Wissen untersucht, was beim Debuggen sehr notwendig ist. Ich bemühe mich sehr, das montagebezogene Wissen so detailliert wie möglich vorzustellen. Die Wissenshierarchie der Versammlung ist jedoch zu enorm, um in einem Artikel zu beschreiben. Bitte beziehen Sie sich auf die oben genannten Referenzen. Darüber hinaus wird das dritte Kapitel von CSApp - Maschinenebene -Darstellung eines Programms auch sehr empfohlen. Es ist ein seltenes gutes Material als Referenz.
Dieser Artikel zeigt das Verfahren des Debuggens in einem realen Fall. Einige Details werden geändert, um die Privatsphäre der Privatsphäre zu schützen.
Das Problem, über das wir sprechen werden, war, als ich einen Login -SDK entwickelte. Ein Benutzer behauptete, die App habe abgestürzt, als er auf die Schaltfläche "QQ" auf der Anmeldeseite gedrückt habe. Als wir dieses Problem debuggen, stellten wir fest, dass der Absturz stattgefunden hat, wenn die QQ -App nicht gleichzeitig installiert wurde. Wenn der Benutzer die QQ -Taste drückt, um eine Anmeldung zu benötigen, versucht das QQ -Login SDK, eine Autorisierungs -Webseite in unserer App zu starten. In diesem Fall tritt ein unerkannter Auswahlfehler [TCWebViewController setRequestURLStr:] auf.
PS: Um sich auf das Thema zu konzentrieren, sind die unnötigen Geschäftsdebug -Informationen unten nicht aufgeführt. In der Zwischenzeit wird Aadebug als App -Name verwendet.
Hier ist die Stapelspur dieses Absturzes:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TCWebViewController setRequestURLStr:]: unrecognized selector sent to instance 0x7fe25bd84f90'
*** First throw call stack:
(
0 CoreFoundation 0x0000000112ce4f65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001125f7deb objc_exception_throw + 48
2 CoreFoundation 0x0000000112ced58d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 AADebug 0x0000000108cffefc __ASPECTS_ARE_BEING_CALLED__ + 6172
4 CoreFoundation 0x0000000112c3ad97 ___forwarding___ + 487
5 CoreFoundation 0x0000000112c3ab28 _CF_forwarding_prep_0 + 120
6 AADebug 0x000000010a663100 -[TCWebViewKit open] + 387
7 AADebug 0x000000010a6608d0 -[TCLoginViewKit loadReqURL:webTitle:delegate:] + 175
8 AADebug 0x000000010a660810 -[TCLoginViewKit openWithExtraParams:] + 729
9 AADebug 0x000000010a66c45e -[TencentOAuth authorizeWithTencentAppAuthInSafari:permissions:andExtraParams:delegate:] + 701
10 AADebug 0x000000010a66d433 -[TencentOAuth authorizeWithPermissions:andExtraParams:delegate:inSafari:] + 564
………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
Lines of irrelevant information are removed here
………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
236
14 libdispatch.dylib 0x0000000113e28ef9 _dispatch_call_block_and_release + 12
15 libdispatch.dylib 0x0000000113e4949b _dispatch_client_callout + 8
16 libdispatch.dylib 0x0000000113e3134b _dispatch_main_queue_callback_4CF + 1738
17 CoreFoundation 0x0000000112c453e9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
18 CoreFoundation 0x0000000112c06939 __CFRunLoopRun + 2073
19 CoreFoundation 0x0000000112c05e98 CFRunLoopRunSpecific + 488
20 GraphicsServices 0x0000000114a13ad2 GSEventRunModal + 161
21 UIKit 0x0000000110d3f676 UIApplicationMain + 171
22 AADebug 0x0000000108596d3f main + 111
23 libdyld.dylib 0x0000000113e7d92d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Bevor wir über das Debuggen sprechen, lassen Sie uns mit der Nachricht in Objective-C vertraut. Wie wir wissen, verwendet Objective-C eher eine Messaging-Struktur als eine Funktionsaufruf. Der Hauptunterschied besteht darin, dass in der Messaging -Struktur die Laufzeit entscheidet, welche Funktion nicht kompiliert wird. Das heißt, wenn eine nicht anerkannte Nachricht an ein Objekt gesendet wird, wird während der Kompilierzeit nichts passieren. Und während der Laufzeit, wenn es eine Methode empfängt, die es nicht versteht, durchläuft ein Objekt eine Nachrichtenweiterleitung, ein Prozess, mit dem Sie als Entwickler der Nachricht mitteilen können, wie die unbekannte Nachricht umgeht.
Unter vier Methoden sind normalerweise während der Nachrichtenweiterung beteiligt:
+ (BOOL)resolveInstanceMethod:(SEL)sel : Diese Methode wird aufgerufen, wenn eine unbekannte Nachricht an ein Objekt übergeben wird. Diese Methode nimmt den Selektor an, der nicht gefunden wurde, und gibt einen booleschen Wert zurück, um anzugeben, ob eine Instanzmethode zur Klasse hinzugefügt wurde, die jetzt diesen Selektor verarbeiten kann. Wenn die Klasse diesen Selektor verarbeiten kann, geben Sie Ja zurück, dann ist der Vorwärtsverfahren abgeschlossen. Diese Methode wird häufig verwendet, um auf @dynamic -Eigenschaften von NsmanagedObjects in Coredata auf dynamisch zuzugreifen. + (BOOL)resolveClassMethod:(SEL)sel -Methode ist ähnlich wie die obige Methode. Der einzige Unterschied ist diese eine Klassenmethode, die andere ist die Instanzmethode.
- (id)forwardingTargetForSelector:(SEL)aSelector : Diese Methode bietet einen zweiten Empfänger für die Handhabung einer unbekannten Nachricht, und es ist schneller als forwardInvocation: . Diese Methode kann verwendet werden, um einige Merkmale der multiplen Vererbung zu imitieren. Beachten Sie, dass es keine Möglichkeit gibt, die Nachricht mit diesem Teil des Weiterleitungswegs zu manipulieren. Wenn die Nachricht vor dem Senden an den Ersatzempfänger geändert werden muss, muss der vollständige Weiterleitungsmechanismus verwendet werden.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector : Wenn der Weiterleitungsalgorithmus so weit gekommen ist, wird der vollständige Weiterleitungsmechanismus gestartet. NSMethodSignature wird nach dieser Methode zurückgegeben, die die Methodenbeschreibung im Asselektorparameter enthält. Beachten Sie, dass diese Methode überschrieben werden muss, wenn Sie ein NSInvocation -Objekt erstellen möchten, das Selektor, Ziel und Argumente während der Nachrichtenübermittlung enthält.
- (void)forwardInvocation:(NSInvocation *)anInvocation : Die Implementierung dieser Methode muss unterhalb der Teile enthält: Ermitteln Sie das Objekt, das die AnInvocation -Nachricht verarbeiten kann. Wenn Sie eine Nachricht an dieses Objekt senden, speichert die AnInvocation den Rückgabewert, Laufzeit und sendet dann den Rückgabewert an den ursprünglichen Nachrichtensend. Tatsächlich kann diese Methode das gleiche Verhalten mit forwardingTargetForSelector: Methode durch einfaches Ändern des Aufrufziels haben und danach aufrufen, aber wir tun das kaum.
Normalerweise werden die ersten beiden Methoden, die für die Weiterleitung der Nachrichten verwendet werden, als schnelle Weiterleitung bezeichnet, da es eine viel schnellere Möglichkeit bietet, die Nachrichtenweiterleitung durchzuführen. Um von der schnellen Weiterleitung zu unterscheiden, werden die Methode 3 und 4 als normale Weiterleitung oder regelmäßige Weiterleitung bezeichnet. Es ist viel langsamer, weil es ein NSInvocation -Objekt erstellen muss, um die Nachricht zu vervollständigen.
HINWEIS: Wenn die
methodSignatureForSelector-Methode nicht außer Kraft gesetzt oder die zurückgegebeneNSMethodSignatureNIL ist, wirdforwardInvocationnicht aufgerufen, und die Nachrichtenweiterleitung wird beendet, wobei der Fehler mitdoesNotRecognizeSelector-Fehler angesprochen wird. Wir können es aus dem Quellcode der__forwarding__-Funktion unten sehen.
Der Prozess der Nachrichtenweiterleitung kann durch ein Durchflussdiagramm beschrieben werden, siehe unten.

Wie im Flussdiagramm beschrieben, hat der Empfänger bei jedem Schritt die Möglichkeit, die Nachricht zu verarbeiten. Jeder Schritt ist teurer als der vor ihm. Die beste Praxis besteht darin, den Nachrichten -Weiterleitungsprozess so früh wie möglich zu behandeln. Wenn die Nachricht nicht über den gesamten Prozess behandelt wird, wird doesNotRecognizeSeletor , dass der Fehler nicht anerkannt wird, dass der Selektor vom Objekt nicht erkannt werden kann.
Es ist Zeit, den theoretischen Teil zu beenden und zu dem Thema zurückzukehren.
Laut den TCWebViewController -Informationen aus dem Trace -Stack verbinden wir sie natürlich mit dem Tencent SDK Tencentopenapi.Framework , aber wir haben das Tencent SDK kürzlich nicht aktualisiert, was bedeutet, dass der Absturz nicht durch Tencentopenapi.Framework verursacht wurde.
Zunächst haben wir den Code abgebrochen und die Struktur der TCWebViewController -Klasse erhalten
@class TCWebViewController : UIViewController<UIWebViewDelegate, NSURLConnectionDelegate, NSURLConnectionDataDelegate> {
@property webview
@property webTitle
@property requestURLStr
@property error
@property delegate
@property activityIndicatorView
@property finished
@property theData
@property retryCount
@property hash
@property superclass
@property description
@property debugDescription
ivar _nloadCount
ivar _webview
ivar _webTitle
ivar _requestURLStr
ivar _error
ivar _delegate
ivar _xo
ivar _activityIndicatorView
ivar _finished
ivar _theData
ivar _retryCount
-setError:
-initWithNibName:bundle:
-dealloc
-stopLoad
-doClose
-viewDidLoad
-loadReqURL
-viewDidDisappear:
-shouldAutorotateToInterfaceOrientation:
-supportedInterfaceOrientations
-shouldAutorotate
-webViewDidStartLoad:
-webViewDidFinishLoad:
-webView:didFailLoadWithError:
-webView:shouldStartLoadWithRequest:navigationType:
}
Aus dem Ergebnis der statischen Analyse gab es in TCWebViewController keine Setter- und Getter -Methode von requestURLStr . Da es in der vorherigen App -Version keinen solchen Absturz gab, haben wir eine Idee herausgekommen: Würde die Eigenschaft in TCWebViewController auf dynamische Weise implementiert, die @dynamic verwendet, um dem Compiler nicht Getter und Setter für die Eigenschaft während der Kompilierzeit zu erstellen, aber dynamisch erstellt, um zu Laufzeiten wie Kerndatenrahmen zu erstellen? Dann beschlossen wir, tief über die Idee zu gehen, um zu sehen, ob unsere Vermutung korrekt war. Während unserer Verfolgung stellten wir fest, dass es eine Kategorie NSObject(MethodSwizzlingCategory) für NSObject in Tencentopenapi.Framework gab, was sehr misstrauisch war. In dieser Kategorie gab es einen Methode switchMethodForCodeZipper , dessen Implementierung die Methoden für methodSignatureForSelector und forwardInvocation -Methoden von QQmethodSignatureForSelector und QQforwardInvocation -Methoden ersetzte.
void +[ NSObject switchMethodForCodeZipper ]( void * self, void * _cmd) {
rbx = self;
objc_sync_enter (self);
if (*( int8_t *)_g_instance == 0x0 ) {
[ NSObject swizzleMethod: @selector ( methodSignatureForSelector: ) withMethod: @selector ( QQmethodSignatureForSelector: )];
[ NSObject swizzleMethod: @selector ( forwardInvocation: ) withMethod: @selector ( QQforwardInvocation: )];
*( int8_t *)_g_instance = 0x1 ;
}
rdi = rbx;
objc_sync_exit (rdi);
return ;
} Anschließend verfolgten wir QQmethodSignatureForSelector -Methode und es gab eine Methode namens _AddDynamicPropertysSetterAndGetter . Aus dem Namen können wir leicht davon abhalten, dass diese Methode die Setter- und Getter -Methode für Eigenschaften dynamisch hinzufügen kann. Dieser gefundene Fund kann wesentlich überprüfen, ob unsere ursprüngliche Vermutung korrekt ist.
void * -[ NSObject QQmethodSignatureForSelector: ]( void * self, void * _cmd, void * arg2) {
r14 = arg2;
rbx = self;
rax = [ self QQmethodSignatureForSelector: rdx];
if (rax == 0x0 ) {
rax = sel_getName (r14);
_AddDynamicPropertysSetterAndGetter ();
rax = 0x0 ;
if ( 0x0 != 0x0 ) {
rax = [rbx methodSignatureForSelector: r14];
}
}
return rax;
} Aber warum kann der Setter in TCWebViewController -Klasse nicht erkennen? Liegt es daran, dass die Methode QQMethodSignatureForSelector während unserer Entwicklung dieser Version behandelt wurde? Wir konnten jedoch keine Ahnung finden, dass wir überall im Code überall durchgingen. Das war sehr enttäuschend. Bisher erfolgt die statische Analyse. Der nächste Schritt besteht darin, LLDB zu verwenden, um den Tencent SDK dynamisch zu debuggen, um herauszufinden, welchen Pfad die Erstellung von Getter und Setter im Nachrichten -Weiterleitungsprozess gebrochen hat.
Wenn wir versuchen, den Breakpoint auf
setRequestURLStrdurch LLDB -Befehl festzulegen, werden wir feststellen, dass wir es nicht schaffen. Der Grund dafür ist, dass der Setter während der Kompilierzeit nicht verfügbar ist. Dies kann auch unsere ursprüngliche Vermutung überprüfen.
Laut der Crash -Stack -Trace können wir schließen, dass setRequestURLStr in -[TCWebViewKit open] -Methode genannt wird. Dies bedeutet, dass der Absturz während der Überprüfung der SDK -Überprüfung des Tencent stattfindet, wenn die QQ -App installiert ist, und das Öffnen des Fortschritts der Authentifizierungs -Webseite.
Dann verwenden wir den folgenden LLDB -Befehl, um den Breakpoint für diese Methode festzulegen:
br s -n "-[TCWebViewKit open]"
br sist die Abkürzung fürbreakpoint set,-nstellt den Breakpoint nach dem Methodennamen nach ihm dar, der das gleiche Verhalten mit symbolischem Breakpoint hat.br s -Fkann auch den Haltepunkt festlegen.b -[TCWebViewKit open]funktioniert auch hier, aberbist hier die Abkürzung von_regexp-break, der den regulären Ausdruck verwendet, um den Haltepunkt festzulegen. Am Ende können wir auch den Haltepunkt in der Speicheradresse wiebr s -a 0x000000010940b24eeinstellen, um den Block zu debuggen, wenn die Adresse des Blocks verfügbar ist.
Inzwischen wird der Haltepunkt erfolgreich eingestellt.
Breakpoint 34: where = AADebug`-[TCWebViewKit open], address = 0x0000000103157f7d
Wenn die App die Webauthentifizierungsseite starten wird, wird das Projekt auf diesem Haltepunkt gestoppt. Siehe unten:

Dieser Screenshot wird erfasst, wenn die App auf Simulator ausgeführt wird, sodass der Montagecode auf X64 basiert. Wenn Sie das iPhone -Gerät verwenden, sollte der Montagecode Arm sein. Die Analysemethode ist für sie jedoch gleich. Bitte bemerken Sie sie.
Legen Sie einen Haltepunkt in Zeile 96 fest, dieser Assembly -Code ist der Aufruf setRequestURLStr -Methoden und drucke dann den Inhalt des rbx -Registers aus, und dann können wir feststellen, dass die TCWebViewController -Instanz in diesem Register gespeichert ist.

Als nächstes können wir LLDB verwenden, um den Haltepunkt für QQmethodSignatureForSelector -Methode festzulegen:
br s -n "-[NSObject QQmethodSignatureForSelector:]"
Geben Sie c in LLDB ein, um den Breakpoint fortzusetzen, und dann wird der Breakpoint innerhalb QQmethodSignatureForSelector -Methode stehen, die nachweisen kann, dass unsere vorherige Vermutung zu QQmethodSignatureForSelector -Methoden, die mit unserem Code widersprüchlich sind, ungültig ist.

Legen Sie einen Haltepunkt am Ende der QQmethodSignatureForSelector -Methode fest, dh der Befehl retq in Zeile 31. Drucken Sie dann die Speicheradresse von Register rax , siehe unten: Screenshot:

Durch das Drucken der Speicheradresse 0x00007fdb36d38df0 des Registers rax wird das NSMethodSignature -Objekt zurückgegeben. Gemäß der Entwurfskonvention über X86 -Assemblersprache wird der Rückgabewert in Register rax gespeichert. Anscheinend wird die QQmethodSignatureForSelector -Methode aufgerufen und gibt den richtigen Wert zurück, was bedeutet, dass wir das Problem weiter verfolgen müssen.
Setzen Sie den Haltepunkt auf QQforwardInvocation über LLDB:
br s -n "-[NSObject QQforwardInvocation:]"
Nachdem der Haltepunkt festgelegt wurde, setzen Sie die Programmausführung fort, die App stürzt ab. Und die QQforwardInvocation -Methode wurde noch nicht aufgerufen. Damit können wir schließen, dass die QQforwardInvocation -Methode von unserem Code in Konflikt gerät.

___forwarding___ Die Funktion enthält die gesamte Implementierung des Nachrichten-Weiterleitungsmechanismus. Der Dekompilierungscode wird aus Objective-C 消息发送与转发机制原理 ausgewählt. In diesem Artikel gibt es ein Urteil, das zwischen forwarding und receiver bei der Aufrufen forwardingTargetForSelector falsch sein sollte. Hier sollte es ein Urteil zwischen forwardingTarget und receiver sein. Siehe Code unten:
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// call forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// Zombie Object
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// call methodSignatureForSelector first to get method signature , then call forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// if selector already registered in Runtime
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
Grundsätzlich können wir ein klares Verständnis haben, indem wir den Dekompilierungscode lesen: Zuerst forwardingTargetForSelector -Methode während des Nachrichten -Weiterleitungsprozesses, um den Ersatzempfänger zu erhalten, der auch als schnelle Weiterleitungsphase bezeichnet wird. Wenn die forwardingTarget NIL zurückgibt oder denselben Empfänger zurückgibt, verwandelt sich die Meldung in regelmäßige Weiterleitungsphase. Grundsätzlich Invoching methodSignatureForSelector -Methode, um die Methodensignatur zu erhalten, und dann mit frameStackPointer zum Instanziieren invocation -Objekten verwendet. Rufen Sie dann forwardInvocation: Methode des receiver auf und geben Sie das vorherige invocation als Argument über. Letztendlich wird die methodSignatureForSelector -Methode nicht implementiert und der selector bereits im Laufzeitsystem registriert, wird doesNotRecognizeSelector: um einen Fehler zu werfen.
Wenn Sie die ___forwarding___ aus der Crash -Stapel -Trace untersuchen, können wir feststellen, dass es als zweiter Pfad unter dem gesamten Meldungspfad bezeichnet wird, was bedeutet, dass NSInvocation -Objekt beim Aufrufen forwardInvocation aufgerufen wird.
Sie können den Befehlsbefehl auch Schritt für Schritt nach dem Haltepunkt ausführen, um den Ausführungspfad des Montagecode zu beobachten. Das gleiche Ergebnis sollte beobachtet werden.

Und welche Methode wird ausgeführt, wenn forwardInvocation aufgerufen wird? Aus der Stapelverfolgung können wir sehen, dass eine Methode mit dem Namen __ASPECTS_ARE_BEING_CALLED__ ausgeführt wird. Wenn Sie sich über diese Methode des gesamten Projekts ansehen, finden wir endlich herausgefunden, dass forwardInvocation nach Aspects Framework süchtig ist.
static void aspect_swizzleForwardInvocation ( Class klass) {
NSCParameterAssert (klass);
// If there is no method, replace will act like class_addMethod.
IMP originalImplementation = class_replaceMethod (klass, @selector ( forwardInvocation: ), ( IMP )__ASPECTS_ARE_BEING_CALLED__, " v@:@ " );
if (originalImplementation) {
class_addMethod (klass, NSSelectorFromString (AspectsForwardInvocationSelectorName), originalImplementation, " v@:@ " );
}
AspectLog ( @" Aspects: %@ is now aspect aware. " , NSStringFromClass (klass));
} // This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__ (__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSLog ( @" selector: %@ " , NSStringFromSelector (invocation. selector ));
NSCParameterAssert (self);
NSCParameterAssert (invocation);
SEL originalSelector = invocation. selector ;
SEL aliasSelector = aspect_aliasForSelector (invocation. selector );
invocation. selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject (self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass ( object_getClass (self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc ] initWithInstance: self invocation: invocation];
NSArray *aspectsToRemove = nil ;
// Before hooks.
aspect_invoke (classContainer. beforeAspects , info);
aspect_invoke (objectContainer. beforeAspects , info);
// Instead hooks.
BOOL respondsToAlias = YES ;
if (objectContainer. insteadAspects . count || classContainer. insteadAspects . count ) {
aspect_invoke (classContainer. insteadAspects , info);
aspect_invoke (objectContainer. insteadAspects , info);
} else {
Class klass = object_getClass (invocation. target );
do {
if ((respondsToAlias = [klass instancesRespondToSelector: aliasSelector])) {
[invocation invoke ];
break ;
}
} while (!respondsToAlias && (klass = class_getSuperclass (klass)));
}
// After hooks.
aspect_invoke (classContainer. afterAspects , info);
aspect_invoke (objectContainer. afterAspects , info);
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation. selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString (AspectsForwardInvocationSelectorName);
if ([ self respondsToSelector: originalForwardInvocationSEL]) {
(( void ( *)( id , SEL , NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
} else {
[ self doesNotRecognizeSelector: invocation.selector];
}
}
// Remove any hooks that are queued for deregistration.
[aspectsToRemove makeObjectsPerformSelector: @selector ( remove )];
} Da TCWebViewController eine private Klasse von Tencent SDK ist, wurde sie unwahrscheinlich von anderen Klasse. Es ist jedoch möglich, dass seine Superklasse begeistert ist, was auch diese Klasse beeinflussen kann. Mit dieser Vermutung haben wir weiter gegraben. Endlich tauchte die Antwort auf! Durch Entfernen oder Kommentieren des Code, der UIViewController haken, stürzt die App nicht zum Stürzen, wenn Sie über QQ anmelden. Bisher waren wir uns definitiv sicher, dass der Absturz nach Aspects Framework beteiligt war.

doesNotRecognizeSelector: error is thrown by __ASPECTS_ARE_BEING_CALLED__ method which is used to replace the IMP of forwardInvocation: method by Aspects . The implementation of __ASPECTS_ARE_BEING_CALLED__ method has the corresponding time slice for before, instead and after the hooking in Aspect . Among above code, aliasSelector is a SEL which is handled by Aspects , like aspects__setRequestURLStr: .
In Instead hooks part, invocation.target will be checked if it can respond to aliasSelector. If subclass cannot respond, the superclass will be checked, the superclass's superclass, until root class. Since the aliasSelector cannot be responded, respondsToAlias is false. Then originalSelector is assigned to be a selector of invocation. Next objc_msgSend invokes the invocation to call the original SEL. Since TCWebViewController cannot respond the originalSelector:setRequestURLStr: method, it finally runs to ASPECTS_ARE_BEING_CALLED method of Aspects and doesNotRecognizeSelector: method is threw accordingly, which is the root cause of the crash we talked about in the beginning of this article.
Some careful reader might already realize the crash could be involved with Aspects, since seeing line ASPECTS_ARE_BEING_CALLED at line 3 of the crash stack trace. The reason I still listed all the attempts here is that I hope you can learn how to locate a problem from a third-part framework without source code through static analysis and dynamic analysis. Hope the tricks and technology mentioned in this article can be helpful for you.
There are two available ways to fix the crash. One is hooking the method of Aspects which is less invasive, for example Method Swizzling, then the setter creation during the message forwarding process for TencentOpenAPI would not be interrupted. Another is replace forwardInvocation: with ours implementation, if both aliasSelector and ``originalSelector cannot response to the message forwarding, we can forward the message forwarding path back into the original path. Refer to the code below:
if (!respondsToAlias) {
invocation. selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString (AspectsForwardInvocationSelectorName);
(( void ( *)( id , SEL , NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
} In fact, Aspects has conflicts with JSPatch . Since the implementation of these two SDK are similar too, doesNotRecognizeSelector: happens too when they are used together. Please Refer to 微信读书的文章.
The root cause of this crash is the conflict between Aspects and TencentOpenAPI frameworks. The life cycle method of UIViewController class is hooked by Aspects, and the forwardInvocation method is replaced with the Aspects's implementation. Also, because of the superclass of TCWebViewController is UIViewController class. As a result, QQforwardInvocation method of TCWebViewController class is hooked by Aspects too. That leads to the message forwarding process failed, thus, the creation of getter and setter fails too.
This case tells us, we should not only learn how to use a third-part framework, but also need to look into the mechanism of it. Only then, we can easily to locate the problem we meet during our work.
We introduce different kinds of tips in this article, but we hope you can also master a way of thinking when debugging. Skills are easy to be learned, but the way you think when resolving problem is not easy to be formed. It takes time and practice. Besides kinds of debugging techniques, you also have to have a good sense of problem analysis, then the problem will be handy for you.
Special thanks to below readers, I really appreciate your support and valuable suggestions.