📢 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 Base 주소: mps3-an536 문서
- APB UART 설정: arm : apb-uart 문서
- PCLK 값 참고: QEMU 코드 (pclk = 50MHz)
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 코드라고 불러요
- 이 과정에서 시스템 초기화 및 예외 핸들러 등록 후 main 함수로 분기!
🚀 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 repository를 참고해주세요 😎
- https://github.com/geongupark/qemu-mps3-an536-hello-world
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 환경 세팅
'TIL > 2025' 카테고리의 다른 글
| 'docker run --rm' 명령의 의미와 환경 변수 (0) | 2025.04.15 |
|---|---|
| vi 에디터 라인 이동 하기 (0) | 2025.04.09 |
| C/C++ 코드 빌드 과정 이해하기 (0) | 2025.02.14 |
| GitHub PR 머지 했는데, 원치 않는 Merge Commit이?! (0) | 2025.02.14 |
| git 커밋 메시지 에디터 vim으로 변경하기 (0) | 2025.02.14 |