本帖最后由 creep 于 2017-12-26 08:44 编辑 & ~" u7 C% t. p; S4 V1 A% J* e
6 f- _; J; V5 _. ?; ]7 t) k
& Q" r1 f$ |% d5 r" M# @. `: b+ ~
原文:http://blog.feabhas.com/2013/02 ... cortex-m3cortex-m4/ C9 `. _% y# B+ @; }( h
9 T5 v- S& k1 l% e/ j3 eDivide by zero errorGiven the following C function
& N7 L/ l: r5 m2 e$ R
- int div(int lho, int rho)# v3 p: m" P I4 ?& L7 ^
- {
& h, |/ ?3 Y5 L8 U: W - return lho/rho;9 v* \5 H1 z+ \. R
- }
复制代码 3 J) _5 U8 f. c# B. Q q, T0 h1 M2 Z
called from main with these arguments - int main(void)
$ ~ W$ }# ^2 j F3 |' s - {9 D6 C) Y! r0 v
- int a = 10;
& u. _. ?: ?! {' o T0 I. U - int b = 0;6 i- C# j o, t" P& m U
- int c;
* j" U3 C' M q4 ^+ ~6 m - c = div(a, b);
' ^' g, P* m+ p$ X0 @ - // other code; \2 q; [) m* \6 [
- }
复制代码
1 ~; X$ x) S, G7 L# @You would expect a hardware “divide-by-zero” (div0) error. Possibly surprisingly, by default the Cortex-M3 will not report the error but return zero. Configuration and Control Register (CCR)
& C! D" V2 _/ b& u E+ V( u8 t; t
' `7 W' ]' ]9 H" K
To enable hardware reporting of div0 errors we need to configure the CCR. The CCR is part of the Cortex-M’s System Control Block (SCB) and controls entry trapping of divide by zero and unaligned accesses among other things. The CCR bit assignment for div0 is:
& ?) f# X4 W+ M$ V ?) u4 o
[4] | DIV_0_TRP | Enables faulting or halting when the processor executes an SDIVor UDIV instruction with a divisor of 0:0 = do not trap divide by 01 = trap divide by 0. When this bit is set to 0, a divide by zero returns a quotient of 0. | ; T. K: Q9 t/ u; c" a5 w2 c
So to enable DIV_0_TRP, we can use CMSIS definitions for the SCB and CCR, as in:
0 j: q8 @+ v1 y % ?. i6 I* x/ L. _" A0 r
If we now build and run the project you will need to stop execution as it will appear to run forever. When execution is stopped you’ll find debugger stopped in the file startup_ARMCM3.s in the CMSIS default Hard Fault exception handler:
! g# w, U, W- h) r& ^
3 r+ c& P- A! Z2 _* j Override The Default Hard Fault_HandlerAs all the exceptions handlers are build with “Weak” linkagein CMSIS, it is very easy to create your own Hard Fault handler. Simply define a function with the name “HardFault_Handler”, as in: ; m2 b: v9 @: B9 Y: q
- void HardFault_Handler(void)
/ B8 O3 Q2 B, F- e- z# n0 Y - {
" a6 l2 o$ {4 k0 I' q - while(1);
8 U2 q6 c0 h9 E - }
复制代码
1 h T/ f: ]$ \" {/ k; }
5 y3 {' F) \5 [; N- G. T6 NIf we now build, run and then stop the project, we’ll find the debugger will be looping in our new handler rather than the CMSIS default one (alternatively we could put a breakpoint at the while(1) line in the debugger).
# U2 H: h7 `; k7 V& k% U+ T/ J
- m- [# ]- w/ B* m' r- }
Rather than having to enter breakpoints via your IDE, I like to force the processor to enter debug state automatically if a certain instruction is reached (a sort of debug based assert). Inserting the BKPT (breakpoint) ARM instruction in our code will cause the processor to enter debug state. The immediate following the opcode normally doesn’t matter (but always check) except it shouldn’t be 0xAB (which is used for semihosting). 8 t) E% k- h1 A0 y
- #include "ARMCM3.h"
+ `; T7 K3 }# e1 k" ? - void HardFault_Handler(void)
3 H/ {, A& f$ G1 U8 l - {
, r0 Z* j& z6 a - __ASM volatile("BKPT #01");
. L3 |% X& F; q0 N( A - while(1); H2 \" _& @( ?; |" i/ T
- }
复制代码 % Z1 [5 s# x9 q0 M. Z
: ?$ ~. z1 M& h- C j, j% @7 i% R# L2 x7 o$ S; o( s
If we now build and run, the program execution should break automatically at the BKPT instruction.
; I! }( o4 N% A2 N9 E# y
( r3 ?- d4 F7 X% ]. \2 v Error Message OutputThe next step in developing the fault handler is the ability to report the fault. One option is, of course, to use stdio (stderr) and semihosting. However, as the support for semihosting can vary from compiler to compiler, I prefer to use Instrumented Trace Macrocell (ITM) utilizing the CMSIS wrapper function ITM_SendChar, e.g. 0 N) r4 P$ I/ s! M% d5 B8 A2 z
- void printErrorMsg(const char * errMsg)
4 _+ T: E$ U' l - {
: L# K# W! Y' |+ Z4 z! v! t - while(*errMsg != ''){
7 T+ J- E; m& d, J7 A - ITM_SendChar(*errMsg);4 k; ^$ ~6 N+ x3 \, @
- ++errMsg;/ k- {' a% E6 m* N9 I6 C
- }- Q! ~ q6 w4 \8 u
- }
复制代码 $ P( K# g2 z( ?+ O
0 `$ j- P: _- Z
8 o( R" k$ R2 x: ^
6 a: c5 ?" ^+ a. F8 |6 \' U$ L: u
# W2 @; Q1 Z, X+ x4 {7 a# F
Fault ReportingNow that we have a framework for the Hard Fault handler, we can start reporting on the actual fault details. Within the Cortex-M3’s System Control Block (SCB) is theHardFault Status Register (SCB->HFSR). Luckily again for use, CMSIS has defined symbols allowing us to access these register: . X6 v- o# }$ F& Y5 b+ V/ V
- void HardFault_Handler(void)
& }8 m1 i: V( T+ y - {: a8 b& [" w( B W( y
- static char msg[80];
( M& Z" [4 z, u8 ?; g- S& R6 @ - printErrorMsg("In Hard Fault Handler\n");
: d) q8 u! z6 C1 @0 z - sprintf(msg, "SCB->HFSR = 0x%08x\n", SCB->HFSR);% L5 I* l) t v4 `: z" z( H. k
- printErrorMsg(msg);! p1 k' _3 K. \% F# Z8 g3 E W
- __ASM volatile("BKPT #01");7 r3 _* W+ |) b# o9 d
- while(1);% ?5 \' ~" I0 j; ] w8 \7 f
- }
复制代码
9 c' S7 `6 Z/ E! U7 D2 A7 M
. i6 ^' x% B( x6 b" |0 C* b- |& T. n& q4 _
Building and running the application should now result in the following output: 6 r0 H+ ~: H. U5 N# N( r6 x
5 j4 e6 ]1 k( }) V9 y |' P: j4 K
By examining the HFSR bit configuration, we can see that the FORCED bit is set.
- M! M) v/ P! o& }* E. S1 i
When this bit is set to 1, the HardFault handler must read the other fault status registers to find the cause of the fault. Configurable Fault Status Register (SCB->CFSR)A forced hard fault may be caused by a bus fault, a memory fault, or as in our case, a usage fault. For brevity, here I am only going to focus on the Usage Fault and cover the Bus and Memory faults in a later posting (as these have additional registers to access for details).
/ s4 ^ g" a; F; [* ~. F3 i. m% S
So given what we know to date, our basic Fault Handler:
a9 K0 \( Z* Q+ o
- checks if the FORCED bit is set ( if ((SCB->HFSR & (1 << 30)) != 0) )
- prints out the contents of the CFSR! e' s7 v& Y H& P4 {$ ~
$ r8 o. Y1 P! T# R# u& v( f
+ ]# K) b7 W4 A) `+ R v# s
- void HardFault_Handler(void)) C# ~6 B( e5 q
- {
4 d7 n* [' x* \: @/ ?# R7 | - static char msg[80];
t' x ~* v' u2 d& c, h - printErrorMsg("In Hard Fault Handler\n");$ o8 o( d* m( `; {. ^
- sprintf(msg, "SCB->HFSR = 0x%08x\n", SCB->HFSR);- e2 n# P- @* p! D/ {. P1 k# B
- printErrorMsg(msg);9 w( a. Q6 ]7 l2 q# _1 V" R: a1 b
- if ((SCB->HFSR & (1 << 30)) != 0) {
$ R7 M% j: N7 L - printErrorMsg("Forced Hard Fault\n");
+ e; V+ B5 y0 \( c6 G - sprintf(msg, "SCB->CFSR = 0x%08x\n", SCB->CFSR );
: Z4 V* `) ?# S. `% L; c - printErrorMsg(msg);
8 G4 j# z! k8 p' E" S1 \; A - }1 i, v* N, z# p! }# v
- __ASM volatile("BKPT #01");7 b2 a4 U! R$ I0 ?
- while(1);, j4 G% D. |5 W3 C9 G. I
- }
复制代码
5 q$ k6 x. f2 b' I4 K/ E: f! i. k* J! {; G$ h8 P
Running the application should now result in the following output: The output indicated that bit 25 of the UsageFault Status Register (UFSR)part of the CFSR is set. UsageFault Status RegisterThe bit configuration of the UFSR is shown below, and unsurprisingly the outpur shows that bit 9 (DIVBYZERO) is set. We can now extend the HardFault handler to mask the top half of the CFSR, and if not zero then further report on those flags, as in: - void HardFault_Handler(void)
! b4 j7 y9 F6 g. v - {
% B) K$ g* _0 e- ~ ^% e# j - static char msg[80];
{( I6 l1 j- w - printErrorMsg("In Hard Fault Handler\n");
2 I) C# P0 t1 E/ S - sprintf(msg, "SCB->HFSR = 0x%08x\n", SCB->HFSR);
) M0 B& G4 K: V - printErrorMsg(msg);6 i) v1 a3 A4 a* ]# a& N
- if ((SCB->HFSR & (1 << 30)) != 0) {' _2 b! V! ~# N N
- printErrorMsg("Forced Hard Fault\n");
1 Q, _8 T$ W8 [" a# q6 W$ b - sprintf(msg, "SCB->CFSR = 0x%08x\n", SCB->CFSR );
1 Y" Z. T- J- Q6 F - printErrorMsg(msg);
; [& x9 U0 N3 l6 f6 H! L - if((SCB->CFSR & 0xFFFF0000) != 0) {
m" B0 ^; B: _: [6 y I - printUsageErrorMsg(SCB->CFSR);
3 s5 y* a X( }1 { Z - }5 D3 D# m) d6 O
- }
. v: {: ^( A3 O3 i# b8 S - __ASM volatile("BKPT #01");
" n. Y- m( O5 {' \- @5 p - while(1);
! q; y% o1 [: X - }
& ~6 K) ~5 Z( S* j F) ~; X" Z7 u - void printUsageErrorMsg(uint32_t CFSRValue)- ?5 s0 d9 d+ f7 f F7 c
- {7 n5 U1 j8 ?3 u, Y4 t
- printErrorMsg("Usage fault: ");
2 {$ o& a4 e% G- ^, v: J8 A - CFSRValue >>= 16; // right shift to lsb
6 ]! y6 }8 P9 |/ p \; r - if((CFSRValue & (1 << 9)) != 0) {5 U- O* T: H4 l- M7 S
- printErrorMsg("Divide by zero\n");( L9 X& g8 W5 n4 a1 ?" e3 ]
- }' w. f6 R+ x7 ?) T+ z# g
- }
复制代码
4 G! c& q% [2 E. `$ G. zA run should now result in the following output: " n/ U0 I# e) A7 }
0 A# Z9 ^* x" Q; P4 o: [ Register dumpOne final thing we can do as part of any fault handler is to dump out known register contents as they were at the time of the exception. One really useful feature of the Cortex-M architecture is that a core set of registers are automatically stacked (by the hardware) as part of the exception handling mechanism. The set of stacked registers is shown below: 2 z, t, ~) H, t m- ~
7 b" O# ?' i Q& h3 h: J; T+ P
Using this knowledge in conjunction with AAPCS we can get access to these register values. First we modify our original HardFault handler by:
9 w O% W( T6 R4 v
- modifying it’s name to “Hard_Fault_Handler”
; ~7 ?4 x3 K. @% F5 u! F
- adding a parameter declared as an array of 32–bit unsigned integers.
" r/ Q# V" v+ f( }7 a4 T% P& x9 \ void Hard_Fault_Handler(uint32_t stack[])" | i/ }6 ]2 `% |9 n: A3 x
Based on AAPCS rules, we know that the parameter label (stack) will map onto register r0. We now implement the actual HardFault_Handler. This function simply copies the current Main Stack Pointer (MSP) into r0 and then branches to our Hard_Fault_Handler (this is based on ARM/Keil syntax): - __asm void HardFault_Handler(void) { MRS r0, MSP B __cpp(Hard_Fault_Handler) }
复制代码
0 j$ w$ k' v2 n7 C Y; xFinally we implement a function to dump the stack values based on their relative offset, e.g. - enum { r0, r1, r2, r3, r12, lr, pc, psr};
& m0 y' T9 l, e; L6 \: H7 K7 N
4 s; W* |1 a" k. ]2 p) x; `- void stackDump(uint32_t stack[])
9 S: [" W/ Y. C+ L - {. a7 {& R8 ?9 \7 s' F
- static char msg[80];3 ~; Q0 |' T7 t- s
- sprintf(msg, "r0 = 0x%08x\n", stack[r0]); printErrorMsg(msg);( z) k- t3 q1 h* x) k! @
- sprintf(msg, "r1 = 0x%08x\n", stack[r1]); printErrorMsg(msg);
4 S( X$ ~' {* a - sprintf(msg, "r2 = 0x%08x\n", stack[r2]); printErrorMsg(msg);- Y/ o8 Y$ U3 h: v, O
- sprintf(msg, "r3 = 0x%08x\n", stack[r3]); printErrorMsg(msg);$ k4 |1 a2 g; S- h3 r5 y4 \% q
- sprintf(msg, "r12 = 0x%08x\n", stack[r12]); printErrorMsg(msg);
& h' x6 x$ D8 [: H Z - sprintf(msg, "lr = 0x%08x\n", stack[lr]); printErrorMsg(msg);
; O# ^# u3 d' |+ F( k! } - sprintf(msg, "pc = 0x%08x\n", stack[pc]); printErrorMsg(msg);% E5 K i6 f! S, v* [9 g- a* t b
- sprintf(msg, "psr = 0x%08x\n", stack[psr]); printErrorMsg(msg);
3 A& d- B- D* l6 q" i* m - }
复制代码 ; _0 @4 a8 a" @
This function can then be called from the Fault handler, passing through the stack argument. Running the program should result is the following output: . Q2 t' R- h/ q% k- O; G! m: a
; O+ }* O/ U4 l# n/ _
Examining the output, we can see that the program counter (pc) is reported as being the value 0x00000272, giving us the opcode generating the fault. If we disassemble the image using the command: fromelf -c CM3_Fault_Handler.axf —output listing.txt
8 ]* q2 u1 [8 O
By trawling through the listing (listing.txt) we can see the SDIV instruction at offending line (note also r2 contains 10 and r1 the offending 0).
1 H1 m. f$ P. A8 P* a7 @
- .text/ p( x1 z! R r. b
- div+ `' _2 T, e3 l6 ]/ M
- 0x00000270: 4602 MOV r2,r03 E4 q1 ^0 d7 ~% K7 `9 E' I3 I1 ^
- 0x00000272: fb92f0f1 SDIV r0,r2,r1. \ i" M9 H3 u( P
- 0x00000276: 4770 BX lr0 }& @/ y9 l7 B d1 Y
- .text
复制代码
) o4 K' i. j. h0 J1 A: R4 V+ p
7 U0 H9 Y4 w8 g' `4 {/ _ p! B9 I3 h* m/ V0 \
Finally, if you’re going to use the privilege/non–privilege model, you’ll need to modify the HardFault_Handler to detect whether the exception happened in Thread mode or Handler mode. This can be achieved by checking bit 3 of the HardFault_Handler’s Link Register (lr) value. Bit 3 determines whether on return from the exception, the Main Stack Pointer (MSP) or Process Stack Pointer (PSP) is used.4 n% N( v+ \. S( y
' j6 H+ v7 w; s. d7 d- __asm void HardFault_Handler(void)( C1 a) }4 Q& V! t6 U
- {7 s/ V) J/ f" K( X$ `' j
- TST lr, #4 // Test for MSP or PSP
3 s0 Q: `( V g/ P( z: O- O - ITE EQ
8 A; O4 b% A( k! g% u1 p* d4 H; X - MRSEQ r0, MSP
( _& L( d% q" P* _3 |/ t. A - MRSNE r0, PSP
$ i0 ]6 Q9 k1 l* n - B __cpp(Hard_Fault_Handler)
6 p, |1 K! G& F Q, |8 Q - }
复制代码
- w* r0 a% \; s. @1 _$ h6 p p; BIn a later post I shall develop specific handler for all three faults. Notes: : q2 \* k7 S0 N8 z/ f+ E
- The initial model for fault handling can be found in Joseph Yiu’s excellent book “The Definitive Guide to the ARM Cortex-M3”
- The code shown was built using the ARM/Keil MDK-ARM Version 4.60 development environment (a 32Kb size limited evaluation is available from the Keil website)
- The code, deliberately, has not been refactored to remove the hard-coded (magic) values.
- Code available on GitHub at git://github.com/feabhas/CM3_Fault_Handler.git
" I4 L& n& x9 } ; J" i: |2 Q- d7 i3 y6 R# l
; u) z. q- ?/ d0 j% j9 I) K
0 H3 O0 `0 R q6 Z; z. i6 j
|