push {}

General discussions about working with the Astrobe IDE and programming ARM Cortex-M3, M4 and M7 microcontrollers.
Post Reply
gray
Posts: 52
Joined: Tue Feb 12, 2019 2:59 am

push {}

Post by gray » Tue May 14, 2019 10:19 am

I am dabbling with getting a stack trace upon run-time errors. For this, I obviously need to understand the structure of the stack when entering the error handler (SVC call).

A related questions about interrupt handlers in general.

Code: Select all

PROCEDURE runtimeErrorHandler[0];
  BEGIN
.   632  E92D0000H  push     { }
.   636  EA4F0C0DH  mov      fp,sp
    (* ... *)
.   796  EA4F0D0CH  mov      sp,fp
.   800  E8BD9FF0H  pop      { r4 r5 r6 r7 r8 r9 r10 r11 fp pc }
I scoured the ARM docs, but didn't find any info on the 'push {}' instruction there in the prologue, ie. with an empty register list.

From the 'pop' instruction in the epilogue, I infer that this must do 'push { r4 r5 r6 r7 r8 r9 r10 r11 fp lr }', ie. the registers not pushed by the processor itself during the stacking phase of the exeception entry. (The processor will itself pop the other registers it has pused upon exit from the exception handler).

My stack trace works (but only indicating the module names and addresses, as I don't have the information about the procedures corresponding to the addresses), so I think my view of the stack structure, and how it is being built upon exception entry, is not far off. But I'd like to be sure.

Can you please enlighten me what 'push {}' means and does?

cfbsoftware
Site Admin
Posts: 405
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: push {}

Post by cfbsoftware » Tue May 14, 2019 11:34 am

The disassembler generates the listing 'on the fly' as each instruction is generated. There are a few situations (this is one of them) where the instruction that is created initially is just a placeholder which is patched retrospectively. Push only needs to save the registers that are used but that is not known until the procedure is ended. At that time the Push {} instruction is modified to include all of the registers that you see in the subsequent Pop.

gray
Posts: 52
Joined: Tue Feb 12, 2019 2:59 am

Re: push {}

Post by gray » Thu May 16, 2019 3:13 am

I see. Thanks for the clarification. May I place a feature request here? If yes, I'd like an extended ResData (".ref") that not only gives the name for a module's address range, but also the procedure names (with address range) within the module.

cfbsoftware
Site Admin
Posts: 405
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: push {}

Post by cfbsoftware » Thu May 16, 2019 11:20 am

Thank you for your request. It is already one of the features on our list of prospective enhancements.

In the meantime, if you require this information to assist with diagnosing runtime errors, refer to Section 4.5 Diagnosing System Exceptions in the Oberon for Cortex-M documents. Given the following:
  • The module name and exception address that is reported in a runtime error message
  • The map file which is created when you linked the application
  • The disassembler listing of the module
a formula is supplied for you to use with this information to locate the offending line of source code.

gray
Posts: 52
Joined: Tue Feb 12, 2019 2:59 am

Re: push {}

Post by gray » Fri May 17, 2019 7:01 am

Good hint, thanks. For the module with the run-time error (I only consider run-time error exceptions here, not hardware fault exceptions), the compiler/linker embeds the source line number right in the code at the return address, so that case is covered. The modules in the stack trace need another approach -- here the formula that works for hardware fault exceptions should work.

Here's the relevant code for the stack trace:

Code: Select all

  TYPE
    ModuleName* = ARRAY 16 OF CHAR;
    TracePoint* = RECORD
      moduleName*: ModuleName;
      address*: INTEGER
    END;
    Trace* = RECORD
      tp*: ARRAY TraceDepth + 1 OF TracePoint;
      count*: INTEGER
    END;

  PROCEDURE stackTrace(fpAddr: INTEGER; VAR trace: Trace);
    (* called from the run-time error handler *)
    VAR
      lr: INTEGER;
  BEGIN
    trace.count := 0;
    SYSTEM.GET(fpAddr, fpAddr);
    REPEAT
      SYSTEM.GET(fpAddr + 4, lr);
      GetModuleName(lr - 4, trace.tp[trace.count].moduleName);
      trace.tp[trace.count].address := lr - 4;
      INC(trace.count);
      SYSTEM.GET(fpAddr, fpAddr);
    UNTIL (fpAddr >= LinkOptions.StackStart - 8) OR (trace.count = TraceDepth);
    (* handling of the case when the stack trace would be deeper than TraceDepth *)
  END stackTrace;

  PROCEDURE runtimeErrorHandler[0];
    (* called via SVC *)
  BEGIN
    (* getting info on the offending module, similar to Traps.mod *)
    stackTrace(SYSTEM.FP + 56, errorDesc.trace);
    errorHandler(errorDesc)
  END runtimeErrorHandler;
I retrieve the link register values on the stack and subtract 4 to find the calling procedure. For a test case, I get the following, using 'ASSERT(FALSE, Error.Trace)', with Error.Trace defined as 100 and the message below message (the annotations as comments are not part of the real output):

Code: Select all

code:    100
message: stack trace
module:  DisplayTask
line:    43
address: 08005298H
trace: (* format: 'module name': 'address' from link register minus 4 *)
  Task1: 08005AE5H     (* test task *)
  Tasks: 080046B1H     (* task scheduler *)
  M: 08005C35H         (* main module *)
Applying the formula to find the calling procedure, without the '-8' correction, I get these offsets:

Code: Select all

  Task1:  37
  Tasks:  1873
  M:      197
Now, let's check.

Code: Select all

  (* case 1: from Task1.mod *)
    DisplayTask.SetNumItem(0, r);
.    28  F2400B00H  mov      r11,0
.    32  F85CAC04H  ldr      r10,[fp,-4]
.    36  04010000H  bl       Proc #1
37 looks fine, even though I don't fully understand yet why it's not 36.

Code: Select all

  (* case 2: from Tasks.mod *)
    ct.taskState;
.  1856  F8DFB000H  ldr      r11,[pc+offset]  <Global:4>
.  1860  F8DBB000H  ldr      r11,[r11]
.  1864  F8DBB000H  ldr      r11,[r11]
.  1868  EA4F000BH  mov      r0,r11
.  1872  3001H      add      r0,1
.  1874  4780H      blx      r0
1873 looks off in this case. 'ct.taskState' refers to a procedure variable. I realise that subtracting 4 from the link register value causes this.

Code: Select all

  (* case 3: from M.mod *)
  Tasks.Go
.   196  040F0027H  bl       Proc #15
Same as with case 1, Task1.mod.

Apologies for going on that long, but I thought I'd present the details to finally ask my questions.

1) Do you see any flaws or hidden issues in creating the stack trace as above?
2) Why exactly is the offset off by 1 for cases 1 and 3?
3) Can I detect case 2, and subtract only 2 from the link register value?

Depending on the structure and contents of an extended ".ref" ResData, I probably do not even need to do the subtractions from the link registers if the goal is to identify the procedure name in the source code.

Thanks!

cfbsoftware
Site Admin
Posts: 405
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: push {}

Post by cfbsoftware » Sun May 19, 2019 5:03 am

Nice work!

Read up on how the Frame Pointer is used by the Oberon compiler to check if there are any flaws or hidden issues. This is detailed in An Oberon Compiler for the ARM Processor which you can download from the Oberon page on Niklaus Wirth's website.

The odd addresses are explained in Section 3.1.4 Link Register R14 of Joseph Yiu's book The Definitive Guide to the ARM Cortex-M3.
Despite the fact that bit 0 of the PC is always 0 (because instructions are word aligned or half word aligned), the LR bit 0 is readable and writable. This is because in the Thumb instruction set, bit 0 is often used to indicate ARM/Thumb states. To allow the Thumb-2 program for the Cortex-M3 to work with other ARM processors that support the Thumb-2 technology, this least significant bit (LSB) is writable and readable.
If you are planning more work at this level of detail I recommend that you get a copy of the book.

When bit 0 is set to 1 the instruction is interpreted as a Thumb-2 instruction. Hence the requirement to call add r0,1 before blx r0 in your second example. These are both 16-bit instructions but you can interpret the combined pair as a single 32-bit instruction for this exercise.

gray
Posts: 52
Joined: Tue Feb 12, 2019 2:59 am

Re: push {}

Post by gray » Tue May 21, 2019 11:13 am

FWIW, here's an improved version of the stack trace procedure.

Code: Select all

  PROCEDURE stackTrace(fpAddr: INTEGER; VAR trace: Trace);
    VAR
      lr, stopAt: INTEGER;
  BEGIN
    trace.count := 0;
    stopAt := LinkOptions.StackStart - 8;
    SYSTEM.GET(fpAddr, fpAddr);
    WHILE (fpAddr < stopAt) & (trace.count # TraceDepth) DO
      SYSTEM.GET(fpAddr + 4, lr);
      GetModuleName(lr, trace.tp[trace.count].moduleName);
      trace.tp[trace.count].address := lr;
      INC(trace.count);
      SYSTEM.GET(fpAddr, fpAddr);
    END;
    (* handle stack depth of more then TraceDepth levels *)
  END stackTrace;
This version also works correctly for the "base" code execution path in the main program, ie. without calling procedures. The former version collects garbage in this case. Sorry.

gray
Posts: 52
Joined: Tue Feb 12, 2019 2:59 am

Re: push {}

Post by gray » Tue May 28, 2019 1:13 pm

Another version of the stack trace procedure. The above one stops at the address of the last FP-entry on the stack, this one stops with the contents of this FP-entry.

I ran into the problem of where to stop the stack trace when I implemented simple coroutines (as basis for a cooperative task scheduler), as each coroutine has its own stack. Hence, 'LinkOptions.StackStart - 8' could not be used for these stacks, obviously. A first attempt simply changed a corresponding variable upon transfer from one coroutine to the next, which works, but is clumsy, as it encumbers each context switch with an operation only needed for error reporting.

Now I mark the end of the FP-call-chain up the stack by setting the first FP-entry to point to itself. It's the FP-entry that gets created by 'push {fp lr}' right at the start of the main program. Practically, I set this FP stack entry to point to itself in my Main.mod module. If coroutines are used, their stacks are prepared accordingly by the 'Init' procedure for the coroutines.

Code: Select all

PROCEDURE stackTrace(fp1: INTEGER; VAR trace: Trace);
    VAR
      lr, fp2: INTEGER;
  BEGIN
    trace.count := 0;
    SYSTEM.GET(fp1, fp1);
    SYSTEM.GET(fp1, fp2);
    WHILE (fp1 # fp2) & (trace.count # TraceDepth) DO
      SYSTEM.GET(fp1 + 4, lr);
      GetModuleName(lr, trace.tp[trace.count].moduleName);
      trace.tp[trace.count].address := lr;
      INC(trace.count);
      fp1 := fp2;
      SYSTEM.GET(fp1, fp2);
    END;
    (* handle stack depth of more then TraceDepth levels *)
  END stackTrace;
For more context, this is the definition of the coroutine module. As said, simple concepts. The stack is passed as array upon initialisation of the coroutine. As this is the basis for a scheduler for embedded programs, it is assumed that the coroutines never terminate. The scheduler on top of the coroutines works, but needs more testing, and probably some API clean-up.

Code: Select all

  TYPE
    PROC* = PROCEDURE;
    Coroutine* = POINTER TO CoroutineDesc;
    CoroutineDesc* = RECORD END;

  PROCEDURE Init*(cor: Coroutine; proc: PROC; stack: ARRAY OF BYTE);
  PROCEDURE Transfer*(from, to: Coroutine);
This loosely follows the definitions (not implementations) of Modula-2's NEWPROCESS and TRANSFER procedures provided by SYSTEM. No IOTRANSFER-equivalent though. In fact, my coroutines stay out of the handling of interrupts. There be dragons. Interrupts are handled in modules on a lower architectural level.

cfbsoftware
Site Admin
Posts: 405
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: push {}

Post by cfbsoftware » Tue Oct 08, 2019 10:22 am

gray wrote:
Thu May 16, 2019 3:13 am
I'd like an extended ResData (".ref") that not only gives the name for a module's address range, but also the procedure names (with address range) within the module.
We have implemented a full application disassembler to be included in the Professional Edition in the next release. Each procedure and its address is identified as shown in this screenshot:
Capture1.jpg
Disassemble Application
Capture1.jpg (80.11 KiB) Viewed 94 times
In addition we have implemented a stack callback trace similar to your suggestion with optional source code line number information included. For example if you try to execute the I2C temperature example without a sensor connected:
Capture2.JPG
Stack Callback Trace
Capture2.JPG (16.23 KiB) Viewed 94 times

gray
Posts: 52
Joined: Tue Feb 12, 2019 2:59 am

Re: push {}

Post by gray » Tue Oct 15, 2019 7:00 am

Oh, the disassembler looks nice and very, very useful. Do you have an ETA for the new Astrobe version?

Question regarding the stack trace: so there's no way of getting the procedure names? Address and line number is useful, so I am not complaining, but also having the procedure name right in the output would be super useful. :)

Post Reply