当前位置: 首页 > news >正文

linux ptrace 图文详解(六) gdb单步调试

目录

一、gdb单步调试介绍

二、单步调试原理

三、MDSCR_EL1对单步调试的支持、及起作用时机

四、代码实现

五、总结


(代码:linux 6.3.1,架构:arm64)

One look is worth a thousand words.  —— Tess Flanders

相关链接:

linux ptrace 图文详解(一)基础介绍

linux ptrace 图文详解(二) PTRACE_TRACEME 跟踪程序

linux ptrace 图文详解(三) PTRACE_ATTACH 跟踪程序

linux ptrace 图文详解(四) gdb设置软断点

linux ptrace 图文详解(五) gdb设置硬断点、观察点

一、gdb单步调试介绍

        单步调试是一种调试技术,它允许开发者逐行执行程序代码,以便观察程序的执行流程和变量状态。这种调试方式对于理解程序逻辑、定位错误和验证程序行为至关重要。通过单步调试,开发者可以:

  • 逐行执行代码,观察每一行代码的执行结果。

  • 检查程序中变量的值和状态。

  • 观察程序的控制流,包括函数调用和返回。

  • 设置断点,以便在特定条件下暂停程序执行。

        单步调试主要包括两种模式:逐行调试(step)和逐指令调试(stepi)。逐行调试以源代码行为单位,适合高层次的逻辑分析;逐指令调试以机器指令为单位,适用于底层分析,如汇编代码或驱动开发。GDB通过与操作系统提供的调试接口(如ptrace)协作,控制目标进程的执行,捕获其状态,并在每个步骤后返回控制权给开发者。

二、单步调试原理

        gdb 单步调试 主要依赖硬件debug寄存器,以下是gdb单步调试的实现原理:

        1)gdb通过ptrace(PTRACE_SINGLESTEP)系统调用陷入内核;

        2)ptrace系统调用在内核中找到被调试任务的task_struct对象,并设置TIF_SINGLESTEP标志,代表该任务返回用户态执行时需要使能单步调试功能,并设置该任务内核task_struct对象的寄存器上下文中的spsr,置位DBG_SPSR_SS;

        3)接着,将被调试程序添加到内核的ready list中,等待被调度运行,然后ptrace系统调用返回;

        4)当内核调度器选中被调试程序调度运行时,会判断其是否置位TIF_SINGLESTEP标志,若置位了话,就将MDSCR_EL1寄存器的SS位置1,使能硬件的单步调试功能;

        5)当被调试程序返回用户态执行完第一条指令后,硬件会主动触发一次同步异常并陷入内核;

        6)在内核中的异常处理流程中,判断异常类型(EC)为ESR_ELx_EC_SOFTSTP_LOW,于是调用相应处理函数,最终给被调试任务发送一个SIGTRAP信号;

        7)被调试任务从异常处理流程中返回后,在返回用户态前夕,检查发现有SIGTRAP信号,于是调用ptrace_signal,给父进程gdb发送信号,并唤醒gdb,最后将自己挂起。

三、MDSCR_EL1对单步调试的支持、及起作用时机

        MDSCR(Monitor Debug System Control Register)中的第0位,是Software step control bit,用于single step。

        在每次进入内核(kernel_entry的时候、以及从内核返回用户态(ret_to_user)的时候,检查任务的flag是否有TIF_SINGLESTEP,然后调用disable_step_tsk、enable_step_tsk去清除、设置MDSCR_EL1寄存器中的DBG_MDSCR_SS位。详见:DDI0487J_a_a-profile_architecture_reference_manual.pdf D2.12 Software Step exceptions      

        可见,单步异常触发的时机是被调试任务在用户态执行完第一条指令后,立刻触发一个单步异常陷入内核。

四、代码实现

1、ptrace(PTRACE_SINGLESTEP) 内核实现

        ptrace(PTRACESINGLESTEP)在内核中的实现,主要就是为被调试任务的task_struct对象中存储的寄存器上下文中的pstate置上DBG_SPSR_SS标志,代表当前任务需要单步调试,后续该任务被调度时,设置对应debug寄存器去使能单步调试功能。

ptrace_requestswitch (request)case PTRACE_SINGLESTEP:ptrace_resume(child, request, data = 0) {clear_task_syscall_work(child, SYSCALL_TRACE)if (is_singlestep(request)) {user_enable_single_step(task = child) {struct thread_info *ti = task_thread_info(task)if (!test_and_set_ti_thread_flag(ti, TIF_SINGLESTEP))	/* 1) Set TIF_SINGLESTEP to tracee. 若该位已设置则返回1, 否则返回0 */set_regs_spsr_ss(task_pt_regs(task)) {set_user_regs_spsr_ss(&(r)->user_regs)regs->pstate |= DBG_SPSR_SS					/* 2) Set DBG_SPSR_SS into child's pstate */}}} else {														/* ### if request is not PTRACE_SINGLESTEP, and clear flag */user_disable_single_stepclear_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP)}child->exit_code = datawake_up_state(child, __TASK_TRACED)	{							/* 3) Change child's state to TASK_RUNNING and enqueue it into rq */try_to_wake_up												// if child->state equal to __TASK_TRACED, then wake up itttwu_queuettwu_do_activate {activate_task {enqueue_task(rq, p, flags)p->on_rq = TASK_ON_RQ_QUEUED}ttwu_do_wakeup {WRITE_ONCE(p->__state, TASK_RUNNING)}}}}

2、被调试程序在内核中被调度器选中运行时,针对单步调试的处理

        在内核schedule过程中,会检查被调试任务是否标记了需要单步调试。若被标记了话,会设置相应的debug寄存器(MDSCR_EL1.SS位),随后返回用户态执行被调试程序。

entry_handler {kernel_entry \el, \regsize {save hardware contextldr	x19, [tsk, #TSK_TI_FLAGS]disable_step_tsk x19, x20										// 4) 进入内核保存现场的时候, 若Task有TIF_SINGLESTEP位,A.K.A															//    则将mdscr_el1的DBG_MDSCR_SS位置0{.macro	disable_step_tsk, flgs, tmptbz	\flgs, #TIF_SINGLESTEP, 9990fmrs	\tmp, mdscr_el1bic	\tmp, \tmp, #DBG_MDSCR_SSmsr	mdscr_el1, \tmpisb	// Synchronise with enable_dbg9990:.endm}}mov	x0, spbl	el\el\ht\()_\regsize\()_\label\()_handlerA.K.Ael0t_64_irq_handler {__el0_irq_handler_commonel0_interruptexit_to_user_mode {prepare_exit_to_user_modeif (unlikely(flags & _TIF_WORK_MASK)) {do_notify_resumeif (thread_flags & _TIF_NEED_RESCHED) {local_daif_restore(DAIF_PROCCTX_NOIRQ)schedule() {									/* 5) schedule tracee */}}}}}.if \el == 0b	ret_to_userA.K.ASYM_CODE_START_LOCAL(ret_to_user) {ldr	x19, [tsk, #TSK_TI_FLAGS]	// re-check for single-stepenable_step_tsk x19, x2A.K.A{.macro	enable_step_tsk, flgs, tmptbz	\flgs, #TIF_SINGLESTEP, 9990fmrs	\tmp, mdscr_el1orr	\tmp, \tmp, #DBG_MDSCR_SS											/* 6) If task has TIF_SINGLESTEP, then set DBG_MDSCR_SS to MDSCR_EL1 */msr	mdscr_el1, \tmp}kernel_exit 0					// It will restore general-purpose register}
}

3、单步调试异常触发后,内核中的处理

        当被调试程序在用户态执行完第一条指令后,硬件就会主动触发同步异常陷入内核,异常类型为ESR_ELx_EC_SOFTSTP_LOW。在内核中的处理跟前几篇文章断点触发时的处理流程大体相似:将自身挂起,并发送信号给gdb。

entry_handler {kernel_entry \el, \regsize {}mov	x0, spbl	el\el\ht\()_\regsize\()_\label\()_handlerA.K.Ael0t_64_sync_handler {unsigned long esr = read_sysreg(esr_el1)switch (ESR_ELx_EC(esr))			// #define ESR_ELx_EC(esr)		(((esr) & ESR_ELx_EC_MASK) >> ESR_ELx_EC_SHIFT)	 获取ESR中的EC所对应的6位内容case ESR_ELx_EC_SOFTSTP_LOW:		// #define ESR_ELx_EC_SOFTSTP_LOW	(0x32, aka: 0b110010)el0_dbg {unsigned long far = read_sysreg(far_el1)enter_from_user_mode(regs)do_debug_exception(unsigned long addr_if_watchpoint = far,unsigned int  esr                = esr,struct pt_regs *regs             = regs) {const struct fault_info *inf = esr_to_debug_fault_info(esr)return debug_fault_info + DBG_ESR_EVT(esr)					// #define	DBG_ESR_EVT(x)		(((x) >> 27) & 0x7)inf->fn(addr_if_watchpoint, esr, regs)A.K.Asingle_step_handler {send_user_sigtrap(si_code = TRAP_TRACE)						/* 7) Send SIG_TRAP with siginfo to tracee itself */arm64_force_sig_fault(SIGTRAP, si_code, instruction_pointer(regs), "User debug trap") {force_sig_fault(signo = SIGTRAP, code = TRAP_TRACE, (void __user *)far = instruction_pointer(regs))force_sig_fault_to_task(sig, code, addr, current) {struct kernel_siginfo infoinfo.si_signo = sig				// SIGTRAPinfo.si_errno = 0info.si_code  = code			// TRAP_TRACEinfo.si_addr  = addr			// instruction_pointer(regs)force_sig_info_to_task(&info, t, HANDLER_CURRENT)}}if (!handler_found && user_mode(regs)) {user_rewind_single_step(current)						/* 8) Re-enable single step for syscall restarting. */if (test_tsk_thread_flag(task, TIF_SINGLESTEP))set_regs_spsr_ss(task_pt_regs(task))set_user_regs_spsr_ss(&(r)->user_regs)regs->pstate |= DBG_SPSR_SS				/* 9) Set DBG_SPSR_SS into pstate */}}}exit_to_user_mode(regs) {prepare_exit_to_user_modelocal_daif_maskdo_notify_resume {if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))do_signal {get_signalptrace_signalptrace_stop(exit_code = signo, why = CLD_TRAPPED, 0, info)	/* Stop tracee itself, and notify parent tracer */{current->last_siginfo = infocurrent->exit_code = exit_codedo_notify_parent_cldstop(current, true, why)info.si_signo  = SIGCHLDinfo.si_code   = why						// A.K.A: CLD_TRAPPEDinfo.si_status = tsk->exit_code & 0x7f__group_send_sig_info(SIGCHLD, &info, parent)}}}}}}
}

五、总结

        gdb的单步调试,主要依赖硬件debug寄存器(MDSCR_EL1),ptrace(PTRACE_SINGLESTEP) 的作用主要是给task置上需要单步调试的标志,后续被调试任务被调度运行时,会根据是否置位“单步调试”的位,去设置MDSCR_EL1中的SS位,使能单步调试。当程序返回用户态执行完第一条指令后,硬件会主动触发一个单步异常陷入内核。

相关文章:

  • OJ笔试强训_1至24天
  • PHP腾讯云人脸核身获取SIGN Ticket
  • 强化学习算法系列(六):应用最广泛的算法——PPO算法
  • vcpkg缓存问题研究
  • [Redis]1-高效的数据结构P2-Set
  • C++获取程序的所有用到的库
  • Flash存储器(二):SPI NAND Flash与SPI NOR Flash
  • 2025-04-19 Python 强类型编程
  • GEO优化之企业客服知识库搭建全流程实例(医疗健康行业)
  • Kafka系列之:计算kafka集群topic占的存储大小
  • Kafka安全认证技术:SASL/SCRAM-ACL方案详解
  • 【KWDB 创作者计划】_算法篇---Stockwell变换
  • Kubernetes Pod 调度策略:从基础到进阶
  • 每天学一个 Linux 命令(22):pwd
  • 有哪些好用的仓库管理系统
  • [工具]Java xml 转 Json
  • 招商信诺原点安全:一体化数据安全管理解决方案荣获“鑫智奖”!
  • ios精灵脚本辅助软件,有根和无根roothide越狱区别
  • 【技术派后端篇】技术派通用敏感词替换:原理、实现与应用
  • Benewake(北醒) TF-NOVA 在通过TTL-USB转接板更改配置教程
  • 95后男中音胡斯豪敲开芝加哥抒情歌剧院大门
  • “不可见社会”:一周城市生活
  • 人民日报和音:开启中马关系新的“黄金五十年”
  • 肯尼亚总统鲁托将访华,外交部:中肯两国元首将举行会谈
  • 法治课|男子同时与两名女子办婚礼闹剧,是否应受处罚?
  • 中国金茂与建发国际联合收购北京丰台地块,总价18.4亿元