TIL/2025

QEMU Emulator(mps3-an536)에서 hello world 출력하기

고무 오리 2025. 4. 5. 23:11
728x90
📢 QEMU 환경에서 mps3-an536 보드와 Cortex-R52 CPU를 사용해, 콘솔에 "Hello, world" 를 출력하는 간단한 애플리케이션을 빌드하고 실행해보자

 

 

🧐 왜 이 작업을 했나?

ASPICE 등의 인증에 많이 사용되는 vectorCAST는 테스트 실행 환경으로 QEMU를 선택 할 수 있어요

그치만, 내가 타겟으로 하는 Board, CPU 설정이 존재하지 않아 Custom QEMU 환경을 구축해야 했죠..

 

이를 위해 QEMU(mps3-an536, cortex-r52)에서 hello world를 출력하는 application을 빌드하고 실행하는 환경을 구축했어요

 

 

🪛 UART 드라이버를 만들어야 하네?

QEMU 환경에서 콘솔 출력을 위해선 UART 설정이 필요해요

UART 드라이버 코드를 작성하기 위해 아래 문서를 참고했어요

 

UART 드라이버 코드와 메인 로직 코드를 아래와 같이 작성 가능해요

자세한 내용은 주석을 참고 해주세요!

// Step1. 매크로 정의 부분

#define APB_UART0_BASE  0xE7C00000ul // 다른 UART 레지스터들의 Offset 값으로 사용
#define __REG32(x)      (*((volatile unsigned int *)(x))) // Memory mapped I/O 레지스터 주소를 접근
#define UART_DATA(base) __REG32(base + 0x00) // 데이터 입력(송/수신)을 위한 레지스터
#define UART_STATE(base) __REG32(base + 0x04) // 현재 UART의 상태 확인
#define UART_CTRL(base) __REG32(base + 0x08) // UART 송,수신 활성화 등을 설정
#define UART_BAUDDIV(base) __REG32(base + 0x10) // Baudrate 설정

// Step2. UART Controller 제작

class UartController {
private:
    // UART 레지스터들의 Offset 값
    static constexpr unsigned long BASE_ADDR = APB_UART0_BASE;

public:
    // Step3. UART 초기화 메서드
    static void init() {
        UART_CTRL(BASE_ADDR) = 0; // 제어 레지스터 모든 비트 clear
        UART_BAUDDIV(BASE_ADDR) = 434; // Bauddiv = Clock Freqency/Baud Rate = 50MHz/115200 = 434
        UART_CTRL(BASE_ADDR) = (1 << 0) | (1 << 1) | (1 << 2);
    }

    static void putc(unsigned char ch) {
        while (UART_STATE(BASE_ADDR) & (1 << 0));
        UART_DATA(BASE_ADDR) = ch;
    }

    static void puts(const char* str) {
        while (*str) {
            putc(*str++);
        }
    }
};

extern "C" {
    int main(void) {
        UartController::init();
        UartController::putc('h');
        UartController::putc('e');
        UartController::putc('l');
        UartController::putc('l');
        UartController::putc('o');
        UartController::putc(',');
        UartController::putc('w');
        UartController::putc('o');
        UartController::putc('r');
        UartController::putc('l');
        UartController::putc('d');
        UartController::putc('!');

        UartController::puts("bye,world");
        return 0;
    }
}

 

 

🔨 App 빌드를 위해 필요한 4가지

파일 역할
Makefile 빌드 설정 및 자동화
linker.ld 메모리 구조 설정
vectors.s 시스템 초기화 및 예외 핸들러 등록
application.cpp 메인 로직 구현 (Hello world 출력)

 

Makefile은 빌드를 자동화하는 스크립트에요

  • 빌드 과정 중 링킹 과정에서 linker.ld가 사용
  • 빌드가 끝나면 bin, elf 파일 2가지가 생성

맨처음 App을 실행하면 vectors.s 파일이 시작되고 startup 코드라고 불러요

 

 

🚀 QEMU로 디버깅 해보기

QEMU에 Binary를 올려 App을 실행 할 수 있어요

GDB 서버를 활성화하면 디버깅도 가능하죠

 

디버깅을 하려면 bin 파일이 아닌 디버깅 정보가 존재하는 elf 파일을 사용해야해요

 

 

Step1. GDB 서버 활성화

터미널 하나 열고 아래 명령을 수행해서 GDB 서버를 열어줘요 (기본 포트 : 1234)

qemu-system-arm -M mps3-an536 -kernel application.elf -serial mon:stdio -nographic -s -S

 

명령어 내 각 옵션 설명은 아래와 같아요

  • -M mps3-an536 : 타겟 보드 지정
  • -kernel application.elf : 실행할 ELF 파일 지정
  • -serial mon:stdio : QEMU Monitor를 사용할 수 있도록 설정
  • -nographic : 그래픽 없이 콘솔 모드로 실행
  • -s : GDB 서버 기본 포트(1234)로 활성화
  • -S : 디버깅 위해 진입점에서 정지

 

Step2. GDB 클라이언트로 디버깅 하기

GDB 서버와 다른 터미널을 하나 열고 elf 파일이 있는 위치에서 아래 명령을 입력해요

arm-none-eabi-gdb application.elf

 

다음으로 GDB 서버에 원격 연결을 해줘요

target remote :1234

 

이제 마음껏 Debugging을 해요!

 

 

🛍️ Takeaway

QEMU Hello,world 통해 Custom test 환경 구축 완료!

 

GitHub - geongupark/qemu-mps3-an536-hello-world: "Hello, World!" Application on QEMU(mps3-an536, cortex-r52)

"Hello, World!" Application on QEMU(mps3-an536, cortex-r52) - geongupark/qemu-mps3-an536-hello-world

github.com

 

 

이번 작업을 통해 아래의 것들을 배울 수 있었어요

  • Document를 참고하여 UART Driver를 개발하는 방법
  • 4가지 빌드 필수 요소와 함께 App 빌드하기
  • QEMU Debugging 환경 세팅
728x90