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