EmbDev.net

Forum: ARM programming with GCC/GNU tools Cortex-M3 Fault Handlers


von jrmymllr j. (jrmymllr)


Rate this post
useful
not useful
I'm a little confused about writing a fault handler. I'd like to write 
one to track down the occasional crash and display it on my LCD. I'd 
guess that most, if not all the faults I see are because of invalid 
memory address access (as in, trying to r/w a RAM address that doesn't 
exist), so I believe that would be a Bus Fault. I've found there is a 
BFAR register I could use, or by looking at the stack by way of a 
register, PSP or MSP, although I don't know which one would be valid, or 
what the disadvantages of each are.  It seems like there's some 
conflicting info out there regarding this.

What I have really had trouble with is how to get a register into a C 
variable. I've searched the web looking for answer, but nothing that 
seems clear.  I though I could use

register int x asm("r1")

to get r1 into the variable "x", for example, but I'm not sure if this 
will always work. What is a good way to do this in GCC? I've used my 
debugger before to view the offending address, but I can't rely on that 
(because random crashes never happen when it's connected).

Is there a set of C fault handler functions somewhere that do a good job 
of this?

von 900ss (900ss)


Rate this post
useful
not useful
Hi Jerry,

this is helpful for you I guess:
https://my.st.com/public/STe2ecommunities/mcu/Lists/ARM%20CortexM3%20STM32/Hard%20Fault%20and%20stack%20pushing

The guy who give the hint there has written a very good book, may be you 
know it already: The definitive guide to the ARM Cortex-M3

I can suggest this book. It is really good.

von Marcus H. (mharnisch)


Rate this post
useful
not useful
Jerry Milner wrote:
> so I believe that would be a Bus Fault.

Look at the fault status registers.

> I've found there is a BFAR register I could use, or by looking at the
> stack by way of a register, PSP or MSP, although I don't know which
> one would be valid, or what the disadvantages of each are.

What information are you looking for on the stack? The return address? 
You will want to find out which stack pointer was used before entering 
the exception (information in link register). Then look for the return 
address at the appropriate offset.

> What I have really had trouble with is how to get a register into a C
> variable.

A decent compiler will do a good job allocating registers for variables.

>  I though I could use
>
> register int x asm("r1")
>
> to get r1 into the variable "x", for example, but I'm not sure if this
> will always work.

What this does is a hardwired mapping of the variable x to the register 
r1.
While this is very useful for special purpose registers (e.g. PSP, MSP), 
I don't recommend this for general use unless you can explain why 
exactly you need this.

Regards
Marcus
http://www.doulos.com/arm/

von 900ss (900ss)


Rate this post
useful
not useful
Marcus Harnisch wrote:
> What information are you looking for on the stack? The return address?
> You will want to find out which stack pointer was used before entering
> the exception (information in link register). Then look for the return
> address at the appropriate offset.

The return address and all the other status informations are found on 
the stack at a exception.

The third posting of my link above gives an example (in 'C') how to get 
these informations from the stack.

von jrmymllr j. (jrmymllr)


Rate this post
useful
not useful
Marcus, I wanted to map a register to a C variable (whichever way is 
best) so I could have the fault information in a C variable, since often 
asm is given to extract this information, then left in "r1" for example.

900ss, thanks for the link.  This looks like something I would like to 
try, though they don't say it's for GCC.  I'm going to see if I can get 
it to work.

von Marcus H. (mharnisch)


Rate this post
useful
not useful
Hi Jerry

Jerry Milner wrote:
> Marcus, I wanted to map a register to a C variable (whichever way is
> best) so I could have the fault information in a C variable, since often
> asm is given to extract this information, then left in "r1" for example.

I don't think the mapping using named register variables is what you 
are looking for.

Locate fault status and address via appropriate memory mapped NVIC 
registers (as you apparently did already) and determine the address of 
the faulting instruction by retrieving the return address from stack and 
subtracting 2. The code example posted by 900ss illustrates this 
approach.

--
Marcus
http://www.doulos.com/arm/

von 900ss (900ss)


Rate this post
useful
not useful
Hi Jerry,

have the code converted to GNU (Codesourcery Toolchain).

The assembler code:
1
.type _hard_fault_handler_asm, %function
2
3
_hard_fault_handler_asm:
4
  TST LR, #4
5
  ITE EQ
6
  MRSEQ R0, MSP
7
  MRSNE R0, PSP
8
  B hard_fault_handler_c

The C code (modified a little):
1
// hard fault handler in C,
2
// with stack frame location as input parameter
3
void hard_fault_handler_c(unsigned int * hardfault_args)
4
{
5
unsigned int stacked_r0;
6
unsigned int stacked_r1;
7
unsigned int stacked_r2;
8
unsigned int stacked_r3;
9
unsigned int stacked_r12;
10
unsigned int stacked_lr;
11
unsigned int stacked_pc;
12
unsigned int stacked_psr;
13
unsigned int rBFAR;
14
unsigned int rCFSR;
15
unsigned int rHFSR;
16
unsigned int rDFSR;
17
unsigned int rAFSR;
18
19
stacked_r0 = ((unsigned long) hardfault_args[0]);
20
stacked_r1 = ((unsigned long) hardfault_args[1]);
21
stacked_r2 = ((unsigned long) hardfault_args[2]);
22
stacked_r3 = ((unsigned long) hardfault_args[3]);
23
24
stacked_r12 = ((unsigned long) hardfault_args[4]);
25
stacked_lr = ((unsigned long) hardfault_args[5]);
26
stacked_pc = ((unsigned long) hardfault_args[6]);
27
stacked_psr = ((unsigned long) hardfault_args[7]);
28
29
rBFAR = (*((volatile unsigned long *)(0xE000ED38)));
30
rCFSR = (*((volatile unsigned long *)(0xE000ED28)));
31
rHFSR = (*((volatile unsigned long *)(0xE000ED2C)));
32
rDFSR = (*((volatile unsigned long *)(0xE000ED30)));
33
rAFSR = (*((volatile unsigned long *)(0xE000ED3C)));
34
35
printf ("[Hard fault handler]\n");
36
printf ("R0 = %x\n", stacked_r0);
37
printf ("R1 = %x\n", stacked_r1);
38
printf ("R2 = %x\n", stacked_r2);
39
printf ("R3 = %x\n", stacked_r3);
40
printf ("R12 = %x\n", stacked_r12);
41
printf ("LR = %x\n", stacked_lr);
42
printf ("PC = %x\n", stacked_pc);
43
printf ("PSR = %x\n", stacked_psr);
44
printf ("BFAR = %x\n", rBFAR);
45
printf ("CFSR = %x\n", rCFSR);
46
printf ("HFSR = %x\n", rHFSR);
47
printf ("DFSR = %x\n", rDFSR);
48
printf ("AFSR = %x\n", rAFSR);
49
50
while(1);
51
52
return;
53
}

von jrmymllr j. (jrmymllr)


Rate this post
useful
not useful
Thanks again 900ss.  I'm not too good at doing asm on ARM, especially 
the GCC syntax.

von Ulrich P. (uprinz)


Rate this post
useful
not useful
Sorry for warming it up again,

but I tried those code above but I cannot reliably execute this code.
Even the Hardfault is set into the vector table correctly it is not 
called.

I reduced the code to just call my default interrupt function but it 
works only with other interrupts, not the ARM special ones.

But I have some speciality:
My system has only the reset vector and the stack pointer in the flash. 
At boot time the complete vector table is created in RAM and the 
SCB->VTOR pointer is set to this table. This is because the operating 
system allowes dynamic registration of interrupts.

Is it possible, that the Hardfault is always called from the flash 
vector table?
Do I have to setup any other StackPointer area besides the standard one?

Best regards
Ulrich

von Marcus H. (mharnisch)


Rate this post
useful
not useful
Ulrich P. wrote:
> My system has only the reset vector and the stack pointer in the flash.
> At boot time the complete vector table is created in RAM and the
> SCB->VTOR pointer is set to this table.

The minimal vector table must contain initial SP, as well as Reset-, 
NMI- and HardFault Handlers. Having just two entries isn't safe.

> Is it possible, that the Hardfault is always called from the flash
> vector table?

Only if the fault occurs before the VTOR change. Perhaps also, if the 
vector table alignment is wrong -- don't know, never tried this.

Regards
Marcus

von Ulrich P. (uprinz)


Rate this post
useful
not useful
Hi Marcus,

Everything works fine with the RAM vector table. The Exception Vectors 
are fetched from there. Adn there is no exception before the VTOR is 
changed.

The problem is, that my system is running in Thread mode and per default 
privileged is set. I configure
1
SCB->SHCSR |= (SCB_SHCSR_MEMFAULTENA|SCB_SHCSR_BUSFAULTENA|SCB_SHCSR_USGFAULTENA);
but if I check via debug output the fault handlers are disabled, the 
register shows 0x00020000 instead of 0x00070000.
Now I added another setup of SHCSR register somwhere later in the code 
and now the hardfaults are intercepted and interpreted. But I chased any 
access to SHCSR and IntEnable/IntDisable and so on, there isn't any line 
of code modifying this set of registers.

I cam to this as the self made handler works fine if there is a running 
debug session via JTAG active. If no JTAG no hardfault handler. Only by 
writing SHCSR two times

Very funny.

von Ulrich P. (uprinz)


Rate this post
useful
not useful
You mentioned about _set_CONTROL(0x00) doesn't set Privileged if you are 
not privileged.
Question 1: How to become privileged?
Question 2: The system runs already in privileged mode.
1
CONTROL is 0x00000000
2
MSP is     0x200023b4
3
PSP is     0x20001a00
4
SHCSR is   0x00020000
If I then give a debug command to my device it again sets SHCSR and it 
works fine:
1
CONTROL is 0x00000000
2
MSP is     0x200023b4
3
PSP is     0x20001a00
4
SHCSR is   0x00070000
If I then force an unaligned access my own hardfault handler takes over 
control.

von Marcus H. (mharnisch)


Rate this post
useful
not useful
Ulrich P. wrote:
> You mentioned about _set_CONTROL(0x00) doesn't set Privileged if you are
> not privileged.
> Question 1: How to become privileged?

By executing an exception. Often an SVC is used for this purpose.

> Question 2: The system runs already in privileged mode.

Sure. Your code example showed that something about privileges in C-M3 
wasn't fully understood (no offense), otherwise you wouldn't have tried 
to gain privileges. What exactly the issue is, I don't know from the 
bits you posted.

--
Marcus

von Ulrich P. (uprinz)


Rate this post
useful
not useful
Hi Marcus,

it is a bit complicated to post the code as it is a complete operating 
system. Even it is open source and BSD licensed it would break the 
upload limits :)
And it is a bit difficult to pick out the sources that rely to that 
fault handler.

But you're right, I am new to Cortex, or at least to the part of the 
handlers. The other 98% of drivers for any hardware work fine that I 
ported already.

So, the cortex is working in thread mode from start onwards and it 
states that it is set privileged. The documentation tells that I need to 
be in exactly that state to setup these bits for the handlers. And now 
you tell me that I have to be in such a handler to switch the handler...
Ok, that was not mentioned in the documentation I read, but how so ever 
I'll try that next time I have a short break.
I already had the idea to isolate OS and Application a bit by using the 
different modes. May be this is a good training :)

Thanks for the hint!

Best regards
Ulrich

von Marcus H. (mharnisch)


Rate this post
useful
not useful
Ulrich P. wrote:
> So, the cortex is working in thread mode from start onwards and it
> states that it is set privileged.

OK.

> The documentation tells that I need to be in exactly that state to
> setup these bits for the handlers.

Yes.

> And now you tell me that I have to be in such a handler to switch the
> handler...

I never said that. All I said was that you had to be in privileged mode. 
The fact that your code tried to gain privileges by clearing CONTROL[0] 
suggested that you weren't quite sure about that. Never mind that this 
attempt to elevate your privileges wouldn't have worked anyway. One way 
to transition from unprivileged to privileged operation in a program is 
an SVC exception.

Since your CONTROL register value seems to be 0, I would also have a 
look at the stack pointer value. In your code snippet you are setting 
PSP, while the CONTROL[1] bit, when 0, configures Thread mode to use 
MSP.

--
Marcus

von Ulrich P. (uprinz)


Rate this post
useful
not useful
Hi!

Sorry, I misunderstood you.

Ok, the debug output showed that it is running privileged. I just double 
checked, that it is running by setting the mode that was already set.

The MSP/PSP I check again later.

The funny thing is, what I wrote some posts above:
The system runs in privileged mode from reset onwards. I set the flags 
for the handlers somwhere in the init part of the idle task. My main is 
called and all my threads are running fine. I use my commander to check 
the values of the register and see I am still running in P-mode. But I 
see that the flags are not set 7 but stay as 2.
With the same commander, without calling _set_CONTROL() I just write 7 
to the same register and by calling the same commander I read it out and 
it is 7.

I then added the setting of 7 to the main() and it doesn't work too. 
Only if I set it in the commander it is set correctly and it keeps its 
state until the next reset.
Ah, and it works without any problem, even I set 7 in the init() or 
idle_init() if the debugger is connected and runs the code.

So I have to find out why I can set the handler bits to 7 if the 
debugger controls the chip or why I can set it 'manually' in the 
commander during runtime but not in any other function that is called at 
runtime. And that in a software the does not change the modes of the CPU 
at all.

Best regards
Ulrich

Please log in before posting. Registration is free and takes only a minute.
Existing account
Do you have a Google/GoogleMail account? No registration required!
Log in with Google account
No account? Register here.