1. Hintergrundwissen1. Einführung in ARM64-Register2. Detaillierte Erklärung der STP-Anweisung (ARMV8-Handbuch)Schauen wir uns zunächst das Befehlsformat (64 Bit) und den Einfluss der Befehle auf die Ausführungsergebnisse der Registermaschine an. Typ 1, STP <Xt1>, <Xt2>, [<Xn|SP>],#<imm> Speichern Sie Xt1 und Xt2 im Adressspeicher, der Xn | SP entspricht, und ändern Sie dann die Adresse von Xn | SP in die neue Adresse von Xn | SP + imm-Offset Typ 2, STP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]! Speichern Sie Xt1 und Xt2 im Adressspeicher, der der Adresse von Xn | SP plus imm entspricht, und ändern Sie dann die Adresse von Xn | SP nach dem Offset von Xn | SP + imm in die neue Adresse. Typ 3, STP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}] Speichern Sie Xt1 und Xt2 im Adressspeicher, der der Adresse von Xn|SP plus imm entspricht Im Handbuch gibt es drei Arten von Opcodes, und wir besprechen nur die letzten beiden, die im Programm vorkommen. Der Pseudocode lautet wie folgt: Gemeinsame Dekodierung für alle Kodierungen Ganzzahl n = UInt(Rn); Ganzzahl t = UInt(Rt); Ganzzahl t2 = UInt(Rt2); wenn L:opc<0> == '01' || opc == '11' dann UNDEFINIERT; Ganzzahlskala = 2 + UInt(opc<1>); ganzzahlige Datengröße = 8 << Skalierung; Bits (64) Offset = LSL (SignExtend (imm7, 64), Skalierung); Boolescher Wert tag_checked = wback || n != 31; Betrieb für alle Kodierungen Bits(64)-Adresse; Bits (Datengröße) Daten1; Bits (Datengröße) Daten2; Konstante Ganzzahl dbytes = Datengröße DIV 8; Boolescher Wert rt_unbekannt = FALSCH; wenn HaveMTEExt() dann SetNotTagCheckedInstruction(!tag_checked); wenn wback && (t == n || t2 == n) && n != 31 dann Einschränkung c = ConstrainUnpredictable(); behaupten c IN {Constraint_NONE, Constraint_UNKNOWN, Constraint_UNDEF, Constraint_NOP}; Fall c von wenn Constraint_NONE rt_unknown = FALSE; // gespeicherter Wert ist vor dem Rückschreiben wenn Constraint_UNKNOWN rt_unknown = TRUE; // gespeicherter Wert ist UNKNOWN wenn Constraint_UNDEF UNDEFINED; wenn Constraint_NOP EndOfInstruction(); wenn n == 31 dann Überprüfen Sie die SPA-Ausrichtung (); Adresse = SP[]; anders Adresse = X[n]; wenn !postindex dann Adresse = Adresse + Offset; wenn rt_unknown && t == n dann Daten1 = Bits (Datengröße) UNBEKANNT; anders Daten1 = X[t]; wenn rt_unknown && t2 == n dann Daten2 = Bits (Datengröße) UNBEKANNT; anders Daten2 = X[t2]; Mem[Adresse, dbytes, AccType_NORMAL] = Daten1; Mem[Adresse+Dbytes, Dbytes, AccType_NORMAL] = Daten2; wenn wir damals wenn Postindex dann Adresse = Adresse + Offset; wenn n == 31 dann SP[] = Adresse; anders X[n] = Adresse; Der rote Teil entspricht der Schlüssellogik des Stapelschiebens. Die Bedeutung anderer Montageanweisungen finden Sie im armv8-Handbuch oder bei Baidu. 2. Ein BeispielNachdem wir nun mit den oben genannten Teilen vertraut sind, schauen wir uns ein Beispiel an: Der C-Code lautet wie folgt: Die Demontage mehrerer verwandter Funktionen erfolgt wie folgt (normalerweise gibt es nur zwei Anweisungen im Zusammenhang mit dem Stapel-Push): Haupt\f3\f4\strlen Nach dem Durchlaufen von gdb können wir sehen, dass strlen SEGFAULT auslöst, was dazu führt, dass der Prozess hängen bleibt Nachdem der obige Code kompiliert wurde, gibt es keinen Streifen, sodass die Elf-Datei Symbole enthält Überprüfen Sie den Betriebsstatus (Inforegister): Achten Sie auf die vier Register $29, $30, SP und PC Eine Kernidee: Die CPU führt Anweisungen und keinen C-Code aus, und Funktionsaufrufe und -rückgaben sind tatsächlich der Prozess des Pushens und Poppens des Thread-Stapels. Als nächstes schauen wir uns an, wie die obige Aufrufbeziehung im aktuellen Aufgabenstapel funktioniert: Die Beziehung zwischen Funktionsaufrufen im Stapel (Funktionsaufruf verschiebt den Stapel, die Adresse verringert sich; Rückgabe verschiebt den Stapel, die Adresse erhöht sich): Nachfolgend sehen Sie den Vorgang des Stapelschiebens (Hervorhebung) Werfen wir einen Blick zurück auf die vorherige Zusammenstellung: Haupt\f3\f4\strlen Ausgehend vom aktuellen sp ist Frame 0 strlen und der Stapel ist nicht geöffnet, sodass die aufrufende Funktion der vorherigen Ebene immer noch x30 ist. Daraus lässt sich schließen, dass Frame 1 f3 aufruft Die Starteintragsassemblierung der Funktion f3: (gdb) x/2i f3 0x400600 <f3>: stp x29, x30, [sp,#-48]! 0x400604 <f3+4>: mov x29, sp Es ist ersichtlich, dass der von der Funktion f3 geöffnete Stapelspeicher 48 Byte beträgt. Daher ist die Oberseite des Stapels von Frame2 der aktuelle SP + 48 Byte: 0xffffffffff2c0 (gdb) x/gx 0xfffffffff2c0+8 0xffffffffff2c8: 0x000000000040065c (gdb) x/i 0x000000000040065c 0x40065c <f4+36>: mov w0, #0x0 // #0 Die Funktion von Frame2 ist sp+8: 0x000000000040065c -> <f4+36> Weiter mit dem Zurückschieben der Funktion von Frame1 von sp = 0xffffffffff2c0 Die Starteintragsassemblierung der Funktion f4 ist: (gdb) x/2i f4 0x400638 <f4>: stp x29, x30, [sp,#-48]! 0x40063c <f4+4>: mov x29, sp Es ist ersichtlich, dass der von der Funktion f4 geöffnete Stapelspeicher ebenfalls 48 Byte beträgt. Daher ist die Oberseite des Stapels von Frame3 das aktuelle 0xffffffffff2c0 + 48 Byte: 0xffffffffff2f0 Die Funktion von Frame2 ist 0xffffffff2c0 + 8: 0x000000000040065c -> <f4+36> (gdb) x/gx 0xfffffffff2f0+8 0xffffffffff2f8: 0x0000000000400684 (gdb) x/i 0x0000000000400684 0x400684 <main+28>: mov w0, #0x0 // #0 Daher ist die Funktion von Frame3 die Hauptfunktion, und die Spitze des Stapels, die der Hauptfunktion entspricht, ist 0xffffffffff320 Damit ist die Ableitung abgeschlossen (Interessierte können die Ableitung fortsetzen und sehen, wie libc main startet). Zusammenfassen: Der Schlüssel zum Push-Stack:
3. Praktische ErklärungDer folgende Kern ist in der Szene verfügbar: Wie Sie sehen, können alle Symbole nicht gefunden werden. Auch nach dem Laden der Symboltabelle funktioniert es immer noch nicht und der eigentliche Aufrufstapel kann nicht analysiert werden. (gdb) bt #0 0x0000ffffaeb067bc in ?? () von /lib64/libc.so.6 #1 0x0000aaaad15cf000 in ?? () Backtrace gestoppt: vorheriges Frame innerhalb dieses Frames (Stapel beschädigt?) Schauen Sie sich zunächst das Inforegister an und achten Sie auf die Werte der vier Register x29, x30, sp und pc Abgeleiteter Aufgabenstapel: Exportieren Sie zuerst den SP-Inhalt: Die folgende Abbildung zeigt das tatsächliche Ergebnis. Lassen Sie uns im Detail beschreiben, wie man es ableitet. pc stellt den aktuell ausgeführten Funktionsbefehl dar. Wenn der aktuelle Befehl nicht geöffnet ist, stellt x30 im Allgemeinen den nächsten Befehl des vorherigen Frames dar, der die aktuelle Funktion aufruft. Wenn man sich die Assembly ansieht, kann sie in die folgende Funktion umgewandelt werden (gdb) x/i 0xaaaacd3de4fc 0xaaaacd3de4fc <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+108>: mov x27, x0 Nachdem Sie die oberste Funktion des Stapels gefunden haben, überprüfen Sie den Stapelbetrieb der Funktion: (gdb) x/6i PGXCNodeConnStr 0xaaaacd3de490 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)>: sub sp, sp, #0xd0 0xaaaacd3de494 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+4>: stp x29, x30, [sp,#80] 0xaaaacd3de498 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+8>: füge x29, sp, #0x50 hinzu Es ist ersichtlich, dass der vorherige Frame beim aktuellen sp + 0xd0 - 0x80 vorhanden ist, also 0xfffec4cebd40 + 0xd0 - 0x80 = 0xfffec4cebd90, und dass sich der untere Teil des Stapels bei 0xfffec4cebd40 + 0xd0 = 0xfffec4cebe10 befindet. Daher finden wir die Oberseite des Stapels, die dem Rahmen der nächsten Ebene und der LR-Rückgabeanweisung der vorherigen Ebene entspricht. Durch Umkehren können wir die Funktion build_node_conn_str erhalten (gdb) x/i 0x0000aaaacd414e08 0xaaaacd414e08 <build_node_conn_str(Oid, DatabasePool*)+224>: mov x21, x0 Wenn wir die obige Ableitung wiederholen, können wir sehen, dass die Funktion build_node_conn_str einen 176-Byte-Stapel öffnet. (gdb) x/4i build_node_conn_str 0xaaaacd414d28 <build_node_conn_str(Oid, DatabasePool*)>: stp x29, x30, [sp,#-176]! 0xaaaacd414d2c <build_node_conn_str(Oid, DatabasePool*)+4>: mov x29, sp Also weiter mit 0xfffec4cebe10 + 176 = 0xfffec4cebec0 Überprüfen Sie den Anrufer 0xfffec4cebe10+8 für reload_database_pools Weiter zu reload_database_pools (gdb) x/8i Datenbankpools neu laden 0xaaaacd4225e8 <reload_database_pools(PoolAgent*)>: sub sp, sp, #0x1c0 0xaaaacd4225ec <reload_database_pools(PoolAgent*)+4>: adrp x5, 0xaaaad15cf000 0xaaaacd4225f0 <reload_database_pools(PoolAgent*)+8>: adrp x3, 0xaaaacf0ed000 0xaaaacd4225f4 <reload_database_pools(PoolAgent*)+12>: adrp x4, 0xaaaaceeed000 <_ZN4llvm18ConvertUTF8toUTF16EPPKhS1_PPtS3_NS_15ConversionFlagsE> 0xaaaacd4225f8 <reload_database_pools(PoolAgent*)+16>: füge x3, x3, #0x9e0 hinzu 0xaaaacd4225fc <reload_database_pools(PoolAgent*)+20>: adrp x1, 0xaaaacf0ee000 <_ZZ25PoolManagerGetConnectionsP4ListS0_E8__func__+24> 0xaaaacd422600 <reload_database_pools(PoolAgent*)+24>: stp x29, x30, [sp,#-96]! Der eigentliche Stapel wird bei 0x220 Bytes geöffnet, daher ist der Stapelboden dieses Frames 0xfffec4cebec0 + 0x220 = 0xfffec4cec0e0 Daher ist die Struktur der grundlegenden Anrufbeziehung wie folgt Das Obige reicht grundsätzlich aus, um das Problem zu analysieren, sodass keine weiteren Ableitungen erforderlich sind TIPPS: Diese Anweisung wird im Allgemeinen bei Aufrufen unter der ARM-Architektur verwendet. stp x29, x30, [sp,#immediate]! mit oder ohne Ausrufezeichen Daher speichert jede Frame-Ebene die Stapel-Top-Adresse und den LR-Befehl der vorherigen Frame-Ebene. Durch genaues Auffinden des Stapel-Tops des unteren Frames 0 können alle Aufrufbeziehungen schnell abgeleitet werden (der Teil, der von roten gestrichelten Kreisen umkreist ist). Die umgekehrte Lösung der Funktion hängt von der Symboltabelle ab. Solange das Symbolsegment der ursprünglichen Elf-Datei nicht entfernt wird, kann das entsprechende Funktionssymbol gefunden werden (überprüfen Sie es mit readelf -S). Nachdem der Rahmen ermittelt wurde, kann der Inhalt in jeder Ebene des Rahmens in Kombination mit der Baugruppe grundsätzlich zum Ableiten der Prozessvariablen verwendet werden. Oben finden Sie eine kurze Analyse des detaillierten Inhalts des Funktionsaufrufprozesses unter der ARM-Architektur. Weitere Informationen zum Funktionsaufrufprozess unter der ARM-Architektur finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Beispielcode mit SCSS in Uni-App
>>: Detaillierte Erklärung zur Verwendung von Element-Plus in Vue3
Der ECS-Cloud-Server, der mit dem historischen Li...
Leerer Link: Das heißt, es besteht keine Verbindu...
Vorwort Dieser Artikel stellt hauptsächlich die d...
Hinweis: Beim Schreiben der Datei docker-compose....
Frage: Als ich kürzlich an Projektstatistiken arb...
1. Laden Sie Navicat für MySQL 15 herunter https:...
Inhaltsverzeichnis 1. Constraint-Konzepte und Kla...
1. Methoden zur Implementierung von Komponenten:組...
Die Indizierung ähnelt dem Erstellen bibliografis...
Vorwort Solche Spezialeffekte sollte man oft sehe...
1. Finden Sie eine geeignete Version von Redis fü...
Vor einiger Zeit musste das Projekt die Funktion ...
Hintergrund Beim Ausführen einer SQL-Abfrage habe...
Hintergrund Heutzutage werden die Projekte des Un...
sed ist ein Zeichenstromeditor unter Unix, also e...