부팅
- 부팅이란?
- 시스템에 전원이 들어가고 모든 초기화 작업을 마친 후 idle 상태에서 대기하기 까지
- 시스템에 전원이 들어가고 arm 코어가 리셋 익셉션 핸들러를 모두 처리한 뒤 본격적인 C 코드로 진입하기 직전까지
- 보통은 전자가 일반적이나, 우리는 후자를 부팅이라고 보자
메모리 설계
- 실행파일은 메모리를 크게 3가지로 나눠 사용한다
| 영역 | 용도 |
|---|
| text 영역 | 코드가 있는 공간 |
| data 영역 | 초기화한 전역 변수가 있는 공간 |
| bss 영역 | 초기화하지 않은 전역 변수가 있는 공간 빌드된 바이너리에는 symbol과 크기만 들어가있다 |
- 임베디드 시스템에서 메모리는 어떻게 배치하는 것이 좋을까?
속도
- 메모리는 속도와 크기가 반비례한다
- 속도가 빠르고 크기가 작은 메모리에는 text 영역, 일부 속도에 민감한 변수들이 적합할 것이다
- 속도가 느리고 크기가 큰 메모리에는 나머지 영역이 적합할 것이다
- qemu는 그런 구분이 없으므로, 우선은 순서대로 배치하자
크기
- 메모리의 크기는 얼마나 잡아야할까?
- text 영역
- RTOS를 사용하는 펌웨어는 많아야 수십 KB면 충분하다
- 우리는 qemu 환경이고 예제이니, text영역에 1mb를 할당하자
- 1mb는 0x00000000~0x000FFFFF의 범위이다
- data 영역, bss 영역
- 데이터의 성격을 생각해보자
- 데이터의 형태: 동작 모드별 스택, 태스크 스택, 전역 변수, 동적 메모리 할당 영역이 있다
- 데이터의 속성: 성능을 중시하는 데이터, 큰 공간이 필요한 데이터, 공유 데이터
- 태스크 스택에 성능 속성을 부여해 배치하거나, 공간만 생각해 배치할 수 있다
- 그러나 qemu에선 의미가 없으니 전역 변수 영역에 그냥 쭉 연속적으로 배치하자
설계
- text 영역에 1mb를 할당했다
- 각 동작 모드별로 1mb를 할당했다
- usr모드와 sys모드는 메모리 공간과 레지스터를 공유하므로 하나로 묶어서 보았다
- 태스크 스택 영역은 태스크마다 1mb씩 총 64개 태스크, 64mb를 할당했다
- 전역 변수를 위해 1mb를 할당했다
- 남는 공간은 동적 할당 메모리 영역으로 할당했다
| 영역 | 주소 |
|---|
| 동적 할당 영역 | 0x04900000 ~ 0x07FFFFFF (55mb) |
| 전역 변수 영역 | 0x04800000 ~ 0x048FFFFF (1mb) |
| 태스크 스택 영역 | 0x00800000 ~ 0x047FFFFF (64mb) |
| und 모드 영역 | 0x00700000 ~ 0x007FFFFF (1mb) |
| abt 모드 영역 | 0x00600000 ~ 0x006FFFFF (1mb) |
| fiq 모드 영역 | 0x00500000 ~ 0x005FFFFF (1mb) |
| irq 모드 영역 | 0x00400000 ~ 0x004FFFFF (1mb) |
| svc 모드 영역 | 0x00300000 ~ 0x003FFFFF (1mb) |
| usr, sys 모드 영역 | 0x00100000 ~ 0x002FFFFF (2mb) |
| text 영역 | 0x00000000 ~ 0x000FFFFF (1mb) |
[참고] 동작 모드
| 모드 | 이름 | 설명 |
|---|
| usr | user mode | 일반 사용자 프로그램이 실행되는 모드, 대부분 어플리케이션이 여기에 해당 |
| sys | system mode | 시스템 모드, 커널 내부에서 사용되는 privileged 모드 |
| svc | supervisor mode | OS의 커널 코드가 실행되는 보호 모드, 시스템 콜 등 software interrupt 발생 시 진입 |
| irq | IRQ mode | 일반 interrupt 발생 시 진입하는 모드, hardware interrupt에 사용 |
| fiq | FIQ mode | 빠른 인터럽트 발생 시 진입하는 모드, 빠른 응답을 위해 별도 레지스터 뱅크를 가짐 |
| abt | abort mode | 메모리 접근 오류 발생 시 진입하는 모드, 예외 상황 처리에서 사용 |
| und | undefined mode | 정의되지 않은 명령 실행 시 진입, 잘못된 명령어 예외를 처리 |
- arm에서는 여러 동작모드가 있다, 각 모드들은 특정 상황에서 cpu가 어떻게 동작할지 결정한다
- 예외 모드마다 메모리 공간이 다른데, 각 예외 처리 모드가 자신만의 독립적인 스택 메모리 공간을 가지고 있다는 의미이다
- usr모드와 sys모드는 메모리 공간, 레지스터를 공유한다
- CPSR(Current Program Status Register)만 달라 해당 값으로 구분한다
- 이를 통해 예외 처리 과정에서 기존 코드의 스택과 충돌하지 않고 독립적으로 예외를 처리할 수 있다
[참고] 뱅크드 레지스터
- arm은 범용 레지스터 31개, 상태 레지스터 6개로 37개의 레지스터를 갖는다
- 동작 모드별로 범용 레지스터 16개, 상태 레지스터 2개를 최대로 사용할 수 있다
- 하지만 동작모드는 7가지이다, 모두 각각 레지스터를 따로 사용한다면 18 * 7 = 126개의 레지스터가 필요하다, 즉 arm에서는 동작 모드들이 레지스터를 공유해 사용한다

- 레지스터는 아래와 같이 배정된다
- r0 ~ r15, CPSR은 모든 동작모드에서 공유된다
- r8_fiq ~ r14_fiq, SPSR_fiq는 FIQ 모드에서 사용된다
- r13_svc, r14_svc, SPSR_svc는 SVC 모드에서 사용된다
- ABT, UND, IRQ, MON 모드에서도 SVC모드와 같이 3개의 레지스터를 사용한다
- FIQ 모드의 r8_fiq ~ r14_fiq까지의 레지스터는 FIQ 모드에서만 쓸 수 있다. 따라서, FIQ 모드에서 해당 레지스터들은 이전 레지터의 값을 백업할 필요가 없는데, fast interrupt라는 이름은 이런 동작방식 때문이다
- 이렇게 각 동작모드마다 공유하지 않고 전용으로 사용되는 레지스터들을 뱅크드 레지스터라고 한다
- 레지스터마다 용도는 아래와 같다
| 레지스터 | 용도 |
|---|
| R0 ~ R12 | 범용 레지스터, 데이터를 일반적으로 처리하는데 사용 |
| R13 | 스택 포인터(SP), 스택을 사용하기 위한 stack의 top 위치 |
| R14 | 링크 레지스터(LR), 함수 분기 이후 return address를 저장 예를 들어, funcA에서 funcB를 호출한 상황에서 funcA의 호출 지점을 저장 |
| R15 | 프로그램 카운터(PC), 현재 명령어 메모리 주소 arm 모드에서는 4byte, thumb 모드에서는 2byte가 증가 |
익셉션 벡터 테이블 만들기
- 익셉션 핸들러 동작을 위한 익셉션 벡터 테이블을 만들어보자
.text
.code 32
.global vector_start
.global vector_end
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
vector_end:
reset_handler:
LDR R0, =0x10000000
LDR R1, [R0]
dummy_handler:
B .
.end
- 익셉션 벡터 테이블에 각 핸들러로 점프하는 코드만 작성했다
- reset_handler에는 이전에 만들었던 ID Register 값을 읽어오는 핸들러이다
- dummy_handler는 나중에 작성할 내용이니, 우선 무한루프를 돌도록 만들었다
- 가장 먼저 리셋 익셉션 핸들러를 만들자
- 리셋 익셉션 핸들러에서는 메모리 맵을 설정해주어야 한다
- 동작 모드 별 스택 주소를 각 동작 모드의 뱅크드 레지스터 SP에 설정해주자
- 7개 동작모드 중 USR 모드와 SYS 모드는 레지스터를 공유하므로, SP 레지스터는 총 6개의 뱅크드 레지스터로 제공된다
- 리셋 익셉션 핸들러에서는 이 동작모드를 순서대로 변경해 가면서 SP 레지스터에 정해진 값을 넣을 것이다
[참고] 익셉션 핸들러
- ARM 머신에서는 전원이 켜지면 익셉션 벡터 테이블의 리셋 벡터를 읽는다
- 익셉션 벡터 테이블은 기본적으로 0x00000000에 위치한다
- 익셉션은 총 7개가 있다
| offset | 이름 | 설명 |
|---|
| 0x00 | Reset | 전원이 켜지면 실행됨 |
| 0x04 | Undefined Instruction | 잘못된 명령어를 실행했을 때 |
| 0x08 | SVC(Supervisor Call) | SVC 명령으로 발생시키는 익셉션 |
| 0x0C | Prefetch Abort | 명령어 메모리에서 명령어를 읽다가 문제가 생김 |
| 0x10 | Data Abort | 데이터 메모리에서 데이터를 읽다가 문제가 생김 |
| 0x14 | Not used | 사용하지 않음 |
| 0x18 | IRQ interrupt | IRQ 인터럽트가 발생한 경우 |
| 0x1C | FIQ interrupt | FIQ 인터럽트가 발생한 경우 |
- ARM은 익셉션 벡터 테이블에 정의된 상황이 발생하면 PC(Program Counter)를 익셉션 벡터 테이블에 정의된 주소로 강제 변환된다
- 익셉션은 각각 4byte = 32bit씩 할당되어있다. 즉, 32bit 머신이므로 한 익셉션에 한 명령어만 실행할 수 있다
- 개발자 / 시스템 / 펌웨어마다 익셉션에 필요한 코드는 다를 것이다
- 특정한 크기의 익셉션 코드 공간을 만들어두면, 낭비되는 공간이 생기는 경우가 있을 것이다
- 따라서, 명령어를 쓸 수 있는 최소 공간(4byte)를 배정해두고, 그 익셉션을 처리하는 코드로 점프하게 만든다
- 이때 익셉션을 처리하는 코드를 익셉션 핸들러라고 부른다
익셉션 종류
- 익셉션이란, 주변장치 등에서 발생한 신호를 처리하기 위해 순차적으로 진행하던 프로그램의 수행을 끊는 것 이다
- 익셉션이 발생하면 프로그램의 흐름과 상관 없이 무조건 익셉션 벡터를 통해 익셉션 핸들러로 진입한다
- 이때, 기존 프로그램의 레지스터 유효성이 깨지므로 익셉션 처리 이후에 다시 돌아가기 위한 조치를 해주어야 한다
- ARM은 익셉션 처리 후 원래 프로그램 흐름으로 복귀할 수 있도록 하드웨어 수준에서 최소한의 작업, R14(LR)에 복귀할 주소를 저장해준다.
- 예를 들어, USR 모드에서 프로그램이 수행되다가 익셉션이 발생해서 IRQ 모드로 바뀌면, ARM은 자동으로 R14_irq에 다음에 실행할 명령어 위치인 PC+4를 저장한다
- 다만 ARM 파이프라인 때문에 각 익셉션 별로 정상 복귀를 위해서 R14에 각각 다른 연산이 들어갈 수 있다
- ARM 파이프라인은 컴퓨터 구조론에서 이야기하는 fetch-decode-execute를 의미한다
- 16bit instruction set을 사용하는 thumb 모드 / 32bit instructino set을 사용하는 arm 모드
| 익셉션 | 복귀 주소 | 동작모드 arm | 동작모드 thumb |
|---|
| SVC (Supervisor call) | MOVS PC, R14 svc | PC+4 | PC+2 |
| SMC (Secure Monitor Call) | MOVS PC, R14 mon | PC+4 | - |
| UNDEF (Undefined Instruction) | MOVS PC, R14 und | PC+4 | PC+2 |
| PABT (Prefetch Abort) | MOVS PC, R14 #4 | PC+4 | PC+2 |
| FIQ (Fast Interrupt Request) | SUBS PC, R14 q, #4 | PC+4 | PC+4 |
| IR (Interrupt Request) | QSUBS PC, R14 irq, #4 | PC+4 | PC+4 |
| DABT (Data Abort) | SUBS PC, R14 abt, #8 | PC+8 | PC+8 |
| RESET | - | - | - |
| BKPT (Breakpoint) | SUBS PC, R14 abt, #4 | PC+4 | PC+4 |
스택 만들기
- c언어 헤더 파일도 gcc로 컴파일하면 Entry.S 어셈블리 파일에서 사용할 수 있다
- 우선 각 동작 모드 별 스택 시작 주소, 스택 크기, 스택 마지막 주소를 정의해보자
# MemoryMap.h
#define INST_ADDR_START 0x0 // text 영역
#define USRSYS_STACK_START 0x00100000 // usr, sys 모드 영역
#define SVC_STACK_START 0x00300000 // svc 모드 영역
#define IRQ_STACK_START 0x00400000 // irq 모드 영역
#define FIQ_STACK_START 0x00500000 // fiq 모드 영역
#define ABT_STACK_START 0x00600000 // abt 모드 영역
#define UND_STACK_START 0x00700000 // und 모드 영역
#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_STACK_SIZE (55 * 1024 * 1024)
#define USRSYS_STACK_TOP (USRSYS_STACK_START + USRSYS_STACK_SIZE - 4)
#define SVC_STACK_TOP (SVC_STACK_START + SVC_STACK_SIZE - 4)
#define IRQ_STACK_TOP (IRQ_STACK_START + IRQ_STACK_SIZE - 4)
#define FIQ_STACK_TOP (FIQ_STACK_START + FIQ_STACK_SIZE - 4)
#define ABT_STACK_TOP (ABT_STACK_START + ABT_STACK_SIZE - 4)
#define UND_STACK_TOP (UND_STACK_START + UND_STACK_SIZE - 4)
- 다음으로, CPSR에 값을 설정해 동작모드를 바꿀 수 있는 값을 정의해보자
# ARMv7AR.h
#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
- 정의한 헤더들을 리셋 익셉션 핸들러에 포함시켜보자
#include "MemoryMap.h"
#include "ARMv7AR.h"
.text
.code 32
.global vector_start
.global vector_end
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
vector_end:
reset_handler:
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_SVC
MSR cpsr, r1
LDR sp, =SVC_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_IRQ
MSR cpsr, r1
LDR sp, =IRQ_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_FIQ
MSR cpsr, r1
LDR sp, =FIQ_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_ABT
MSR cpsr, r1
LDR sp, =ABT_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_UND
MSR cpsr, r1
LDR sp, =UND_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_SYS
MSR cpsr, r1
LDR sp, =USRSYS_STACK_TOP
dummy_handler:
B .
.end
- reset_handler 동작 후 별도 작업 없이 RTOS의 기본 동작 모드인 SYS모드로 작업을 이어나가기 위해 SYS모드를 마지막으로 설정하였다
- 실무에서는 시스템 클럭 설정, 메모리 컨트롤러 초기화 등 추가 작업이 필요하다
- reset_handler는 아래 코드를 반복한다
@ cpsr의 값을 r0로 읽어옴
MRS r0, cpsr
@ r0에서 하위 5bit(0x1f)를 NOT AND해서 r1에 저장
@ 즉 하위 5bit는 0이 됨, 하위 5bit는 동작 모드를 의미
BIC r1, r0, #0x1F
@ r1에 동작 모드를 입력
ORR r1, r1, #{동작 모드}
@ r1의 값을 cpsr을 입력
MSR cpsr, r1
@ 해당 모드에서 사용할 스택 포인터를 뱅크드 레지스터인 sp에 입력
LDR sp, ={동작모드 별 stack top 주소}
- 빌드를 위해 Makefile을 수정하자
- 어셈블리 코드에 추가된 #include 같은 전처리기는 arm-none-eabi-gcc를 사용해야 하므로 컴파일러를 바꿔준다
- 어셈블리 코드에 include한 헤더파일이 포함될 수 있도록 INC_DIRS를 -I 옵션으로 gcc에 추가해야 한다
- gcc는 기본적으로 실행파일을 만들지만 우리는 오브젝트 파일을 만들어야 하므로, -c 옵션을 추가해야 한다
# Makefile
...
ASM_SRCS = $(wildcard boot/*.S)
ASM_OBJS = $(patsubst boot/%.S, build/%.o, $(ASM_SRCS))
INC_DIRS = include
navilos= build/navilos.axf
navilos_bin = build.navilos.navilos_bin
...
build/%.o: boot/%.S
mkdir -p $(shell dirname $@)
$(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
vector_end () at boot/Entry.S:37
37 MRS r0, cpsr
(gdb) s
38 BIC r1, r0, #0x1F
(gdb) s
39 ORR r1, r1, #ARM_MODE_BIT_IRQ
(gdb) s
40 MSR cpsr, r1
(gdb) s
vector_end () at boot/Entry.S:41
41 LDR sp, =IRQ_STACK_TOP
(gdb) s
vector_end () at boot/Entry.S:43
43 MRS r0, cpsr
(gdb) info register
r0 0x400001d3 1073742291
r1 0x400001d2 1073742290
r2 0x0 0
r3 0x0 0
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0x0 0
sp 0x4ffffc 0x4ffffc
lr 0x0 0
pc 0x64 0x64 <vector_end+40>
cpsr 0x400001d2 1073742290
- IRQ 모드의 스택을 확인해보자
- sp는 0x4ffffc이므로, IRQ_STACK_TOP 값으로 설정되었다
- cpsr이 0x400001d2, 하위 5bit는 0x12이므로 ARM_MODE_BIT_IRQ 값으로 잘 설정되었다
main함수로 진입
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_SYS
MSR cpsr, r1
LDR sp, =USRSYS_STACK_TOP
BL main
- reset_handler 마지막에 main함수로 진입하는 코드를 추가하자
- main함수는 boot/Main.c에 정의하자
# Main.c
#include "stdint.h"
void main(void)
{
uint32_t* dummyAddr = (uint32_t*)(1024 * 1024 * 100);
*dummyAddr = sizeof(long);
}
- main함수는 아직 의미있는 동작을 하지는 않는다
- 100mb 메모리 주소 0x64000000에 4(sizeof long)을 쓴다
- 어셈블리에서 브랜치 명령인 BL로 점프하기 위해서는 점프하려는 레이블이 .global에 선언되어있어야 한다
- 컴파일러는 c언어 함수 이름을 링커가 자동으로 접근할 수 있도록 전역 심벌로 만든다
- 어셈블리어를 쓸 때 .global 지시어로 선언하면 전역 심벌이 된다
- c언어를 쓸 때, extern으로 선언하면 전역 심벌이 된다
- 반대로 어셈블리에서 .global로 선언한 이름은 c언어에서 사용할 수 있다
- stdint는 아래에서 다운받아서 include 안에 넣어주었다
- Makefile을 수정해 c언어 소스 파일을 컴파일할 수 있도록 하자
ARCH=armv7-a
MCPU=cortex-a8
CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-ld
OC=arm-none-eabi-objcopy
LINKER_SCRIPT = ./navilos.ld
MAP_FILE = build/navilos.map
ASM_SRCS = $(wildcard boot/*.S)
ASM_OBJS = $(patsubst boot/%.S, build/%.os, $(ASM_SRCS))
C_SRCS = $(wildcard boot/*.c)
C_OBJS = $(patsubst boot/%.c, build/%.o, $(C_SRCS))
INC_DIRS = include
navilos= build/navilos.axf
navilos_bin = build.navilos.navilos_bin
.PHONY: all clean run debug gdb
all: $(navilos)
clean:
@rm -rf build
run: $(navilos)
qemu-system-arm -M realview-pb-a8 -kernel $(navilos)
debug: $(navilos)
qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4
gdb:
gdb-multiarch
$(navilos): $(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Map=$(MAP_FILE)
$(OC) -O binary $(navilos) $(navilos_bin)
build/%.os: $(ASM_SRCS)
mkdir -p $(shell dirname $@)
$(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
build/%.o: $(C_SRCS)
mkdir -p $(shell dirname $@)
$(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
- Makefile에서 아래 내용들을 수정하였다
- 어셈블리 오브젝트 파일들은 .os로 정의
- c 오브젝트 파일들은 .o로 정의
- map 파일 정의
- map파일은 링커가 생성하는 파일로, 링킹 작업 때 심벌에 할당된 메모리 주소를 기록
- C_SRCS, C_OBJS 변수를 만들고 build 명령어 추가
- navilos 생성 시 C_OBJS를 추가하고 map 파일 설정
main 함수 동작 확인
(gdb)
(gdb) target remote:1234
Remote debugging using :1234
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x000000b8 in ?? ()
(gdb) file build/navilos.axf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from build/navilos.axf...
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
dummy_handler () at boot/Entry.S:70
70 B .
(gdb) x/8wx 0x06400000
0x6400000: 0x00000004 0x00000000 0x00000000 0x00000000
0x6400010: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)
- gdb에서
c명령어로 qemu 펌웨어를 실행하자
- 우리가 지금까지 만든 펌웨어는 우선 reset_handler가 실행되고
- reset_handler에 이어 main함수가 호출된 후
- main 함수 종료 후 dummy_handler가 호출되어 무한루프를 돌게 될 것이다
c명령을 입력하면 무한루프로 인해 종료되지 않으므르 ctrl+c로 강제종료하자
- main 함수가 잘 동작했다면 0x06400000에 4가 들어가있을것이다
x/8wx 명령은 입력된 메모리부터 8개를 4byte씩 16진수로 출력한다
- 0x06400000에 예상대로 4가 들어가있음을 확인할 수 있다