LoginSignup
0
1

More than 3 years have passed since last update.

arch/arm/oprofile/common.c を読んでいく

Last updated at Posted at 2020-05-21

0. 呼び出し元 drivers/oprofile/oprof.c

arch固有実装を呼び出しているのは、 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()

armでは、arm用のbacktraceが登録される。

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. user_mode()ではない場合 ( walk_stackframe() )

2.1 arm_get_current_stackframe()

arm_get_current_stackframe() は、regsの中身をframeに書き戻すだけなので、うん、わかった!という感じ。

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()

これは、frameを下りながら、関数ポインタfnを実行するお仕事ですね。

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()

framepointerから、fp,sp,pcを抜くお仕事。これで順次スタックを参照すると……。

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()

oprofileにがしがしtraceを記録していく処理。

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. user_mode()の場合 ( user_backtrace () )

3.1 user_backtrace

上位レイヤーで、各stackに追加、このuser_backtrace()を呼び出すとなっているので、ここでは単独stackに対する処理。

そして、oprofileに登録して終了!

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;
}

4. oprofile_arch_exit()

これは大本の、oprofile_perf_exit()を呼び出すだけ。

arch/arm/oprofile/common.c
void oprofile_arch_exit(void)
{
    oprofile_perf_exit();
}

5.(補足)pt_regs

そういえば、他CPU対応とかどうしているのかなー、と見たら、pt_regsが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;
};

以上になります。


もともと、Linux Kernelのソースコードの一部なので、GPLv2扱いになる(はずの認識)。

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1