Using Watchpoints register to monitor momery access on MIPS

1. 一般MIPS CPU 都有一对 WatchPoints register:



可以设置 WatchLo 指定一个监测的内存地址,一旦有对该地址的操作,即触发一个 watch 异常

下面的设置监测 0x8000000004190000 的内存写操作:

    unsigned long cat_wlo1 = 0x8000000004190001;
    unsigned long cat_whi1 = 0x40000000;

    __write_64bit_c0_register($18, 1, cat_wlo1);
    __write_32bit_c0_register($19, 1, cat_whi1);


上面的设置 WatchHi[G] = 1,则表明不管其 ASID 为何,只要有写该地址的操作即触发 watch 异常。

如果 WatchHi[G] = 0,则要检查执行写该地址的操作的进程 ASID 是否与 WatchHi[ASID]吻合,如果不吻合是不触发异常的。

注意有些 MIPS CPU 实现是 WatchLo 中写虚拟地址,有些是写物理地址。


2. 内核中默认的 watch 异常处理函数是 do_watch() 位于 arch/mips/kernel/traps.c

asmlinkage void do_watch(struct pt_regs *regs)
{
    dump_tlb_all();
    show_regs(regs);
    panic("Caught WATCH exception - probably caused by stack overflow.");
}

很明显,在 dump 出所有 TLB,显示出保存的寄存器后,直接就panic了。

一般情况下,我们只想监测某个内存区被修改,不想让其 panic,而想让 kernel 继续执行,则需修改为:

asmlinkage void do_watch(struct pt_regs *regs)
{
    show_regs(regs);
    compute_return_epc(regs);
}

在显示完必要的寄存器后(主要是epc,看是那个地址的指令的操作,然后反汇编一下内核,就可以定位到具体的函数中去了),将 epc 的值修改为引发异常的下一条指令处,这个地方要特别留意,不能简单的直接将 reg->cp0_epc += 4, 因为引发异常的指令有可能在branch delay slot 中,这个有个复杂的处理,位于:

[arch/mips/kernel/branch.h]

extern int __compute_return_epc(struct pt_regs *regs);

static inline int compute_return_epc(struct pt_regs *regs)
{
    if (!delay_slot(regs)) {
        regs->cp0_epc += 4;
        return 0;
    }

    return __compute_return_epc(regs);
}

static inline int delay_slot(struct pt_regs *regs)
{
    return regs->cp0_cause & CAUSEF_BD;   #如果异常指令位于branch delay slot 中,CPU 会设置
                                                                                  CP0_Status 的 BD 位为1
}

主要处理函数 __compute_return_epc 位于:
[arch/mips/kernel/branch.c]

int __compute_return_epc(struct pt_regs *regs)
{
    unsigned int *addr, bit, fcr31, dspcontrol;
    long epc;
    union mips_instruction insn;

    epc = regs->cp0_epc;
    if (epc & 3)
        goto unaligned;

    addr = (unsigned int *) epc;
    if (__get_user(insn.word, addr)) {
        force_sig(SIGSEGV, current);
        return -EFAULT;
    }

    regs->regs[0] = 0;
    switch (insn.i_format.opcode) {
    /*
     * jr and jalr are in r_format format.
     */
    case spec_op:
        switch (insn.r_format.func) {
        case jalr_op:
            regs->regs[insn.r_format.rd] = epc + 8;
            /* Fall through */
        case jr_op:
            regs->cp0_epc = regs->regs[insn.r_format.rs];
            break;
        }
        break;
    ......

}

注意当引发异常的指令位于 branch delay slot 中时,CPU 设置epc的值是指向那条分支跳转指令,因此直接读取epc指向的地址就能获得这个指令的内容,然后根据不同的类型,计算应该执行的下一条指令,则有可能就是直接 regs->cp0_epc += 8,亦或是分支跳转的目标地址赋值给 regs->cp0_epc。

完了异常返回前 RESTORE_ALL 时,会将 regs->cp0_epc 写回到 EPC中,则 eret 时就直接跳向 EPC指向的地址执行了。


3. 注意点

因为 do_watch 要在 trap_init() 以后才会被安装,因此在start_kernel() 中,trap_init() 之前是没法使用的。

这个可以自己写个 early_do_watch,然后挂钩上去 :)


4. 例子

在作出上述修改后,内核启动过程中的log为:

Jan 1 00:00:10 cavium kernel: Primary instruction cache 32kB, virtually tagged, 4 way, 64 sets, linesize 128 bytes.
Jan 1 00:00:10 cavium kernel: Primary data cache 8kB, 64-way, 1 sets, linesize 128 bytes.
Jan 1 00:00:10 cavium kernel: Synthesized TLB refill handler (36 instructions).
Jan 1 00:00:10 cavium kernel: Synthesized TLB load handler fastpath (50 instructions).
Jan 1 00:00:10 cavium kernel: Synthesized TLB store handler fastpath (50 instructions).
Jan 1 00:00:10 cavium kernel: Synthesized TLB modify handler fastpath (49 instructions).
Jan 1 00:00:10 cavium kernel: PID hash table entries: 2048 (order: 11, 16384 bytes)
Jan 1 00:00:10 cavium kernel: Using 500.000 MHz high precision timer.
Jan 1 00:00:10 cavium kernel: hwtimer: Added Cavium Octeon timer (Cavium Octeon Kernel jiffy timer) at index=0
Jan 1 00:00:10 cavium kernel: Dentry cache hash table entries: 65536 (order: 7, 524288 bytes)
Jan 1 00:00:10 cavium kernel: Inode-cache hash table entries: 32768 (order: 6, 262144 bytes)
Jan 1 00:00:10 cavium kernel: Memory: 499568k/524288k available (3219k kernel code, 24336k reserved, 847k data, 260k init, 0k highmem)
Jan 1 00:00:10 cavium kernel: Calibrating delay using timer specific routine.. 1000.57 BogoMIPS (lpj=2001149)
Jan 1 00:00:10 cavium kernel: Got watch at ffffffff814fa6c4
Jan 1 00:00:10 cavium kernel: Cpu 0
Jan 1 00:00:10 cavium kernel: $ 0   : 0000000000000000 0000000000000010 8000000004190001 ffffffffffffffaa
Jan 1 00:00:10 cavium kernel: $ 4   : ffffffff815581b8 0000000000000000 0000000000000000 0000000000000002
Jan 1 00:00:10 cavium kernel: $ 8   : a800000008bfc620 a800000008bf75b0 ffffffffffffffff ffffffff814cf500
Jan 1 00:00:10 cavium kernel: $12   : 0000000000000020 ffffffff812c5f1c fffffffffffffff7 ffffffff8143da18
Jan 1 00:00:10 cavium kernel: $16   : 0000000000000010 a8000000875c8ee0 a800000008afef80 0000000000008000
Jan 1 00:00:10 cavium kernel: $20   : ffffffff8153a018 0000000000003528 0000000000000007 ffffffff80069c20
Jan 1 00:00:10 cavium kernel: $24   : 0000000000000000 0000000000000020
Jan 1 00:00:10 cavium kernel: $28   : ffffffff814c4000 ffffffff814c7f70 0000000000000000 ffffffff814fa6cc
Jan 1 00:00:10 cavium kernel: Hi    : 02b020c49bc9b310
Jan 1 00:00:10 cavium kernel: Lo    : 657ef9db22fb6c10
Jan 1 00:00:10 cavium kernel: epc   : ffffffff814fa6c4 start_kernel+0x694/0x848     Not tainted
Jan 1 00:00:10 cavium kernel: ra    : ffffffff814fa6cc start_kernel+0x69c/0x848
Jan 1 00:00:10 cavium kernel: Status: 101084e3    KX SX UX KERNEL EXL IE
Jan 1 00:00:10 cavium kernel: Cause : 8080005c
Jan 1 00:00:10 cavium kernel: PrId : 000d0003Jan 1 00:00:10 cavium kernel: Security Framework v1.0.0 initialized
Jan 1 00:00:10 cavium kernel: Capability LSM initialized
Jan 1 00:00:10 cavium kernel: Mount-cache hash table entries: 256
Jan 1 00:00:10 cavium kernel: Checking for the multiply/shift bug... no.
Jan 1 00:00:10 cavium kernel: Checking for the daddi bug... no.
Jan 1 00:00:10 cavium kernel: Checking for the daddiu bug... no.
Jan 1 00:00:10 cavium kernel: SMP: Booting CPU01 (CoreId 1)...CPU revision is: 000d0003
Jan 1 00:00:10 cavium kernel: SMP: CPU (CoreId 1) started
Jan 1 00:00:10 cavium kernel: SMP: Booting CPU02 (CoreId 2)...CPU revision is: 000d0003
Jan 1 00:00:10 cavium kernel: SMP: CPU (CoreId 2) started
Jan 1 00:00:10 cavium kernel: SMP: Booting CPU03 (CoreId 3)...CPU revision is: 000d0003
Jan 1 00:00:10 cavium kernel: SMP: CPU (CoreId 3) started
Jan 1 00:00:10 cavium kernel: SMP: Booting CPU04 (CoreId 4)...CPU revision is: 000d0003
Jan 1 00:00:10 cavium kernel: SMP: CPU (CoreId 4) started
Jan 1 00:00:10 cavium kernel: SMP: Booting CPU05 (CoreId 5)...CPU revision is: 000d0003
Jan 1 00:00:10 cavium kernel: SMP: CPU (CoreId 5) started
......

你可能感兴趣的:(Using Watchpoints register to monitor momery access on MIPS)