01. Hello World!

박종범·2025년 12월 2일

u-boot 빌드

u-boot 없이 진행해도 되지만, 본인은 u-boot를 활용해볼 예정이다.
나는 u-boot가 귀찮아요... 하면 u-boot 없이도 부팅 충분히 가능하다. 패스할것

wget https://ftp.denx.de/pub/u-boot/u-boot-2025.10.tar.bz2
tar -xvf u-boot-2025.10.tar.bz2
make qemu_arm64_defconfig
make CROSS_COMPILE=aarch64-linux-gnu -j$(nproc)

kernel

kernel/Makefile

TARGET     = kernel
CROSS_COMPILE ?= aarch64-none-elf-

CC      = $(CROSS_COMPILE)gcc
LD      = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

SRC_DIR := src
INC_DIR := include
BUILD_DIR := bin

CFLAGS  = -std=c11 -ffreestanding -nostdlib -nostdinc \
          -Wall -Wextra -g -O0 \
          -mcpu=cortex-a53 -march=armv8-a
CFLAGS += -I$(INC_DIR)
LDFLAGS = -T linker.ld -nostdlib

SRC_C = $(wildcard $(SRC_DIR)/*.c)
SRC_S = $(wildcard $(SRC_DIR)/*.S)

OBJ_C   = $(SRC_C:.c=.o)
OBJ_S   = $(SRC_S:.S=.o)
OBJS    = $(OBJ_S) $(OBJ_C)

.PHONY: all clean dump

all: $(TARGET).elf $(TARGET).bin

$(TARGET).elf: $(OBJS) linker.ld
	$(LD) $(LDFLAGS) -o $@ $(OBJS)

$(TARGET).bin: $(TARGET).elf
	$(OBJCOPY) -O binary $< $@

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

%.o: %.S
	$(CC) $(CFLAGS) -c $< -o $@

dump: $(TARGET).elf
	$(OBJDUMP) -d $(TARGET).elf > $(TARGET).dump

clean:
	rm -f $(OBJS) $(TARGET).elf $(TARGET).bin $(TARGET).dump

.PHONY: all clean dump

kernel/src/start.S

// src/start.S
// AArch64 bare metal 부트 엔트리

.section .text.boot
.global  _start

.extern kmain
.extern __bss_start
.extern __bss_end

_start:
	// Disable Interrupt (DAIF 세트)
	msr DAIFSet, #0b1111

	// 스택 포인터 설정 (임시로 DRAM 상단 근처로 가정)
	// KERNEL_LOAD_ADDR + 16MB 정도 예시
	ldr x0, =0x41000000
	mov sp, x0

	// .bss 영역 0으로 클리어
	ldr x1, =__bss_start
	ldr x2, =__bss_end

bss_clear_loop:
	cmp  x1, x2
	b.ge bss_clear_done
	str  xzr, [x1], #8  // 8바이트씩 0으로 채우기
	b    bss_clear_loop

bss_clear_done:
	// C 엔트리로 진입
	bl kmain

1:  // kmain 반환 시 그냥 멈춰있기
	wfe
	b 1b

kernel/src/kmain.c

// src/kmain.c

#include <stdint.h>

#define UART0_BASE 0x09000000
#define UART0_DR (*((volatile uint32_t *)(UART0_BASE + 0x00)))
#define UART0_FR (*((volatile uint32_t *)(UART0_BASE + 0x18)))
#define UART0_FR_TXFF (1 << 5)

void uart_putc(char c) {
  while (UART0_FR & UART0_FR_TXFF)
    ;
  UART0_DR = (uint32_t)c;
}

void uart_puts(const char *s) {
  while (*s) {
    uart_putc(*s++);
  }
}

void uart_puthex(uint64_t n) {
  const char *hexdigits = "0123456789ABCDEF";
  uart_puts("0x");
  for (int i = 60; i >= 0; i -= 4) {
    uart_putc(hexdigits[(n >> i) & 0xF]);
  }
  uart_puts("\r\n");
}

uint64_t get_current_el(void) {
  uint64_t el;
  /* CurrentEL 시스템 레지스터 읽기 */
  __asm__ volatile("mrs %0, CurrentEL" : "=r"(el));
  return el >> 2; /* 하위 2비트는 reserved라 버림 */
}

void kmain(void) {
  uart_puts("Hello from my kernel!\n");

  uart_puts("\n\r");
  uart_puts("--------------------------------\n\r");
  uart_puts(" Boot Successful!\n\r");

  uint64_t el = get_current_el();
  uart_puts(" Current Exception Level: ");
  uart_puthex(el);
  
  if (el == 1) {
    uart_puts(" -> We are in EL1 (Kernel Mode)\n\r");
  } else if (el == 2) {
    uart_puts(" -> We are in EL2 (Hypervisor Mode)\n\r");
  } else {
    uart_puts(" -> Unknown Level\n\r");
  }
  uart_puts("--------------------------------\n\r");

  while (1) {
    __asm__ volatile("wfe");
  }
}

kernel/src/linker.ld

ENTRY(_start)

/* 커널이 올라갈 물리 주소 (QEMU virt 기준 DRAM 0x4000_0000 근처 예시) */
KERNEL_LOAD_ADDR = 0x40200000;

SECTIONS
{
    . = KERNEL_LOAD_ADDR;

    .text : ALIGN(4K) {
        KEEP(*(.text.boot))   /* 부트코드용 섹션 */
        *(.text .text.*)
    }

    .rodata : ALIGN(4K) {
        *(.rodata .rodata.*)
    }

    .data : ALIGN(4K) {
        *(.data .data.*)
    }

    .bss : ALIGN(4K) {
        __bss_start = .;
        *(.bss .bss.* COMMON)
        __bss_end = .;
    }

    /* 심볼용 섹션 (디버깅용) */
    /DISCARD/ : {
        *(.comment)
    }
}

launch qemu

다음 명령어로 qemu로 커널을 부팅해주자

qemu-system-aarch64 -M virt,virtualization=false \
	-cpu cortex-a76 -m 1G -nographic \
    -bios u-boot/u-boot.bin \
    -device loader,file=kernel/kernel.bin,addr=0x40200000
  • virtualization=false: QEMU가 EL1 레벨에서 부팅진행 (true하면 EL2, secure=true는 EL3에서 부팅)
  • cortex-a76: ARMv8.2

qemu 부팅이 완료되었다면, 다음 u-boot 커맨드로 커널이 로드된 곳으로 점프

go 0x40200000

매번 치는게 귀찮다면, u-boot/.config 파일에서 CONFIG_BOOTCOMMAND="go 0x40200000"로 바꿔줄것..
(2025.10 버전 기준 462라인)

솔직히,,, uboot가 필요없긴 하다.
qemu-system-aarch64 -M virt,virtualization=false \ -cpu cortex-a76 -m 1G -nographic \ -kernel kernel-hello/kernel.bin

실행결과

$ qemu-system-aarch64 \
    -M virt,virtualization=false -cpu cortex-a76 -nographic -m 1G \
    -bios uboot/u-boot.bin \
    -device loader,file=kernel/kernel.bin,addr=0x40200000
    
Bloblist at 0 not found (err=-2)
alloc space exhausted ptr 400 limit 0
Bloblist at 0 not found (err=-2)


U-Boot 2025.10 (Nov 27 2025 - 15:31:11 +0900)

DRAM:  1 GiB
using memory 0x7e65b000-0x7f69b000 for malloc()
Core:  51 devices, 14 uclasses, devicetree: board
Flash: 64 MiB
Loading Environment from Flash... *** Warning - bad CRC, using default environment

In:    serial,usbkbd
Out:   serial,vidconsole
Err:   serial,vidconsole
No USB controllers found
Net:   eth0: virtio-net#32

Hit any key to stop autoboot: 0
## Starting application at 0x40200000 ...
Hello from my kernel!

--------------------------------
 Boot Successful!
 Current Exception Level: 0x0000000000000001
 -> We are in EL1 (Kernel Mode)
--------------------------------

0개의 댓글