instruction - ¿Cómo llamar a las funciones C desde ARM Assembly?
raspberry pi assembler (4)
Estoy escribiendo código de targeting ARM Cortex-A en dispositivos Android (usando ensamblador GNU y compilador), y estoy tratando de hacer interfaz entre Assembly y C. En particular, estoy interesado en llamar funciones escritas en C desde Assembly. Intenté muchas cosas, incluida la directiva .extern
, declarando las funciones C con asm
y __asm__
etc., pero ninguna de ellas funcionó, así que estoy buscando un ejemplo mínimo de hacerlo. Una referencia a tal ejemplo sería igual de bienvenida.
Como dice Brett, todo lo que tienes que hacer es colocar los valores correctos en los registros correctos y bifurcar con el enlace a la dirección de la función. Tendrá que tener en cuenta qué registro sobrescribirá la función compilada, y qué registros restaurará antes de que regrese, todo 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 el compilador espera, y tal vez también de 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 (tanto tiempo como desee) en funciones C normales, y volver a caer en C siempre que sea más conveniente.
Hay casos en los que no basta con tener C gubbins, pero si puedes llamar a las funciones C, supongo que no estás en esas.
Vamos a eso, ¿por qué necesitas usar el ensamblador en absoluto ... C es básicamente ensamblador de alto nivel de todos modos?
Necesita las especificaciones para armeabi-v7a
, que describe la pila de llamadas, los registros (llamado en lugar de la persona que llama), etc. Luego, observe la salida del conjunto del código C compilado para ver la sintaxis, etc. Las cosas son más complicadas cuando intenta llamar a funciones compartidas bibliotecas u objetos reubicables.
Necesitas leer 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 muy bien también debería funcionar bien si usas clang para obtener el código c para un objeto y gnu como para el ensamblador. No estoy seguro de lo que estás usando.
El problema con lo anterior es 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 del enlace a la instrucción después de la instrucción bl, luego, 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.
entonces 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 computadora ldr, instrucción (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 quieres jugar juegos de ensamblador como ese y quieres tener el control, entonces debes crear la ubicación para guardar la dirección de la función y cargarla en la computadora por ti 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 quieres moverte al mundo ARM moderno donde ARM y el pulgar se mezclan o pueden ser (por ejemplo, usa bx lr en lugar de mov pc, lr) entonces querrás 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 mostrar su registro de enlace y el otro registro antes y después de su llamada a C si desea conservarlos.
Ejemplo de Armv7 mínimo ejecutable
Esta pregunta se reduce "¿qué es la convención de llamadas ARM (AAPCS)". Un ejemplo aS
:
/* 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
Salida:
hello world
El error más fácil de hacer en ejemplos más complejos es olvidar que la pila debe estar alineada con 8 bytes. Por ejemplo, quieres
push {ip, lr}
en lugar de:
push {lr}
Ejemplo en GitHub con la plantilla generalizada: https://github.com/cirosantilli/arm-assembly-cheat/blob/82e915e1dfaebb80683a4fd7bba57b0aa99fda7f/c_from_arm.S