Pregunta

Ha pasado un tiempo desde la última vez que codifiqué el ensamblador de brazos y estoy un poco oxidado en los detalles. Si llamo a una función C desde el brazo, solo tengo que preocuparme por guardar r0-r3 y lr, ¿verdad?

Si la función C usa otros registros, ¿es responsable de guardarlos en la pila y restaurarlos? En otras palabras, el compilador generaría código para hacer esto para las funciones de C.

Por ejemplo, si uso r10 en una función de ensamblador, no tengo que insertar su valor en la pila o en la memoria, y abrirlo / restaurarlo después de una llamada en C, ¿o sí?

Esto es para arm-eabi-gcc 4.3.0.

¿Fue útil?

Solución

Depende del ABI para la plataforma para la que está compilando. En Linux, hay dos ABI de ARM; el viejo y el nuevo. AFAIK, el nuevo (EABI) es de hecho AAPCS de ARM. Las definiciones completas de EABI actualmente viven aquí en ARM's centro de información .

De el AAPCS, §5.1.1 :

  • r0-r3 son los registros de argumentos y scratch; r0-r1 también son los registros de resultados
  • r4-r8 son registros de guardado de llamadas
  • r9 podría ser un registro de guardado de llamadas o no (en algunas variantes de AAPCS es un registro especial)
  • r10-r11 son registros de guardado de llamadas
  • r12-r15 son registros especiales

La persona que llama debe guardar un registro de guardado de llamadas (en oposición a un registro de guardado de llamadas, donde la persona que llama guarda el registro); entonces, si esta es la ABI que está utilizando, no tiene que guardar r10 antes de llamar a otra función (la otra función es responsable de guardarla).

Editar: qué compilador está utilizando no hace ninguna diferencia; gcc en particular se puede configurar para varias ABI diferentes, e incluso se puede cambiar en la línea de comando. Mirar el código de prólogo / epílogo que genera no es tan útil, ya que está diseñado para cada función y , el compilador puede usar otras formas de guardar un registro (por ejemplo, guardarlo en medio de un función).

Otros consejos

Para agregar información faltante en registros NEON:

De el AAPCS , § 5.1.1 Registros principales:

  • r0-r3 son los registros de argumentos y scratch; r0-r1 también son los registros de resultados
  • r4-r8 son registros de guardado de llamadas
  • r9 podría ser un registro de guardado de llamadas o no (en algunas variantes de AAPCS es un registro especial)
  • r10-r11 son registros de guardado de llamadas
  • r12-r15 son registros especiales

De AAPCS, §5.1.2.1 Convenciones de uso de registro VFP:

  • s16 – s31 (d8 – d15, q4 – q7) debe conservarse
  • s0 – s15 (d0 – d7, q0 – q3) y d16 – d31 (q8 – q15) no necesitan conservarse

Publicación original:
arm-to-c-calling-convention-neon- registros para guardar

Para ARM de 64 bits, A64 (del procedimiento Llamada estándar para la arquitectura ARM de 64 bits)

Hay treinta y uno, 64 bits, registros de propósito general (entero) visibles para el conjunto de instrucciones A64; estos están etiquetados r0-r30 . En un contexto de 64 bits, normalmente se hace referencia a estos registros utilizando los nombres x0-x30 ; en un contexto de 32 bits, los registros se especifican utilizando w0-w30 . Además, un registro de puntero de pila, SP , se puede utilizar con un número restringido de instrucciones.

  • SP El puntero de pila
  • r30 LR The Link Register
  • r29 FP The Frame Pointer
  • r19 ... r28 Registros guardados por el destinatario
  • r18 El Registro de plataforma, si es necesario; de lo contrario, un registro temporal.
  • r17 IP1 El segundo registro temporal de llamada dentro del procedimiento (se puede utilizar por carillas y código PLT); en otros momentos se puede usar como registro temporal.
  • r16 IP0 El primer registro de scratch de llamada dentro del procedimiento (puede usarse por llamada chapas y código PLT); en otros momentos se puede usar como registro temporal.
  • r9 ... r15 Registros temporales
  • r8 Registro de ubicación indirecta de resultados
  • r0 ... r7 Registros de parámetros / resultados

Los primeros ocho registros, r0-r7 , se utilizan para pasar valores de argumentos a una subrutina y para devolver valores de resultados de una función. También se pueden usar para mantener valores intermedios dentro de una rutina (pero, en general, solo entre llamadas de subrutina).

Los

registros r16 (IP0) y r17 (IP1) pueden ser utilizados por un vinculador como un registro temporal entre una rutina y cualquier subrutina que llame. También se pueden usar dentro de una rutina para mantener valores intermedios entre llamadas de subrutina.

El rol del registro r18 es específico de la plataforma. Si una plataforma ABI necesita un registro de propósito general dedicado para llevar un estado entre procedimientos (por ejemplo, el contexto del hilo), entonces debe usar este registro para ese propósito. Si la plataforma ABI no tiene tales requisitos, entonces debería usar r18 como un registro temporal adicional. La especificación ABI de la plataforma debe documentar el uso de este registro.

SIMD

La arquitectura ARM de 64 bits también tiene otros treinta y dos registros, v0-v31 , que pueden ser utilizados por SIMD y operaciones de punto flotante. El nombre exacto del registro cambiará indicando el tamaño del acceso.

Nota: A diferencia de AArch32, en AArch64 las vistas de 128 bits y 64 bits de un SIMD y un registro de punto flotante no se superponen a varios registros en una vista más estrecha, entonces q1 , d1 y s1 se refieren a la misma entrada en el banco de registro.

Los primeros ocho registros, v0-v7 , se utilizan para pasar valores de argumentos a una subrutina y para devolver valores de resultados de una función. También se pueden usar para mantener valores intermedios dentro de una rutina (pero, en general, solo entre llamadas de subrutina).

Los registros v8-v15 deben ser preservados por un llamado a través de llamadas de subrutina; los registros restantes ( v0-v7, v16-v31 ) no necesitan conservarse (o la persona que llama debe conservarlos). Además, solo deben conservarse los 64 bits inferiores de cada valor almacenado en v8-v15 ; Es responsabilidad de la persona que llama preservar los valores más grandes.

Las respuestas de CesarB y Pavel proporcionaron citas de AAPCS, pero persisten los problemas pendientes. ¿El llamado salva a r9? ¿Qué hay de r12? ¿Qué hay de r14? Además, las respuestas fueron muy generales y no específicas de la cadena de herramientas arm-eabi como se solicitó. Aquí hay un enfoque práctico para averiguar qué registros se guardan y cuáles no.

El siguiente código C contiene un bloque de ensamblaje en línea, que pretende modificar los registros r0-r12 y r14. El compilador generará el código para guardar los registros requeridos por el ABI.

void foo() {
  asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14");
}

Use la línea de comando arm-eabi-gcc-4.7 -O2 -S -o - foo.c y agregue los interruptores para su plataforma (como -mcpu = arm7tdmi por ejemplo). El comando imprimirá el código de ensamblaje generado en STDOUT. Puede verse más o menos así:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    bx  lr

Tenga en cuenta que el código generado por el compilador guarda y restaura r4-r11. El compilador no guarda r0-r3, r12. Que restaura r14 (alias lr) es puramente accidental, ya que sé por experiencia que el código de salida también puede cargar el lr guardado en r0 y luego hacer un '' bx r0 ''. en lugar de "bx lr". Al agregar el -mcpu = arm7tdmi -mno-thumb-interwork o al usar -mcpu = cortex-m4 -mthumb obtenemos un código de ensamblaje ligeramente diferente que se ve así:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

Nuevamente, r4-r11 se guardan y restauran. Pero r14 (alias lr) no se restaura.

Para resumir:

  • r0-r3 son no guardados por la llamada
  • r4-r11 se guardan a la persona que llama
  • r12 (alias ip) es no guardado por llamada
  • r13 (alias sp) está guardado por llamada
  • r14 (alias lr) es no guardado por el destinatario
  • r15 (alias pc) es el contador del programa y se establece en el valor de lr antes de la llamada a la función

Esto se cumple al menos para los valores predeterminados de arm-eabi-gcc. Hay modificadores de línea de comandos (en particular, el modificador -mabi) que pueden influir en los resultados.

También hay una diferencia al menos en la arquitectura Cortex M3 para la función de llamada e interrupción.

Si ocurre una interrupción, hará que R0-R3, R12, LR, PC se coloquen automáticamente en la pila y cuando regrese el IRQ POP automático. Si utiliza otros registros en la rutina IRQ, debe insertarlos / desplegarlos en la pila manualmente.

No creo que este PUSH y POP automático esté hecho para una llamada de Función (instrucción de salto). Si la convención dice que R0-R3 se puede usar solo como un argumento, resultado o registros reutilizables, entonces no hay necesidad de almacenarlos antes de la llamada a la función porque no debería usarse ningún valor más tarde después del retorno de la función. Pero igual que en una interrupción, debe almacenar todos los demás registros de CPU si los usa en su función.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top