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
----------------------------------------------------------------------------------
- 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
- 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으로 압축한 것
- ARM mode는 Thumb mode보다 줄이 간략함
- 32bit 한줄로 수행 가능한 명령을, 16bit에서는 여러줄로 수행함
- 비교 구문의 차이
- Thumb는 CMP로 비교 후, 그 결과를 가지고 판단. ARM은 앞의 산술연산을 이용가능
- 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의 동작 때문에 복귀 주소 조정이 필요함
- 보통 PC는 현재 Execution보다 2단계 앞을 가리킴. fetch 해오는 곳을 가리키고 있음
- ARM 파이프라인 동작을 보상하기 위해 복귀 주소 보정 수행
- Thumb 명령어들은 4byte가 아닌 2byte
- 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도 작성 가능함
- 보통 다음 핸들러에 해당 내용들을 넣음
- Rest Handler
- 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을 거는 것.
- 다음과 같은 경우 사용
- System Call(Kernel) : SVC mode로 진입하여 system mode 변경
- 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 명령어
- Coprocessor의 내부 레지스터를 다룸 :
CDP Coprocessor 번호, Coprocessor 내부 Reigster
- Coprocessor 내부의 레지스터들을 ARM register에 읽거나 쓰기
MCR R C
- 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로 만듬
ⓚ 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 호출이 있음
- 사용자 코드의 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;
}
|