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  = -I include

navilos = build/navilos.axf
navilos_bin = build/navilos.bin

.PHONY: all clean run debug gdb

all$(navilos)

clean:
    @rm -fr 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:
    arm-none-eabi-gdb
    
$(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) $(INC_DIRS) -c -g -o $@ $<
    
build/%.o$(C_SRCS)
    mkdir -p $(shell dirname $@)
    $(CC) -march=$(ARCH) -mcpu=$(MCPU) $(INC_DIRS) -c -g -o $@ $<
    


이번 글 부터는 구형 에디터로 작성하게 때문에 VSCODE로 코드가 바로 올라 가진다.

부팅

  1. 모든 초기화 작업을 마치고 펌웨어가 대기 상태가 될때 까지 상태
  2. ARM 코어가 리셋 익셉션 핸들러를 모두 처리한 다음 C언어 코드로 넘어가기 직전까지

실행 파일은 메모리를 크게 세 가지로 나누어 사용한다.

  1. text 영역
    • 코드가 있는 공간
    • 임의로 변경하면 안됨
  2. data 영역
    • 초기화한 전역 변수가 있는 공간
    • 전역 변수 선언시 초기 값을 할당해서 선언하면 해당 전역 변수가 점유 하는 공간이 할당된됨
  3. BSS영역
    • 초기화하지 않은 전역 변수가 있음
    • 초기화 하지 않은 전역 변수이므로 빈드 완료되어 생성된 바이너리 파일에는 심벌과 크기만 들어 있음

해당 프로젝트에서는 text 영역의 크기를 1MB (0x00000000 ~ 0x000FFFFF), data영역과 bss영역의 할당은 각 모드별로 1MB씩 그냥 할당한다. (크기를 그렇게 신경 안쓰고 일단 할당) RTOS의 위에서 동작할 task stack 영역도 필요하다. 총 64개의 task를 지원할 것이고 task마다 1MB씩 stack 영역을 할당하므로 총 64MB 할당한다. 그 다음 전역 변수용으로 1MB를 할당한다. 남은 공간은 heap으로 사용한다.

영역 주소 (총 128MB)
동적 할당 영역  0x04900000 ~ 0x07FFFFFF (55MB)
전역 변수 영역 0x04800000 ~ 0x48FFFFF (1MB)
태스크 스택 영역 0x00800000 ~ 0x047FFFFF (64MB)
UND 모드 스택 0x00100000 ~ 0x007FFFFF (7MB)
ABT 모드 스택
FIQ 모드 스택
IRQ 모드 스택
SVC 모드 스택
USY/SYS 모드 스택
text 영역  0x00000000 ~ 0x000FFFFF (1MB)

 

.text
    .code 32

    .global vector_start
    .global vector_end

    vector_start:
    	//exception vector table이 작성됨
        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
		
        //변수를 선언해 놓았고 이 변수를 exception vector table에서 사용함
        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:
    	//reset exception handler이다.
        LDR R0, =0x10000000
        LDR R1, [R0]

	//dummy로 해놓은 것이고 무한 루프를 돌게끔 구현함
    dummy_handler:
        B .
.end

부팅 초기시 exeption handler로 처리되며 해당 부분을 위해 Entry.S를 위와 같이 수정하자. 코드는 exception vector table에 각 handler로 jump 하는 코드만 작성되었다. 각 handler의 동작은 구현되지 않고 무한루프로 동작된다. 

$ cd project_directory
$ make clean && make all
$ make debug
$ (다른 console에서) make gdb
(gdb) target remote:1234
(gdb) continue
컨트롤 + C로 정지
(gdb) info registers

handler 처리 결과

2020/01/11 - [Engineering/운영체제] - [임베디드OS개발프로젝트] 3장. 일단 시작하기

지난 글 마지막 결과에서 확인할 수 있던 것 처럼 동일한 동작을 수행 했으므로 R1에 0x1780500값이 들어가 있음을 확인할 수 있다. 

보드가 부팅하면서 가장 먼저 수행되어야 할 것은 reset exception handler이다. 이 reset exception handler는 메모리 맵을 설정하는 작업을 수행해줘야 한다. SP 레지스터에 정해진 값을 넣는 작업을 수행하며 초기화를 수행한다.

$ cd project_directory
$ mkdir include
$ vim MemoryMap.h
$ vim ARMv7AR.h
<Memory.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 GLOBAL_ADDR_SIZE       (DALLOC_ADDR_START-GLOBAL_ADDR_START)
#define DALLOC_ADDR_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)
<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

MemoryMap.h와 ARMv7AR.h는 C언어 헤더 파일이다. GCC로 컴파일 할 경우 asm 파일에서도 사용할 수 있다. Entry.S에서 사용하기 위해 헤더를 추가해 준다.

#include "ARMv7AR.h"
#include "MemoryMap.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

위의 코드는 .h 파일 두개를 추가 후 SP 설정 및 초기화를 위해 reset_handler 부분을 수정한 것이다. 모든 동작 모드를 한 번씩 순회하면서 스택 꼭대기 메모리 주소를 SP에 설정하는 코드이다. SP에 해당모드의 시작 주소를 넣는 것이 아닌 TOP의 주소를 넣는 것은 스택이 높은 주소에서 낮은 주소로 자라는 특징을 가지고 있기 때문이다.( 하지만 이것은 설계자 마음이다. 꼭 Stack이 위에서 아래로 heap은 밑에서 위로 자라는 방향은 아니다.) 

<Makefile 내용>
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

LINK_SCRIPT = ./navilos.ld

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.bin

.PHONY: all clean run debug gdb

all: $(navilos)

clean:
	@rm -fr build

run: $(nvailos)
	@qemu-system-arm -M realview-pb-a8 -kernel $(navilos)

debug: $(nvailos)
	@qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4

gdb:
	arm-none-eabi-gdb

$(navilos): $(ASM_OBJS) $(LINK_SCRIPT)
	$(LD) -n -T $(LINK_SCRIPT) -o $(navilos) $(ASM_OBJS)
	$(OC) -O binary $(navilos) $(navilos_bin)

build/%.o: boot/%.S
	mkdir -p $(shell dirname $@)
	$(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<

ASM 컴파일러를 산용해서 컴파일 할 경우 전처리기가 없기 때문에 .h파일을 읽어올 수 없다. 따라서, 컴파일러를 gcc로 변경한다. 추가적으로 .h 파일들이 존재하는 include 디렉토리도 추가한다. 위의 makefile 내용으로 수정한다.

make all을 수행한 결과이다. 이후 QEMU <-> gdb를 이용해 sp의 메모리 위치를 확인해 보자.

 

34번째 라인까지 실행되고 첫번째 SVC 동작 모드 스택이 설정된다. SVC 모드 스택은 0x00300000 ~ 0x003FFFFF까지 메모리 주소 영역이다. 스택과 스택 경계에 4B를 비워두도록 크기를 설정 했으므로 0x003FFFFC여야 한다. 위 그림의 info register 결과에서 볼수 있듯이 sp의 값이 해당 값을 가리키고 있음을 알수 있다.

CPSR을 보면 마지막 바이트가 0xd3이다. 이것은 11010011(2)이며, 마지막 하위 5비트만 보면 10011이다. 이것은 16진수로 0x13을 나타낸다. SVC 동작 모드를 설정하는 값이다. 

ARM의 CPSR 레지스터의 bit 필드를 나타낸다. 하위 5bit는 해당 모드의 동작 상태를 나타내며 0b10011은 SVC를 나타낸다.  이렇게 모든 모드들에 대해서 sp를 정해 준다. 실제 보드에서는 sp뿐만이 아닌 하드웨어 시스템 클럭 설정, 메모리 컨트롤러 초기화 같은 일들도 필요하다. 이것은 추후에 Cortex-M3를 공부하며 진행하도록 하겠다.

 

<Entry.S 부분>
...

MRS R0, CPSR
BIC R1, R0, #0x1F
ORR R1, R1, #ARM_MODE_BIT_SYS
MSR CPSR, R1
LDR SP, =USRSYS_STACK_TOP

BL main

BL main을 추가해준다. 이것을 통해 asm의 동작이 끝난후 C언어의 main문으로 함수가 점프하게 된다. 

build/Mainc.C를 생성 후 아래 내용 입력

#include "stdint.h"

void main(void)
{
    uint32_t* dummyAddr = (uint32_t*)(1024*1024*100);
    *dummyAddr =  sizeof(long);
}

BL 브랜치 명령으로 점프 하려면 점프 대상 레이블이 같은 파일 안에 있어야 한다. 다른 파일에 있다면 링커가 링킹할 수 있도록 레이블을 .global로 선언해야 한다. 컴파일러는 C언어 함수 이름을 링커가 자동으로 접근할 수 있는 전역 심벌로 만든다. 전역 심볼은 어셈블리로 사용할떄 .global 지시어로 선언하고 C언어로 할때는 extern으로 사용한다.  반대로 어셈블리에서 .global로 선언한 이름은 c언어에서 함수 호출로 진입할 수 있다.

 

새롭게 Make 파일을 수정해 준다.

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  = -I include

navilos = build/navilos.axf
navilos_bin = build/navilos.bin

.PHONY: all clean run debug gdb

all: $(navilos)

clean:
	@rm -fr 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:
	arm-none-eabi-gdb
	
$(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) $(INC_DIRS) -c -g -o $@ $<
    
build/%.o: $(C_SRCS)
	mkdir -p $(shell dirname $@)
	$(CC) -march=$(ARCH) -mcpu=$(MCPU) $(INC_DIRS) -c -g -o $@ $<

make all 수행후 gdb를 통해 실행파일을 run한 후 정지 시키자. 그럼 펌웨어가 main() 함수를 실행하고 나면 다시 reset handler로 돌아가서 무한루프는 도는 것을 확인할 수 있다. 그래서 '컨트롤+c' 명령을 입력하기 전까진 종료되지 않는다. 이후 x/8wx 메모리 주소 명령을 입력한다. 이 명령은 입력된 메모리 주소부터 8개를 4바이트씩 16진수로 값을 출력하라는 명령이다. 그래서 0x6400000 메모리 주소의 값을 4바이트씩 8개 출력을한다. 출력 값은 4가 나온다. 

3.1 Reset Vector

ARM 코어에 전원이 들어온 후 첫 수행되는 동작은 reset vector에 있는 명령을 실행하는 것

32bit기준으로 Reset vector의 주소는 0x00000000이다. (이것은 Core에 dependency하다.)

따라서, 부팅후 첫 동작을 위해 0x00000000에서 동작할 코드를 작성한다.

$sudo apt install gcc-arm-none-eabi //arm용 cross 컴파일러 설치
$sudo apt install qemu-system-arm //arm아키텍처를 가상화해서 사용하기 위한 qemu 설치
$mkdir os_project
$cd os_project
$mkdir boot
$cd boot​

OS프로젝트 디렉토리를 하나 생성후 boot에 필요한 코드들을 포함하기 위해 boot 디렉토리를 생성한다.

boot 디렉토리 내에서 Entry.S를 생성한다.

 

.text //text ~ end까지는 text section을 의미함
    .code 32 //명령어의 크기가 32bit임을 뜻함

    //.global은 C언어에서 extern과 같은 역할
    .global vector_start //vector_start의 주소와 vector_end의 주소를 외부 파일에서 심볼로 읽을수 있게 설정
    .global vector_end

    vector_start:
        MOV R0, R1 //동작 수행
    vector_end:
        .space 1024,0 //해당 위치부터 104바이트를 0으로 채우기
.end //text section 끝​

 

실행파일 구조

하나의 executable file이 완성되면 linking 등 여러가지 작업에 의해 파일이 생성된다. 이떄 .text, .data, .bss 등 다양한 section이 존재하게 되며 Entry.S의 .text ~ .end 까지를 text section이라 한다. .text 섹션에는 code들이 존재한다.

$cd ~/os_project/boot
$arm-none-eabi-as -march=armv7-a -mcpu=cortex-a9 -o Entry.o //어셈블리 컴파일
$arm-none-eabi-objcopy -O binary Entry.o Entry.bin //object파일에서 binary 추출
$hexdump Entry.bin

hexdump Entry.bin 결과

Entry.S 컴파일 후 바이너리 파일을 덤프한 결과를 나타낸다.

arm-none-eabi-as: arm용 어셈블리 소스 파일 에셈블러

arm-none-eabi-object: object파일에서 바이너리 파일만 추출한다. object 파일에는 심볼 정보 등이 포함되어 있다.

1) 0001 e1a0 : MOV R0, R1를 의미

2) * 계속 반속 되는 것을 의미

3) 4B align이므로 0000404가 보여짐

 

실행파일을 만들기 위해서는 다수의 object 파일들을 묶어야 한다. 이 역할을 링커가 하게 되며 링커가 어떤 정보를 바탕으로 해당 동작을 수행하게 되는데 이때 정보들을 모은 파일을 링커 스크립터라고 한다. 하드웨어 환경에 맞춰 section(text, data, rodata 등)을 배치해야 한다. 

$ cd ~/os_project/
$ vi navilos.ld

링커 스크립터를 만들자.

ENTRY(vector_start) //시작 위치의 심벌을 저장
SECTIONS //3 ~ 19줄 까지의 블록이 섹션 배치 설정 정보를 가짐
{
   . = 0x0; //첫 번째 섹션이 메모리 주소 0x00000000에 위치 한다는 것을 나타냄

    .text : //text 섹션의 배치 순서를 지정
    {
        *(vector_start) //0x000000000에 reset_vector가 위치해야 함
        *(.text .rodata)
    }
    .data :
    {
        *(.data)
    }
    .bss :
    {
        *(.bss)
    }
}​

이후에 컴파일을 수행하자.

$ arm-none-eabi-ld -n -T navilos.ld -nostdlib -o navilos.axf boot/Entry.o
$ arm-none-eabi-objdump -D navilos.axf

-n : 링커에 섹션의 정렬을 자동으로 맞추지 말라고 지시

-T : 링커 스크립트의 파일명을 알려 주는 옵션

-nostdlib : 링커가 자동으로 표준 라이브러리를 링킹하지 못하도록 지시

컴파일 완료 후 navilos.axf 파일이 생성됨

-D : disassemble 해서 어떻게 구성되어 있는지 보자

disassemble 한 결과

 

vector_start가 0x00000000에 배치되어 있고 명령이 MOV R0, R1인 것을 확인 가능하다. 기계어로는 0XE1A00001

실행파일을 실행 시켜 보면 "bash: ./navilos.axf: cannot execute binary file: Exec format error" 에러 발생한다.

이것은 cross compile된 것이기 떄문에 x86_64 아키텍처에서는 실행이 되지 않는다.

해당 파일을 실행 하는 방법은 크게 두 가지이다.

1. ARM 기반 보드에서 직접 실행

2. QEMU에서 ARM 에뮬레이트를 통해 실행

$ qemu-system-arm -M realview-pb-a8 -kernel navilos.axf -S -gdb tcp::1234,ipv4​

QEMU 실행 결과

해당 명령을 실행했을때 QEMU의 화면이 실행되며 멈춘 상태의 결과를 확인할 수 있다.

-M : 머신 지정

-S : QEMU가 동작하자마자 정지

-gdb tcp::1234, ipv4 : gdb와 연결하는 소켓 포트를 지정

$ arm-none-eabi-gdb
(gdb) target remote:1234
(gdb) x/4x 0

target remote:1234 : 1234 포트를 통해 qemu에 접속

x/4x 0 : 0x00000000주소에서 4바이트를 출력

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

LINK_SCRIPT = ./navilos.ld

ASM_SRCS = $(wildcard boot/*.S)
ASM_OBJS = $(patsubst boot/%.S, build/%.o, $(ASM_SRCS))

navilos = build/navilos.axf
navilos_bin = build/navilos.bin

.PHONY: all clean run debug gdb

all: $(navilos)

clean:
    @rm -fr build

run: $(nvailos)
    qemu-system-arm -M realview-pb-a8 -kernel $(navilos)

debug: $(nvailos)
    qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S gdb tcp::1234, ipv4

gdb:
    arm-none-eabi-gdb

$(navilos): $(ASM_OBJS) $(LINK_SCRIPT)
    $(LD) -n -T $(LINK_SCRIPT) -o $(navilos) $(ASM_OBJS)
    $(OC) -O binary $(navilos) $(navilos_bin)

build/%.o: boot/%.S
    mkdir -p $(shell dirname $@)
    $(AS) -march=$(ARCH) -mcpu=$(MCPU) -g -o $@ $<
 

빌드를 자동으로 해주기 위한 Makefile을 작성한 것이다.

11번째 줄은 boot 디렉토리에 있는 asm파일은 모두 ASM_SRCS 변수에 값으로 넣으라는 의미

12번째 줄은 boot디렉토리에 있는 asm파일 이름을 찾아서 확장자를 .o로 바꾼 다음 디렉토리로 build로 바꿔 ASM_OBJS변수에 값으로 넣으라는 의미

33~35번째 줄은 링커로 navilos.axf 파일을 생성. 추가로 navilos.bin도 생성

37~39번째 줄은 *.S -> *.o로 변환

 

make all 결과

vector_start부분을 아래 코드를 바꿔 보자

vector_start:
    MOV R0, R1
//아래 코드로 변경
vector_start:
    LDR R0, =0x10000000
    LDR R1, [R0]​

5~6 R0에 저장된 메모리 주소의 데이터 값을 R1 레지스터에 저장

재 컴파일 이후 GDB를 통해 QEMU에 접속하자.

$ make gdb
(gdb) target remote:1234
(gdb) file build/navilos.axf

list를 통한 navilos.axf 정보 읽기

 

list 입력시 Entry.S 파일 심볼에 대해 확인이 가능하다.

초기 부팅 register 정보 상태

코드가 현재 실행되지 않았기 때문에 레지스터에는 아무런 정보가 존재하지 않아야 한다.

1 step 실행 이후 register 정보 상태

gdb) s : 1 step 실행. 즉, r0에 0x1000000주소값을 대입 하는 과정을 실행한 결과를 나타낸다.

LDR R1, [R0] 실행 후 register 정보 상태

(decimal)24642816은 0x1780500을 나타낸다.

책을 좀 더 참조 하자.

SYS_ID 레지스터 값을 분석하자.

[31:28] REV   보드버전           0x0 

[27:16] HBI    HBI 보드 번호    0x178

[15:12] BUILD 보드 빌드 변형   0x0

[11:08] ARCH 버스 아키텍처    0x5

[07:00] FPGA  FPGA 빌드        0x0

해당 결과를 해석하면 보드 리비전은 Rev A, 버스 아키텍처는 AXI를 사용한다.

+ Recent posts