main() 함수가 실행되기 까지

안태욱·2023년 1월 31일
0
post-thumbnail

ARM 코어에 전원이 들어오면 리섹 익센션이 발생하고 리셋 익셉션 핸들러가 실행된다. 해당 핸들러는 HW를 초기화하고 C언어로 작성한 코드 즉 main() 함수로 점프하는 역할을 수행한다. 해당 포스팅에서는 ARM 동작 모드 별 스택 포인터를 초기화하고 C언어 코드로 점프하는 과정을 학습한다. 실무에서는 시스템 클럭 설정, 메모리 컨트롤러 초기화 등 더 많은 작업을 수행해야 하나, QEMU는 가상 에뮬레이터이기 때문에 이것 만으로도 충분하다.


1. 메모리 설계

Stack Pointer를 초기화 하기 위해서는 각 동작 모드 별로 사용할 메모리 영역 등을 나누어야 한다. 즉 메모리 설계가 선행되어야 하는 것이다. QEMU는 default로 128MB의 메모리를 사용하는데, 이는 실제 임베디드 시스템에서 사용하기엔 매우 큰 용량이다. 하지만 현재는 학습하고 있는 단계이기 때문에 메모리를 넉넉하게 사용하고자 한다.

  • 동적 할당 영역 : 55MB (0x0490 0000 ~ 0x07F FFFF)
  • 전역 변수 영역 : 1MB (0x0480 0000 ~ 0x048F FFFF)
  • 스택 영역 (0x0010 0000 ~ 0x047F FFFF)
    • 태스크 스택 영역 : 64MB
    • UND 모드 스택 영역 : 1MB
    • ABT 모드 스택 영역 : 1MB
    • FIQ 모드 스택 영역 : 1MB
    • IRQ 모드 스택 영역 : 1MB
    • SVC 모드 스택 영역 : 1MB
    • USR / SYS 모드 스택 영역 : 2MB
  • text 영역 : 1MB (0x0000 0000 ~ 0x000F FFFF)

메모리 0번지에 text 영역을 배치하고, 동작 모드별 스택 영역, 전역 변수 영역, 동적 할당 영역을 순서대로 배치할 것이다.


헤더 파일 작성하기

include 디렉터리 내부에 헤더 파일들을 생성하고 메모리를 설계한대로 작성하였다.

MemoryMap.h

#define INST_ADDR_START     0

#define USRSYS_STACK_START  0x00100000
#define SVC_STACK_START     0x00300000
#define IRQ_STACK_START     0x00400000
#define FIQ_STACK_START     0x00500000
#define ABT_STACK_START     0x00600000
#define UND_STACK_START     0x00700000
#define TASK_STACK_START    0x00800000
#define GLOBAL_ADDR_START   0x04800000
#define DALLOC_ADDR_START   0x04900000

#define INST_MEM_SIZE       (USRSYS_STACK_START - INST_ADDR_START)
#define USRSYS_STACK_SIZE   (SVC_STACK_START - USRSYS_STACK_START)
#define SVC_STACK_SIZE      (IRQ_STACK_START - SVC_STACK_START)
#define IRQ_STACK_SIZE      (FIQ_STACK_START - IRQ_STACK_START)
#define FIQ_STACK_SIZE      (ABT_STACK_START - FIQ_STACK_START)
#define ABT_STACK_SIZE      (UND_STACK_START - ABT_STACK_START)
#define UND_STACK_SIZE      (TASK_STACK_START - UND_STACK_START)
#define TASK_STACK_SIZE     (GLOBAL_ADDR_START - TASK_STACK_START)
#define DALLOC_MEM_SIZE     (55 * 1024 * 1024)

#define USRSYS_STACK_TOP    (USRSYS_STACK_START + USRSYS_STACK_SIZE)
#define SVC_STACK_TOP       (SVC_STACK_START + SVC_STACK_SIZE)
#define IRQ_STACK_TOP       (IRQ_STACK_START + IRQ_STACK_SIZE)
#define FIQ_STACK_TOP       (FIQ_STACK_START + FIQ_STACK_SIZE)
#define ABT_STACK_TOP       (ABT_STACK_START + ABT_STACK_SIZE)
#define UND_STACK_TOP       (UND_STACK_START + UND_STACK_SIZE)

ARMv7AR.h

// CPSR setting value
#define ARM_MODE_BIT_USR 0x10
#define ARM_MODE_BIT_FIQ 0x11
#define ARM_MODE_BIT_IRQ 0x12
#define ARM_MODE_BIT_SVC 0x13
#define ARM_MODE_BIT_ABT 0x17
#define ARM_MODE_BIT_UND 0x1B
#define ARM_MODE_BIT_SYS 0x1F
#define ARM_MODE_BIT_MON 0x16

#define ARM_MODE_BIT_FLAG 0x1F


2. Exception Vector Table 만들기

이는 ARM 아키텍처에서 발생하는 Exception의 종류이다. Exception이 발생하면 ARM 코어는 지정된 위치의 명령어를 실행하는데, 이는 Exception을 처리하기 위한 핸들러로 점프하는 코드이다. 이처럼 메모리에 각 Exception들을 처리하기 위한 핸들러로 점프하는 명령어들를 순차적으로 기록한 것Exception Vector Table 이라고 부른다.

/* Entry.S */

.text

    .code 32

    .global vector_start

    vector_start:
        LDR		PC, reset_handler_addr
		LDR		PC, undef_handler_addr
		LDR		PC, svc_handler_addr
		LDR		PC, pftch_abt_handler_addr
		LDR		PC, data_abt_handler_addr
		B		.
		LDR		PC, irq_handler_addr
		LDR		PC, fiq_handler_addr

		reset_handler_addr: 	.word reset_handler
		undef_handler_addr: 	.word dummy_handler
		svc_handler_addr: 		.word dummy_handler
		pftch_abt_handler_addr: .word dummy_handler
		data_abt_handler_addr:  .word dummy_handler
		irq_handler_addr:		.word dummy_handler
		fiq_handler_addr:		.word dummy_handler
        
    reset_handler:
		...

    dummy_handler:
    	...

.end

링커 스크립트를 통해 vector_start 심벌을 메모리 0번지에 위치시켰는데, 해당 레이블에 위와 같은 코드를 작성하여 익셉션 테이블을 배치할 수 있다.



3. Stack Pointer 초기화

/* Entry.S */

#include "ARMv7AR.h"
#include "MemoryMap.h"

.text

    .code 32

    .global vector_start
    .global vector_end

    vector_start:
        ...

    reset_handler:
        /* SVC Mode Stack Init */
		MRS r0, cpsr
        BIC r1, r0, #ARM_MODE_BIT_FLAG
        ORR r1, r1, #ARM_MODE_BIT_SVC
        MSR cpsr, r1
        LDR sp, =SVC_STACK_TOP

		/* IRQ Mode Stack Init */
        MRS r0, cpsr
        BIC r1, r0, #ARM_MODE_BIT_FLAG
        ORR r1, r1, #ARM_MODE_BIT_IRQ
        MSR cpsr, r1
        LDR sp, =IRQ_STACK_TOP

		/* FIQ Mode Stack Init */
        MRS r0, cpsr
        BIC r1, r0, #ARM_MODE_BIT_FLAG
        ORR r1, r1, #ARM_MODE_BIT_FIQ
        MSR cpsr, r1
        LDR sp, =FIQ_STACK_TOP

		/* ABT Mode Stack Init */
        MRS r0, cpsr
        BIC r1, r0, #ARM_MODE_BIT_FLAG
        ORR r1, r1, #ARM_MODE_BIT_ABT
        MSR cpsr, r1
        LDR sp, =ABT_STACK_TOP

		/* UND Mode Stack Init */
        MRS r0, cpsr
        BIC r1, r0, #ARM_MODE_BIT_FLAG
        ORR r1, r1, #ARM_MODE_BIT_UND
        MSR cpsr, r1
        LDR sp, =UND_STACK_TOP

		/* USR, SYS Mode Stack Init */
        MRS r0, cpsr
        BIC r1, r0, #ARM_MODE_BIT_FLAG
        ORR r1, r1, #ARM_MODE_BIT_SYS
        MSR cpsr, r1
        LDR sp, =USRSYS_STACK_TOP


		BL main


    dummy_handler:
		...

.end

메모리 설계 후 작성한 헤더 파일을 include 하고 reset_handler를 작성하였다.

MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #동작 모드
MSR cpsr, r1
LDR sp, =스택 꼭대기 주소

코드를 살펴보면 위와 같은 코드가 반복되는 것을 확인할 수 있다.

  • MRS r0, cpsr : CPSR 레지스터 값을 r0 레지스터에 저장
  • BIC r1, r0, #0x1F : r0 레지스터에 저장된 값에서 마스킹 된 비트 클리어하여 r1 레지스터에 저장
    (CPSR 레지스터에서 동작 mode를 나타내는 비트들 클리어)
  • ORR r1, r1, #동작모드 : r1 레지스터에 저장된 값과 #동작모드 값을 OR 연산하여 r1 레지스터에 저장
  • MSR cpsr, r1 : CPSR 레지스터 값을 r1 레지스터에 저장된 값으로 변경
    (동작 모드가 변경된다)
  • LDR sp, =스택 꼭대기 주소 : 해당 동작 모드의 스택 포인터 초기화
    (스택은 거꾸로 자라기 때문에 꼭대기 주소로 초기화)

이처럼 동작 모드 별 스택 포인터를 초기화한 이후에는 BL main 을 통해 C언어의 main 함수로 점프한다. 해당 Entry.S 파일에는 main 심벌이 존재하지 않는데, 이는 다른 .c 파일에 작성하고 링크하여 해결할 수 있다.



4. main() 작성

#include "stdint.h"

void main(void) {
	uint32_t* dummyAddr = (uint32_t*) (1024*1024*100); // 100MB
    *dummyAddr = sizeof(long);
    
    dummyAddr = (uint32_t*) (1024*1024*100 + 4); // 104MB
    *dummyAddr = (uint32_t) 1;
}

동작을 확인하기 위해 의미 없는 코드를 작성하였다. 100MB, 104MB 메모리 영역에 값을 기록하는 코드이다.

빌드 및 디버깅 모드로 실행 후 해당 주소의 값을 확인해보면 0x0000 0004, 0x0000 0001의 값이 들어가 있는 것을 확인할 수 있다. 이를 통해 reset_handler가 실행되고 나서 C언어 코드가 실행되었음을 알 수 있다.

profile
책 읽는 개발자

0개의 댓글