ARM: Verknüpfungsregister und Rahmenzeiger

Lesezeit: 8 Minuten

Benutzeravatar von user2233706
Benutzer2233706

Ich versuche zu verstehen, wie das Link-Register und der Frame-Zeiger in ARM funktionieren. Ich war schon auf ein paar Seiten und wollte mein Verständnis bestätigen.

Angenommen, ich hätte den folgenden Code:

int foo(void)
{
    // ..
    bar();
    // (A)
    // ..
}

int bar(void)
{
    // (B)
    int b1;
    // ..
    // (C)
    baz();
    // (D)
}

int baz(void)
{
    // (E)
    int a;
    int b;
    // (F)
}

und ich nenne foo(). Würde das Verknüpfungsregister die Adresse für den Code an Punkt (A) enthalten und der Rahmenzeiger die Adresse an dem Code an Punkt (B) enthalten? Und der Stapelzeiger könnte irgendwo innerhalb von bar() sein, nachdem alle Einheimischen deklariert wurden?

  • Ich bin mir nicht sicher, was Sie mit “der Stapelzeiger könnte irgendwo innerhalb von bar() sein” meinen. Außerdem scheinen Sie zu fragen, wie der Stand dieser Dinge wann sein könnte foo() Anrufe bar()nicht wenn etwas ruft foo() (aber vielleicht verstehe ich die Frage falsch).

    – Michael Burr

    1. April 2013 um 21:50 Uhr

  • Ja, ich meinte den Stand der Dinge, wenn foo() bar() aufruft. Was ich in Bezug auf den SP meinte, war, dass der SP, nachdem die lokalen Variablen deklariert und auf den Stapel gelegt wurden, auf die Spitze des Stapels zeigt, wo die letzte lokale Variable deklariert wurde.

    – Benutzer2233706

    1. April 2013 um 21:52 Uhr

  • mögliches Duplikat von Was sind SP (Stack) und LR in ARM?

    – ungekünstelter Lärm

    27. Februar 2014 um 19:44 Uhr

  • Kein Duplikat; SP != FP. Dieser Link erwähnt FP nicht.

    – Nick Desaulniers

    18. März 2020 um 17:58 Uhr

  • Ja, es ist kein Duplikat im Sinne eines Stapelüberlaufs. Es enthält relevante Informationen. SP und FP sind verwandt, aber nicht dasselbe. Außerdem sind LR und PC verwandt, aber in der Frage werden sie möglicherweise nicht erwähnt. Ich glaube, dass Betrachter dieser Frage die Funktionsmaschinerie verstehen wollen. Vielleicht hätte ich “relevant” statt “Duplikat” sagen sollen. Sicherlich hängen die Fragen zusammen.

    – ungekünstelter Lärm

    13. November 2020 um 14:32 Uhr

Benutzeravatar von artless noise
kunstloser Lärm

Einige Registeraufrufkonventionen sind abhängig von der ABI (Anwendungs-Binärschnittstelle). Das FP ist erforderlich in der APCS Standard und nicht in den neueren AAPCS (2003). Für die AAPCS (GCC 5.0+) die FP nicht haben zu verwenden, aber sicherlich sein kann; Debug-Informationen werden mit Stack- und Frame-Zeiger-Nutzung für Stack-Tracing und Unwinding-Code mit kommentiert AAPCS. Wenn eine Funktion ist staticmuss sich ein Compiler wirklich an keine Konventionen halten.

Im Allgemeinen sind alle ARM-Register allgemeiner Zweck. Das lr (Linkregister, auch R14) und pc (Programmzähler auch R15) sind speziell und im Befehlssatz verankert. Sie haben Recht, dass die lr darauf hinweisen würde EIN. Das pc und lr sind verwandt. Das eine ist „wo du bist“ und das andere „wo du warst“. Sie sind die Code Aspekt einer Funktion.

Normalerweise haben wir die sp (Stapelzeiger, R13) und die fp (Rahmenzeiger, R11). Auch diese beiden sind verwandt. Dies
Microsoft-Layout macht einen guten Job, Dinge zu beschreiben. Das Stapel wird verwendet, um temporäre Daten zu speichern oder Einheimische in Ihrer Funktion. Alle Variablen in foo() und bar()werden hier gespeichert, auf dem Stapel oder in verfügbaren Registern. Das fp verfolgt die Variablen von Funktion zu Funktion. Es ist ein rahmen oder Bildfenster auf dem Stapel für diese Funktion. Das ABI definiert ein Layout davon rahmen. Typischerweise die lr und andere Register werden hier hinter den Kulissen vom Compiler gespeichert, ebenso wie der vorherige Wert von fp. Das macht ein verknüpfte Liste von Stapelrahmen und wenn Sie möchten, können Sie es bis zum Ende zurückverfolgen main(). Das Wurzel ist fpder auf einen Stapelrahmen zeigt (wie a struct) mit einer Variablen in der struct das vorherige sein fp. Sie können die Liste bis zum Finale durchgehen fp was normalerweise ist NULL.

Also die sp ist, wo der Stapel ist und die fp ist, wo der Stapel war, ähnlich wie der pc und lr. Jeder alt lr (link register) wird im alten gespeichert fp (Rahmenzeiger). Das sp und fp Bereich Daten Aspekt der Funktionen.

Dein Punkt B ist der Aktive pc und sp. Punkt EIN ist eigentlich die fp und lr; es sei denn, Sie rufen noch eine weitere Funktion auf und der Compiler macht sich möglicherweise bereit, die einzurichten fp auf die Daten in zeigen B.

Im Folgenden finden Sie einen ARM-Assembler, der möglicherweise demonstriert, wie dies alles funktioniert. Dies hängt davon ab, wie der Compiler optimiert, aber es sollte eine Vorstellung geben,

; Prologue - setup
mov     ip, sp                 ; get a copy of sp.
stmdb   sp!, {fp, ip, lr, pc}  ; Save the frame on the stack. See Addendum
sub     fp, ip, #4             ; Set the new frame pointer.
    ...
; Maybe other functions called here.
; Older caller return lr stored in stack frame. bl baz ... ; Epilogue - return ldm sp, {fp, sp, lr} ; restore stack, frame pointer and old link. ... ; maybe more stuff here. bx lr ; return.

Das ist was foo() würde aussehen wie. Wenn Sie nicht anrufen bar()dann macht der Compiler a Blattoptimierung und muss nicht gespeichert werden rahmen; nur der bx lr wird gebraucht. Höchstwahrscheinlich sind Sie deshalb von Webbeispielen verwirrt. Es ist nicht immer dasselbe.

Das Mitnehmen sollte sein,

  1. pc und lr sind verwandt Code registriert. Einer ist “Wo du bist”, der andere ist “Wo du warst”.
  2. sp und fp sind verwandt lokale Daten registriert.
    Einer ist “Wo lokale Daten sind”, der andere ist “Wo die letzten lokalen Daten sind”.
  3. Die Arbeit zusammen mit Parameterübergabe zu erstellen Funktion Maschinen.
  4. Es ist schwer, einen allgemeinen Fall zu beschreiben, weil wir wollen, dass Compiler so sind schnell wie möglich, also wenden sie jeden Trick an, den sie können.

Diese Konzepte gelten allgemein für alle CPUs und kompilierten Sprachen, obwohl die Details variieren können. Die Verwendung der Link registrieren, Rahmenzeiger sind Teil der Funktion Prolog und Epilog, und wenn Sie alles verstanden haben, wissen Sie, wie a Paketüberfluss funktioniert auf einem ARM.

Siehe auch: ARM-Aufrufkonvention.
MSDN-ARM-Stack-Artikel

APCS-Übersicht der Universität Cambridge

ARM-Stack-Trace-Blog

Apple ABI-Link

Das grundlegende Rahmenlayout ist,

  • fp[-0] Gerettet pcwo wir diesen Frame gespeichert haben.
  • fp[-1] Gerettet lrdie Rücksendeadresse für diese Funktion.
  • fp[-2] früher spvor dieser Funktion isst Stapel.
  • fp[-3] früher fpdas Letzte Stapelrahmen.
  • viele optionale Register…

Ein ABI kann andere Werte verwenden, aber die oben genannten sind typisch für die meisten Setups. Die obigen Indizes gelten für 32-Bit-Werte, da alle ARM-Register 32 Bit haben. Wenn Sie bytezentriert sind, multiplizieren Sie mit vier. Der Rahmen wird auch auf mindestens vier Bytes ausgerichtet.

Nachtrag: Dies ist kein Fehler im Assembler; es ist normal. Eine Erklärung finden Sie in der Frage zu den von ARM generierten Prologs.

  • OK, lr ist, wo der Code war, und fp ist, wo der Stapel war. Das heißt, wenn ich eine andere Funktion hätte baz(), sp wäre an Punkt (F), weil der Stapelzeiger bewegt wurde, um Variablen zuzuweisen a und b Innerhalb baz(); fp am Punkt (E) sein, weil sp war ganz oben baz(); und lr wäre bei (D)?

    – Benutzer2233706

    2. April 2013 um 16:14 Uhr

  • Ich habe die Antwort aktualisiert. Anstelle von Etiketten wäre es vielleicht besser zu klären. Wenn drin baz()sp würde zeigen baz() Daten. pc ist baz() Code. Das fp weist auf Dinge hin, die wiederhergestellt werden müssen bar() Kontext sowohl Code als auch Daten ODER alt sp und Alt lr==foo() Rückkehr. lr ist die Rückkehr zu bar()wenn nicht baz() ruft mehr Funktionen auf, als der Compiler speichern muss lr in einem anderen Stapelrahmen, da der Aufruf zerstört wird lr.

    – ungekünstelter Lärm

    2. April 2013 um 16:21 Uhr


  • Das ist sinnvoll. Der Strom aktiv lr enthält die Adresse, zu der im vorherigen Stapelrahmen zurückzukehren ist (es ist “wo Sie waren”). Das Aktive fp ist eine Adresse innerhalb des aktuellen Stapelrahmens. fp, lrund ip werden vor der Verzweigung zu einer neuen Funktion auf dem Stack gespeichert. Wenn Sie das tun bx lr Sie kehren zum vorherigen Stapelrahmen zurück. Aber dann sollten Sie in Ihrem Code nicht wiederherstellen lr vom Stapel nach dem bx lr seit lr enthält bereits, wohin Sie zurückkehren müssen? Andernfalls würden Sie zum vorherigen-vorherigen Stapelrahmen verzweigen.

    – Benutzer2233706

    4. April 2013 um 3:56 Uhr


  • Wow, das hätte ich sagen sollen lr enthält die Adresse des Codes vom vorherigen Funktionsaufruf, zu dem zurückgekehrt werden soll nicht die Adresse, zu der im vorherigen Stapelrahmen zurückgekehrt werden soll. lr zeigt irgendwo im Textsegment während fp zeigt auf irgendwo im Stack.

    – Benutzer2233706

    10. April 2013 um 19:02 Uhr

Benutzeravatar von Charles Bergren
Karl Bergren

Haftungsausschluss: Ich denke, das ist ungefähr richtig; bitte bei Bedarf korrigieren.

Beachten Sie, wie an anderer Stelle in diesen Fragen und Antworten angegeben, dass der Compiler möglicherweise keinen (ABI-)Code generieren muss, der Frame-Zeiger verwendet. Frames auf dem Call-Stack können oft erfordern, dass nutzlose Informationen dort abgelegt werden.

Wenn die Compiler-Optionen „keine Frames“ (ein Pseudo-Options-Flag) erfordern, kann der Compiler kleineren Code generieren, der die Call-Stack-Daten kleiner hält. Die aufrufende Funktion wird kompiliert, um nur die erforderlichen Aufrufinformationen auf dem Stapel zu speichern, und die aufgerufene Funktion wird kompiliert, um nur die erforderlichen Aufrufinformationen aus dem Stapel zu entnehmen.

Dies spart Ausführungszeit und Stapelplatz – aber es macht das Rückwärtsverfolgen im aufrufenden Code extrem schwierig (ich habe es aufgegeben, es zu versuchen …)

Informationen über die Größe und Form der aufrufenden Informationen auf dem Stapel sind nur dem Compiler bekannt, und diese Informationen wurden nach der Kompilierzeit verworfen.

  • Das ist im Wesentlichen richtig. Das fp kann auch als allgemeines Register verwendet werden; Dies ist ein großer Vorteil für einige Funktionen, da sie möglicherweise keinen Stack verwenden müssen. Sie benötigen zwei Tabellen, die vom Compiler generiert werden, um Stack-Tracing mit dem neueren AAPCS durchzuführen. Ich habe ARM extab QA als Referenz dafür angegeben.

    – ungekünstelter Lärm

    19. Januar 2020 um 14:36 ​​Uhr


1390040cookie-checkARM: Verknüpfungsregister und Rahmenzeiger

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy