[LINUX] Read arch / arm / oprofile / common.c

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/oprofile/common.c

0. Caller drivers / oprofile / oprof.c

The arch-specific implementation is called drivers / oprofile / oprof.c

drivers/oprofile/oprof.c


static int __init oprofile_init(void)
{
    int err;

    /* always init architecture to setup backtrace support */
    timer_mode = 0;
    err = oprofile_arch_init(&oprofile_ops);
    if (!err) {
        if (!timer && !oprofilefs_register())
            return 0;
        oprofile_arch_exit();
    }

    /* setup timer mode: */
    timer_mode = 1;
    /* no nmi timer mode if oprofile.timer is set */
    if (timer || op_nmi_timer_init(&oprofile_ops)) {
        err = oprofile_timer_init(&oprofile_ops);
        if (err)
            return err;
    }

    return oprofilefs_register();
}


static void __exit oprofile_exit(void)
{
    oprofilefs_unregister();
    if (!timer_mode)
        oprofile_arch_exit();
}
  1. oprofile_arch_init()

In arm, backtrace for arm is registered.

arch/arm/oprofile/common.c


int __init oprofile_arch_init(struct oprofile_operations *ops)
{
    /* provide backtrace support also in timer mode: */
    ops->backtrace      = arm_backtrace;

    return oprofile_perf_init(ops);
}

1.1 arm_backtrace

arch/arm/oprofile/common.c



static void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
{
    struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;

    if (!user_mode(regs)) {
        struct stackframe frame;
        arm_get_current_stackframe(regs, &frame);
        walk_stackframe(&frame, report_trace, &depth);
        return;
    }

    while (depth-- && tail && !((unsigned long) tail & 3))
        tail = user_backtrace(tail);
}

2. If not user_mode () (walk_stackframe ())

2.1 arm_get_current_stackframe()

arm_get_current_stackframe () just writes the contents of regs back to frame, so yeah! Feeling like that.

arch/arm/include/asm/stacktrace.h


static __always_inline
void arm_get_current_stackframe(struct pt_regs *regs, struct stackframe *frame)
{
        frame->fp = frame_pointer(regs);
        frame->sp = regs->ARM_sp;
        frame->lr = regs->ARM_lr;
        frame->pc = regs->ARM_pc;
}

2.2 walk_stackframe()

This is the job of executing the function pointer fn while going down the frame.

arch/arm/kernel/stacktrace.c


void notrace walk_stackframe(struct stackframe *frame,
             int (*fn)(struct stackframe *, void *), void *data)
{
    while (1) {
        int ret;

        if (fn(frame, data))
            break;
        ret = unwind_frame(frame);
        if (ret < 0)
            break;
    }
}
EXPORT_SYMBOL(walk_stackframe);

2.3 unwind_frame()

Work to pull out fp, sp, pc from framepointer. If you refer to the stack sequentially with this ...

arch/arm/kernel/stacktrace.c


#if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND)
/*
 * Unwind the current stack frame and store the new register values in the
 * structure passed as argument. Unwinding is equivalent to a function return,
 * hence the new PC value rather than LR should be used for backtrace.
 *
 * With framepointer enabled, a simple function prologue looks like this:
 *  mov ip, sp
 *  stmdb   sp!, {fp, ip, lr, pc}
 *  sub fp, ip, #4
 *
 * A simple function epilogue looks like this:
 *  ldm sp, {fp, sp, pc}
 *
 * Note that with framepointer enabled, even the leaf functions have the same
 * prologue and epilogue, therefore we can ignore the LR value in this case.
 */

int notrace unwind_frame(struct stackframe *frame)
{
    unsigned long high, low;
    unsigned long fp = frame->fp;

    /* only go to a higher address on the stack */
    low = frame->sp;
    high = ALIGN(low, THREAD_SIZE);

    /* check current frame pointer is within bounds */
    if (fp < low + 12 || fp > high - 4)
        return -EINVAL;

    /* restore the registers from the stack frame */
    frame->fp = *(unsigned long *)(fp - 12);
    frame->sp = *(unsigned long *)(fp - 8);
    frame->pc = *(unsigned long *)(fp - 4);

    return 0;
}
#endif

2.4 report_trace()

The process of recording traces in oprofile.

arch/arm/oprofile/common.c


static int report_trace(struct stackframe *frame, void *d)
{
    unsigned int *depth = d;

    if (*depth) {
        oprofile_add_trace(frame->pc);
        (*depth)--;
    }

    return *depth == 0;
}

3. For user_mode () (user_backtrace ())

3.1 user_backtrace

In the upper layer, it is added to each stack and this user_backtrace () is called, so here it is a process for a single stack.

Then register in oprofile and finish!

arch/arm/oprofile/common.c


/*
 * The registers we're interested in are at the end of the variable
 * length saved register structure. The fp points at the end of this
 * structure so the address of this struct is:
 * (struct frame_tail *)(xxx->fp)-1
 */
struct frame_tail {
    struct frame_tail *fp;
    unsigned long sp;
    unsigned long lr;
} __attribute__((packed));

static struct frame_tail* user_backtrace(struct frame_tail *tail)
{
    struct frame_tail buftail[2];

    /* Also check accessibility of one struct frame_tail beyond */
    if (!access_ok(tail, sizeof(buftail)))
        return NULL;
    if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail)))
        return NULL;

    oprofile_add_trace(buftail[0].lr);

    /* frame pointers should strictly progress back up the stack
     * (towards higher addresses) */
    if (tail + 1 >= buftail[0].fp)
        return NULL;

    return buftail[0].fp-1;
}

  1. oprofile_arch_exit()

This just calls oprofile_perf_exit () in Oomoto.

arch/arm/oprofile/common.c


void oprofile_arch_exit(void)
{
    oprofile_perf_exit();
}

5. (Supplement) pt_regs

Speaking of which, I wonder if it is compatible with other CPUs, but pt_regs is divided for each arch ...

5.1 ARM pt_regs

arch/arm/include/asm/ptrace.h


struct pt_regs {
    unsigned long uregs[18];
};

5.1 ARM64 pt_regs

arch/arm64/include/asm/ptrace.h



/*
 * This struct defines the way the registers are stored on the stack during an
 * exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for
 * stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.
 */
struct pt_regs {
    union {
        struct user_pt_regs user_regs;
        struct {
            u64 regs[31];
            u64 sp;
            u64 pc;
            u64 pstate;
        };
    };
    u64 orig_x0;
#ifdef __AARCH64EB__
    u32 unused2;
    s32 syscallno;
#else
    s32 syscallno;
    u32 unused2;
#endif

    u64 orig_addr_limit;
    /* Only valid when ARM64_HAS_IRQ_PRIO_MASKING is enabled. */
    u64 pmr_save;
    u64 stackframe[2];
};

5.3 RISC-V pt_regs

arch/riscv/include/asm/ptrace.h


struct pt_regs {
    unsigned long epc;
    unsigned long ra;
    unsigned long sp;
    unsigned long gp;
    unsigned long tp;
    unsigned long t0;
    unsigned long t1;
    unsigned long t2;
    unsigned long s0;
    unsigned long s1;
    unsigned long a0;
    unsigned long a1;
    unsigned long a2;
    unsigned long a3;
    unsigned long a4;
    unsigned long a5;
    unsigned long a6;
    unsigned long a7;
    unsigned long s2;
    unsigned long s3;
    unsigned long s4;
    unsigned long s5;
    unsigned long s6;
    unsigned long s7;
    unsigned long s8;
    unsigned long s9;
    unsigned long s10;
    unsigned long s11;
    unsigned long t3;
    unsigned long t4;
    unsigned long t5;
    unsigned long t6;
    /* Supervisor/Machine CSRs */
    unsigned long status;
    unsigned long badaddr;
    unsigned long cause;
    /* a0 value before the syscall */
    unsigned long orig_a0;
};

That's it.


Originally, it is a part of the Linux Kernel source code, so it will be treated as GPLv2 (recognition that it should be).

https://www.kernel.org/doc/html/latest/index.html

Licensing documentation

The following describes the license of the Linux kernel source code (GPLv2), how to properly mark the license of individual files in the source tree, as well as links to the full license text.

https://www.kernel.org/doc/html/latest/process/license-rules.html#kernel-licensing

Recommended Posts

Read arch / arm / oprofile / common.c
Read a little more arch / arm / boot / compressed / Makefile
Also read the contents of arch / arm / kernel / swp_emulate.c