ARM: registro de enlace y puntero de marco

Resuelto user2233706 asked hace 11 años • 2 respuestas

Estoy tratando de entender cómo funcionan el registro de enlace y el puntero de marco en ARM. He estado en un par de sitios y quería confirmar lo que entendí.

Supongamos que tengo el siguiente código:

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

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

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

y llamo foo(). ¿El registro de enlace contendría la dirección del código en el punto (A) y el puntero de marco contendría la dirección del código en el punto (B)? ¿Y el puntero de la pila podría estar en cualquier lugar dentro de la barra(), después de que se hayan declarado todos los locales?

user2233706 avatar Apr 02 '13 04:04 user2233706
Aceptado

Algunas convenciones de llamada de registros dependen de la ABI (Interfaz binaria de aplicación). Se FPrequiere en el estándar APCS y no en el AAPCS más nuevo (2003). Para AAPCS (GCC 5.0+), FPno es necesario utilizarlo, pero ciertamente se puede utilizar; La información de depuración está anotada con el uso del puntero de marco y pila para rastrear la pila y desenredar el código con AAPCS . Si una función es static, un compilador realmente no tiene que cumplir ninguna convención.

Generalmente todos los registros ARM son de propósito general . El lr(registro de enlace, también R14) y pc(el contador de programa también R15) son especiales y están consagrados en el conjunto de instrucciones. Tienes razón en que lrapuntaría a A . Los pcy lrestán relacionados. Uno es "dónde estás" y el otro es "dónde estabas". Son el aspecto del código de una función.

Normalmente, tenemos sp(puntero de pila, R13) y fp( puntero de marco , R11). Estos dos también están relacionados. Este diseño de Microsoft hace un buen trabajo al describir las cosas. La pila se utiliza para almacenar datos temporales o locales en su función. Cualquier variable en foo()y bar()se almacena aquí, en la pila o en los registros disponibles. Realiza un fpseguimiento de las variables de una función a otra. Es un marco o ventana de imagen en la pila para esa función. La ABI define un diseño de este marco . Normalmente, lrel compilador guarda aquí los registros y otros en segundo plano, así como el valor anterior de fp. Esto crea una lista vinculada de marcos de pila y, si lo desea, puede rastrearla hasta main(). La raíz es fp, que apunta a un marco de pila (como a struct) con una variable en structel anterior fp. Puedes ir avanzando en la lista hasta el final fpque normalmente es NULL.

Entonces es spdonde está la pila y fpes donde estaba la pila, muy parecido a pcy lr. Cada antiguo lr(registro de enlace) se almacena en el antiguo fp(puntero de marco). Los spy fpson un aspecto de datos de las funciones.

Tu punto B es el activo pcy sp. El punto A es en realidad el fpy lr; a menos que llame a otra función y luego el compilador podría prepararse para configurar el fppara que apunte a los datos en B .

A continuación se muestra un ensamblador ARM que podría demostrar cómo funciona todo esto. Esto será diferente dependiendo de cómo optimice el compilador, pero debería dar una idea.

; 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.
Así es como foo()se vería. Si no llama bar(), entonces el compilador realiza una optimización de hoja y no necesita guardar el marco ; sólo bx lrse necesita el. Lo más probable es que sea esta la razón por la que los ejemplos web le confunden. No siempre es lo mismo.

La comida para llevar debería ser,

  1. pcy son registros de códigoslr relacionados . Uno es "Dónde estás", el otro es "Dónde estabas".
  2. spy son registros de datos localesfp relacionados . Uno es "Dónde están los datos locales", el otro es "Dónde están los últimos datos locales".
  3. El trabajo en conjunto junto con el paso de parámetros para crear maquinaria funcional .
  4. Es difícil describir un caso general porque queremos que los compiladores sean lo más rápidos posible, por eso utilizan todos los trucos que pueden.

Estos conceptos son genéricos para todas las CPU y lenguajes compilados, aunque los detalles pueden variar. El uso del registro de enlace y el puntero de marco son parte de la función prólogo y epílogo, y si entendiste todo, sabrás cómo funciona un desbordamiento de pila en un ARM.

Ver también: Convención de llamadas ARM .
                Artículo de MSDN sobre la pila ARM
                Universidad de Cambridge Descripción general de APCS
                Blog de seguimiento de la pila ARM
                Enlace ABI de Apple

El diseño básico del marco es,

  • fp[-0] save pc, donde almacenamos este fotograma.
  • fp[-1] guardado lr, la dirección de devolución de esta función.
  • fp[-2] anterior sp, antes de que esta función coma la pila.
  • fp[-3] anterior fp, el último fotograma de la pila .
  • muchos registros opcionales...

Un ABI puede usar otros valores, pero los anteriores son típicos para la mayoría de las configuraciones. Los índices anteriores son para valores de 32 bits, ya que todos los registros ARM son de 32 bits. Si está centrado en bytes, multiplique por cuatro. La trama también está alineada con al menos cuatro bytes.

Anexo: Esto no es un error en el ensamblador; es normal. Hay una explicación en la pregunta de los prólogos generados por ARM .

artless noise avatar Apr 01 '2013 22:04 artless noise