¿Cómo llamar a funciones C desde ARM Assembly?

6 minutos de lectura

Avatar de usuario de Phonon
fonón

Estoy escribiendo código dirigido a ARM Cortex-A en dispositivos Android (usando ensamblador y compilador GNU), y estoy tratando de establecer una interfaz entre Assembly y C. En particular, estoy interesado en llamar a funciones escritas en C desde Assembly. Intenté muchas cosas, incluida la .extern directiva, declarando funciones C con asm y __asm__ y así sucesivamente, pero ninguno de ellos funcionó, así que estoy buscando un ejemplo mínimo de cómo hacerlo. Una referencia a tal ejemplo sería igualmente bienvenida.

  • @ user606723 Estaba pensando en eso, pero C usa archivos de encabezado con declaraciones de funciones para la comunicación entre diferentes archivos. ¿No sería una imagen completamente diferente?

    – Fonón

    7 de diciembre de 2011 a las 21:13


  • El compilador mira la declaración de la función en el archivo de encabezado para compilar la llamada. Deberá hacer lo mismo manualmente.

    – estrella azul

    8 de diciembre de 2011 a las 8:32

Necesitas leer el ARM ARM y/o saber que el conjunto de instrucciones es todo, normalmente querrías hacer algo como esto

asm:

bl cfun

c:
void cfun ( void )
{

}

Puedes probar esto tú mismo. para gnu as y gcc esto funciona bien, también debería funcionar bien si usa clang para obtener el código c en un objeto y gnu como ensamblador. No estoy seguro de lo que estás usando.

El problema con lo anterior es que bl tiene un alcance limitado,

if ConditionPassed(cond) then
  if L == 1 then
    LR = address of the instruction after the branch instruction
    PC = PC + (SignExtend_30(signed_immed_24) << 2)

sabiendo que la instrucción bl establece el registro de enlace a la instrucción después de la instrucción bl, si lee sobre el registro del contador del programa:

For an ARM instruction, the value read is the address of the instruction
plus 8 bytes. Bits [1:0] of this
value are always zero, because ARM instructions are always word-aligned.

así que si haces que tu asm se vea así:

mov lr,pc
ldr pc,=cfun

usted obtiene

d6008034:   e1a0e00f    mov lr, pc
d6008038:   e51ff000    ldr pc, [pc, #-0]   ; d6008040 
...
d6008040:   d60084c4    strle   r8, [r0], -r4, asr #9

El ensamblador reservará una ubicación de memoria, al alcance de la instrucción ldr pc (si es posible, de lo contrario generará un error) donde colocará la dirección completa de 32 bits para la instrucción. el enlazador luego completará esta dirección con la dirección externa. de esa manera puede llegar a cualquier dirección en el espacio de direcciones.

Si no quiere jugar juegos de ensamblador como ese y quiere tener el control, cree la ubicación para mantener la dirección de la función y cárguela en la PC usted mismo:

    mov lr,pc
    ldr pc,cfun_addr

...

cfun_addr:
    .word cfun

compilado:

d6008034:   e1a0e00f    mov lr, pc
d6008038:   e51ff000    ldr pc, [pc, #-0]   ; d6008040 <cfun_addr>
...

d6008040 <cfun_addr>:
d6008040:   d60084c4    strle   r8, [r0], -r4, asr #9

Por último, si desea pasar al mundo ARM moderno donde ARM y el pulgar se mezclan o pueden ser (por ejemplo, use bx lr en lugar de mov pc, lr), entonces querrá usar bx

    add lr,pc,#4
    ldr r1,cfun_addr
    bx r1
...

cfun_addr:
    .word cfun

por supuesto, necesita otro registro para hacer eso y recuerde presionar y abrir su registro de enlace y el otro registro antes y después de su llamada a C si desea conservarlos.

  • Si aún no conoce las convenciones de llamada para saber qué registros se usan para pasar parámetros, por ejemplo, haga una de dos cosas, escriba un código C para llamar a su función y desensamble para ver qué hizo el compilador. la respuesta correcta es buscar la convención de llamadas que otros le han indicado.

    – viejo contador de tiempo

    7 de diciembre de 2011 a las 22:45

  • y tiene razón, solo mostré cómo hacer que las instrucciones ARM llamen a una función para que las instrucciones del pulgar no llamen a una función C. Puedo editar la respuesta y mostrarle cómo hacerlo también … mencionó una corteza A, así que asumo está ejecutando instrucciones de brazo, no de pulgar en su mayor parte, aprovechando la potencia que tiene y no algo limitado por el autobús.

    – viejo contador de tiempo

    7 de diciembre de 2011 a las 22:48

  • Y como Brett mencionó que los objetos reubicables pueden hacerlo más complicado, necesitaría hacer que cfun_addr sea global, y quienquiera que cargue el objeto reubicable debe modificar cfun_addr antes de usarlo.

    – viejo contador de tiempo

    7 de diciembre de 2011 a las 22:51

  • ¡Respeta a old_timer!

    – Sam Hammamy

    24 de noviembre de 2017 a las 20:47

Ejemplo mínimo de armv7 ejecutable

Esta pregunta se reduce a “cuál es la convención de llamadas ARM (AAPCS)”. Un ejemplo a.S:

/* Make the glibc symbols visible. */
.extern exit, puts
.data
    msg: .asciz "hello world"
.text
.global main
main:
    /* r0 is the first argument. */
    ldr r0, =msg
    bl puts
    mov r0, #0
    bl exit

Luego en Ubuntu 16.04:

sudo apt-get install gcc-arm-linux-gnueabihf qemu-user-static
# Using GCC here instead of as + ld without arguments is needed
# because GCC knows where the C standard library is.
arm-linux-gnueabihf-gcc -o a.out a.S
qemu-arm-static -L /usr/arm-linux-gnueabihf a.out

Producción:

hello world

El error más fácil de cometer en ejemplos más complejos es olvidar que la pila debe estar alineada en 8 bytes. Por ejemplo, quieres:

push {ip, lr}

en vez de:

push {lr}

Ejemplo en GitHub con el repetitivo generalizado: https://github.com/cirosantilli/arm-assembly-cheat/blob/82e915e1dfaebb80683a4fd7bba57b0aa99fda7f/c_from_arm.S

  • El OP no estaba preguntando cómo mostrar hola mundo. Quería llamar a un C función.

    – Sam Hammamy

    24 de noviembre de 2017 a las 20:49

  • @SamHammamy no lo es puts una función de C? 🙂

    – Ciro Santilli OurBigBook.com

    24 de noviembre de 2017 a las 20:58

Necesita las especificaciones para el armeabi-v7adescribiendo la pila de llamadas, los registros (persona que llama frente a la persona que llama), etc. Luego, mire la salida del ensamblaje del código C compilado para la sintaxis, etc. Las cosas son más complicadas cuando se intenta llamar a funciones en bibliotecas compartidas u objetos reubicables.

Como dice Brett, todo lo que realmente tiene que hacer es poner los valores correctos en los registros correctos y ramificar con enlace a la dirección de la función. Deberá saber qué registro sobrescribirá la función compilada y qué registros restaurará antes de que regrese; todo eso está escrito en la documentación de ABI en infocentre.arm.com. También deberá asegurarse de que el registro de la pila esté configurado según lo que espera el compilador, y tal vez también otros registros (¿para el modo PIC?)

Pero, ¿realmente necesita escribir el código en archivos ensambladores?

Si usa la función “asm” de GCC, puede incrustar fragmentos de ensamblador (siempre que lo desee) en funciones regulares de C y volver a C cuando sea más conveniente.

Hay casos en los que tener C gubbins cerca no funcionará, pero si puede llamar a funciones C, supongo que no está en esos.

Vamos a eso, ¿por qué necesita usar el ensamblador en absoluto … De todos modos, C es básicamente un ensamblador de alto nivel?

¿Ha sido útil esta solución?