Convertir C++ a ensamblaje ARM
- Use el compilador GCC para convertir C++ a ensamblaje ARM
- Cree una función MOD (módulo de tiempo de ensamblaje) para convertir C++ a ensamblaje ARM
-
Utilice el comando
arm-linux-gnueabi-gcc
para convertir C++ a ensamblaje ARM -
Use el comando
armclang
en ARM Compiler para Linux para convertir C++ a ensamblaje ARM -
Utilice la palabra clave
__asm
para convertir C++ a ensamblaje ARM
La interfaz de C++ con ensamblador ARM sirve a los programadores de muchas maneras, y también es un proceso sencillo que ayuda a C++ a acceder a varias funciones y variables definidas en lenguaje ensamblador y viceversa. Este tutorial le enseñará cómo convertir código o funciones de C++ a ensamblaje ARM.
Los programadores pueden usar módulos de código ensamblador separados para vincularlos con módulos compilados en C++ para usar las variables de ensamblado y el ensamblado en línea incrustado en C++ o modificar el código ensamblador que produce el compilador.
Lo que es más importante, debe conservar todos los registros dedicados modificados por una función, habilitar las rutinas de interrupción para guardar todos los registros, asegurarse de que las funciones devuelvan los valores correctamente de acuerdo con su declaración de C++, ningún módulo ensamblador que use la sección .cinit
, permitir que el compilador asigne vincule nombres a todos los objetos externos y declare cada objeto y función con la directiva .def
o .global
a la que se accede o llama desde C++ en el modificador de ensamblado antes de convertir C++ al ensamblado ARM.
Definir las funciones llamadas desde el lenguaje ensamblador con C
(funciones prototipadas como una C
externa) en un archivo C++. Defina variables en la sección .bss
o asígneles un símbolo de enlace para identificar posteriormente cuál requiere conversión.
Use el compilador GCC para convertir C++ a ensamblaje ARM
El gcc
es una gran fuente para obtener resultados intermedios del código C++ durante su ejecución. Es una función que obtiene la salida del ensamblador usando la opción -S
.
La opción -S
es para la salida después de compilar el código antes de enviarlo al ensamblador.
Su sintaxis es gcc –S your_program.cpp
, y puede escribir un programa C++ simple para convertirlo en ensamblador ARM simplemente declarando este comando. Además de ser uno de los enfoques más simples, su salida es compleja y difícil de entender, incluso para programadores de nivel intermedio.
Archivo GNN.cpp
:
#include <iostream>
using namespace std;
main() {
int i, u, div;
i = 2;
u = 10;
div = i / u;
cout << "Answer: " << div << endl;
}
Ejecute este comando en GCC en Microsoft Windows:
gcc –S GNN.cpp
Producción:
Es posible usar una serie de declaraciones ASM o una sola declaración ASM para una sola línea de inserción de código ensamblador en el archivo ensamblador dentro de su programa C++ que crea el compilador. Estas declaraciones de ensamblaje colocan líneas secuenciales de código (código de ensamblaje) en el compilador (salida del compilador de C++) sin código intermedio (sin interrupciones de código).
Sin embargo, mantenga siempre el entorno C++ porque el compilador no comprueba/analiza las instrucciones insertadas. Siempre evite insertar etiquetas o umps en el código C++, ya que pueden producir resultados impredecibles y confundir los algoritmos de seguimiento de registro que genera el código.
Además, las instrucciones ASM no son una opción válida para insertar directivas de ensamblador, y puede usar el comando symdebug:dwarf
o el comando -g
sin cambiar el entorno de ensamblaje y evitando la creación de macros de ensamblaje en código C++ porque el entorno C++ información de depuración.
Cree una función MOD (módulo de tiempo de ensamblaje) para convertir C++ a ensamblaje ARM
Como ARM Assembly carece de los comandos MOD, puede crear una función MOD con subs y convertir fácilmente C++ a ARM Assembly. Debe cargar la dirección de memoria de la variable a través de ldr reg, =var
, y en caso de que quiera cargar la variable, requiere hacer otro ldr
con ese reg
como ldr r0, =carry ldr r0, [r0]
para cargar el valor almacenado en la dirección de memoria en r0
.
Use sdiv
porque es mucho más rápido que un ciclo de resta, excepto por entradas mínimas, donde el ciclo solo se ejecuta una o dos veces.
Concepto:
;Precondition: R0 % R1 is the required computation
;Postcondition: R0 has the result of R0 % R1
: R2 has R0 / R1
; Example comments for 10 % 7
UDIV R2, R0, R1 ; 1 <- 10 / 7 ; R2 <- R0 / R1
MLS R0, R1, R2, R0 ; 3 <- 10 - (7 * 1) ; R0 <- R0 - (R1 * R2 )
#include <iostream>
using namespace std;
main() {
int R0, R1, R2;
R1 = 7;
R2 = 1;
R0 = 10;
int Sol1, Sol2;
Sol1 = R2 < -R0 / R1;
Sol2 = R0 < -R0 - (R1 * R2);
cout << Sol1 << endl;
cout << Sol2;
}
Producción:
Utilice el comando arm-linux-gnueabi-gcc
para convertir C++ a ensamblaje ARM
El comando arm-linux-gnueabi-gcc
es una forma perfecta de convertir C++ a ensamblaje ARM para máquinas x86 y x64. Como el gcc
no tiene objetivos ARM disponibles, no puede usarlo para sistemas generales, pero solo si está en un sistema ARM donde puede usar el gcc
normal en su lugar.
El comando completo arm-linux-gnueabi-gcc -S -O2 -march=armv8-a GNN.cpp
es increíblemente fuerte donde -S
representa el ensamblado de salida y le dice a gcc
sobre él, -02
es un optimizador de código y reduce el desorden de depuración del resultado. El -02
es opcional; por otro lado, el -march=armv8-a
es obligatorio y le dice que use el objetivo ARM v8 durante la compilación.
Puede cambiar el destino de ARM mientras compila usando las diferentes versiones de ARM v8, que incluyen; armv8-a
, armv8.1-a
a armv8.6-a
, armv8-m.base
, armv8-m.main
y armv8.1-m.main
donde cada uno es ligeramente diferente, y puede realizar un análisis en profundidad y seleccionar el que mejor se adapte a sus necesidades.
El power.c
del comando indica qué archivo compilar, y si no ha especificado un archivo de salida como -o output.asm
, el ensamblaje se generará con el nombre de archivo similar power.s
.
El arm-linux-gnueabi-gcc
es una excelente alternativa a la compilación en una máquina arm
que proporciona el ensamblaje de destino o de salida con gcc
regular.
El gcc
permite a los programadores especificar la arquitectura de destino con -march=xxx
, y debe saber identificar el paquete apt
de su máquina para seleccionar el correcto.
Archivo GNN.cpp
:
#include <iostream>
using namespace std;
int power(int x, int y) {
if (x == 0) {
return 0;
} else if (y < 0) {
return 0;
} else if (y == 0) {
return 1;
} else {
return x * power(x, y - 1);
}
}
main() {
int x, y, sum;
x = 2;
y = 10;
sum = power(x, y);
cout << sum;
}
arm-linux-gnueabi-gcc -S -O2 -march=armv8-a GNN.cpp
Producción :
power(int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 0
jne .L2
mov eax, 0
jmp .L3
.L2:
cmp DWORD PTR [rbp-8], 0
jns .L4
mov eax, 0
jmp .L3
.L4:
cmp DWORD PTR [rbp-8], 0
jne .L5
mov eax, 1
jmp .L3
.L5:
mov eax, DWORD PTR [rbp-8]
lea edx, [rax-1]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
call power(int, int)
imul eax, DWORD PTR [rbp-4]
.L3:
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 2
mov DWORD PTR [rbp-8], 10
mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
call power(int, int)
mov DWORD PTR [rbp-12], eax
mov eax, DWORD PTR [rbp-12]
mov esi, eax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov eax, 0
leave
ret
__static_initialization_and_destruction_0(int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 1
jne .L10
cmp DWORD PTR [rbp-8], 65535
jne .L10
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
.L10:
nop
leave
ret
_GLOBAL__sub_I_power(int, int):
push rbp
mov rbp, rsp
mov esi, 65535
mov edi, 1
call __static_initialization_and_destruction_0(int, int)
pop rbp
ret
Alternativamente, puede instalar el compilador ARM para Linux cargando el módulo para el compilador ARM ejecutando module load arm<versión-principal>/<versión-paquete>
donde <versión-paquete>
es <versión-principal> .<versión-menor>{.<versión-parche>}
, por ejemplo: module load arm21/21.0
.
El comando armclang -S <fuente>.c
puede ayudarlo a compilar su fuente C++ y especificar una salida de código ensamblador donde -S
representa la salida del código ensamblador y <fuente>.s
es el archivo que contendrá el código convertido .
Use el comando armclang
en ARM Compiler para Linux para convertir C++ a ensamblaje ARM
Puede producir código ensamblador anotado utilizando el compilador ARM C++, que es el primer paso para aprender cómo el compilador vectoriza los bucles. Un compilador ARM para el sistema operativo Linux es un requisito previo para generar el código ensamblador desde C++.
Después de cargar el módulo para el compilador ARM, ejecute el comando module load arm<versión-principal>/<versión-paquete>
, por ejemplo: module load arm21/21.0
poniendo <versión-principal>.<menor -version>{.<versión-parche>}
donde <versión-paquete>
forma parte del comando.
Compile su código fuente usando el comando armclang -S <fuente>.cpp
e inserte el nombre del archivo fuente en la ubicación de <fuente>.cpp
.
El compilador de ensamblaje ARM hace algo diferente al compilador GCC, usando instrucciones y registros SIMD (instrucción única, datos múltiples) para vectorizar el código.
Archivo GNN.cpp
:
#include <iostream>
using namespace std;
void subtract_arrays(int a, int b, int c) {
int sum;
for (int i = 0; i < 5; i++) {
a = (b + c) - i;
sum = sum + a;
}
cout << sum;
}
int main() {
int a = 1;
int b = 2;
int c = 3;
subtract_arrays(a, b, c);
}
armclang -O1 -S -o source_O1.s GNN.cpp
Producción :
subtract_arrays(int, int, int):
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
mov DWORD PTR [rbp-28], edx
mov DWORD PTR [rbp-8], 0
jmp .L2
.L3:
mov edx, DWORD PTR [rbp-24]
mov eax, DWORD PTR [rbp-28]
add eax, edx
sub eax, DWORD PTR [rbp-8]
mov DWORD PTR [rbp-20], eax
mov eax, DWORD PTR [rbp-20]
add DWORD PTR [rbp-4], eax
add DWORD PTR [rbp-8], 1
.L2:
cmp DWORD PTR [rbp-8], 4
jle .L3
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
nop
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 2
mov DWORD PTR [rbp-12], 3
mov edx, DWORD PTR [rbp-12]
mov ecx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, ecx
mov edi, eax
call subtract_arrays(int, int, int)
mov eax, 0
leave
ret
__static_initialization_and_destruction_0(int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 1
jne .L8
cmp DWORD PTR [rbp-8], 65535
jne .L8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
.L8:
nop
leave
ret
_GLOBAL__sub_I_subtract_arrays(int, int, int):
push rbp
mov rbp, rsp
mov esi, 65535
mov edi, 1
call __static_initialization_and_destruction_0(int, int)
pop rbp
ret
Utilice la palabra clave __asm
para convertir C++ a ensamblaje ARM
Se sabe que es el enfoque más válido, ya que el compilador proporciona un ensamblador en línea para escribir código ensamblador en su código fuente de C++ y le permite acceder a funciones del procesador de destino que no forman parte de C++ ni están disponibles en él.
Utilizando la sintaxis de ensamblaje en línea de GNU, la palabra clave _arm
lo ayuda a incorporar o escribir código de ensamblaje en línea en una función.
Sin embargo, no es un buen enfoque para migrar el código ensamblador de sintaxis armasm
a la sintaxis GNU, ya que el ensamblador en línea no es compatible con el código ensamblador heredado escrito en la sintaxis ensambladora armasm
.
El __asm [volátil] (código); /* Sintaxis de ensamblado en línea básica */
La declaración de ensamblado en línea muestra la forma general de una declaración _arm
, y también hay una versión extendida de la sintaxis de ensamblado en línea que encontrará en el código de ejemplo a continuación.
Usar el calificador volátil
para las instrucciones del ensamblador es beneficioso, pero puede tener algunos inconvenientes que el compilador podría no conocer, entre ellos; las posibilidades de deshabilitar ciertas optimizaciones del compilador que pueden llevar al compilador a eliminar el bloque de código.
Como el calificador volátil
es opcional, usarlo puede garantizar que el compilador no elimine los bloques de código ensamblador al compilar con -01
o superior.
#include <stdio.h>
int add(int x, int y) {
int sum = 0;
__asm("ADD %[_sum], %[input_x], %[input_y]"
: [_sum] "=r"(sum)
: [input_x] "r"(x), [input_y] "r"(y));
return sum;
}
int main(void) {
int x = 1;
int y = 2;
int z = 0;
z = add(x, y);
printf("Result of %d + %d = %d\n", x, y, z);
}
Producción :
add(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-20]
mov edx, DWORD PTR [rbp-24]
ADD eax, eax, edx
mov DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
pop rbp
ret
.LC0:
.string "Result of %d + %d = %d\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 2
mov DWORD PTR [rbp-12], 0
mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
call add(int, int)
mov DWORD PTR [rbp-12], eax
mov ecx, DWORD PTR [rbp-12]
mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
La palabra clave code
en la declaración de ensamblado _arm
es la instrucción de ensamblado, y code_template
es su plantilla; si solo lo especifica en lugar de código
, debe especificar output_operand_list
antes de especificar input_operand_list
y clobbered_register_list
opcionales.
La lista_operando_salida
(como lista de operandos de salida) está separada por comas, y cada operando consta de un nombre simbólico entre corchetes con el formato [resultado] "=r" (res)
.
Puede usar el ensamblado en línea para definir símbolos como __asm (".global __use_no_semihosting\n\t");
o para definir etiquetas usando el signo :
después del nombre de la etiqueta como __asm ("my_label:\n\t");
.
Además, le permite escribir varias instrucciones dentro de la misma declaración _asm
y también le permite escribir un ensamblaje incrustado utilizando la palabra clave __attribute__((naked))
.
El compilador de Microsoft C++ (MSVC) puede proporcionar resultados diferentes en la arquitectura ARM que en máquinas o arquitecturas x86 o x64 para el mismo código fuente de C++, y es posible que encuentre muchos problemas de migración o conversión.
Los problemas pueden invocar un comportamiento indefinido, definido por la implementación o no especificado y otros problemas de migración atribuidos a las diferencias de hardware entre las arquitecturas ARM y x86 o x64 que interactúan con el estándar C++ de manera diferente.
Hassan is a Software Engineer with a well-developed set of programming skills. He uses his knowledge and writing capabilities to produce interesting-to-read technical articles.
GitHub