Getting the current PC value

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Getting the current PC value

1,701 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Fri Aug 03 04:48:09 MST 2012
Does anyone know how to get the PC value into a C variable, the MSR instruction doesn't allow the PC as an operand and I can see no obvious way to get it.

Why? I want my error handler to be able to print the address at which a run-time error occurred.
0 Kudos
18 Replies

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Tue Aug 07 01:59:42 MST 2012

Quote:
toggling pins can be quite entertaining.


There's something very satisfying about a flashing LED, possibly because that's the embedded equivalent of "Hello world" and seeing it means you are finally getting somewhere with your new hardware.


Quote:
I think you will eventually make your own try:catch C++ implementation soon..


Already done, but not quite as good :)

Thanks for the link, I'll need to delve deeper into the guts of the processor at some point and that looks pretty interesting.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Brutte on Mon Aug 06 07:31:23 MST 2012

Quote:
Not necessarily, a failed assertion can easily be non-fatal.

Typical implementation calls abort() so I guess the idea is it has to be fatal :) but you can make your own "sometimes non-fatal" version.


Quote:
for years I've just toggled pins and looked at them with a LA. Features  like semi-hosting have not been available on chips/environments I've  been using

Semihosting is a purely software thing. Nothing fancy really. AFAIK even PICs support it.

Anyway, toggling pins can be quite entertaining.


Quote:
Correct, I can just retask the assert macro to work differently according to the definition of RELEASE.

I think you will eventually make your own try:catch C++ implementation soon..


Quote:
All references I can find refer to the M3, I'm using the M0.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0432c/BABJHEIG.html
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Mon Aug 06 06:12:54 MST 2012

Quote:
It just terminates the execution because the flow or timing didn't meet the expectation. All passengers are already dead so you do not need to be worried about other resources

Not necessarily, a failed assertion can easily be non-fatal. For example in my situation a remote node may ask for the value of "input17" when I only have 16 inputs. This is a cock up on the part of the remote node but not my fault and certainly not an error that requires a shutdown.

In this case the error should probably be published to the network (on the assumption that there is a node somewhere that will record the event) then ignored.

If the error is internal then that's a different story, you probably have to assume that the ability to publish to a network is compromised, in this case I think the blinking LED/software serial option is the only thing to do. How that is read is another story, maybe a tech has to plug in a terminal.


Quote:
An asm("bkpt 0x00") adds exactly two bytes

But outside of a development environment doesn't actually tell you anything, OTOH the full standard assert tells you a lot but uses quite a lot of memory. I do get it, I'm just trying to reach a balance between using no resources and providing lots of information.


Quote:
Or are you using logic analyser as an alternative for semihosting?

I'm in the process of realising I have access to new tools, for years I've just toggled pins and looked at them with a LA. Features like semi-hosting have not been available on chips/environments I've been using so I still think in terms of toggling pins. That will pass.

There is still the issue of reading a fatal error code in the field, as I said maybe a terminal but an LA is also an option. Unfortunately no user is likely to have either so we're maybe back to the flashing LED.


Quote:
What about ARM infocenter and google?

All references I can find refer to the M3, I'm using the M0. Anyway I think I no longer need to know the PC after our discussion.


Quote:
AFAIK asserts are purely for development stage.
You should remove it in RELEASE because those consume CPU and rom.  Perhaps you need something different than assert. Like log or something  similar..

Correct, I can just retask the assert macro to work differently according to the definition of RELEASE. Out to semihost when debugging, out to network or flashing LED in the field according to the nature of the error.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Brutte on Mon Aug 06 04:02:29 MST 2012

Quote:
Each assert eats about 40-60 bytes

An asm("bkpt 0x00") adds exactly two bytes. An IDE will tell you when, what and where. You can provide just any implementation you like. From two bytes up to a nice BSOD with LPCXpresso logo.


Quote:
so as you say I'm modifying the assert code to be a little less verbose and just print the file/line and maybe an error code.

You didn't get the idea. An assert does not necessarily print out the strings. It is you who gives the implementation.
It just terminates the execution because the flow or timing didn't meet the expectation. All passengers are already dead so you do not need to be worried about other resources - the whole chip is yours.

__attribute__((noinline))
__attribute__((noreturn))
void assert_abort(char* file,char* fun, uint16_t line,...){

overtake_arbitrary_resources_and_shift_out_some_chars_out(SDIO); //or ethernet, or semihosting or blinking led if you like.

puts("Oops, assertion failed in ");
puts(file);
..
..
//if you really need that PC, push out lr 
..
puts(" and you are kindly  requested to fix that.\n");
shutdown();
}
#define assert(condition) if(condition){void(0)}else {assert_abort(__FILE__,__FUNC__,__LINE__)}//these are const strings in gcc, I hope.
This one will take exactly the same size per assert as your "less verbose".


Quote:
While developing of course I have the semihost serial connection

Then where did you take that logic analyser from? Semihosting while developing and a logic analyser in the field? Or are you using logic analyser as an alternative for semihosting?


Quote:
I grep'd "DCRSR" in both my project folder and the Xpresso install folder.

What about ARM infocenter and google?

Quote:
I assume then that this is only available with an M3 and that's why I could find no mention of it in the LPC1227 UM.    

ARMv6 also have SCB (and DCRSR). Actually the register looks the same as in ARMv7.
Perhaps you should start with simpler PCSR:
my_program_counter=DWT->PCSR;
Make sure DWT is enabled at first, because it is disabled by default. Perhaps LPCXpresso does it implicitly because it constantly displays DWT->CYCCNT in a register view so it must have DWT on.


Quote:
in the field these devices will be connected to a control network and I  think it makes sense to have the production version of the assert code  packetise the same string and send it to the network.

AFAIK asserts are purely for development stage.
You should remove it in RELEASE because those consume CPU and rom. Perhaps you need something different than assert. Like log or something similar..
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Mon Aug 06 02:16:17 MST 2012
I've had a good look at assert and remember now why I haven't used it in the past, the standard version chews too much program memory. Each assert eats about 40-60 bytes depending on the file name and the assertion text.

That said I like the idea of being more in line with the standard technique and with these chips maybe a few k used for that is a good trade off, so as you say I'm modifying the assert code to be a little less verbose and just print the file/line and maybe an error code.


Quote:
Why necessarily a blinking  LED, a.k.a. logic analyser

Because I come from a blinking  LED/logic analyser background :) and am having a hard time getting used to all the great debugging tools now available.

While developing of course I have the semihost serial connection, in the field these devices will be connected to a control network and I think it makes sense to have the production version of the assert code packetise the same string and send it to the network. 

I grep'd "DCRSR" in both my project folder and the Xpresso install folder. The string only appears in two core_cm3.h files, I assume then that this is only available with an M3 and that's why I could find no mention of it in the LPC1227 UM.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Brutte on Sun Aug 05 07:34:49 MST 2012

Quote:
This is a single-chip embedded system with no standard IO, I need a  simple 1-2 words that can be serially pushed out a spare pin and read  with a logic analyser.

An assert.h is a standard feature. Works great, from attinys to x86.
It is you who provide the implementation of the core of the assert().
I sometimes insert asm("bkpt 0x00"), sometimes asm("nop") and nothing stops you from providing a more/less advanced BSOD.

I wonder why cannot you use OCD for asserts? Why necessarily a blinking  LED, a.k.a. logic analyser, when (most of) ARMs have full-blown features  on-board?

But, if you really need that IO, unless it is tn4-ish "single-chip embedded system", I suggest a software driven UART tx on an arbitrary pin.


Quote:
If I have the address I can look at the map file to find the culprit.

Sure, you can find the file, function, the line and the cause. But that is what assert was made for. Finally, after 10 minutes of searching *.lst you will get to the conclusion:


Quote: Target, complaining
>>Oops, assertion failed in MyProject/src/foo.c in function boo(),  at line 17, because somehow pinNumber >= MAX_PINS and you are kindly  requested to fix that. PC is 0xef45c03 if that can help.


but looks like reinventing the wheel.


Quote:
I can't find any mention of using the SCB for this in the UM or anywhere  else and a Google of SCB_DCRSR only brings up a single page


Quote: ARM ARMv7-M
Debug Core Register Selector Register (DCRSR)
The DCRSR write-only register generates a handshake to the core to transfer the selected register to/from the DCRDR. The DHCSR S_REGRDY bit is cleared when the DCRSR is written, and remains clear until the core transaction completes. This register is only accessible from Debug state.
(...)
[4:0] REGSEL:
00000: R0
00001: R1

01100: R12
01101: the current SP
01110: LR
01111: DebugReturnAddress()
10000: xPSR / Flags, Execution Number, and state information
10001: MSP (Main SP)
10010: PSP (Process SP)
10100: CONTROL bits [31:24], FAULTMASK bits [23:16],      
BASEPRI bits [15:8], and PRIMASK bits [7:0]



Search CMSIS for "DCRSR" and I am sure you will eventually find it:) It belongs to System Control Block (SCB).

A Debug Return Address should give you a PC value.

As an alternative, you can also try reading DWT_PCSR.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Sat Aug 04 15:26:12 MST 2012

Quote:
What about assert.h?
Besides, what is the use of the PC in HLL? You need to pinpoint the bug in the source, not in the hex.

This is a single-chip embedded system with no standard IO, I need a simple 1-2 words that can be serially pushed out a spare pin and read with a logic analyser.

If I have the address I can look at the map file to find the culprit.

I haven't used assert for years, maybe it can be made to do this I'm not sure but certainly a nice human-readable error message is no good to this application.


Quote:
you can rw any register through SCB_DCRSR (0xe000edf4).    

I can't find any mention of using the SCB for this in the UM or anywhere else and a Google of SCB_DCRSR only brings up a single page, this one :) Can you elaborate?
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Brutte on Sat Aug 04 10:28:02 MST 2012

Quote:
this is a system that records errors raised by functions, for example

What about assert.h?
Besides, what is the use of the PC in HLL? You need to pinpoint the bug in the source, not in the hex.


Quote: Target, complaining:
>>Oops, assertion failed in MyProject/src/foo.c in function boo(), at line 17, because somehow pinNumber >= MAX_PINS and you are kindly requested to fix that.



Besides, you can rw any register through SCB_DCRSR (0xe000edf4).
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Sat Aug 04 07:08:30 MST 2012
I think we are at crossed purposes here, I am not writing an exception handler, this is a system that records errors raised by functions, for example

someFunc(int pinNumber) {
   if (pinNumber >= MAX_PINS)
      SYS_ERROR (ERR_BAD_PIN_NUMBER | pinNumber);
  ...
}
So it's purely a software device to help detect and track dodgy parameters to functions, NULL pointers etc.

In the above example SYS_ERROR is a macro that records the address of the code then calls a raiseError() function that handles it.

As far as I can tell this is working and I've been using it for a few days now.

That said, now that I have another look at my get_PC() function I can't for the life of me see how it does work, and yet it appears to.

It's too late to investigate now, that's a tomorrow job.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by gbm on Sat Aug 04 06:08:37 MST 2012
Graynomad:
Read some documents on Cortex M exception handling first.

During any exception, Cortex M pushes some registers on the stack, then jumps to the handler. The handler is an ordinary C function with standard stack frame and prolog/epilog stuff. Since exception entry is NOT an ordinary function call (athough it looks like that to C compiler), the microcode first saves some registers and the real return address on the stack, then it loads LR with special value (not a real return address), which is later recognized by Cortex microcode during exception return and handled in a special way.

In your dump, the values starting with 4 zeros are good candidates for exception PC. Look at the memory map and check which routines are located at these addresses.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Sat Aug 04 04:33:37 MST 2012
Yes but I did a memory dump of that address.

I did try your example before, suitably modified (where does print_hex32 come from?)

    uint32_t stack_frame[1];
    for (int i = 0; i < 20; i++) {
        printf ("%08x\n", stack_frame);
    }
It shows

000006ad
00000000
10001fe0
00000003
10001fe8
[B]0000075b[/B]
10001f68
0000269d
00000000
00000159
40048000
1fff0acf
Where the 75B is the return address, but is this offset on the stack constant? And given that on entry the function's the only stack use is

push {r7, lr}
How come the return address is 5 locations up the stack?


Quote:
Ignore the LR - it's value is fixed and points nowhere - it's a special value interpreted by Cortex microcode.    

That's not what I'm seeing. So far all functions I've looked at push the LR (and other regs) on entry

push {r7, lr}

and return with a

pop {r7, pc}

which effectively puts the LR into the PC.

And my previous

push lr
pop reg

function appears to be working which indicates that LR is reliable.

According to the UM


Quote:
It stores the return information for subroutines, function calls, and exceptions.

Which also indicates that this approach is good.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by gbm on Sat Aug 04 03:25:00 MST 2012
Yeah, printing the address of handler's stack frame is probably not what you want to achieve. Why don't you try the code I provided? You can put the print statement inside FOR loop and print some 20 locations - one of them will surely contain the exception PC. Ignore the LR - it's value is fixed and points nowhere - it's a special value interpreted by Cortex microcode.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Fri Aug 03 16:05:28 MST 2012
I've tried that and a few variations like

    uint8 stack_frame;
    printf("%08x\n", &stack_frame);


But although the value for stack_frame looks reasonable a dump of RAM at that point does not make much sense at all. Certainly I cannot see anything like the address of the calling function.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Fri Aug 03 14:51:17 MST 2012
I'll give that a try. It does mean having to decode the stack frame but that could be useful as well.

With a bit of luck the LR is always at the same offset.

EDIT: I don't have print_hex32(), I notice this with just about [B]every [/B]example I find on the web, I don't have the same functions/macros/whatever. Is this because they are for Kiel or something?
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by gbm on Fri Aug 03 08:52:58 MST 2012
void the_handler(void)
{
uint32_t stack_frame[1];
print_hex32(stack_frame[PC_LOC]);
}


Guessing the value of PC_LOC constant is left to the implementer. Should be something between 2 and 20 ;).
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Fri Aug 03 07:02:47 MST 2012
As is often the case explaining the problem causes one to find the solution. So far I have this function called by a macro

__attribute__( ( always_inline ) )  __INLINE uint32 _get_PC(void) {

  register uint32 result;

  __ASM volatile ("push    {lr}");
  __ASM volatile ("pop    {%0}"  : "=r" (result) );

  return(result);
}
and the declaration

extern __attribute__( ( always_inline ) )  __INLINE uint32     _get_PC();
in a header file and the following macro

#define SYS_ERROR(_err)    __sys_pc = _get_PC(); sysErrRaise ((uint32)_err);
The good news is that it works so I thought it's worth posting for posterity.

The bad news is that I'm getting warnings every time the macro is used,

warning: inline function '_get_PC' declared but never defined
It is defined AFAIK, is this something to do with it being inline?

EDIT: It looks like the extern declaration should just be

extern uint32     _get_PC(void);
So until further notice or someone has a better idea this seems to be a working method to get the PC.
0 Kudos

1,508 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by graynomad on Fri Aug 03 06:15:54 MST 2012
Yes and no.

It's a macro that calls a function, so maybe the macro can get the PC and pass that to the function. I think the problem with having a function do the job is finding out the offset of the PC on the stack. But that could be figured out.

There does seem to be a fundamental problem though, in the past I would have done something like this

CALL X   ; PC goes onto the stack
X:
POP R0   ; R0 now has the PC


However the ARM doesn't seem to have a CALL/JSR/whatever instruction, it has BL, branch with link. From what I can see this puts the return address into the link reg and does a jump.

Here's an example from some code that calls a function called setup()

init();
bl 0x688 <init>


The LR is then used to do the return. So with only a single LR how the heck do nested functions work. The answer is in the function preamble.

00000688: init+0                 push {r7, lr}


So the LR is pushed and presumably popped just before the return.

I think I'm starting to answer my own question here, maybe

push lr
pop  r0


is the way to do it.

I'm well open to suggestions though.
0 Kudos

1,509 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Polux rsv on Fri Aug 03 05:40:46 MST 2012
Is the PC reg pushed on the stack before calling the error handler ? It could probably be retreived from there using some assembly instructions.

Angelo
0 Kudos