임베디드/ARM

ARM 아키텍처 어셈블리

우제혁 2021. 1. 11. 17:45

ARM Register

 R0 ~ R12 : 범용 레지스터 (다목적 레지스터), R11(스택 프레임 포인터)
 R0 : 함수 리턴 값 저장 (EAX 같은 느낌) 
 R0 ~ R3 : 함수 호출 인자 전달 
 R13 ~ R15 : 특수 레지스터 
 R13(SP) : 스택 포인터 : 스택의 맨 위를 가리킴 
 R14(LR) : 링크 레지스터 : 서브루틴 후에 돌아갈 리턴 주소 저장 
 R15(PC) : 프로그램 카운터 : 현재 fetch되고 있는 명령어의 주소 - 따라서 현재 실행되는 명령어의 다음다음 주소
 CPSR : 현재 프로그램 상태 레지스터

 

ARM 어셈블리 명령어

OP Code<cond> Rd, Rn, Rm

  • OP Code : push, add, sub, mov 등 어셈블리 명령어
  • Rd : Destination Register, 반드시 R0~R15인 Register만 가능하며 연산작업의 결과값을 저장하는 레지스터 
  • Rn : Operand1 Register, 반드시 R0~R15인 Register만 가능
  • Rm : Operand2, 레지스터뿐만 아니라 상수(#), 주소([]), 쉬프트 연산식 등 다양한 값이 사용가능.

ARM 어셈블리 명령어

참고:https://m.blog.naver.com/gangst11/145839687

 

1) 산술 연산

  • ADD(SUB) Rd, Rn, Rm 
    Rd = Rn +(-) Rm

2) 비트 연산

  • AND Rd, Rn, Rm
    Rd = Rn & Rm
EX)AND R0, R1, R2, lsl #2 
=> R2를 left shift 2 (<< 2)한 값과 R1을 AND 연산해서 그 결과값을 R0에 저장

 

  • ORR Rd, Rn, Rm
    Rd = Rn | Rm

  • EOR Rd, Rn, Rm
    Rd = Rn ^ Rm

  • BIC Rd, Rn, Rm
    Rd = Rn & !Rm

3) 대입 연산

  • MOV Rd, Rm
    Rd = Rm

4) 쉬프트 연산

  • LSL Rm
    Rm = Rm << 2
  • RSL Rm
    Rm = Rm >> 2

5) 분기 명령

  • B : C언어에서 goto와 같은 역할, return 하지 않음,분기 주소에는 label,메모리 주소,register를 사용할 수 없음
  • BL : C언어에서 function call과 유사한 개념, ARM에서는 서브루틴이 끝난 후 복귀할 주소를 R14 레지스터에 저장

6) 비교 연산

  • CMP Rn, Rm : Rn과 Rm을 비교, Rn - Rm을 수행하여 Status Register에 상태를 저장한다.
  • push {Rn, Rm} : Rm, Rn 순서로 스택에 push 된다.
  • pop {Rn, Rm} : Rm, Rn 순서로 스택에서 pop된다.

CMP : if(Rn - Rm == 0). Rn과 Rm의 값이 같은지 비교하는 명령어

 

Status Register (4개)

  • N (Negative) : 연산결과가 음수인 경우 set 1
  • Z (Zero) : 연산결과가 0인 경우 set 1
  • C (Carry) : 덧셈 연산에서 Carry가 발생할 경우 set 1, 뺄셈 연산에서 Borrow가 발생할 경우 set 0
  • V (Overflow) : signed 연산의 덧셈, 뺄셈에서 overflow가 발생할 경우 set 1

 

조건부 실행 옵션(14개)

  1. unsigned 형태로 비교할때 사용.
        HS (>=), LO(<), HI(>), LS(<=)
  2. signed 형태로 비교할때 사용.
        GE (>=), LT(<), GT(>), LE(<=)
  3. 단순비교인 같다 또는 같지 않다에 사용.
       EQ(==), NE(!=)
  4. 연산결과가 음수/양수에 사용.
        MI(음수), PL(0또는 양수)
  5. 연산결과가 Overflow가 발생유무에 따라 사용.
        VS(overflow), VC(No overflow)
EX)

CMP R0,R1         
ADDEQ R0,R2,#100         
MOVNE R0,#1

=> R0와 R1의 값을 비교해서 만약 값이 같다면 R0=R2+100을 수행하고, 틀리면 R0에 1을 대입.

 

메모리 연산

  • STR R1, [R0] : [R0] = R1, 메모리 주소에 레지스터 값 저장
  • LDR R1, [R0] : R1 = [R0], 해당 메모리 주소의 값을 레지스터에 저장
  EX 1) 
  
  MOV R0,#0x100
  MOV R1,#0xFF
  STR R1,[R0]
          

=> 0x100 번지에 0xFF를 저장.

=> 레지스터를 중괄호[] 안에 넣으면 해당 레지스터의 값이 있는 메모리 주소가 됨(C언어에서 포인터 역할)

 

EX 2)

STR R1, [R0],#4

=> R0 번지에 R1 레지스터값을 저장하고, R0의 주소값을 +4 증가

 

EX 3)

STR R1, [R0,#3]


EX 4)

STR R1, [R0,#3]!

=> R0 + 3 번지에 R1 레지스터값을 저장.

=> 중괄호안에서 콤마(,)와 숫자상수 #을 이용해서 해당 R0에서 상대적인 offset 위치의 메모리 주소를 access

 

=>이때 !가 있으면 R0의 주소값을 +3 증가까지 진행

 

 

ARM 간단한 코드 분석 (에필로그, 프롤로그)

참고:https://5kyc1ad.tistory.com/187

 

hello world를 출력하는 코드를 분석

 

#include <stdio.h>

int main(int argc, char *argv[]){
	printf("hello world\n");
	return 0;
}
   0x000103fc <+0>:	push	{r11, lr}
   0x00010400 <+4>:	add	r11, sp, #4
   0x00010404 <+8>:	sub	sp, sp, #8
   0x00010408 <+12>:	str	r0, [r11, #-8]
   0x0001040c <+16>:	str	r1, [r11, #-12]
   0x00010410 <+20>:	ldr	r0, [pc, #16]	; 0x10428 <main+44>
   0x00010414 <+24>:	bl	0x102dc <puts@plt>
   0x00010418 <+28>:	mov	r3, #0
   0x0001041c <+32>:	mov	r0, r3
   0x00010420 <+36>:	sub	sp, r11, #4
   0x00010424 <+40>:	pop	{r11, pc}
   0x00010428 <+44>:	muleq	r1, r12, r4

 

 

ARM에서 stack과 heap은 만드는 사람에 따라 쌓이는 방향이 정해지지만 보통 x86과 유사하게 stack은 낮은 주소로 heap은 높은 주소로 쌓인다

 

<+0> push {r11, lr} (프롤로그)

lr(r14)과 r11을 순서대로 스택에 push 해준다.

이는 <+40>에서 pop {r11, pc} 를 해주는데 여기에 다시 들어갈 값을 저장하는 함수 프롤로그 부분이다.

<+4> add r11, sp, #4

r11에 sp(r13)+4를 해준값을 저장해준다

여기서 sp는 현재 스택 포인터 즉 스택의 맨 위를 가리킴킨다

 

<+8> sub sp, sp, #8

sp(스택의 값)을 8바이트 만큼 뺀다.

이는 <+12>, <+16>에서 들어갈 인자 공간을 생성해 주는 코드이다.

<+12> str r0, [r11, #-8]

(r11-8) 번지에 r0값을 넣어준다.

앞서 말했듯이 main 함수의 첫번째 인자 값을 sp - 4에 넣는 코드이다.

 

<+16> str r1, [r11, #-12]

sp - 8에 main 함수의 두번째 인자 값을 넣는 코드이다.

 

<+20> ldr r0, [pc, #16] ; 0x10428 <main+44>

r0 값에 pc(r15)+16의 주소 값을 넣어주는 코드이다. 세미콜론을 참고하면 main+44의 주소인 0x10428을 넣는 코드이다.

이는 hello world의 문자열의 주소를 의미한다.

 

<+24> bl 0x102dc puts@plt

bl 명령어로 함수(puts)를 call 해주는 명령어이다.

앞서 말했듯이 첫번째 인자 r0를 puts해주기에 hello world가 출력될 것이다.

<+28> mov r3, #0

r3에 0값을 넣어주기 위해 작동되는 코드

 

<+32> mov r0, r3

r0에 r3의 값을 넣어준다.

함수 return시 r0는 return 값이 들어가기 때문에 main 함수의 종료로 return 0을 수행해 주기 위해 실행되는 코드인것 같다.

<+36> sub sp, r11, #4

sp에 r11-4의 값을 넣어준다.준다.

stack pointer를 원상 복귀 해주는 코드인 것 같다.

<+4>에서 stack pointer에 + 4 해준 코드를 원상 복귀 시켜주기 위해 -4를 해주는 코드라고 해석된다.

 

<+40> pop {r11, pc} (에필로그)

<+0>에서 push 명령어로 stack에 박아놓았던 return address를 다시 pop하여 r11과 pc에 넣어 주는 역할을 하는 코드이다.

이 코드를 통해 PC에 return address가 들어갔기에 다음 코드를 정상적으로 수행할 수 있게 된다.

 

<+44> muleq r1, r12, r4

r12와 r를 곱한값이 r1과 같은지 비교하는 코드이다 

코드에서 이부분이 의미하는 역할은 좀더 공부해봐야 할것같다.

 

 

 

 

+ stack 그림을 잘못 그려넣은것 같아 빠른 시일내로 수정할 계획입니다.