【ARM Coresight Debug 系列 16 -- Linux 断点 BRK 中断使用详细介绍】

这篇具有很好参考价值的文章主要介绍了【ARM Coresight Debug 系列 16 -- Linux 断点 BRK 中断使用详细介绍】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】

1.1 ARM BRK 指令

ARMv8 架构的 BRK 指令是用于生成一个软件断点的。当处理器执行到 BRK 指令时,会触发一个断点异常。
arm brk 指令,# ARM Coresight 系列,ARMv8 BRK,coresight BRK,断点 BRK,断点指令 BRK

BRK 指令的格式如下:

BRK #<imm>

其中<imm>是一个16位的立即数,它可以在断点异常发生时将立即数保存到 ESR.ISS 域中,从可以用来区分不同目的的 BRK 断点指令。

下面是一个简单的例子:

MOV R0, #1 
BRK #0x1234 
MOV R0, #2

在这个例子中,当处理器执行到BRK #0x1234这条指令时,并且可以在ESR.ISS中看到BRK #0x1234这条指令的立即数0x1234

需要注意的是,BRK指令只能在ARMv8及之后的ARM架构中使用。在早期的ARM架构中,生成软件断点通常使用的是SWI或BKPT指令。

1.2 BRK 立即数宏定义介绍

上节内容介绍了 BRK 后面跟的立即数会在断点中断发生时,保存到ESR.ISS中,那么我们看下linux 中 BRK 后面的立即数宏定义种类有哪些并分别作用是什么?

ARM64中BRK 立即数的定义位于文件 linux/arch/arm64/include/asm/brk-imm.h 中:

/*
 * #imm16 values used for BRK instruction generation
 * 0x004: for installing kprobes
 * 0x005: for installing uprobes
 * 0x006: for kprobe software single-step
 * Allowed values for kgdb are 0x400 - 0x7ff
 * 0x100: for triggering a fault on purpose (reserved)
 * 0x400: for dynamic BRK instruction
 * 0x401: for compile time BRK instruction
 * 0x800: kernel-mode BUG() and WARN() traps
 * 0x9xx: tag-based KASAN trap (allowed values 0x900 - 0x9ff)
 */
#define KPROBES_BRK_IMM                 0x004
#define UPROBES_BRK_IMM                 0x005
#define KPROBES_BRK_SS_IMM              0x006
#define FAULT_BRK_IMM                   0x100
#define KGDB_DYN_DBG_BRK_IMM            0x400
#define KGDB_COMPILED_DBG_BRK_IMM       0x401
#define BUG_BRK_IMM                     0x800
#define KASAN_BRK_IMM                   0x900
#define KASAN_BRK_MASK                  0x0ff
  • KPROBES_BRK_IMM:这是用于 Kprobes 的BRK指令的立即数值。Kprobes是Linux内核中的一个动态追踪工具,它允许你在运行时插入断点到内核代码中;

  • UPROBES_BRK_IMM :这是用于Uprobes的BRK指令的立即数值。Uprobes是Linux内核中的一个动态追踪工具,它允许你在运行时插入断点到用户空间程序中;

  • KPROBES_BRK_SS_IMM:这是用于Kprobes的单步执行模式的BRK指令的立即数值;

  • FAULT_BRK_IMM :这是用于处理页故障的BRK指令的立即数值;

  • KGDB_DYN_DBG_BRK_IMM:这是用于KGDB(内核调试器)的动态调试的BRK指令的立即数值;

  • BUG_BRK_IMM:这是用于BUG_ON宏的BRK指令的立即数值;BUG_ON是Linux内核中的一个宏,用于在满足某个条件时生成一个故障;

  • KASAN_BRK_IMM:这是用于KASAN(内核地址无效访问检测器)的BRK指令的立即数值。

1.3 断点异常处理流程

断点异常属于同步异常,所以我们需要从同步异常开始,ARMv8 的同步异常处理函数位于汇编文件linux/arch/arm64/kernel/entry.S中,定义如下:

/*
 * EL1 mode handlers.
 */
        .align  6
SYM_CODE_START_LOCAL_NOALIGN(el1_sync)
        kernel_entry 1
        mov     x0, sp
        bl      el1_sync_handler
        kernel_exit 1
SYM_CODE_END(el1_sync)

从上面汇编代码可以看到将栈指针的值SP赋值给X0,然后跳转到函数el1_sync_handler中,接下来继续跟踪该函数。

1.3.1 el1_sync_handler

el1_sync_handler 函数的定义位于linux/arch/arm64/kernel/entry-common.c 中:

asmlinkage void noinstr el1_sync_handler(struct pt_regs *regs)
{
        unsigned long esr = read_sysreg(esr_el1);

        switch (ESR_ELx_EC(esr)) {
        case ESR_ELx_EC_DABT_CUR:
        case ESR_ELx_EC_IABT_CUR:
                el1_abort(regs, esr);
                break;
        /*
         * We don't handle ESR_ELx_EC_SP_ALIGN, since we will have hit a
         * recursive exception when trying to push the initial pt_regs.
         */
        case ESR_ELx_EC_PC_ALIGN:
                el1_pc(regs, esr);
                break;
        case ESR_ELx_EC_SYS64:
        case ESR_ELx_EC_UNKNOWN:
                el1_undef(regs);
                break;
        case ESR_ELx_EC_BREAKPT_CUR:
        case ESR_ELx_EC_SOFTSTP_CUR:
        case ESR_ELx_EC_WATCHPT_CUR:
        case ESR_ELx_EC_BRK64:
                el1_dbg(regs, esr);
                break;
        case ESR_ELx_EC_FPAC:
                el1_fpac(regs, esr);
                break;
        default:
                el1_inv(regs, esr);
        }
}

首先读取异常状态寄存器 ESR_EL1EC 域 判断当前异常类型,然后根据异常类型跳转到对应的处理函数,本篇文章组要介绍 ARMv8/ARMv9 debug 相关的内容,所先只关注 el1_dbg 这个异常处理函数。
arm brk 指令,# ARM Coresight 系列,ARMv8 BRK,coresight BRK,断点 BRK,断点指令 BRK

当异常类型为 Breakpoint Instruction exceptions,Breakpoint exceptions,Watchpoint exceptions,Software Step exceptions 四种中的一种时就会跳转执行 el1_dbg 函数。

gcc 编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用 c 语言函数,并且想通过堆栈传递参数,你定义的 c 函数时要在函数前加上宏asmlinkage

  • Breakpoint Instruction exceptions: 执行 BRK 指令时触发的异常;
  • Breakpoint exceptions: 硬件断点异常,比如配置指令地址到硬件断点对应的寄存器中,当执行到该指令时就会触发硬件断点异常;
  • Watchpoint exceptions:观察点异常,主要用来监控变量的,比如,将变量的地址写入到对应的寄存器中,当访问这个变量是就会触发该异常;
  • Software Step exceptions:软件单步执行异常。

详细内容可以见 DDI0487_I_a_a-profile_architecture_reference_manual.pdf 中的 D2章节。

1.3.2 el1_dbg 跟踪

上节内容说到 当检查到异常类型为 debug 异常类型时就会执行el1_dbg 函数,该函数的实现如下:

static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
{
        unsigned long far = read_sysreg(far_el1);

        arm64_enter_el1_dbg(regs);
        do_debug_exception(far, esr, regs);
        arm64_exit_el1_dbg(regs);
}

该函数首先读取 far_el1 寄存器中产生导致异常发生的虚拟地址,然后再将虚拟地址,esr_el1的值,SP栈地址作为参数传给了 do_debug_exception 函数:

834 void do_debug_exception(unsigned long addr_if_watchpoint, unsigned int esr,
835                         struct pt_regs *regs)
836 {
837         const struct fault_info *inf = esr_to_debug_fault_info(esr);
838         unsigned long pc = instruction_pointer(regs);
839			
842			...
843         debug_exception_enter(regs);
			...
848         if (inf->fn(addr_if_watchpoint, esr, regs)) {
849                 arm64_notify_die(inf->name, regs,
850                                  inf->sig, inf->code, (void __user *)pc, esr);
851         }
			...
854 }
855 NOKPROBE_SYMBOL(do_debug_exception);

这里我们主要关注 837 行和 848行,这两行的作用是根据 ESR.EC 阈值判断当前异常类型,然后调佣该异常类型的处理函数。例如 BRK 软件断点异常的处理函数就是 linux/arch/arm64/kernel/debug-monitors.c中的函数 brk_handler。那么 brk_handler 异常的处理函数是如何注册的?

1.4 debug 异常处理函数注册

linux 对于类型相似的问题,比如许多类型相似 debug 异常,处理套路都是先定义一个全局的结构体数组(如 struct fault_info fault_info[], struct fault_info debug_fault_info[]),然后将异常的处理函数,异常类型,异常描述等信息填入结构体数组中:

struct fault_info {
        int     (*fn)(unsigned long addr, unsigned int esr,
                      struct pt_regs *regs);
        int     sig;
        int     code;
        const char *name;
};

/*
 * __refdata because early_brk64 is __init, but the reference to it is
 * clobbered at arch_initcall time.
 * See traps.c and debug-monitors.c:debug_traps_init().
 */
static struct fault_info __refdata debug_fault_info[] = {
        { do_bad,       SIGTRAP,        TRAP_HWBKPT,    "hardware breakpoint"   },
        { do_bad,       SIGTRAP,        TRAP_HWBKPT,    "hardware single-step"  },
        { do_bad,       SIGTRAP,        TRAP_HWBKPT,    "hardware watchpoint"   },
        { do_bad,       SIGKILL,        SI_KERNEL,      "unknown 3"             },
        { do_bad,       SIGTRAP,        TRAP_BRKPT,     "aarch32 BKPT"          },
        { do_bad,       SIGKILL,        SI_KERNEL,      "aarch32 vector catch"  },
        { early_brk64,  SIGTRAP,        TRAP_BRKPT,     "aarch64 BRK"           },
        { do_bad,       SIGKILL,        SI_KERNEL,      "unknown 7"             },
};

在异常发生的时候只要需要索引值,就可以直接调用到对应的异常处理函数。对于数组debug_fault_info[] 索引值的获取是根据 ESR.EC的值计算来的:

static inline const struct fault_info *esr_to_debug_fault_info(unsigned int esr)
{
        return debug_fault_info + DBG_ESR_EVT(esr);
}

#define DBG_ESR_EVT(x)          (((x) >> 27) & 0x7)

DBG_ESR_EVT 中右移27位是因为ESR_EL1bit26开始时EC域:
arm brk 指令,# ARM Coresight 系列,ARMv8 BRK,coresight BRK,断点 BRK,断点指令 BRK

debug_fault_info表中的内容是默认的一些异常的处理函数,对于 debug 异常的处理函数注册还需要在代码中调用 linux/arch/arm64/mm/fault.c 中的注册函数hook_debug_fault_code来完成:

void __init hook_debug_fault_code(int nr,
                                  int (*fn)(unsigned long, unsigned int, struct pt_regs *),
                                  int sig, int code, const char *name)
{
        BUG_ON(nr < 0 || nr >= ARRAY_SIZE(debug_fault_info));

        debug_fault_info[nr].fn         = fn;
        debug_fault_info[nr].sig        = sig;
        debug_fault_info[nr].code       = code;
        debug_fault_info[nr].name       = name;
}

对于 BRK和单步执行的异常处理函数的注册是在linux/arch/arm64/kernel/debug-monitors.c中函数 debug_traps_init(void) 中完成的:

void __init debug_traps_init(void)
{
        hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,
                              TRAP_TRACE, "single-step handler");
        hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,
                              TRAP_BRKPT, "BRK handler");
}

对于 watchpoint 和 breakpoint 的异常处理函数的注册位于linux/arch/arm64/kernel/hw_breakpoint.c中的arch_hw_breakpoint_init(void)函数中:

/*
 * One-time initialisation.
 */
static int __init arch_hw_breakpoint_init(void)
{
		...
        /* Register debug fault handlers. */
        hook_debug_fault_code(DBG_ESR_EVT_HWBP, breakpoint_handler, SIGTRAP,
                              TRAP_HWBKPT, "hw-breakpoint handler");
        hook_debug_fault_code(DBG_ESR_EVT_HWWP, watchpoint_handler, SIGTRAP,
                              TRAP_HWBKPT, "hw-watchpoint handler");
        ...
}
arch_initcall(arch_hw_breakpoint_init);

由于本篇文章主要介绍 BRK 指令异常,所以还需要继续跟踪器异常处理函数 brk_handler

326 static int brk_handler(unsigned long unused, unsigned int esr,
327                        struct pt_regs *regs)
328 {
329         if (call_break_hook(regs, esr) == DBG_HOOK_HANDLED)
330                 return 0;
331
332         if (user_mode(regs)) {
333                 send_user_sigtrap(TRAP_BRKPT);
334         } else {
335                 pr_warn("Unexpected kernel BRK exception at EL1\n");
336                 return -EFAULT;
337         }
338
339         return 0;
340 }
341 NOKPROBE_SYMBOL(brk_handler);

这里我们只关注第329行,它的作用是遍历注册到链表 kernel_break_hook 上的所有node, 比较 node 节点上的的立即数 imm 是否和 异常症状寄存器 ESR.ISS域中的值是否匹配, 如果匹配成功就会调用它的 handler。

static LIST_HEAD(kernel_break_hook);

static int call_break_hook(struct pt_regs *regs, unsigned int esr)
{
        struct break_hook *hook;
        struct list_head *list;
        int (*fn)(struct pt_regs *regs, unsigned int esr) = NULL;

        list = user_mode(regs) ? &user_break_hook : &kernel_break_hook;

        /*
         * Since brk exception disables interrupt, this function is
         * entirely not preemptible, and we can use rcu list safely here.
         */
        list_for_each_entry_rcu(hook, list, node) {
                unsigned int comment = esr & ESR_ELx_BRK64_ISS_COMMENT_MASK;

                if ((comment & ~hook->mask) == hook->imm) // 比较 BRK 后面的立即数
                        fn = hook->fn;
        }

        return fn ? fn(regs, esr) : DBG_HOOK_ERROR;
}
NOKPROBE_SYMBOL(call_break_hook);

arm brk 指令,# ARM Coresight 系列,ARMv8 BRK,coresight BRK,断点 BRK,断点指令 BRK

1.4.1 brk 处理函数的注册

上文提到了当 debug 异常发生后,会遍历 kernel_break_hook 上的所有 node,那么我们看下有哪些类型的事件注册到这个链表上呢?

register_kernel_break_hook(&kgdb_brkpt_hook);
register_kernel_break_hook(&kgdb_compiled_brkpt_hook);
register_kernel_break_hook(&kprobes_break_hook);
register_kernel_break_hook(&kprobes_break_ss_hook);
register_kernel_break_hook(&bug_break_hook);
register_kernel_break_hook(&fault_break_hook);
register_kernel_break_hook(&kasan_break_hook);

我们在看下这些 BRK事件对应的处理函数:

static struct break_hook kgdb_brkpt_hook = {
        .fn             = kgdb_brk_fn,
        .imm            = KGDB_DYN_DBG_BRK_IMM,
};
static struct break_hook kgdb_compiled_brkpt_hook = {
        .fn             = kgdb_compiled_brk_fn,
        .imm            = KGDB_COMPILED_DBG_BRK_IMM,
};
static struct break_hook kprobes_break_hook = {
        .imm = KPROBES_BRK_IMM,
        .fn = kprobe_breakpoint_handler,
};
static struct break_hook kprobes_break_ss_hook = {
        .imm = KPROBES_BRK_SS_IMM,
        .fn = kprobe_breakpoint_ss_handler,
};
static struct break_hook bug_break_hook = {
        .fn = bug_handler,
        .imm = BUG_BRK_IMM,
};
static struct break_hook fault_break_hook = {
        .fn = reserved_fault_handler,
        .imm = FAULT_BRK_IMM,
};
static struct break_hook kasan_break_hook = {
        .fn     = kasan_handler,
        .imm    = KASAN_BRK_IMM,
        .mask   = KASAN_BRK_MASK,
};

这里挑我们最常用到的处理函数 bug_handler 来介绍:

static int bug_handler(struct pt_regs *regs, unsigned int esr)
{
        switch (report_bug(regs->pc, regs)) {
        case BUG_TRAP_TYPE_BUG:
                die("Oops - BUG", regs, 0);
                break;

        case BUG_TRAP_TYPE_WARN:
                break;

        default:
                /* unknown/unrecognised bug trap type */
                return DBG_HOOK_ERROR;
        }

        /* If thread survives, skip over the BUG instruction and continue: */
        arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
        return DBG_HOOK_HANDLED;
}

这里看到了我们经常遇到的Oops - BUG 了。

到目前为止介绍了整个 BRK 点断指令的处理流程与对应的异常处理函数注册流程,那么我们什么时候会用到 BRK 指令呢

在 linux中最常用的地方也就是 WARNBUG 这两个地方,这里以BUG为例进行介绍:文章来源地址https://www.toymoban.com/news/detail-770973.html

#define BUG() do {                                      \
        __BUG_FLAGS(0);                                 \
        unreachable();                                  \
} while (0)
#define __BUG_FLAGS(flags)                              \
        asm volatile (__stringify(ASM_BUG_FLAGS(flags)));
#define ASM_BUG()       ASM_BUG_FLAGS(0)
#define ASM_BUG_FLAGS(flags)                            \
        __BUG_ENTRY(flags)                              \
        brk     BUG_BRK_IMM

到了这里,关于【ARM Coresight Debug 系列 16 -- Linux 断点 BRK 中断使用详细介绍】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【ARM Coresight 系列文章 10.1 - ARM Coresight STM 介绍及使用】

    上篇文章:ARM Coresight 系列文章 10 - ARM Coresight STM 介绍及使用 下篇文章:ARM Coresight 系列文章 10.2 - ARM Coresight STM Trace packets STM 支持两种类型的软件的数据,一种是 guaranteed的,另一种是 invariant 的, 对于 guaranteed 类型的数据传输,当STM 无法接收 AXI总线发过来的数据时,会通

    2024年02月16日
    浏览(45)
  • 【ARM Coresight 系列文章 22 -- linux frace 与 trace-cmd】

    ftrace 是 Linux 内核中的一个跟踪工具,主要用于帮助开发者分析和调试内核的行为。ftrace 的名字来源于 “ function tracer ”,它最初是用于跟踪内核函数调用的,但现在已经发展成一个功能强大的通用跟踪框架,可以跟踪函数调用、调度事件、中断、定时器、内存映射等各种内

    2024年02月11日
    浏览(51)
  • 【ARM Coresight 系列文章 2 - ARM Coresight 介绍】

    请阅读 【ARM Coresight SoC-400/SoC-600 专栏导读】

    2024年02月12日
    浏览(42)
  • 【ARM Coresight 系列文章 10.2 - ARM Coresight STM Trace packets】

    上篇文章:ARM Coresight 系列文章 10.1 - ARM Coresight STM 介绍及使用 下篇文章:ARM Coresight 系列文章 10.3 - ARM Coresight STM 寄存器介绍 及STM DMA 传输介绍 STM 产生的trace的是遵守 MIPI System Trace Protocol version 2 (STPv2) 规范的,STM 提供 65536(64K) 个 STMv2 chan

    2024年02月16日
    浏览(39)
  • 【ARM Coresight 系列文章 2.3 - Coresight 寄存器】

    请阅读 【ARM Coresight SoC-400/SoC-600 专栏导读】 下篇文章:【ARM Coresight 系列文章 2.4 - Coresight 寄存器:DEVARCH,DEVID, DEVTYPE】 上篇文章:【ARM Coresight 系列文章 2.2- ATB 总线简介】 Coresight 对于每个 coresight 组件,规定了一些寄存器,这些寄存器的偏移是固定的,这些寄存器有的是

    2024年02月14日
    浏览(41)
  • ARM Coresight 系列文章 7 - ARM Coresight 通过 AHB-AP 访问 cpu 内部 coresight 组件

    请阅读 【ARM Coresight SoC-400/SoC-600 专栏导读】 如下图所示, 如果A78想去访问M33的内部 coresight 组件 ETM,需要要怎么做 ? 答案也正是在图中,首先A78 通过AXI 互联,接入到 APBIC 的 slave port,再通过APBIC 的 master 送出,而APBIC中的master port 可以 master 的身份来访问对应的 AHB-AP上,

    2024年02月15日
    浏览(42)
  • 【ARM Coresight 系列文章 10.3 - ARM Coresight STM 寄存器介绍 及STM DMA 传输介绍】

    上篇文章:ARM Coresight 系列文章 10.2 - ARM Coresight STM Trace packets STM 的寄存器主要可以分为以下几类: STM DMA 相关的; STM HW Trigger 相关的; 系统控制及状态寄存器; 只读寄存器。 STM DMA 相关的寄存器

    2024年02月15日
    浏览(42)
  • 【ARM Coresight 系列文章 2.4 - Coresight 寄存器:DEVARCH,DEVID, DEVTYPE】

    请阅读 【ARM Coresight SoC-400/SoC-600 专栏导读】 上篇文章:【ARM Coresight 系列文章 2.3 - Coresight 寄存器】 下篇文章:【ARM Coresight 系列文章 2.5 - Coresight 寄存器:PIDR0-PIDR7,CIDR0-CIDR3 介绍】 DEVARCH 寄存器标识了coresight 组件的架构信息。 bits[31:21] 定义了组件架构: Bits[31:28] 指明 J

    2024年02月12日
    浏览(42)
  • 【ARM Coresight OpenOCD 系列 1 -- OpenOCD 介绍】

    请阅读 【ARM Coresight SoC-400/SoC-600 专栏导读】 OpenOCD (Open On-Chip Debugger) 是一个开源的硬件调试器,可以提供 调试 、芯片编程和 边界扫描 等功能。OpenOCD使用标准的 JTAG 接口与芯片进行通信,因此,可以用于各种不同的硬件平台,包括 ARM 和 MIPS等。 OpenOCD最初是为了支持ARM7和

    2024年02月02日
    浏览(48)
  • 【ARM Coresight OpenOCD 系列 2 -- OpenOCD 脚本语法详细介绍】

    请阅读 【ARM Coresight SoC-400/SoC-600 专栏导读】 介绍之前我们先了解下什么是 SWJ-DP , SWJ-DP (Serial Wire JTAG Debug Port) 是 ARM CoreSight 架构中的一个重要部分,它提供了一个调试端口,允许调试工具通过 JTAG 或 SWD (Serial Wire Debug) 接口访问目标设备。 SWJ-DP 有以下主要特性: 可以通过

    2024年02月19日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包