Infinite Stack Unwinding ARM

Duane Ellis duane@duaneellis.com
Sat Apr 8 05:00:00 GMT 2017


> On Apr 7, 2017, at 12:15 AM, Johannes Stoelp <Johannes.Stoelp@synopsys.com> wrote:
> 
> Yao Qi <qiyaoltc@gmail.com> writes:
> 
>> I don't expect prologue analyzer supporting SYSRegs and instruction MRS.  All the prologue analyzers in GDB are written in a way that understanding instructions according to the ABI/calling convention of each architecture and  compiler's behavior, so it should be able to parse the instruction in prologues complying to the ABI.  GDB prologue analyzer may not understand what does handwritten assembly do.
> 
> 
> Hi Yao,
> 
> I see what you are saying about the prologue analyzer and the ABI/calling conventions. 
> 
> I understand that gdb does not have to understand every hand written assembler routine, but I would like to emphasize that gdb in this particular case ends in an "infinite" loop printing the backtrace line by line (I put infinite in quotes because the loop is limited by the lower boundary of an integer). 
> I would expect gdb to be more defensive in this case and either try other unwinding techniques like backward unwinding (from bottom up) or just stop unwinding because of to less information.
> In my understanding situations like this can also occur when the stack gets corrupted. There I would also expect gdb to not end in an infinite loop since gdb is intended to analyze the non-expected situation.
> 
> One other question that came up by comparing the arm and the aarch64 analyzer:
>    * Is there a special reason/trick why the arm analyzer (gdb/arm-tdep.c:arm_analyze_prologue(...)) skips instructions that it doesn't recognize while the aarch64 analyzer (gdb/aarch64-tdep.c:aarch64_prologue_analyzer(...)) stops when the first unrecognized instruction is hit?
> 
> Best,
> Johannes 
> 

All of the below are techniques I have used in a custom port of GDB to a custom CPU.

Some might already exist, some are not, it is a balancing act between the software being debugged (helping the debugger) and the debugger trying the best it can.

It’s not uncommon for some bare metal RTOS to push ZEROS onto the stack, or for example on the ARM cores, to set the LR register to ZERO in the assembly handler, yes, this is an extra opcode or two - on every interrupt - in some cases, this is trivial an not a problem if extra opcodes are painful - it can be done in a compile time conditional in other cases the top 16 bytes or so of the interrupt stack is zeroed

Another thing unwinders do - is make sure the stack is still going on the same direction, and it is not a very large distance away from the previous stack, it’s a heuristic - for example 90% of code does not have huge arrays (i.e.: 10K bytes) on the stack, so - as you walk up the stack you make a decision - is distance(THIS_FRAME -vrs- NEXT_FRAME ) greater then MAXVALUE - abandon the backtrace.

Another thing the unwinder can do is inspect a few bytes before the return address, and make sure (1) the call site is within the application being debugged and not in outer space - and (2) the opcodes at that point are actually a subroutine call.

If the unwinder has access to current thread information and RTOS information - it might know the range of the current threads stack.

Again - along the idea of the target memory map - in an embedded system, RAM is in a fixed region… as is code (flash memory) if you know this (or it is set-able) you the unwinder can make other educated decisions.

-Duane.






More information about the Gdb mailing list