ARM 기초

2021. 9. 22. 01:51Soft_Ware/Assembly

필자는 마이크로프로세서 과목을 통해 ARM architecture를 배우며, 하드웨어 구조 그리고 ARM 어셈블리에 대해서 공부하고 있다.따라서 오늘은 ARM프로세서에 대해서 알아보도록 하자.

 

64비트 프로세서를 사용하는 이유

 우선 우리가 다루는 데이터는 32비트로 충분하다.연산을 위해서 2의 32제곱을 넘어가는 경우는 거의 없기 때문이다.문제는 주소이다.데이터를 다루는 것 뿐만 아니라, 이동시키고 기억하기 위해서 주소라는 개념을 쓴다.주소공간이 많으면 많을수록 그 만큼 쓸 수 있는 레지스터 수도 많아진다는 뜻일테고, 이는 조금 더 자유롭게 하드웨어를 조작 할 수 있다는 뜻이 된다.또한 64비트 프로세서는 32비트 운영체제와 64비트 운영체제 그리고 어플리케이션도 64비트,32비트 모두 사용이 가능하지만, 32비트는 32비트 운영체제와 어플리케이션만 제한적으로 사용할 수 있다.당연하게 32비트 메모리 공간으로 64비트 메모리 공간의 역할을 할 수 없기 때문이다.

 

ARM8 Assembly

ARM 아키텍쳐의 간단한 어셈블리어에 대해 알아보자.

우선 어셈블리어는 개발하는 사람들은 대부분 알고 있겠지만, 우리가 프로그래밍 할 때 사용하는 언어인 C,Python,Java 등등과는 다르게 컴퓨터와 조금 더 친한 LOW Level Language이다.그렇기 때문에 우리가 지금까지 학습한 프로그래밍 언어에 비해서는 확실히 어려움을 느낄 수 있고 하드웨어에 종속적이기 때문에, 명령어 또한 달라질 수 있다.

필자는 어셈블리 과제를 하다가 printf의 인자를 넣는데만 몇시간을 쏟아부운거 같은데, 어셈블리를 능숙하게 다루려면 진짜로 책을 한권 떼거나 어셈블러어 과목을 수강하는게 나을거 같다.

 

우선 필자는 ARM8 아키텍쳐로 어셈블리어를 실행시켜봤는데, 우선 section이라는 것이 존재하고 data section과 text section이 존재한다.

 

Data section

.section .data
 message: .ascii "Hello World\n"
 x: .byte 
 y: .word 
 z: .dword

Data section은 다음과 같이 구성한다.다른 아키텍쳐에서는 db나 dd등의 크기 지정자를 이용하여 데이터를 선언해주는데 ARM8은 위와 같이 구성할 수 있다.

.ascii String
.byte 1 byte
.word 2 byte
.dword 4 byte

위 표와 같이 크기 지정자가 구성되어 있으며, 아마 다른 지정자들도 많을 것이다.

 

Data section은 필자가 해석하기로는 전역변수와 같은 것이다.특정 공간 외부에서 지정하는 것이고 메모리 주소공간에 값이 들어가 있다.함수에서 활용하는 방법은 뒤에 차차 설명하도록 한다.

 

데이터를 선언하는 Data section이 있다면, 코드를 작성하는 Text section또한 존재한다.이것도 마찬가지로 다른 아키텍쳐에서는 .code라고 사용하는 것도 봤었는데 하드웨어 종속적이라서 ARM에서는 text로 사용하는 것 같다.

bss section이라고, 데이터를 저장하는 또하나의 section이 있다.이것도 data section과 유사하게 구성되어 있다.

더보기

컴퓨터 프로그래밍에서 .bss 또는 bss는 수많은 컴파일러 링커가 처음에 0 값의 비트로 표현되는 정적으로 할당된 변수를 포함하는 데이터 세그먼트의 한 부분으로 사용한다. "bss 섹션"(bss section), "bss 세그먼트"(bss segment)라고도 부른다.

일반적으로 데이터가 없는 bss 섹션의 길이만이 오브젝트 파일에 저장된다. 프로그램 로더는 프로그램을 로드할 때 bss 섹션을 위한 메모리를 할당하고 초기화한다. 운영 체제는 zero-fill-on-demand라는 기술을 사용하여 bss 세그먼트를 효율적으로 구현한다. (McKusick & Karels 1986) 임베디드 소프트웨어에서 bss 세그먼트는 main()에 들어가기 전에 C 런타임 시스템에 의해 0으로 초기화되는 메모리로 매핑된다.

일부 컴퓨터 아키텍처에서 ABI 또한 조그마한 데이터에 대한 sbss 세그먼트를 지원한다. 일반적으로 이러한 데이터 항목들은 특정한 범위의 주소에만 접근할 수 있는 더 짧은 명령을 이용하여 접근할 수 있다.

위는 .bss와 관련된 위키백과 내용이다.요약해보면, 0으로 초기화되는 데이터를 선언하는 자리라고 생각할 수 있다.

Text section

.section .text
.global main
main:
	....

코드를 구성하는 section은 위와 같다. .global뒤에 함수명을 적어주며, global은 외부에 노출시킬 수 있도록 해주는 것이다.반대로 static을 사용하면 외부에서 사용할 수 없게 된다.global뒤에 main이라고 적어두었는데, 이는 함수와도 같은 의미로 원하는 이름을 넣어서 사용할 수 있다.근데 ARM에서 _start이름을 주고 해봤는데 main이 없다는 에러가 떴던거 같은데 이거는 다음에 포스팅하기로 한다.

아 그리고 .section 키워드도 마찬가지로 하드웨어에 따라서 조금씩 다르다. .segment를 사용하기도 하더라.

매크로라는 개념을 통해 명령어를 커스텀하기도 하는데 이것도 다음에 다뤄본다.

 

다음은 ARMv8의 간단한 어셈블리 코드이다.

.section .data
 message: .ascii "Hello, World\n"
 msg_parameter: .ascii "%d\n"

.section .text
.global main
main:
    ldr x0, =message
    bl printf
    mov x1, #5
    ldr x0,, =msg_parameter
    bl printf

앞서 말한것처럼 data는 data section에 넣고, 코드에 관련된 부분은 text section에 넣어서 작성한다.

main의 가장 첫 부분에 나와있는 ldr은 메모리 영역의 값을 레지스터로 가져오는 것이다. 현재 작성된 부분을 살펴보면,

와 같은 형식으로 되어있다.레지스터 값에 =데이터라고 표시된, 코드에서는 =message의 메모리 주소가 가리키는 값을 가져와서 넣으라는 명령어이다.

LDR 레지스터, =데이터

ARM64 bit에서는 주소를 위한 레지스터로 64bit를 사용할 수 있고 정수형 레지스터를 위해 32bit를 지원한다.

32bit assembly에서는 pc가 15번 lr가 14번 sp가 13번 등으로 정해져 있고, 레지스터 이름도 x가 아닌 r로 되어있을 것이다.또한 r15대신 pc를 사용할 수도 있다.

레지스터 이름은 주소를 위해서는 x, 값을 위해서는 w로 정해져 있고 레지스터 번호에 따른 역할은 다음과 같다.

레지스터 휘발성 여부 역할
x0 O 매개변수/스크래치 레지스터, 결과 레지스터
x1~x7 O 매개변수/스크래치 레지스터
x8~x15 O 스크래치 레지스터
x16~x17 O 프로시저 호출 내 스크래치 레지스터
x18 X 플랫폼 레지스터: 커널 모드에서 현재 프로세서의 KPCR을 가리킵니다. 사용자 모드에서 TEB를 가리킵니다.
x19~x28 X 스크래치 레지스터
x29/fp X 프레임 포인터
x30/lr X 링크 레지스터
  • KPCR : 프로세서 관련 데이터를 저장하는 커널 구조체
  • TEB  : 프로세스에서 실행되는 스레드에 대한 정보를 담고 있는 구조체
  • 프레임 포인터 :  함수가 호출되기 전의 스택메모리 주소

 

스크래치 레지스터는 함수 내 손실될수 있는 레지스터라는 뜻이다.

설명이 길었는데, 앞서 코드의 ldr x0, =message는 메모리 공간의 message가 가리키는 주소의 값인 String을 가져와서 x0 레지스터에 넣으라는 뜻이다.결국 다음 실행코드인 bl printf에 의해서 String이 출력되는 것이다.

bl을 통하여 서브루틴에 진입하는데, 서브루틴에 진입할 때 해야할 일이 두가지가 있다.

1. 다음 실행될 스택 포인터를 X30레지스터에 저장

2. 서브루틴 메모리 주소로 PC 이동

서브루틴인 printf 작업을 마치고 다시 기존 코드를 실행하기 위해서 다음 명령어 주소를 저장시켜주어야서브루틴 이후에 기존 작업을 수행할 수 있다.하지만 이것들은 모두 ARM 프로세서들이 해주므로 우리는 따로 해줄것이 없다.

이와는 반대로 RET이라는 명령어를 통해 X30 레지스터에 PC값을 넣어서 PC주소로 분기하게 할 수도 있다.

 

다음은 매개변수가 있는 printf의 동작을 살펴볼텐데, 우선 MOV명령어를 보자.

MOV 레지스터, 값

MOV는 위와 같이 데이터의 이동을 명령한다.필자는 X1레지스터에 5라는 정수값을 넣어주었고, #5로 표현하였다.이후에 다시 ldr 명령어로 메모리에 있는 String을 가져왔다. 이후 printf 서브루틴을 통하여 출력하였고 실행결과는 아래와 같다.

Hello, World ;첫번째 서브루틴
5	     ;두번째 서브루틴

필자는 첫번째 서브루틴은 message에 인자가 없기 때문에 결과 레지스터인 x0에 Hello, World를 넣어주고 실행하면 그대로 출력되는 것이라고 이해가 되고, 두번째 서브루틴은 message에 인자가 있기 때문에 x1을 MOV 명령어를 통해 값을 넣어주고 실행하였을때 %d값이 첫번째 인자인 x1값으로 출력된다고 이해했다.

 

부동소수점/SIMD 레지스터,시스템 레지스터 등 ARM64 레지스터 관련 설명

https://docs.microsoft.com/ko-kr/cpp/build/arm64-windows-abi-conventions?view=msvc-160M

 

ARM64 ABI 규칙 개요

자세한 정보: ARM64 ABI 규칙 개요

docs.microsoft.com

산술 명령어(ADD,SUB 등), 메모리 관련 명령어(STR 등), 스택 관련 명령어 (STP, LDP) 등 여러가지가 더 사용되는데, 어셈블리어를 다시 다뤄볼 때 설명하도록 하겠다.