본문 바로가기

학교/시스템 프로그래밍

1. LDR/STR/ADR, 논리 연산

1. LDR/STR

레지스터 데이터를 전송하는 두 가지 명령어 : LDR, STR

 

LDR : 메모리값를 레지스터로 복사한다.

STR : 레지스터의 값을 메모리에 저장

 

EX)

LDR r1, [r2] @ r2의 메모리 주소를 r1에 저장. 두 번째 인자가 레이블 같은 상수가 아닌, 레지스터라면, []로 감싸준다.

STR r3, [r4] @ r3의 값을 r4가 가리키는 메모리 주소에 저장

 

C언어로 치면 LDR은

int r2 = 1;

int *r1 = &r2;  // r1 : r2가 저장된 메모리 주소 값

라 볼 수 있고,

STR은

*r1 = 2          // r1이 가리키고 있는 메모리 주소 값에 2를 씀

라고 볼 수 있다.

 

.global _start
_start:
	ldr r1, =array	@ *r1 = &array
	mov r2, #2		@ r2 =2
	str r2, [r1]	@ *r1 = 2
	b exit

exit:
	mov r0, #0
    mov r7, #1
    swi 0

.data
array: .skip 4

우리가 볼 건, _start 브랜치의 위에서 3개의 코드다. 아직 나머지는 이해할 필요는 없다.

 

r1에 array주소 값이 담긴 것을 확인

r2에 2를 할당하는건 건너뛴다.

 

r2의 값을, r1이 가리키는 메모리 주소에 저장한 것을 확인

 

단일로만 전송하는 명령어가 있다면, 여러개를 전송하는 명령어도 있다.

 

LDM r0, { r2-r9 }  @ r0의 값을, 8개의 word { r2 - r9 } 에 저장

STR r1, { r2-r9 }   @ { r2 - r9 }에 있는 8 개의 word를 r1에 저장

 

아마 배열에 주로 사용되는 거 같은데.. 일단 만만한 스택을  가지고 실험해 보자

코드는 이렇다. 먼저 STR부터 본다.

  1 .global _start
  2 _start:
  3     mov r2, #1
  4     mov r3, #2
  5     mov r4, #3
  6     mov r5, #4
  7     mov r6, #5
  8     mov r7, #6
  9     mov r8, #7
 10     mov r9, #8
 11     STMIA sp, { r2 - r9 }
 12     b exit
 13 
 14 exit:
 15     mov r0, #0
 16     mov r7, #1
 17     swi 0

r2~r9 레지스터에 1~8까지 넣고 이 8개의 word를 sp에 저장한다.

STM으로 넣기 전, 스택 상위 8word의 값

이제 해당 명령을 실행하면

상위 8word가 1~8로 변경된 걸 볼 수 있다.

물론 스택에 사용할거면, 먼저 8word의 크기를 확보하고 나중에 정리해주는게 맞긴 한데, 예시를 위해 이건 생략한다.

 

반대로 LDM을 봐보자. 역시 스택을 사용한다.

 

  1 .global _start
  2 _start:
  3     LDMIA sp, { r2 - r9 }
  4     b exit
  5 
  6 exit:
  7     mov r0, #0
  8     mov r7, #1
  9     swi 0

SP의 상위 8WORD의 값들과 LDM을 사용하기 전, 레지스터
레지스터에 순서대로 들어간 것을 확인할 수 있다.

LDM, STM을  보면, 뒤에 IA, IB같은 수식어가 있는데
이거는 스택을 먼저 움직이고 값을 넣을지, 값을 넣고 스택을 움직일지 이 차이다.

이거는 그냥 아키텍쳐에 따르거나, 짜는 사람 맘대로다 다만, 한 방식을 사용하면 계속 이걸 써야 한다.

 

지금까진 이걸 스택에 값을 복사하거나, 붙여넣는 식으로만 썼는데,
push pop처럼 쓰게 할려면 어떻게 해야 할까? 바로 !를 붙이는 거다.

 

LDMIA sp!, { r2 - r9 }

STMIA sp!, { r2 - r9 } 

이렇게 하면 된다.

여기서 !란, 한 번의 연산을 끝내면, 해당 레지스터의 값을 지정된 크기만큼 감소한다.
여기선 레지스터들이 word 사이즈니 str r2, [sp] 를 해주고 난 뒤, sp의 값이 4byte만큼 감소한다.

이렇게 코드를 바꿔주면 gdb에서 보여주는 내용이 달라진다.

LDM SP!를 했을 때 결과 노란칠한 부분만 보면 된다.

 

STM sp! 의 결과 마찬가지로 노란 곳만 보면 된다.
STM !sp를 한 뒤의 스택

STM sp! 를 해주면 push로 변경될 줄 알았는데 이건 안 바뀐다.

그래도 원리는 push와 같다.

 

ldr, str에 대해선 4장 쯤에서 자세히 다루겠다.

 

2. ADR

ADR은 ARM의 실제 명령어는 아니고, 의사 명령어(PSEUDO INSTRUCTION)이다.

즉, ADR란 명령어가 진짜 있는게 아니라, ADD와 SUB로 이루어진 라이브러리 함수이다.

 

사용법

ADR rd, LABEL

 

간단한 예를 보자.

r0 레지스터에 HELLO 레이블을 넣는 코드다.

.global _start
_start:
    ADR r0, HELLO
    b exit

exit:
    mov r0, #0
    mov r7, #1
    swi 0

HELLO:
    .asciz "Hello, world!\n"

 

pc + 12의 위치(주소)를 r0레지스터에 저장한다.

보시다시피, adr이 아닌, r0가 돼있고, 레이블은 pc + 상대주소로 되있다. 

 

ADR을 실행하기 전 상태

 

여기서 흥미로운 사실이 있는데, 해당 위치의 pc + 12 값은 exit + 4의 위치다. 왜 이렇게 나온 걸까?

pc + 12 (0x8054 + 12)의 위치는 exit +4 이다.

 

이게 가능한 이유가, 명령어 파이프라인 이란 것인데,

명령어를 하나씩 읽지 않고, 그 다음 여러개를 읽어오는 기능이다.

 

즉 명령어를 동시다발적으로 실행해주는 기능인데, 서브루틴이 많을 수록 효율이 떨어진다.

왜냐면 ,서브루틴 들어가는 순간, 기존의 파이프라인은 의미가 없기 때문에 새로 재구성해야 하기 때문

 

여기 ARM 환경에서는 명령어 2개를 미리 읽어오기 때문에,

실제 HELLO 레이블의 위치는 pc + 20의 위치이다.

HELLO LABEL 위치
ADR로 HELLO LABEL 위치를 R0에 저장

 

추가로 코드 예시에서 asciz는 문자열을 저장하고 끝에 null(zero)을 붙이겟단 거다.

 

3. 기본 연산

논리 연산

형식 : XXX RD, RM, OPR

AND R0, R1, R2  @ R0 = R1 & R2

ORR R0, R1, R2   @ R0 = R1 | R2

EOR R0, R1, R2   @ R0 = R1 ^ R2

BIC R0, R1, R3    @ R0 = R1 & ~R3 (BIT CLEAR)

 

BIC는 말 그대로 R3레지스터 값의 비트가 1인 부분은 0으로 클리어 시켜주겠다는 거다.

만약 R1과 R3이 같으면 R0은 0이 된다.

만약 R1가 7이고 R0를 2로 만들고 싶으면
BIC R0, R1, #5를 해주면 된다.

 

4. Conditoin Code Flags

연산이나 조건 명령을 할 때, 값의 초과나, 참 거짓 유무를 판단할 때 쓰이는 레지스터.

CPSR이라고도 하며, 자주 사용하는 4가지 상태가 있다.

 

자주 쓰는 4개가 있는데

N : (Negatvie Flag) 음수가 되면 1로 세팅됨

Z : 연산 결과가 0이면 1로 세팅됨 (조건 분기에 사용)

V : signed 자료형에서 오버플로우가 나면 1로 세팅

C : (Carry Flag) 덧셈에선 높은 자리로 올라가거나, ( 8 +9 = 17에서 1 ) 뺄셈에서 앞에 자리수 에서 수를 빌려와야 할 때 ( 18 – 9 ) 1로 세팅됨.

'학교 > 시스템 프로그래밍' 카테고리의 다른 글

Signal  (0) 2020.12.05
PC, LR, SP  (0) 2020.11.17
LDR / STR Indexing  (0) 2020.11.17
section  (0) 2020.11.13
shift 연산자  (0) 2020.11.11