root/arch/arm/lib/backtrace-clang.S
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 *  linux/arch/arm/lib/backtrace-clang.S
 *
 *  Copyright (C) 2019 Nathan Huckleberry
 *
 */
#include <linux/kern_levels.h>
#include <linux/linkage.h>
#include <asm/assembler.h>
                .text

/* fp is 0 or stack frame */

#define frame   r4
#define sv_fp   r5
#define sv_pc   r6
#define mask    r7
#define sv_lr   r8
#define loglvl  r9

ENTRY(c_backtrace)

#if !defined(CONFIG_FRAME_POINTER) || !defined(CONFIG_PRINTK)
                ret     lr
ENDPROC(c_backtrace)
#else


/*
 * Clang does not store pc or sp in function prologues so we don't know exactly
 * where the function starts.
 *
 * We can treat the current frame's lr as the saved pc and the preceding
 * frame's lr as the current frame's lr, but we can't trace the most recent
 * call.  Inserting a false stack frame allows us to reference the function
 * called last in the stacktrace.
 *
 * If the call instruction was a bl we can look at the callers branch
 * instruction to calculate the saved pc.  We can recover the pc in most cases,
 * but in cases such as calling function pointers we cannot. In this case,
 * default to using the lr. This will be some address in the function, but will
 * not be the function start.
 *
 * Unfortunately due to the stack frame layout we can't dump r0 - r3, but these
 * are less frequently saved.
 *
 * Stack frame layout:
 *              <larger addresses>
 *              saved lr
 *      frame=> saved fp
 *              optionally saved caller registers (r4 - r10)
 *              optionally saved arguments (r0 - r3)
 *              <top of stack frame>
 *              <smaller addresses>
 *
 * Functions start with the following code sequence:
 * corrected pc =>  stmfd sp!, {..., fp, lr}
 *              add fp, sp, #x
 *              stmfd sp!, {r0 - r3} (optional)
 *
 *
 *
 *
 *
 *
 * The diagram below shows an example stack setup for dump_stack.
 *
 * The frame for c_backtrace has pointers to the code of dump_stack. This is
 * why the frame of c_backtrace is used to for the pc calculation of
 * dump_stack. This is why we must move back a frame to print dump_stack.
 *
 * The stored locals for dump_stack are in dump_stack's frame. This means that
 * to fully print dump_stack's frame we need both the frame for dump_stack (for
 * locals) and the frame that was called by dump_stack (for pc).
 *
 * To print locals we must know where the function start is. If we read the
 * function prologue opcodes we can determine which variables are stored in the
 * stack frame.
 *
 * To find the function start of dump_stack we can look at the stored LR of
 * show_stack. It points at the instruction directly after the bl dump_stack.
 * We can then read the offset from the bl opcode to determine where the branch
 * takes us.  The address calculated must be the start of dump_stack.
 *
 * c_backtrace frame           dump_stack:
 * {[LR]    }  ============|   ...
 * {[FP]    }  =======|    |   bl c_backtrace
 *                    |    |=> ...
 * {[R4-R10]}         |
 * {[R0-R3] }         |        show_stack:
 * dump_stack frame   |        ...
 * {[LR]    } =============|   bl dump_stack
 * {[FP]    } <=======|    |=> ...
 * {[R4-R10]}
 * {[R0-R3] }
 */

                stmfd   sp!, {r4 - r9, fp, lr}  @ Save an extra register
                                                @ to ensure 8 byte alignment
                movs    frame, r0               @ if frame pointer is zero
                beq     no_frame                @ we have no stack frames
                mov     loglvl, r2
                tst     r1, #0x10               @ 26 or 32-bit mode?
                moveq   mask, #0xfc000003
                movne   mask, #0                @ mask for 32-bit

/*
 * Switches the current frame to be the frame for dump_stack.
 */
                add     frame, sp, #24          @ switch to false frame
for_each_frame: tst     frame, mask             @ Check for address exceptions
                bne     no_frame

/*
 * sv_fp is the stack frame with the locals for the current considered
 * function.
 *
 * sv_pc is the saved lr frame the frame above. This is a pointer to a code
 * address within the current considered function, but it is not the function
 * start. This value gets updated to be the function start later if it is
 * possible.
 */
1001:           ldr     sv_pc, [frame, #4]      @ get saved 'pc'
1002:           ldr     sv_fp, [frame, #0]      @ get saved fp

                teq     sv_fp, mask             @ make sure next frame exists
                beq     no_frame

/*
 * sv_lr is the lr from the function that called the current function. This is
 * a pointer to a code address in the current function's caller.  sv_lr-4 is
 * the instruction used to call the current function.
 *
 * This sv_lr can be used to calculate the function start if the function was
 * called using a bl instruction. If the function start can be recovered sv_pc
 * is overwritten with the function start.
 *
 * If the current function was called using a function pointer we cannot
 * recover the function start and instead continue with sv_pc as an arbitrary
 * value within the current function. If this is the case we cannot print
 * registers for the current function, but the stacktrace is still printed
 * properly.
 */
1003:           ldr     sv_lr, [sv_fp, #4]      @ get saved lr from next frame

1004:           ldr     r0, [sv_lr, #-4]        @ get call instruction
                ldr     r3, .Lopcode+4
                and     r2, r3, r0              @ is this a bl call
                teq     r2, r3
                bne     finished_setup          @ give up if it's not
                and     r0, #0xffffff           @ get call offset 24-bit int
                lsl     r0, r0, #8              @ sign extend offset
                asr     r0, r0, #8
                ldr     sv_pc, [sv_fp, #4]      @ get lr address
                add     sv_pc, sv_pc, #-4       @ get call instruction address
                add     sv_pc, sv_pc, #8        @ take care of prefetch
                add     sv_pc, sv_pc, r0, lsl #2@ find function start

finished_setup:

                bic     sv_pc, sv_pc, mask      @ mask PC/LR for the mode

/*
 * Print the function (sv_pc) and where it was called from (sv_lr).
 */
                mov     r0, sv_pc

                mov     r1, sv_lr
                mov     r2, frame
                bic     r1, r1, mask            @ mask PC/LR for the mode
                mov     r3, loglvl
                bl      dump_backtrace_entry

/*
 * Test if the function start is a stmfd instruction to determine which
 * registers were stored in the function prologue.
 *
 * If we could not recover the sv_pc because we were called through a function
 * pointer the comparison will fail and no registers will print. Unwinding will
 * continue as if there had been no registers stored in this frame.
 */
1005:           ldr     r1, [sv_pc, #0]         @ if stmfd sp!, {..., fp, lr}
                ldr     r3, .Lopcode            @ instruction exists,
                teq     r3, r1, lsr #11
                ldr     r0, [frame]             @ locals are stored in
                                                @ the preceding frame
                subeq   r0, r0, #4
                mov     r2, loglvl
                bleq    dump_backtrace_stm      @ dump saved registers

/*
 * If we are out of frames or if the next frame is invalid.
 */
                teq     sv_fp, #0               @ zero saved fp means
                beq     no_frame                @ no further frames

                cmp     sv_fp, frame            @ next frame must be
                mov     frame, sv_fp            @ above the current frame
#ifdef CONFIG_IRQSTACKS
                @
                @ Kernel stacks may be discontiguous in memory. If the next
                @ frame is below the previous frame, accept it as long as it
                @ lives in kernel memory.
                @
                cmpls   sv_fp, #PAGE_OFFSET
#endif
                bhi     for_each_frame

1006:           adr     r0, .Lbad
                mov     r1, loglvl
                mov     r2, frame
                bl      _printk
no_frame:       ldmfd   sp!, {r4 - r9, fp, pc}
ENDPROC(c_backtrace)
                .pushsection __ex_table,"a"
                .align  3
                .long   1001b, 1006b
                .long   1002b, 1006b
                .long   1003b, 1006b
                .long   1004b, finished_setup
                .long   1005b, 1006b
                .popsection

.Lbad:          .asciz  "%sBacktrace aborted due to bad frame pointer <%p>\n"
                .align
.Lopcode:       .word   0xe92d4800 >> 11        @ stmfd sp!, {... fp, lr}
                .word   0x0b000000              @ bl if these bits are set

#endif