포스트

4. ARM 미장센 - ARM 제어의 구현

Embedded Recipes

목차

  • ⓐ ARM Assembly를 파헤쳐 보자 ADS vs GNU
  • ⓑ 대충의 초간단 Assembly와 Reverse Engineering
  • ⓒ ARM, Thumb mode와 S 접미사
  • ⓓ ARM과 Thumb 사이의 Veneer
  • ⓔ Inline Assembly와 INTLOCK의 구현
  • ⓕ Pipe line과 Exception 관계 그리고 ^접미사
  • ⓖ Exception Vector Table (EVT)과 각 Handler의 구현
  • ⓗ SWI 의 진실
  • ⓘ Coprocessor Assembly
  • ⓙ Bootloader와 Memory Budget (Mapfile) 어떻게 변수초기화를 할 것인가
  • ⓚ Reset Handler에서 main까지 (Entry Point)
  • ⓛ Scatter Loading과 Bootup - __user_initial_stackheap

ⓐ ARM Assembly를 파헤쳐 보자 ADS vs GNU


  • Assembly program 예제를 통해 어떻게 구성되는지 알아보기
Helloworld.s -----------------------------------------------------------------------

      CODE32
      AREA Helloworld CODE, READONLY
      ENTRY

BEGIN ; LABEL

      ADR r0, THUMB+1
      BX r0
      CODE16

THUMB ; LABEL

      ADR r1, TEXT

LOOP ; LABEL
      LDRB r0, [r1], #1
      CMP r0, #0  
      BLNE print_ch
      BNE LOOP
      B EXIT

TEXT = "Hello World ", 0 ; TEXT → LABEL

      END
----------------------------------------------------------------------------------
  • Hello World를 출력하는 예제

assem

  • AREA
    • Code의 모음을 하나의 AREA로 묶을 수 있음
    • block의 속성. Code, Data, Readonly, ReadWrite 등을 지정
  • ENTRY
    • AREA에서 첫 번째로 수행하는 위치
    • CODE32/CODE16 : 32 bit ARM인지, 16bit Thumb code인지 알려주는 지시어
    • LABEL : 사용자 지정 라벨. 심볼의 의미가 되어 주소가 됨.
  • END : 어셈블리 파일의 끝을 알림
  • 함수 이름 PROC : 함수 시작
  • ENDP : 함수 끝
  • ALIGN : bit 수 만큼 컴파일러가 정렬함
  • EQU : C에서 #define과 같음
  • MACRO : MACRO로 시작해 MEND로 끝남.
  • ADS과 GNU 차이
    • ARM사에서 내놓은 ADS, RVCT와 GNU에서 내놓은 ARM Compiler 사이에 Assembly Syntax grammer는 거의 같음

ⓑ 대충의 초간단 Assembly와 Reverse Engineering


  • 기본적인 Assembly 명령어와 특징들, Syntax들
  • ARM assembly는 OP 코드 + Operand형식을 따름
  • Branch 명령어
    • pc에 주소를 넣어, 프로그램 실행 번지를 바꾸는 역할. jump
  • Data Processing
    • 대상 레지스터 rd에 사칙연산 및 이동 등을 수행
  • Load/ Store
    • 대상 레지스터에 Memory 주소가 가리키는 곳의 값을 가져옴
  • 의사 명령어
    • 편의를 위해 Assembly에는 있지만, 실제 처리는 다른 방법으로 처리하는 명령어들
    • LRD, ADR 등
  • SWAP 명령
    • memory 영역ㅇ의 값과 register 값을 교환하는데 사용
  • PSR 명령
    • CPSR, SPSR 두개와 일반 register 사이에 값을 서로 복사할 수 있는 명령어
  • SWI 명령
    • 소프트웨어적으로 인터럽트를 발생시키는 명령어. 다른 모드로 전환 시 사용함
  • DCD directives
    • Data를 위한 메모리 할당
  • EXPORT, IMPORT directive
    • Assembly에서 사용된 Symbol을 외부에서 사용 가능하도록 하는 Directive
    • Export, Import 등
  •  FIELD와 MAP
    • C에서 구조체 선언할때와 비슷한 용법으로 사용
  •  덧붙임 명령어들 ! ^ S

ⓒ ARM, Thumb mode와 S 접미사


  • 32bit ARM에서 돌아가는 16bit 기계어
    • 16bit bus line을 가진 memory에서 효율적으로 사용할 수 있도록 32bit ARM 명령어를 16bit으로 압축한 것
      1. ARM mode는 Thumb mode보다 줄이 간략함
    • 32bit 한줄로 수행 가능한 명령을, 16bit에서는 여러줄로 수행함
      1. 비교 구문의 차이
    • Thumb는 CMP로 비교 후, 그 결과를 가지고 판단. ARM은 앞의 산술연산을 이용가능
      1. Stack 명령어가 다름
    • ARM은 Multiple Register Transer 명령어, Thumb는 PUSH, POP으로 이루어짐

ⓓ ARM과 Thumb 사이의 Veneer


  • CPSR T bit가 1이냐 0이냐에 따라 ARM mode인지 Thumb 모드인지 달라짐
  • T bit 설정 대신 bx명령어를 통해 설정할 수도 있음
    • bx 명령어에 jump 주소를 주는데, 그 주소가 홀수면 thumb 모드로, 짝수면 ARM mode로 세팅해줌
  • Veneer : Linker가 Link할 때 이런 코드들 사이를 interwking 할 수 있는 중간 Layer를 끼워 넣어줌
    • 컴파일러가 자동으로 mode가 전환이 되는 코드를 넣어줌

ⓔ Inline Assembly와 INTLOCK의 구현


  • 소프트웨어를 작성하면서 가끔 Assembly로 짜야하는 부분이 나올 때도 있음
    • .s 파일을 만들어서, Assembler로 컴파일 후, 기존 C로 짜던 object와 link 거는법
    • 혹은 간단한 Inline Assembly를 이용
  • Assembly가 필요한 경우
    • Low Level을 직접 다룰 때, Coprocessor를 다뤄야할 때
    • ARM을 직접 다룰 때, PSR를 다룰 때, Interrupt Lock을 걸고 싶을때
    • Register를 직접 다룰 때
  • __asm으로 C파일에서 어셈블리 언어임을 알릴 수 있음
  • 예시1) stack pointer를 가져올 수 있게 하는 함수
1
2
3
4
5
6
7
8
9
10
11
getsp.c ----------------------------------------------------------------
typedef unsigned long word;

word *get_StackPointer(word *stackPointer)  {  
	__asm
	{  
		mov stackPointer, sp;  // sp에서 stackPointer로 값을 복사
	}
	return;  
}
------------------------------------------------------------------------
  • 예시2) CPSR를 만져서 Interrupt가 발생하지 않도록 하는 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
intlock.c --------------------------------------------------------------------------

#define PSR_Irq_Mask 0x80  
#define PSR_Fiq_Mask 0x40  
#define PSR_INT_Mask 0xC0  
// FIQ, IRQ모두 BLOCK
// CPSR에서 IRQ mask 는 0x80, FIQ mask 는 0x40

void Interrupt_lock(void){  
	__asm
	{  
		mrs     a1, CPSR  
		orr     a2, a1, #PSR_INT_Mask  // a1과 interrupt bit field 를 OR해서 a2에 저장
		msr     CPSR_c, a2  // 
	}  
	return;  
}
-----------------------------------------------------------------------------------
  • Inline Assembly의 제약
    • 의사 명령어인 LDR register ,= Expression과 ADR 형식 이용 불가
    • 라벨 사용 불가
    • BL, BLX 등의 branch 사용 불가
    • Processor Mode나 Coprocessor의 상태를 바꿀 수 있으나, 컴파일러는 이를 알아채지 못함
    • Thumb mode에서 inline된 어셈블리는 r0~r7만 접근 가능
    • inline된 레지스터는 컴파일러가 stack에 저장하고 restore하는걸 자동으로 함

ⓕ Pipe line과 Exception 관계 그리고 ^접미사


  • 예외 발생 시
    • ARM 실행 모드로 전환
    • Exception Vector로 PC branch
    • CPSR은 SPSR에 저장
  • 그러나 ARM Pipeline의 동작 때문에 복귀 주소 조정이 필요함 pipeline
  • 보통 PC는 현재 Execution보다 2단계 앞을 가리킴. fetch 해오는 곳을 가리키고 있음
  • ARM 파이프라인 동작을 보상하기 위해 복귀 주소 보정 수행
    • Thumb 명령어들은 4byte가 아닌 2byte handler
  • PC_exception : 문제 발생 지점
  • PC_next_op : 바로 다음 실행 주소
  • Return Instruction : 예외 발생으로부터 어디로 돌아갈 것인지

    ⓖ Exception Vector Table (EVT)과 각 Handler의 구현


  • Exception Vector Table의 실제 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; ADDRESS EXCEPTION MODE ON ENTRY
;----------------------------------------------------------------------------
; 0x00000000 Reset Supervisor
;----------------------------------------------------------------------------
; 0x00000004 Undefined Exception Undefined
;----------------------------------------------------------------------------
; 0x00000008 Software Interrupt Supervisor
;----------------------------------------------------------------------------
; 0x0000000C Abort (prefetch) Abort
;----------------------------------------------------------------------------
; 0x00000010 Abort (data) Abort
;----------------------------------------------------------------------------
; 0x00000014 Nothing Nothing
;----------------------------------------------------------------------------
; 0x00000018 IRQ IRQ
;----------------------------------------------------------------------------
; 0x0000001C FIQ FIQ
  • 각각 32bit씩 차지. 각 주소별로 어디론가 branch하는 코드가 들어있음
1
2
3
4
5
6
7
8
9
10
11
AREA INIT_VECTOR, CODE, READONLY → INIT_VECTOR라는 부분 기억해 두세요. 
	CODE32 ; 	32 bit ARM instruction set. 
	ENTRY
	B Reset_Handler ; 0x0 
	B Undefined_Handler ; 0x4 
	B SWI_Handler ; 0x8 
	B Prefetch_Handler ; 0xC 
	B Abort_Handler ; 0x10 
	B Nothing_Handler ; 0x14 
	B IRQ_Handler ; 0x18 
	B FIQ_Handler ; 0x1C
  • 이 테이블에 원하는 branch를 추가해 Undefined Interrupt도 작성 가능함
    • 보통 다음 핸들러에 해당 내용들을 넣음
      1. Rest Handler
      2. undefined Handler
      • ARM Computing 파워가 모자를 때, coprocessor에게 작업을 수행하게 할 때도 사용 3. Prefetch Abort Handler
      • Debugging 용도의 code 삽입 4. Data abort Handler
      • Align을 강제로 맞춰서 원상복구하는 형태의 핸들러를 넣기도 함 5. FIQ, IRQ 6. SWI
  • 각 핸들러마다 뭘 넣느냐는 개인 취향임

ⓗ SWI 의 진실


  • Hardware없이 인터럽트 거는 방법? Software Interrupt
  • 그러나 SWI는 Interrupt가 아님!
    • Software적으로, Exception을 거는 것.
  • 다음과 같은 경우 사용
    1. System Call(Kernel) : SVC mode로 진입하여 system mode 변경
    2. Semi hosting
      • Target에서 I/O에 관련된 것들을 경로를 바꾸어, Debugger에서 실행하고 있는 Host System에서 수행하도록 할 때
      • ex) UART 로 message 발송하는 target routine을 UART가 아닌 Debugger로 target의 report를 직접 받을 수 있음
        • 추후 how to debug 장에서 다룰 예정
  • C에서 Software Interrput 거는 방법
    • SWI {condition} 명령어 걸어주면, 예외 발생
      • SWI Exception vector(0x0008)번지로 branch
    • SWI 뒤의 parameter로 condition switch걸어 여러 case 처리 가능
  • 예시 1) SWI 호출
1
2
3
4
5
void SWI_OOOPS(){
	...
	SWI_Exception();
	...
}
SWI_Exception
	SWI 0x121212
  • SWI 0x121212를 만나는 순간 인터럽트 발생 후 Exception Vector로 branch
  • Handler 구현
1
2
3
4
5
6
SWI_Handler  
    STMFD sp!, {lr} ; 돌아갈 주소 저장  
    LDR r0, [lr, #-4] ; SWI 주소의 값을 가져옴.  
    BIC r0, r0, #0xFF000000 ; LSB 24bit 건져냄.  
    BL SWI_C_Handler ; r0에 넣었으니까, AAPCS에 의하여 첫번째 argument로  
    LDMFD sp!, {lr} ; 다 처리하고 왔으면 돌아가야지~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void SWI_C_Handler (int OPTion)  
{  
    switch(option)  
    {  
      case 0x123432:  
          UART_MESSAGE ("SWI 0x123432");  
      break ;  
      case 0x121212 :  
         LCD_MESSAGE ("SWI 0x121212");  
      break ;  
      case 0x654321:  
         USB_MESSAGE ("SWI 0x654321");  
      break ;  
     }  
}

ⓘ Coprocessor Assembly


  • Coprocessor : CPU의 기능을 보충하기 위해 사용하는 컴퓨터 프로세서
    • 메인 CPU 프로세서에 집중된 작업들의 짐을 덜어 시스템 수행 속도를 빠르게 해줌
    • 전용 레지스터를 통해 대화 가능
    • Coprocessor Data 명령어
      1. Coprocessor의 내부 레지스터를 다룸 : CDP Coprocessor 번호, Coprocessor 내부 Reigster
      2. Coprocessor 내부의 레지스터들을 ARM register에 읽거나 쓰기 MCR R C
      3. Corprocessor 내부의 레지스터를 메모리에 직접 읽거나 쓰기

ⓙ Bootloader와 Memory Budget (Mapfile) 어떻게 변수초기화를 할 것인가


  • Bootloader
    • Booting : 컴퓨터가 동작을 시작해서 정상적으로 사용하기 전까지의 과정.
    • 부트로더는 하드웨어를 정상적으로 사용 가능하도록 초기화하고, 실제 동작이 가능하도록 ROM에서 RAM으로 OS를 싣는 것 -
  • 보드 구성 시 MCP(Multichip Package)에 따라 달라지긴 하나
    • ROM에는 code와 const data, RO, RW
    • RAM에는 RW, ZI
  • RAM에 적재할 내용들을 ROM에 넣어둠
    • 부트로더가 이를 이용해 적재
    • NOR MCP에서는 RW, ZI를, NAND MCP에서는 RO,RW,ZI를 적재
  • Scatter Loading은 load view와 Execution view를 제공
    • Load view는 FLASH에 어떻게 담겨있을 것이냐
    • Execution view는 RAM에 어떻게 복사되어 실행될 것이냐를 의미함
  • Scatter Loading을 사용하지 않는 Default Memory Model에서의 bootloader의 copy 동작
    • RO,RW,ZI를 어떻게 구분해서 RAM에 적재하는지
    • linker가 Ro,RW,ZI 세개의 섹션에 대해 각각 시작과 끝주소를 symbol로 만듬
      • 이를 이용해 각 섹션에 access

ⓚ Reset Handler에서 main까지 (Entry Point)


  • ARM core의 임베디드 시스템에 전원을 넣으면, Reset Exception과 같은 의미
    • SVC mode로 reset vector로 pc가 setting 됨
    • 이후 Reset Handler로 branch
  • Hardware적인 설정
    • 하드웨어적으로 자동으로 IRQ/FIQ disable이고, SVC로 이곳에 진입
    • 기본적으로 사용되는 HW block들의 clock 설정
    • 기본적인 MCU의 pin 설정
    • MCU에서 bootup에 필요없는 HW block을 죽임
    • Memory setting -> Memory Sizer(Bus width 등), wait state 등
  • Software적인 설정
    • 각 mode 별 Stack setting
    • RW에 RAM 복사. RO도 XIP가 SDRAM에서 이루어질 경우 복사
  • Reset Handler 마지막에 C함수로 건너뛰기 위한 main 호출이 있음
    • 보통 __rt_entry라는 키워드로 호출함 resethandler
  • 사용자 코드의 C의 main()이 호출 되기 전, C library 의 __main__rt_entry가 먼저 호출됨
  • C lib __main : 실행 시, Memory Map을 세팅함.
  • __rt_entry : real time의 약자. C library 자체를 실행 가능하도록 해줌. C libary에서 사용하는 Stack과 Heap을 잡고, 라이브러리 함수와 내부의 static data를 초기화 하는 작업

ⓛ Scatter Loading과 Bootup - __user_initial_stackheap


  • Scatter Loading을 사용하게 되면, 컴파일러가 만들어주는 stack과 heap은 사용하지 않음
  • Scatter Loading을 사용하게 되면, 몇가지 심볼이 무시됨
    • 컴파일러가 stack과 heap을 자동으로 만들어 낼 때는, 자신이 만든 심볼을 참조하지만, 이 심볼이 무시되어 에러가 발생
  • 이를 위해 __user_initial_stackheap() 함수를 재정의 해줘야함
  • 함수 원형
1
2
3
#include <rt_misc.h>
__value_in_regs struct __initial_stackheap
__user_initial_stackheap (unsigned R0, unsigned SP, unsigned R2, unsigned SL)
  • Scatter loading을 사용하고, RTOS를 사용하는 경우 다음과 같이 구현함
1
2
3
4
5
__value_in_regs struct __initial_stackheap
__user_initial_stackheap(unsigned R0, unsigned SP, unsigned R2, unsigned SL)  
{  
     return;  
}
  • initial stack과 heap을 만들어 주고 싶은 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char _initial_heap[0x2000];  
char _initial_stack[0x2000];

__value_in_regs struct __initial_stackheap

__user_initial_stackheap (unsigned r0, unsigned sp, unsigned r2, unsigned sl)  
{  
    struct __initial_stackheap config;

    config.heap_base = (unsigned)_initial_heap;  
    config.stack_base = (unsigned)_initial_stack + sizeof(_initial_stack) - 1;  
    config.heap_limit = (unsigned)_initial_heap + sizeof(_initial_heap) - 1;  
    config.stack_limit = (unsigned)_initial_stack;  
    return config;  
}
  • config라는 구조체에 넣어주면, lib이 알아서 init 해 줌

  • 3장과 마찬가지로 어려운 파트가 많았음. 꼭 다시 복습할 것!

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.