검증 끝났으면 앱으로 점프해야 한다. 단순히 함수 호출하면 안 된다.


점프 코드

void Jump_To_App(void) {
    uint32_t app_addr = 0x08042800;
    
    // 인터럽트 비활성화
    __disable_irq();
    
    // Vector Table 재배치
    *(uint32_t *)0xE000ED08 = app_addr;  // SCB->VTOR
    
    // Stack Pointer 설정
    __set_MSP(*(uint32_t *)app_addr);
    
    // Reset Handler로 점프
    void (*app_reset)(void) = (void (*)(void))(*(uint32_t *)(app_addr + 4));
    app_reset();
}

VTOR 설정

0xE000ED08은 SCB->VTOR (Vector Table Offset Register).

앱이 0x08042800에서 시작하니까 VTOR도 거기로 바꿔줘야 한다. 안 바꾸면 앱에서 인터럽트 발생할 때 부트로더 Vector Table 참조해서 엉뚱한 데로 간다.


MSP 설정

__set_MSP(*(uint32_t *)app_addr);

Main Stack Pointer를 앱의 Initial SP로 설정. 부트로더가 쓰던 스택 버리고 새로 시작.


함수 포인터로 점프

void (*app_reset)(void) = (void (*)(void))(*(uint32_t *)(app_addr + 4));
app_reset();

app_addr + 4에 있는 Reset_Handler 주소를 함수 포인터로 캐스팅해서 호출.

Ghidra 디컴파일러는 이걸 이상하게 보여줄 수 있다:

(*(code *)((uint *)(app_addr + 4)))();

어셈블리로 보면

    ldr   r0, =0x08042800
    ldr   r1, [r0]          ; SP 읽기
    msr   msp, r1           ; MSP 설정
    ldr   r0, [r0, #4]      ; Reset Handler
    bx    r0                ; 점프

깔끔하다.


다음 글에서 버퍼 → 앱 복사 로직.

#22 - 버퍼 복사 로직