ARM Cortex-M3/M4 Processor - 3

Pages List
List view
Home
Portfolio
HW
FW
SW
FPGA / Adaptive SoC
Daily
Photo
Etc
 
STM32 Peripheral

ARM Cortex-M3/M4 Processor - 3


notion image
💡
Embedded Systems Programming on ARM Cortex-M3/M4 Processor by Kiran

Interrupt priority and configuration


Interrupt priority explanation

notion image
ARM Cortex-Mx Processor에서 Priority value가 작을수록 Priority가 높다.
예를 들어, 위 그림에서 ADC의 Priority value는 5이고, Timer의 Priority value는 4이다.
Timer의 Priority value가 ADC의 Priority value보다 작기 때문에, Timer의 Priority가 더 높다.
그래서 두 Peripheral의 Interrupt Req가 동시에 NVIC에 도착하면, NVIC은 Timer interrupt Req를 먼저 허용하여 Processor는 Timer ISR을 실행하게 된다.
 
ARM Cortex-Mx Processor에는 얼마나 많은 Priority level (=value)가 있을까?
→ Priority level의 수는 MCU vendor의 Interrupt Priority Register의 Implementation에 따라 다르다.
ST의 STM32F4x MCU의 경우에는 16개의 Priority levels이 존재하는 반면에, TI의 TM4C123Gx는 8개의 Priority levels이 존재한다.
 
다음은 ARM Cortex-M3 Processor의 IPR (Interrupt Priority Register)이다.
notion image
각 IPR 레지스터에서 4개의 IRQ에 대한 Priority를 설정할 수 있다.
 
그러면 이제 Interrupt Priority Register의 실제 Implementation을 살펴보자.
notion image
IPR 레지스터는 8bit를 갖는 4개의 섹션으로 나누어지는데, 각 섹션에서 그것에 대응하는 IRQ의 Priority를 설정한다.
그런데 섹션의 8bit가 모두 구현되지 않고, 몇 개의 bit만 구현된다.
그림의 오른쪽과 같이 실제 MCU vendor가 구현하는 경우에는 예를 들어, 상위 3개의 bit ( [7:5] )만 구현되고, 나머지 bit는 구현되지 않는다.
 
결과적으로, 몇 개의 bit를 어떻게 구현할 것인가는 MCU vendor가 결정한다.
notion image
STM의 경우에는 위 그림의 오른쪽 MCU vendor의 방식으로 Interrpt Priority Register를 구현한다.
 
(추가) System exception의 Priority는 SCB (System Control Block)에서 설정한다.
notion image
NVIC의 IPR 레지스터에서는 Interrupt에 대한 Priority 설정만 가능하다.
 

Pre-empt priority and sub priority

만약 동일한 Priority를 가진 Interrupt가 동시에 NVIC에 도착하면 어떻게 될까?
→ 그러면 Interrupt의 Sub-Priority value를 확인해서 Conflict를 해결하는데, Sub-Priority value가 더 낮은 interrupt를 먼저 허용한다.
 
Priority Grouping에 대해 알아보자.
Priority Grouping에는 두 개의 Priority가 존재한다.
  1. Pre-Empt Priority :
    Processor가 Interrupt handler를 실행하고 있을 때 다른 Interrupt가 나타나면, Pre-Empt Priority를 비교해서 더 높은 Pre-Empt Priority를 가진 Interrupt를 허용한다.
  1. Sub Priority :
    동일한 Pre-Empt Priority를 가진 Interrupt가 동시에 발생했을 때, Sub Priority를 비교해서 더 높은 Sub Priority를 가진 Interrupt를 허용한다.
 
Priority Group은 IPR (Interrupt Priority Register)를 어떻게 사용하는가에 따라 다르다.
(자세히 설명하면, Pre-Empt Priority와 Sub Priority가 각각 차지하는 bit field의 길이에 따라 다양한 Priority Group을 만든 것이다.)
notion image
ARM Cortex-Mx Processor는 Priority Group 0를 default로 사용한다.
Priority Group 0는 Bit[7:1]을 Pre-Empt Priority field로 사용하고 Bit[0]을 Sub Priority field로 사용한다.
만약, 동일한 Pre-Empt Priority를 가진 Interrupt가 동시에 발생하면, NVIC은 Bit[0]의 값이 0인 Interrupt를 허용한다.
 
만약, Priority Group을 바꾸고 싶다면 SCB의 AIRCR (Application Interrupt and Reset Control Register)에서 바꿀 수 있다.
notion image
 
Priority Group 0일 때를 자세하게 알아보자.
notion image
Pre-Empt Priority는 7개의 bit를 갖지만, 그 중에서 3개의 bit만 구현되기 때문에 사용할 수 있는
Pre-Empt Priority level은 8개이다.
Sub Priority는 1개의 bit를 갖지만, Bit 0는 구현되지 않기 때문에, 사용할 수 있는 Sub Priority level은 존재하지 않는다.
 
Priority Group 5일 때를 자세하게 알아보자.
notion image
Pre-Empt Priority는 2개의 bit를 갖기 때문에, 사용할 수 있는 Pre-Empt Priority level은 4개이다.
Sub Priority는 6개의 bit를 갖지만, Bit[4:0]은 구현되지 않기 때문에, 사용할 수 있는 Sub Priority level은 2개이다.
 
만약에 동일한 Pre-Empt Priority와 동일한 Sub Priority를 갖는 두 개의 Interrupt가 동시에 발생하면 어떻게 될까?
→ 이 경우에는 더 낮은 IRQ number를 갖는 Interrupt가 먼저 허용된다.
 

Interrupt priority configuration exercise

NVIC의 ISPR (Interrupt Set Pending Register) 레지스터를 사용해서 TIM2 global interrupt와 I2C1 event interrupt를 generate한다.
그리고 두 Interrupt의 Priority가 동일한 경우와 다른 경우의 ISR 실행을 관찰해본다.
 
다음은 두 Interrupt의 Priority가 동일한 경우의 코드이다.
notion image
notion image
I2C1의 Interrupt를 Enable했지만 TIM2 Interrupt만 pending 시켰기 때문에, I2C1 Handler는 실행되지 않고 TIM2 Handler만 실행된다.
TIM2 Handler안에서는 I2C1 Interrupt를 pending시키는데, 두 Interrupt의 Priority가 0x80으로 동일하기 때문에 I2C1 Handler는 실행되지 못한다.
그런데 낮은 Priority의 Interrupt Handler가 실행되고 있을 때 높은 Priority의 Interrupt가 활성화되면, 낮은 Priority의 Interrupt는 Pre-Empted되고 Processor는 높은 Priority의 Interrupt handler를 실행한다.
그래서 I2C1 Interrupt의 Priority를 0x80보다 높은 0x70으로 바꾸면, Tim2 Handler는 Pre-Empted되고 I2C1 Handler가 실행된다. (이것을 Interrupt nesting이라고 부른다.)
 

Pending interrupt behavior

System에 1개의 Interrupt request가 있는 경우와 2개의 Interrupt requents가 있는 경우를 살펴본다.
 
우선, System에 1개의 Interrupt request가 있는 경우를 살펴보자.
notion image
  1. Processor가 Thread mode로 동작하고 있다고 가정한다.
  1. Peripheral의 Interrupt req가 NVIC으로 들어온다.
  1. IRQ number에 대응하는 NVIC의 ISPR (Interrupt Set Pending Register)의 bit가 Set된다.
  1. Processor가 Interrupt를 Accept하면, ISR을 실행하기 위해 Handler mode로 바뀌게 된다.
  1. Processor가 ISR을 실행하게 되면, 이에 대응하는 IABR (Interrupt Active Bit Register)의 bit가 Set된다. 그리고 ISPR의 Interrupt pending bit는 Clear된다.
  1. ISR 실행이 모두 끝나서 빠져나올 때, Processor가 더 이상 ISR을 실행하고 있지 않음을 알리기 위해 IABR의 해당 bit를 Clear한다.
    그리고 Processor는 Handler mode에서 Thread mode로 돌아간다.
 
다음으로 System에 2개의 Interrupt request가 있는 경우를 살펴보자.
notion image
위 그림의 7번 이전까지는 Case1과 동일하기 때문에 7번에서부터 살펴보자.
 
  1. Processor가 이미 어떤 Peripheral의 Interrupt request에 대한 ISR을 실행하고 있다고 가정하자.
  1. 그러던 중에 다른 Peripheral의 Interrupt request가 NVIC으로 전달되었을 때, 새로운 Interrupt request의 Priority가 현재 실행중인 ISR의 Priority보다 낮으면, 해당 Interrupt를 pending시킨다.
  1. 기존의 ISR 실행을 끝내면, Processor는 해당 IABR bit를 Clear하고, Thread mode로 전환된다.
  1. 그러나 다른 pending중인 Interrupt request가 있기 때문에, Processor는 바로 다시 Handler mode로 전환되고, ISPR의 bit를 Clear하는 동시에 IABR의 bit를 Set하면서 ISR을 실행한다.
 
 

Exception entry and exit sequences


Exception entry and exit sequences

notion image
Exception이 발생했을 때, Exception entry sequence는 다음과 같다.
  1. NVIC의 Pending bit가 Set된다.
  1. Processor가 Stack Frame을 stacking하고 Vector table에서 Exception handler의 주소를 Fetch한다.
  1. Processor는 Handler에 진입하고, 해당 Exception의 Active bit를 Set한다.
    그리고 Processor가 알아서 Pending Status bit를 Clear한다.
  1. Processor의 mode는 이제 Handler mode로 바뀌게 된다.
  1. Processor는 Exception handler의 코드를 실행한다.
  1. Handler안에서 수행되는 모든 Stack operations들에는 MSP가 사용된다.
 
Exception handler를 모두 실행했을 때, Exception exit sequence는 다음과 같다.
  1. Cortex-M3/M4 Processor에서 Exception return mechanism은 EXC_RETURN이라는 Special return address를 사용해서 trigger된다.
  1. EXC_RETURN은 Exception entry 과정에서 생성되는 값인데, LR 레지스터에 저장된다.
  1. EXC_RETURN 값이 PC에 write되면, Exception return이 trigger된다.
 
EXC_RETURN 값에 대해 자세히 알아보자.
  • Exception entry 과정에서 Return address(PC) 값은 LR에 저장되지 않는다. (normal C function을 호출할 때와는 다르게)
  • 그 대신에 Exception mechanism은 Special return address라고 불리는 EXC_RETURN을 LR에 저장한다.
 
다음은 EXC_RETURN 값의 내부 bit 구성이다.
notion image
Bit[1 :0]은 Reserved 되어 있고, Bit[31:4]는 크게 중요하지 않다.
중요한 bit는 Bit[2]와 Bit[3]이다.
이 bit들은 Exception entry과정에서 update되는데, Exception handler를 실행하기 전에 Processor의 Operation mode와 사용하고 있던 Stack pointer의 정보를 저장한다.
Processor는 Exception exit 과정에서 EXC_RETURN을 decode하는데, 이 때 어떤 Stack pointer를 사용해서 Un-Stacking을 해야하고, 어떤 Operation mode로 돌아가야 하는지를 결정한다.
 
다음은 Exception exit 과정에서 EXC_RETURN 값을 decode하는 절차를 담은 그림이다.
notion image
 
다음은 EXC_RETURN이 가질 수 있는 값의 목록이다.
notion image
 

Analyzing stack contents during exception entry and exit

다음은 Exception entry와 exit 과정에서의 Stack contents를 살펴보기 위한 예제이다.
(001_Operaton_modes 예제 코드와 동일하다.)
notion image
 
notion image
notion image
IRQ3를 SW적으로 trigger하는 명령을 실행하기 전의 SP 값은 0x2000_bfe8이다. (=MSP)
 
다음 그림은 Exception entry 직전의 Stack 상태이다.
notion image
 
다음 그림은 Exception entry와 이후의 Stack 상태이다.
notion image
Interrupt가 발생하면, Stacking 과정이 수행된다. MSP는 감소하고, Stack Frame은 HW에 의해 자동으로 Stack으로 Push된다. (ARM Cortex-Mx는 Full Descending 방식으로 Stack을 운영)
 
이제 Stacking된 Stack Frame을 분석해보자.
 
① xPSR
위 그림에서 Last stacked item의 address가 0x2000_bfe8이므로 Exception entry로 인한 Stacking 과정 이후에, 0x2000_bfe4 번지에는 xPSR의 값이 0x0100_0000 저장되어 있을 것이다.
notion image
notion image
 
Memory에서 0x2000_bfe4번지를 찾아보면, xPSR의 값인 0x0100_0000이 저장되어 있음을 확인할 수 있다. (ARM Cortex-Mx는 Little-endian 방식이라서 아래 그림처럼 보인다.)
notion image
 
② 나머지는 다음과 같다.
notion image
notion image
Stack Frame을 구성하는 레지스터들은 MSP가 가리키는 Stack의 장소에 저장된다.
이것들을 Thread mode code의 State 또는 Thread mode code의 Stack Frame이라고 부른다.
그리고 이 State (Stack Frame)은 Processor가 ISR을 빠져나올 때, 복구되어야 한다.
 
이번에는 Interrupt handler에서 LR 값을 살펴보자.
notion image
notion image
LR에 이상한 값이 Load되어 있는데, 이 값은 사실 EXC_RETURN 값이다.
Processor가 ISR에 진입하면, LR은 EXC_RETURN 값으로 Load된다.
그리고 Processor가 ISR에서 빠져나가려고 할 때, EXC_RETURN 값을 PC에 write하면, Exception return이 trigger된다.
 
이제 Exception exit 과정에서의 Un-Stacking을 살펴보자.
notion image
 
notion image
notion image
IRQ handler의 끝에서 R7과 PC의 값을 Stack에서 POP한다.
 
위 시점에서 Stack pointer의 값은 0x2000_bfc0인데,
notion image
 
이 address에 저장되어 있는 값은 다음과 같다.
notion image
0x2000_bfc0에 저장되어 있는 값은 R7으로 POP 해야하는 값이고, 0x2000_bfc4에 저장되어 있는 값은 PC로 POP 해야하는 값이다.
그런데 0x2000_bfc4의 값은 EXC_RETURN 값인 0xfffffff9이므로, 이 값을 PC로 POP하면 Exception return이 trigger된다.
 
notion image
Processor는 ISR에서 빠져나갈 때, EXC_RETURN 값인 0xFFFFFFF9를 decode해서 다음과 같은 Return 정보를 획득한다.
  • Thread mode로 return 해야 한다.
  • Main stack에서 Stack Frame을 꺼내야 한다.
  • Return 이후에 MSP를 Current stack pointer로 사용해야 한다.
 
Processor는 Main stack에서 Stack Frame을 꺼내고, 그 안의 Return address(PC)로 Jump한다.
그리고 거기서부터 Thread mode 코드를 이어서 실행한다.
 
기존 코드는 Thread mode에서 MSP를 사용했지만, 다음 코드는 PSP를 사용하는 경우이다.
이 때, EXC_RETURN 값을 살펴보자.
 
notion image
 
다음의 그림처럼 Thread mode에서 PSP를 Current Stack pointer로 사용했다면, EXC_RETURN 값도 달라진다.
notion image
notion image
 
 

Fault handling and analysis


Introduction to processor faults

Fault란 무엇일까?
  • Fault는 Processor에 의해 생성되는 System exception인데, Error가 발생했음을 알려준다.
 
Fault가 발생하는 이유는 무엇일까?
  • 프로그래머가 Processor를 다룰 때, Design rules이나 Processor가 연결된 Interrface 때문에 Fault가 발생할 수 있다.
  • Fault가 발생하면, Fault의 type, Fault가 발생한 Instruction의 address가 Processor의 Internal registers들에 기록된다. 그리고 Fault에 대한 Exception이 enable되어 있다면, 해당 Exception handler가 Processor에 의해 Call된다.
  • 프로그래머는 Exception handler 안에서 Fault를 report, resolve, recover 하기 위한 코드를 구현할 수 있다.
  • 예를 들어, 프로그래머의 코드가 어떤 수를 0으로 나누려고 할 때, HW에 의해 ‘divide by 0’ Fault가 나타난다. 그리고 이는 Usage fault Exception handler를 유발한다. (Enable 되어 있다면)
    Exception handler안에서 프로그래머는 해당 문제를 해결하기 위해 어떤 결정을 내릴 수 있다.
    (예를 들어, task를 종료하는 등)
 
Cortex-Mx Processor의 다양한 Fault exceptions들에 대해 간략하게 알아보자.
  • Hard fault exception :
    Hard fault exception은 default로 enable되어 있는데, FAULTMASK 레지스터를 사용해서 disable할 수 있다. 그리고 Priority를 configure할 수 없다.
  • Usage fault exception,
    Mem manage fault exception,
    Bus fault exception은 모두 default로 disable되어 있고, Priority를 configure할 수 있다.
 
Fault의 발생 원인들을 알아보자.
  • Divide by zero (if enabled)
  • Undefined instruction
  • Attempt to execute code from memory region which is marked as execute never (XN) to prevent code injection
  • MPU guarded memory region access violation by the code
  • Unaligned data access (if enabled)
  • Returning to thread mode keeping active interrupt alive
  • Bus error (example no response from memory device (e.g. SDRAM))
  • Executing SVC instruction inside SVC handler or calling a function in SVC handler which eventually execute hidden SVC instruction
  • Debug monitor settings and related exceptions
  • etc… (위에 열겨한 원인들로 인해 Fault가 자주 발생한다.)
 
① HardFault exception
HardFault exception에 대해 자세히 알아보자.
→ HardFault exception은 Exception processing 과정에서의 error 또는 Exception을 manage 할 수 있는 Exception mechanism이 없을 때, 발생한다.
HardFault exception은 Reset, NMI 다음으로 3번째로 높은 Priority (-1)을 가지고, Priority configurable한 임의의 Exception들보다 높은 Priority를 가진다.
 
HardFault exception의 발생 원인은 다음과 같다.
  • Escalation of configurable fault exceptions
  • Bus error returned during a vector fetch
  • Execution of break point instruction when both halt mode and debug monitor is disabled
  • Executing SVC instruction inside SVC handler
 
다음 그림과 같이, 발생한 Fault Exception이 Enable된 경우와 그렇지 않은 경우를 살펴보자.
notion image
Configurable한 Priority를 가지는 Fault Exceptions이 Enable되지 않은 상태에서, 그와 관련된 Fault Exceptions이 발생한다면, 해당 Fault는 HardFault로 escalated되고 HardFault Exception handler가 실행된다.
 
다음 그림은 HardFault Status Register이다.
notion image
  • VECTTBL ( Bit[1] ) :
    Vector fetch 과정에서 BusFault가 발생하면, Bit[1]이 Set된다.
    이 Error는 항상 HardFault handler에 의해 처리된다.
  • FORCED ( Bit[30] ) :
    Disable되어 있거나 Priority 문제로 인해 Handle 될 수 없는 Configurable한 Priority를 갖는 Fault가 escalated되서 Forced HardFault가 발생하면,Bit[30]이 Set된다.
  • DEBUGEVT ( Bit[31] ) :
    Debug event로 인해 HardFault가 발생하면, Bit[31]이 Set된다.
 
HardFault Excpetion이 발생하면, HardFault Status Register를 보고 Fault가 발생한 원인을 찾아야 한다.
 
 

Other configurable faults


① Mem-manage fault exception

  • Mem-manage fault exception은 configurable한 Fault exception이고, default로 disable되어 있다.
  • 이 Exception을 enable하고 싶다면, Processor 레지스터인 SHCSR (System Handler Control and State Register)를 configure하면 된다.
    • notion image
 
  • Mem-manage fault가 발생하면, Mem-manage fault exception handler가 Precessor에 의해 실행된다.
  • Mem-manage fault exception의 priority는 configurable하다.
 
Mem-manage fault exception의 발생 원인을 살펴보자.
  • Memory Access violation이 감지된 경우. (memory read / write 과정에 violation이 있는 경우)
    다시 말해, Processor나 MPU에 의해 설정된 Access permission을 violate하면 exception이 발생한다.
  • Un-privileged thread mode code (user application or RTOS task)가 MPU에 의해 ‘Privileged Access only’로 마킹된 Memory 영역을 Access하려고 시도하는 경우.
  • MPU에 의해 ‘Read-only’로 표시된 Memory 영역에 Write하는 경우.
  • ‘Peripheral’ memory 영역에서 Program 코드를 실행하려고 하는 경우.
    이 Memory 영역은 Peripherals을 통한 Code injection attcaks을 피하기 위해, Processor 설계 단계에서 XN (eXecute Never) 영역으로 설정되어 있다.
notion image
 

② Bus fault exception

  • Bus fault exception은 configurable한 Fault exception이고, default로 disable되어 있다.
  • 이 Exception을 enable하고 싶다면, Processor 레지스터인 SHCSR (System Handler Control and State Register)를 configure하면 된다.
notion image
  • Bus fault가 발생하면, Bus fault exception handler가 Processor에 의해 실행된다.
  • Bus fault exception의 priority는 configurable하다.
 
Bus fault의 발생 원인을 살펴보자.
  • Memory에 Access하는 도중에 Processor Bus interface로부터 Error response를 받는 경우.
      1. During Instruction fetch
      1. During Data read or write to memory devices
       
  • Vector fetch 과정에서 Bus error가 발생하면, Bus fault exception이 Enable되어 있더라도 HardFault로 escalated된다.
  • Processor Bus interface가 Invaild 또는 Restricted memory locations에 Access하려고 할 때, Memory devices는 Error response를 보낸다.
    그리고 이 Error response가 Bus fault를 생성한다.
  • Device가 Memory transfer를 Accept할 준비가 되지 않았을 때
  • DRAM controllers를 통해 연결된 SDRAM과 같은 External memories를 사용하는 경우에 Bus fault가 발생할 수 있다.
  • Private Peripheral Bus에 Un-privileged Access를 하는 경우
    (Processor의 SCB, NVIC, DEBUG registers, etc..)
 

③ Usage fault exception

  • Usage fault exception은 configurable한 Fault exception이고, default로 disable되어 있다.
  • 이 Exception을 enable하고 싶다면, Processor 레지스터인 SHCSR (System Handler Control and State Register)를 configure하면 된다.
    • notion image
 
  • Usage fault가 발생하면, Usage fault exception handler가 Processor에 의해 실행된다.
  • Usage fault exception의 priority는 configurable하다.
 
Usage fault의 발생 원인을 살펴보자.
  • Undefined instruction을 실행하는 경우
    (Cortex-Mx Processor는 오로지 Thumb ISA만 지원한다. 그래서 ARM ISA같은 Instruction을 실행하면, Fault가 발생한다.)
  • Floating point unit이 disable된 상태에서 Floating point instruction을 실행하는 경우
  • Thumb state가 아닌 ARM state로 전환해서 ARM ISA instructions를 실행하려고 하는 경우.
    Processor의 T bit는 ARM state인지 Thumb state인지를 결정한다.
    Cortex-Mx Processor는 Thumb state만 지원하기 때문에, T bit는 항상 1로 유지되어야 한다.
    만약 T bit가 0이 되면, Fault가 발생한다. (Function pointer를 사용해서 function을 call 하는데, Function pointer의 0th bit가 1로 유지되지 않는 경우)
  • Exception / Interrupt가 Active 상태인데, Thread mode로 돌아가려고 하는 경우
  • Multiple load 또는 Multiple store instructions로 Unaligned memory access를 하는 경우
  • 어떤 수를 0으로 나누는 경우 (‘divide by zero’ trap은 default로 disable 되어있기 때문에, Enable해야 Usage Fault가 발생한다.)
  • Memory의 모든 unaligned data access를 하는 경우 (‘unaligned data access’ trap은 default로 disable 되어있기 때문에, Enable해야 Usage Fault가 발생한다.)
 
 

Configurable fault exception exercise-1


다음과 같이 Configurable fault exception에 대한 예제를 해보자.
notion image
 

① Execute an undefined instruction

notion image
notion image
  • Line 14에서 pSRAM을 uint32_t 포인터로 선언한 이유는 Line 15에서 0xFFFF_FFFF를 저장하기 위함이다. (0xFFFF_FFFF를 32bit 공간에 넣어야 하기 때문)
  • Line 19에서 void* 형으로 형변환하는 이유는 다음의 링크를 참조해라
  • 강의 영상에서 0xFFFF_FFFF를 저장할 address를 0x2001_0000번지로 정했는데, STM32F103RCT6은 SRAM의 크기가 48KB라서 0x2000_C000이 SRAM END이다.
    그래서 그냥 0x2000_A000으로 정해서 예제를 진행했다.
 
다음 그림은 0x2000_A001 번지에 저장된 값 0xFFFF_FFFF이다.
notion image
 
Disassembly로 0xFFFF_FFFF를 보면, Undefined instruction으로 나타난다.
Function pointer를 사용해서 0x2000_A001 번지를 PC에 Load시키면, Processor는 해당 주소의 값인 0xFFFF_FFFF를 Instruction으로 Fetch한다.
 
그런데 0xFFFF_FFFF는 Thumb ISA에 define되지 않은 Instruction이기 때문에, Instruction decoder가 decode하지 못하고 다음과 같이Usage Fault를 발생시킨다.
notion image
 
Usage Fault가 발생한 원인은 다음과 같이 Undefined instruction을 실행하려고 시도했기 때문이다.
notion image
 

(추가) Debugger를 사용하지 않고, Fault의 발생 원인을 찾는 방법

Debugger를 사용할 수 없는 상황에서 Fault가 발생한 원인을 찾아야 하는 경우가 생길 수 있다.
그러면 프로그래머가 작성한 코드를 사용해서 원인을 찾아야 하는데, printf나 interfacing peripheral을 이용한 debug message를 출력하는 것이 일반적이다.
 
다음과 같이 Fault Status register와 Fault Address register를 통해, Fault에 대한 자세한 정보를 얻을 수 있다. (Fault의 Status, Fault가 발생한 Instruction의 주소를 알 수 있다.)
notion image
프로그래머는 이 레지스터들을 읽어서 message로 출력하는 방식으로 Application의 debugging을 효율적으로 수행할 수 있다.
 
다음은 Usage Fault Status register를 읽어서, Usage Fault가 발생한 원인을 코드에서 찾는 예제이다.
notion image
notion image
 
위의 코드를 실행하면, UFSR의 값은 ‘Undefined instruction UsageFault’를 의미하는 1로 나타난다.
notion image
 
Debugger로 UFSR의 값을 직접 살펴보면, 실제로 UFSR은 1로 나타나는 것을 확인할 수 있다.
notion image
 
notion image
 
 

Analyzing stack frame


바로 위에서 우리는 Fault의 원인을 찾기 위해 Status register를 살펴보았다.
이 방법은 Fault의 원인을 찾기 위해 Fault handler안에서 사용하는 Debugging technique이다.
 
다음 그림은 Fault handler가 실행되면서, Thread mode context가 stacking되는 과정이다.
notion image
Thread mode에서 Exception이 trigger되면 Excption handler가 실행되는데, 위 경우에는 Usage Fault handler가 실행된다.
이 떄, Processor는 Thread mode에서 Handler mode로 바뀌면서 Thread mode의 Context를 알아서 Stacking 한다.
Handler에서 Thread mode code의 Stack frame을 분석해서 Fault가 발생한 장소를 알 수 있다.
보드를 Debugger에 연결하지 못하거나 연결하지 않는 상황이 종종 있기 때문에, 프로그래머는 Stack memory contents를 보고, Fault가 발생한 원인이나 장소를 찾아야 한다.
 
다음 그림은 Thread mode의 Stack frame이다.
notion image
Exception이 trigger되면 SP가 새로운 Top of Stack을 가리키는데, 이 SP값으로 Stack frame의 모든 contents를 알 수 있다.
코드에 문제가 발생했을 때, 이 Stack frame의 contents가 문제를 해결할 실마리가 될 수 있다.
 
다음은 Thread mode code의 Stack frame으로 Fault가 발생한 원인과 위치를 찾는 코드이다.
notion image
notion image
  • Usage fault handler의 Line 1에서는 MSP값을 레지스터 R0로 Copy한다.
  • Line 2에서는 R0에 저장되어 있는 MSP값을 C 변수 msp_vaule로 Copy한다.
    이 때, 앞 쪽에는 ‘register’라는 attribute가 붙고, 뒤 쪽에는 ‘__asm(“r0”)’라는 special attribute가 붙는다.
    ‘register’ attibute는 Stack에 해당 변수를 할당하지 말고, 레지스터에 할당하라는 의미이다.
    그리고 ‘__asm(“r0”)’ attribute는 헤당 변수의 값이 R0 레지스터값이라는 의미이다.
    이렇게 하는 이유는 변수를 Stack에 저장하지 않음으로써 레지스터 R0의 값이 바뀌지 않게 하기 위함이다.
 
다음 그림은 UsageFault handler의 Disassembly이다.
notion image
Fault handler가 실행되자마자 Stack frame의 Top을 가리키는 SP(MSP)값이 R0에 Copy되는 것이 바람직하다.
그러나 위의 Disassembly를 보면, MSP값을 R0로 copy하기 전에 Stack manipulation이 존재한다.
이 Stack manipulation을 Function의 Epilogue라고 부른다.
Epilogue는 모든 C function이 갖는 두 개의 Section 중에 하나이다. (Epilogue, Prologue)
Epilogue에서 Compiler가 Stack pointer를 manipulate하기 때문에, R0로 Copy되는 SP(MSP)값은 우리가 원하는 Stack frame의 Top의 주소가 아니다.
 
그래서 위과 같이 C function으로 UsageFault handler를 작성하면 안된다.
(Compiler가 Stack pointer를 manipulate하는 Epilogue를 생성하기 때문)
 
 

Configurable fault exception exercise-2


Naked functions

그러면 .c 파일에서 어떻게 Assembly function 또는 Assembly subroutine을 작성할 수 있을까?
→ 이러한 경우에 사용할 수 있는 것이 바로 Naked function이다.
notion image
GCC의 ‘naked’ attribute를 사용해야 한다.
  • 이 attribute는 Compiler에게 해당 function이 embedded assembly function이라고 알려준다.
    프로그래머는 function의 전체 body를 __asm statements를 이용한 Assembly code로 작성할 수 있다.
  • Compiler는 ‘attribute((naked))’가 붙은 function에 대해서는 Prologue와 Epilogue를 생성하지 않는다.
  • 오로지 Assembly instructions (__asm statements)를 작성하기 위해 naked function을 사용해야 한다. naked function안에 C 코드가 섞여 있으면 정상적으로 실행되지 않는다.
 
다음은 GCC의 naked attribute를 UsageFault handler에 사용한 코드이다.
notion image
notion image
notion image
UsageFault handler에 naked attribute를 부여해서 Body 전체를 Pure assembly code로 작성하면, C function의 Epilogue와 같은 Stack manipulation이 발생하지 않는다.
naked attributed UsageFault handler에서 SP(MSP)값을 R0로 Copy하고, C function인 UsageFault_Handler_C로 Branch한다.
 
그러면 다음 그림의 AAPCS에 따라,
notion image
R0가 Callee의 Argument register 1으로 사용되기 때문에, R0의 값이 UsageFault_Handler_C의 argument인 pBaseStackFrame에 저장된다.
 
결과적으로 pBaseStackFrame이 가지고 있는 주소가 Thread mode code의 Stack frame의 Top이다.
Top이 가리키는 위치에 있는 R0부터 R1, R2, … , PC, XPSR 값은 다음과 같이 나타난다.
notion image
프로그래머는 이 Stack frame을 분석함으로써 Thread mode code의 어디에서 어떤 것이 잘못되어, Fault가 발생했는지 알 수 있다.
 
이제 Stack frame을 분석해보자.
  • Usage Fault의 발생 원인 :
    Stack frame에 저장된 Thread mode code의 PC값을 보면, 우리가 앞에서 Undefined instruction 0xFFFF_FFFF를 저장한 주소인 0x2000_A000임을 바로 알 수 있다.
    따라서, 0x2000_A000 번지의 Undefined instruction인 0xFFFF_FFFF를 실행해서 Usage Fault가 발생했음을 알 수 있다.
  • Usage Fault를 발생시킨 명령 :
    LR에는 Exception handler가 종료된 이후에 Thread mode code에서 실행해야할 명령의 주소가 저장된다.
    따라서, LR에 저장된 주소의 바로 이전의 명령이 Usage Fault를 발생시켰다는 사실을 알 수 있다.
    LR에 저장된 주소는 0x0800_01B3인데, 이 주소를 List 파일에서 찾아보면 다음과 같다.
    • 0x0800_01B3 바로 이전 주소의 명령이 "some_address()"이고, 이 명령이 Usage Fault를 발생시켰음을 알 수 있다
      0x0800_01B3 바로 이전 주소의 명령이 "some_address()"이고, 이 명령이 Usage Fault를 발생시켰음을 알 수 있다
 
 

Analyzing stack frame


💡
강좌에서 이 제목과 위의 제목이 중복되는데, 이건 ‘Divide by zero’에 대한 Stack frame 분석 예제이다.
 
앞에서는 Undefined instruction을 실행하는 예제를 진행했는데, 이번에는 Divide by zero에 대한 예제를 살펴보자.
notion image
 
다음은 Thread mode code의 Stack frame으로 Fault가 발생한 원인과 위치를 찾는 코드이다.
notion image
notion image
notion image
‘Divide by zero’ trap은 default로 Disable 되어 있기 때문에, SCB의 CCR 레지스터에서 Enable해주어야 UsageFault exception이 발생한다.
 
다음 그림은 SCB의 CCR 레지스터이다.
notion image
notion image
Divide by zero trap을 Enable하려면, SCB의 CCR 레지스터의 Bit[4]를 Set해주어야 한다.
 
다음 그림은 위의 코드를 실행했을 때의 UsageFault Status register이다.
notion image
UsageFault의 발생 원인이 Divide by zero 임을 확인할 수 있다.
 
앞에서 언급했듯이, Thread mode code의 Stack frame의 Top은 R0이고, 그 다음부터 차례대로 R1, R2, … , PC, XPSR이 위치한다.
notion image
프로그래머는 이 Stack frame을 분석함으로써 Thread mode code의 어디에서 어떤 것이 잘못
되어, Fault가 발생했는지 알 수 있다.
 

(추가) Fault Anlayzer에서도 Thread mode code의 Stack frame을 볼 수 있다.

notion image
 
이제 Stack frame을 분석해보자.
  • Usage Fault의 발생 원인 :
    Stack frame에 저장된 Thread mode code의 PC값을 보면, 0x0800_02d2의 명령을 실행하고나서 UsageFault가 발생했음을 알 수 있다.
    해당 주소의 명령인 “return x/y;”는 두 수를 나누고 반환하는 것인데, 이 명령에서 UsageFault가 발생할 수 있는 원인은 ‘Divide by zero’가 유일하다.
    • notion image
 
  • (추가) SDIV 명령에 대해 간략하게 알아보자.
    SDIV는 ‘Signed divide’의 의미를 갖는데, “sdiv r3, r2, r3″는 r2를 r3로 나누고 결과값을 r3에 저장하라는 명령이다. Thread mode code의 Stack frame을 보면, r2는 0xa (=10)이고 r3는 0임을 확인할 수 있다. 그래서 “sdiv r3, r2, r3” 명령을 실행하면, ‘Divide by zero’ trap으로 인해 Usage Fault exception이 발생하는 것이다.
 
  • Usage Fault를 발생시킨 명령 :
    LR에는 Exception handler가 종료된 이후에 Thread mode code에서 실행해야할 명령의 주소가 저장된다.
    따라서, LR에 저장된 주소의 바로 이전의 명령이 Usage Fault를 발생시켰다는 사실을 알 수 있다.
    LR에 저장된 주소는 0x0800_01B7인데, 이 주소를 List 파일에서 찾아보면 다음과 같다.
    • 0x0800_01B7 바로 이전 주소의 명령이 “func_divide(10, 0);”이고, 이 명령이 Usage Fault를 발생시켰음을 알 수 있다.
      0x0800_01B7 바로 이전 주소의 명령이 “func_divide(10, 0);”이고, 이 명령이 Usage Fault를 발생시켰음을 알 수 있다.
 
정리하면, Fault가 발생했을 때, 프로그래머는 Error를 report하기 위해 다음과 같은 항목을 따라야 한다.
notion image