C++를 ARM 어셈블리로 변환
- GCC 컴파일러를 사용하여 C++를 ARM 어셈블리로 변환
- C++를 ARM 어셈블리로 변환하는 MOD(Assembly-Time Modulus) 함수 생성
-
arm-linux-gnueabi-gcc
명령을 사용하여 C++를 ARM 어셈블리로 변환 -
Linux용 ARM 컴파일러에서
armclang
명령을 사용하여 C++를 ARM 어셈블리로 변환 -
__asm
키워드를 사용하여 C++를 ARM 어셈블리로 변환
ARM 어셈블리와 C++의 인터페이스는 프로그래머에게 다양한 방식으로 도움이 되며 C++가 어셈블리 언어에 정의된 다양한 기능과 변수에 액세스하는 데 도움이 되는 간단한 프로세스이기도 합니다. 이 자습서에서는 C++ 코드 또는 함수를 ARM 어셈블리로 변환하는 방법을 알려줍니다.
프로그래머는 별도의 어셈블리 코드 모듈을 사용하여 C++ 컴파일된 모듈과 연결하여 C++에 포함된 어셈블리 변수 및 인라인 어셈블리를 사용하거나 컴파일러가 생성하는 어셈블리 코드를 수정할 수 있습니다.
가장 중요한 것은 함수에 의해 수정된 전용 레지스터를 보존하고, 인터럽트 루틴이 모든 레지스터를 저장하도록 하고, 함수가 C++ 선언에 따라 값을 올바르게 반환하는지 확인하고, .cinit
섹션을 사용하는 어셈블리 모듈이 없도록 하고, 컴파일러가 할당할 수 있도록 해야 합니다. 이름을 모든 외부 개체에 연결하고 C++를 ARM 어셈블리로 변환하기 전에 어셈블리 수정자에서 C++에서 액세스하거나 호출하는 .def
또는 .global
지시문을 사용하여 모든 개체와 함수를 선언합니다.
C++ 파일에서 C
(extern C
로 프로토타입화된 함수)를 사용하여 어셈블리 언어에서 호출된 함수를 정의합니다. .bss
섹션에서 변수를 정의하거나 나중에 변환이 필요한 변수를 식별하기 위해 링커 기호를 할당합니다.
GCC 컴파일러를 사용하여 C++를 ARM 어셈블리로 변환
gcc
는 실행 중에 C++ 코드에서 중간 출력을 얻을 수 있는 훌륭한 소스입니다. -S
옵션을 사용하여 어셈블러 출력을 가져오는 기능입니다.
-S
옵션은 어셈블러로 보내기 전에 코드를 컴파일한 후 출력을 위한 것입니다.
구문은 gcc –S your_program.cpp
이며 이 명령을 선언하기만 하면 ARM 어셈블리로 변환하는 간단한 C++ 프로그램을 작성할 수 있습니다. 가장 간단한 접근 방식 중 하나일 뿐만 아니라 출력이 복잡하고 중급 프로그래머도 이해하기 어렵습니다.
GNN.cpp
파일:
#include <iostream>
using namespace std;
main() {
int i, u, div;
i = 2;
u = 10;
div = i / u;
cout << "Answer: " << div << endl;
}
Microsoft Windows의 GCC에서 이 명령을 실행합니다.
gcc –S GNN.cpp
출력:
컴파일러가 생성하는 C++ 프로그램 내의 어셈블리 파일에 한 줄의 어셈블리 코드 삽입을 위해 일련의 ASM 문 또는 단일 ASM 문을 사용할 수 있습니다. 이러한 어셈블리 문은 개입 코드 없이(코드 중단 없이) 순차적인 코드 줄(어셈블리 코드)을 컴파일러(C++ 컴파일러 출력)에 배치합니다.
단, 삽입된 명령어는 컴파일러에서 확인/분석하지 않으므로 항상 C++ 환경을 유지하십시오. 예측할 수 없는 결과를 생성하고 코드가 생성하는 레지스터 추적 알고리즘을 혼동할 수 있으므로 레이블이나 ump를 C++ 코드에 삽입하지 마십시오.
또한 ASM 문은 어셈블러 지시문을 삽입하기 위한 유효한 선택이 아니며 어셈블리 환경을 변경하지 않고 symdebug:dwarf
명령 또는 -g
명령을 사용할 수 있으며 C++ 환경은 C++ 코드에서 어셈블리 매크로 생성을 방지합니다. 정보를 디버깅합니다.
C++를 ARM 어셈블리로 변환하는 MOD(Assembly-Time Modulus) 함수 생성
ARM 어셈블리에는 MOD 명령이 없으므로 subs로 MOD 함수를 만들고 C++를 ARM 어셈블리로 쉽게 변환할 수 있습니다. ldr reg, =var
를 통해 변수의 메모리 주소를 로드해야 하며 변수를 로드하려는 경우 ldr r0, =carry ldr r0과 같이 해당
reg를 사용하여 또 다른
ldr을 수행해야 합니다. , [r0]
는 r0
의 메모리 주소에 저장된 값을 로드합니다.
루프가 한 번 또는 두 번만 실행되는 최소 입력을 제외하고 빼기 루프보다 훨씬 빠르기 때문에 sdiv
를 사용하십시오.
개념:
;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;
}
출력:
arm-linux-gnueabi-gcc
명령을 사용하여 C++를 ARM 어셈블리로 변환
arm-linux-gnueabi-gcc
명령은 C++를 x86 및 x64 머신용 ARM 어셈블리로 변환하는 완벽한 방법입니다. gcc
에는 사용 가능한 ARM 대상이 없으므로 일반 시스템에서는 사용할 수 없지만 대신 일반 gcc
를 사용할 수 있는 ARM 시스템에 있는 경우에만 사용할 수 있습니다.
arm-linux-gnueabi-gcc -S -O2 -march=armv8-a GNN.cpp
명령은 믿을 수 없을 정도로 강력합니다. 여기서 -S
는 출력 어셈블리를 나타내고 gcc
에 이에 대해 알려줍니다. -02
는 코드 옵티마이저는 결과에서 디버그 혼란을 줄입니다. -02
는 선택 사항입니다. 반면 -march=armv8-a
는 필수이며 컴파일하는 동안 ARM v8 대상을 사용하도록 지시합니다.
다음을 포함하여 다양한 버전의 ARM v8을 사용하여 컴파일하는 동안 ARM 대상을 변경할 수 있습니다. armv8-a
, armv8.1-a
에서 armv8.6-a
, armv8-m.base
, armv8-m.main
및 armv8.1-m.main
으로 각각 하나는 약간 다르며 심층 분석을 수행하고 필요에 완벽하게 맞는 것을 선택할 수 있습니다.
명령의 power.c
는 컴파일할 파일을 알려주며 -o output.asm
과 같은 출력 파일을 지정하지 않은 경우 어셈블리는 유사한 파일 이름 power.s
로 출력됩니다.
arm-linux-gnueabi-gcc
는 대상 또는 출력 어셈블리에 일반 gcc
를 제공하는 arm
머신에서 컴파일하는 데 대한 훌륭한 대안입니다.
gcc
를 사용하면 프로그래머가 -march=xxx
로 대상 아키텍처를 지정할 수 있으며 올바른 패키지를 선택하려면 컴퓨터의 apt
패키지를 식별해야 합니다.
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
출력:
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
또는 module load arm<major-version>/<package-version>
을 실행하여 ARM 컴파일러용 모듈을 로드하여 Linux용 ARM 컴파일러를 설치할 수 있습니다. 여기서 <package-version>
은 <major-version>입니다. .<minor-version>{.<patch-version>}
, 예: module load arm21/21.0
.
armclang -S <source>.c
명령은 C++ 소스를 컴파일하고 어셈블리 코드 출력을 지정하는 데 도움이 될 수 있습니다. 여기서 -S
는 어셈블리 코드 출력을 나타내고 <source>.s
는 변환된 코드를 포함하는 파일입니다. .
Linux용 ARM 컴파일러에서 armclang
명령을 사용하여 C++를 ARM 어셈블리로 변환
컴파일러가 루프를 벡터화하는 방법을 배우기 위한 첫 번째 단계인 ARM C++ 컴파일러를 사용하여 주석이 달린 어셈블리 코드를 생성할 수 있습니다. Linux OS용 ARM 컴파일러는 C++에서 어셈블리 코드를 생성하기 위한 전제 조건입니다.
ARM 컴파일러용 모듈을 로드한 후 module load arm<major-version>/<package-version>
명령을 실행합니다. 예: <major-version>.<minor를 입력하여
module load arm21/21.0 -version>{.<패치 버전>}
여기서 <패키지 버전>
은 명령의 일부입니다.
armclang -S <source>.cpp
명령을 사용하여 소스 코드를 컴파일하고 <source>.cpp
위치에 소스 파일 이름을 삽입합니다.
ARM 어셈블리 컴파일러는 코드를 벡터화하기 위해 SIMD(Single Instruction Multiple Data) 명령어 및 레지스터를 사용하여 GCC 컴파일러와 다른 작업을 수행합니다.
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
출력:
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
__asm
키워드를 사용하여 C++를 ARM 어셈블리로 변환
이는 컴파일러가 인라인 어셈블러를 제공하여 C++ 소스 코드에 어셈블리 코드를 작성하고 C++의 일부가 아니거나 C++에서 사용할 수 없는 대상 프로세서의 기능에 액세스할 수 있도록 하므로 가장 유효한 접근 방식으로 알려져 있습니다.
GNU 인라인 어셈블리 구문을 사용하는 _arm
키워드는 인라인 어셈블리 코드를 함수에 통합하거나 작성하는 데 도움이 됩니다.
그러나 인라인 어셈블러가 armasm
어셈블리 구문으로 작성된 레거시 어셈블리 코드를 지원하지 않기 때문에 armasm
구문 어셈블리 코드를 GNU 구문으로 마이그레이션하는 것은 좋은 방법이 아닙니다.
__asm [휘발성](코드); /* 기본 인라인 어셈블리 구문 */
인라인 어셈블리 문은 _arm
문의 일반 형식을 보여주며 아래 예제 코드에서 찾을 수 있는 인라인 어셈블리 구문의 확장 버전도 있습니다.
어셈블러 명령어에 휘발성
한정자를 사용하면 유익하지만 다음과 같이 컴파일러가 인식하지 못할 수 있는 몇 가지 단점이 있을 수 있습니다. 컴파일러가 코드 블록을 제거하게 할 수 있는 특정 컴파일러 최적화를 비활성화할 가능성.
휘발성
한정자는 선택적이므로 이를 사용하면 -01
이상으로 컴파일할 때 컴파일러가 어셈블리 코드 블록을 제거하지 않도록 할 수 있습니다.
#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);
}
출력:
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
_arm
어셈블리 문의 code
키워드는 어셈블리 명령어이고 code_template
은 해당 템플릿입니다. code
가 아닌 이를 지정하기만 하는 경우 선택적 input_operand_list
및 clobbered_register_list
를 지정하기 전에 output_operand_list
를 지정해야 합니다.
output_operand_list
(출력 피연산자 목록)는 쉼표로 구분되며 각 피연산자는 [result] "=r" (res)
형식의 대괄호로 묶인 기호 이름으로 구성됩니다.
인라인 어셈블리를 사용하여 __asm (".global __use_no_semihosting\n\t");
과 같은 기호를 정의할 수 있습니다. 또는 __asm ("my_label:\n\t");
과 같이 레이블 이름 뒤에 :
기호를 사용하여 레이블을 정의합니다.
또한 동일한 _asm
문 내에서 여러 명령을 작성할 수 있으며 __attribute__((naked))
키워드를 사용하여 임베디드 어셈블리를 작성할 수도 있습니다.
Microsoft C++ 컴파일러(MSVC)는 동일한 C++ 소스 코드에 대해 x86 또는 x64 시스템이나 아키텍처에서보다 ARM 아키텍처에서 다른 결과를 제공할 수 있으며 많은 마이그레이션 또는 변환 문제가 발생할 수 있습니다.
이 문제는 C++ 표준과 다르게 상호 작용하는 ARM과 x86 또는 x64 아키텍처 간의 하드웨어 차이로 인해 정의되지 않은 동작, 구현 정의된 동작 또는 지정되지 않은 동작 및 기타 마이그레이션 문제를 유발할 수 있습니다.
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