一种调试器及其调试方法

文档序号:6607016阅读:213来源:国知局
专利名称:一种调试器及其调试方法
技术领域
本发明涉及计算机技术领域,尤其涉及一种调试器及其调试方法。
背景技术
在类UNIX操作系统中,提供了一种标准的服务使得用户态程序能够实现对底层硬件和服务的控制(如对文件系统的控制),这种服务称为系统调用(system calls)。而连接用户态程序和系统调用的桥梁称为库函数。当一个程序需要作系统调用的时候,它首先调用与之对应的库函数,通过库函数找到系统调用表和对应的系统调用号,再将相关参数放进与系统调用相关的寄存器中,然后调用软中断。这个中断就像一个让用户态程序得以接触到内核模式的窗口,使得库函数能够将参数和系统调用号交给内核,最后由内核完成系统调用的执行。用户态程序和库函数都是运行在用户地址空间,而系统调用则是运行在内核地址空间。在嵌入式软件开发过程中,往往需要修改内核的相关接口,再由用户态程序通过库函数调用到这些内核接口。要对这类程序进行调试,就需要调试器具有从用户地址空间跟踪调试进内核地址空间的功能。因此,如何能够同时对被调试任务的用户地址空间和内核地址空间进行调试,成为调试器设计的一种需要。调试器是用来帮助开发人员分析和定位程序故障的一种工具。按照调试的地址空间范围进行划分,目前的调试器可以分为内核级调试器和用户态调试器。内核级调试器能够跟踪和调试运行在内核态的任务。而用户态调试器只能对运行在用户态的任务进行跟踪和调试。类UNIX操作系统已经提供了一套完整的机制用于用户态任务的跟踪和调试,即 Ptrace系统调用。ptrace系统调用提供了一种使得父进程得以监视和控制其他进程的方式,它能够改变子进程中的寄存器和内存信息,从而可以实现断点调试和系统调用的跟踪。 目前已有的用户态调试器大多采用Ptrace系统调用进行跟踪调试。虽然ptrace系统调用能够跟踪和调试子进程的运行,但Ptrace系统调用只能对被调试任务的用户地址空间进行跟踪和调试,最多跟踪到库函数一级,无法通过库函数跟踪进内核。内核级调试器是运行在内核态的,它能够对内核任务进行跟踪和调试。以 WindRiver公司的Vxworks ( 一种嵌入式实时操作系统)为例,它提供了一种shell调试器, 这种shell调试器就属于内核级调试器。而目前已有的内核级调试器只针对内核任务进行调试,不能对用户态任务进行调试,更不能从用户地址空间跟踪调试进内核地址空间。在目前已有的各种调试器中,还没有实现能够同时对用户地址空间和内核地址空间进行调试的功能。

发明内容
本发明提供一种调试器及其调试方法,用以解决现有技术中的调试器不能同时对用户地址空间和内核地址空间进行跟踪调试的问题。
具体的,本发明提供一种调试方法,包括调试器在调试关系建立后,运行被调试任务,所述被调试任务运行在内核态或用户态;调试器判断是否停止所述被调试任务,若是,在所述被调试任务中设置停止标志位,停止所述被调试任务的运行后进行任务访问;所述任务访问类型包括访问所述被调试任务的内存信息和/或寄存器信息;其中,所述调制器对于因中断或异常停止的被调试任务,从内核堆栈上访问寄存器信息;对于非中断或非异常停止的内核态被调试任务,以及停止在用户地址空间的用户态被调试任务,通过调用Ptrace系统调用,在threacLstruct结构体中访问寄存器信息。进一步地,本发明所述方法中,所述调试器在所述被调试任务为内核态任务时,直接对内核地址空间进行内存信息访问;在所述被调试任务为用户态任务时,调用Ptrace系统调用对用户地址空间进行内存信息访问。本发明所述方法进一步具有以下特点本发明所述方法中,调试器判断所述被调试任务是否停止的判断条件包括所述调试器接收到上层发送的停止指令;或者,所述调试器在调试所述被调试任务时发生异常事件;或者,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,捕获到关心的信号消息。其中,所述调试器调试所述被调试任务时发生异常事件的处理过程包括所述调试器调用相应的异常处理函数进行异常处理,并在检测出当前异常事件为有效异常事件时,上报当前异常事件,并停止所述被调试任务的运行;所述异常事件至少包括断点异常事件和单步异常事件。其中,所述断点异常事件中断点的设置方式包括所述调试器获取断点设置的内存地址,若为用户地址空间断点,直接将内存指令替换为断点指令;若为内核地址空间断点,待所述被调试任务被调度时,将内存指令替换为断点指令,并在所述被调试任务完成调度时,将内核地址空间中的断点指令恢复成原内存指令。进一步地,本发明所述方法中,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,检测是否存在未处理的信号消息,若是,获取待处理信号消息的信号值,上报信号处理事件,并基于获取的所述信号值得到所关心的信号消息时,停止所述被调试任务。本发明还提供一种调试器,包括调试开启模块,用于建立调试关系,运行被调试任务,所述被调试任务运行在内核态或用户态;任务运行控制模块,用于在判断出需要停止所述被调试任务时,在所述被调试任务中设置停止标志位,停止所述被调试任务的运行;任务访问模块,用于在所述任务运行控制模块停止运行所述被调试任务时,进行任务访问,所述任务访问类型包括访问所述被调试任务的内存信息和/或寄存器信息;其中,所述任务访问模块对于因中断或异常停止的被调试任务,从内核堆栈上获取寄存器信肩、ο
其中,所述任务运行控制模块判断所述被调试任务是否停止的判断条件包括所述调试器接收到上层发送的停止指令;或者,所述调试器在调试所述被调试任务时发生异常事件;或者,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,捕获到关心的信号消息。进一步地,本发明所述的调试器还包括异常处理模块,用于调用相应的异常处理函数进行异常处理,并在检测出当前异常事件为有效异常事件时,上报当前异常事件,并触发所述任务运行控制模块,停止所述被调试任务的运行;所述异常事件至少包括断点异常事件和单步异常事件。进一步地,所述调试器还包括断点设置模块,用于获取断点设置的内存地址,若为用户地址空间断点,直接将内存指令替换为断点指令;若为内核地址空间断点,待所述被调试任务被调度时,将内存指令替换为断点指令,并在所述被调试任务完成调度时,将内核地址空间中的断点指令恢复成原内存指令。进一步地,所述调试器还包括信号处理模块,用于在用户态被调试任务从内核地址空间切换到用户地址空间时,检测是否存在未处理的信号消息,若是,获取待处理信号消息的信号值,上报信号处理事件,并基于获取的所述信号值得到所关心的信号消息时,触发所述任务运行控制模块,停止所述被调试任务。与现有技术相比,本发明有益效果如下本发明提供的调试方法和调试器,克服了现有的各种调试器不能同时支持对用户地址空间和内核地址空间进行调试功能的问题,达到了能够对被调试任务在用户地址空间和内核地址空间的运行进行跟踪和调试的目的。


为了更清楚地说明本发明实施例或现有技术中的技术方案,下面将对实施例或现有技术描述中所需要使用的附图作一简单地介绍,显而易见地,下面描述中的附图仅仅是本发明的一些实施例,对于本领域普通技术人员来讲,在不付出创造性劳动性的前提下,还可以根据这些附图获得其他的附图。图1为本发明提供的一种调试方法流程图;图2为本发明提供的一种调试器的结构图;图3为本发明实施例提供的一种调试器的结构图;图4为本发明中调试器停止被调试任务运行流程图;图5为本发明中调试器恢复被调试任务运行流程图;图6为本发明中调试器访问被调试任务内存流程图;图7为本发明中调试器访问被调试任务寄存器流程图;图8为本发明中调试器断点设置流程图;图9为本发明中调试器断点异常处理流程图;图10为本发明中调试器单步异常处理流程图;图11为本发明中调试器信号处理流程图。
具体实施例方式下面将结合本发明实施例中的附图,对本发明实施例中的技术方案进行清楚、完整地描述,显然,所描述的实施例仅仅是本发明一部分实施例,而不是全部的实施例。基于本发明中的实施例,本领域普通技术人员在没有做出创造性劳动前提下所获得的所有其他实施例,都属于本发明保护的范围。为了解决现有技术中存在的各种调试器不能够同时支持对用户地址空间和内核地址空间进行跟踪调试的问题,本发明提供一种调试器及其调试方法。该调试器实现了对被调试程序在用户地址空间和内核地址空间的运行进行跟踪和调试的目的。该支持双态调试的调试器不仅支持对用户态任务的调试,同时也支持对内核态任务的调试;所述支持双态调试的调试器是一种内核级调试器。如图1所示,为本发明提供的一种调试方法流程图,该方法具体包括步骤S101、调试器在调试关系建立后,运行被调试任务,所述被调试任务运行在内核态或用户态。步骤S102、调试器判断是否停止所述被调试任务,若是,在所述被调试任务中设置停止标志位,停止所述被调试任务的运行。该步骤中,所述调试器判断所述被调试任务是否停止的判断条件包括所述调试器接收到上层发送的停止指令;或者,所述调试器在调试所述被调试任务时发生异常事件;或者,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,捕获到关心的信号消息。其中,所述调试器调试所述被调试任务时发生异常事件的处理过程包括所述调试器调用相应的异常处理函数进行异常处理,并在检测出当前异常事件为有效异常事件时,上报当前异常事件,并停止所述被调试任务的运行;所述异常事件至少包括断点异常事件和单步异常事件。其中,所述断点异常事件中断点的设置方式包括所述调试器获取断点设置的内存地址,若为用户地址空间断点,直接将内存指令替换为断点指令;若为内核地址空间断点,待所述被调试任务被调度时,将内存指令替换为断点指令,并在所述被调试任务完成调度时,将内核地址空间中的断点指令恢复成原内存指令。进一步的,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,检测是否存在未处理的信号消息,若是,获取待处理信号消息的信号值,上报信号处理事件,并基于获取的所述信号值得到所关心的信号消息时,停止所述被调试任务。其中,所述调试器根据获取的信号值,查找预先配置的信号列表,根据信号列表中记录的信息,判断该信号值所对应的信号消息是否为所关心的信号消息。其中,信号列表是用户根据具体需求所配置的。步骤S103、调试器在所述被调试任务停止运行后进行任务访问,所述任务访问类型包括访问所述被调试任务的内存信息和/或寄存器信息;其中,所述调制器对于因中断或异常停止的被调试任务,从内核堆栈上访问寄存器信息。进一步的,调试器在进行任务访问后,恢复被调试任务的运行;其中,恢复方式为将所述被调试任务中设置的停止标志位删除。如图2所示,为本发明提供的一种调试器,该调试器包括调试开启模块210,用于建立调试关系,运行被调试任务,所述被调试任务运行在内核态或用户态;任务运行控制模块220,用于在判断出需要停止所述被调试任务时,在所述被调试任务中设置停止标志位,停止所述被调试任务的运行;任务访问模块230,用于在任务运行控制模块220停止运行所述被调试任务时,进行任务访问;所述任务访问类型包括访问所述被调试任务的内存信息和/或寄存器信息;其中,对于寄存器信息访问,任务访问模块230对于因中断或异常停止的被调试任务,从内核堆栈上获取寄存器信息;对于非中断或异常停止的内核态被调试任务,以及停止在用户地址空间的用户态被调试任务,通过调用Ptrace系统调用,在threacLstruct结构体中获取寄存器信息。进一步地,对于内存信息访问,所述任务访问模块230在所述被调试任务为内核态任务时,直接对内核地址空间进行内存信息访问;在所述被调试任务为用户态任务时,调用ptrace系统调用对用户地址空间进行内存信息访问。进一步地,所述任务运行控制模块220判断被调试任务是否停止的判断条件包括调试器接收到上层发送的停止指令;或者,调试器在调试被调试任务时发生异常事件;或者,调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,捕获到关心的信号消息。基于上述特征,本发明所提供的调试器进一步包括异常处理模块M0,用于调用相应的异常处理函数进行异常处理,并在检测出当前异常事件为有效异常事件时,上报当前异常事件,并触发所述任务运行控制模块220,停止所述被调试任务的运行;所述异常事件至少包括断点异常事件和单步异常事件。断点设置模块250,用于获取断点设置的内存地址,若为用户地址空间断点,直接将内存指令替换为断点指令;若为内核地址空间断点,待所述被调试任务被调度时,将内存指令替换为断点指令,并在所述被调试任务完成调度时,将内核地址空间中的断点指令恢复成原指令。以及,信号处理模块沈0,用于在用户态被调试任务从内核地址空间切换到用户地址空间时,检测是否存在未处理的信号消息,若是,获取待处理信号消息的信号值,上报信号处理事件,并基于获取的所述信号值得到所关心的信号消息时,触发所述任务运行控制模块220,停止所述被调试任务。下面根据图3 图11给出本发明一个较佳的实施例,并结合对实施例的描述,进一步给出本发明的技术细节,使其能够更好地说明本发明的具体实现过程。如图3所示,本发明还提供一种调试器,该调试器包括调试开启模块300、任务运行控制模块310、内存访问控制模块320、寄存器访问控制模块330、断点设置模块340、异常处理模块350、信号处理模块360 ;具体的调试开启模块300,用于建立调试关系,运行被调试任务,所述被调试任务运行在内核态或用户态;
具体的,调试开启模块300通过执行attach操作,建立调试关系。其中,attach被调试任务是支持双态调试的调试器进行调试的第一个操作,在实现了控制被调试任务运行的基础上,attach被调试任务操作除了执行设置被调试任务的 Ptrace属性,建立调试器与被调试任务父子关系外,还需要调用kertSk_irmer_SuSpend_ tsk()接口停止被调试任务的运行。需要修改的是linux/kernel/ptrace. c文件中的 ptrace_attach()函数接口,其关键伪代码如下所示int ptrace_attach (struct task_struct氺task){......task- > ptrace | = PT_PTRACED ; /* 设置被调试任务 attach 属性 */_ptrace_l ink (task, current) ; /氺建立父子进程关系氺/kertsk_inner_suspend_tsk (task) ;/* 停止被调试任务 */......}与attach任务对应的,还存在detach任务;detach被调试任务是支持双态调试的调试器进行调试的最后一个操作。与 attach任务相对应,支持双态调试的调试器detach任务调用的是SyS_ptraCe ()函数接口,执行PTRACE_DETACH操作。detach被调试任务除了删除调试器与被调试任务父子关系外,还需要调用kertSk_irmer_reSume_tSk()接口恢复被调试任务的运行。需要修改的是 linux/kernel/ptrace. c文件中的—ptrace_detach ()函数接口,其关键伪代码如下所示static inline void—ptrace_detach(struct task_struct氺child, unsigned int data){......}任务运行控制模块310,控制被调试任务的运行,是调试器各种调试功能的基础, 用于在判断出需要停止被调试任务时,在相应的被调试任务中设置停止标志位,使得对应被调试任务被停止调用。该任务运行控制模块通过设置停止标志位的方式,控制被调试任务是否能够被内核调度,从而达到控制被调试任务运行的目的。为了清楚的说明该任务运行控制模块310的具体控制过程,下面结合图4给出停止被调试任务运行的基本流程,具体包括步骤401、开始;步骤402、任务运行控制模块310在相应被调试任务中设置停止标志位;步骤403、判断上述被调试任务是否正在CPU上运行,若是,执行步骤404 ;否则,执行步骤410 ;步骤404、CPU停止当前运行的被调试任务,重新选择一个任务进行调度;步骤405、内核调度到被调试任务;
_ptrace_unlink (child) ;/* 取消被调试关系 */ kertsk_inner_resume_tsk(child) ;/* 恢复被调试任务运行 */
步骤406、检测是否设置了停止标志位,若是,执行步骤407 ;否则,执行步骤409。步骤407、将被调试任务从任务队列中删除。步骤408、重新选择一个任务调度,执行步骤412 ;步骤409、执行被调试任务相关代码,执行步骤412 ;步骤410、判断被调试任务是否在任务队列中,若是,执行步骤411 ;否则,执行步骤 405 ;步骤411、将被调试任务从任务队列中删除,执行步骤405 ;步骤412、结束。进一步地,任务运行控制模块310,还用于基于上层指令或任务访问完成后,撤销相应被调试任务中设置的停止标志位,使得对应被调试任务重新被唤醒接受调用。如图5所示,为恢复被调试任务运行的基本流程,包括步骤501、开始;步骤502、删除被调试任务停止标志位;步骤503、唤醒被调试任务;步骤504、内核调度到被调试任务;步骤505、判断是否设置了停止标志位,若是,执行步骤507 ;否则,执行步骤506 ;步骤506、执行被调试任务相关代码,执行步骤509 ;步骤507、将被调试任务从任务队列中删除;步骤508、重新选择一个任务调度;步骤509、结束。上述任务运行控制模块310从Iinux系统方面的改进,主要体现在以下几个方面(1)定义被调试任务停止标志位在linux/include/sched. h文件的task_struct结构体中,增加被调试任务停止标志掩码值和相应的成员变量,如下所示struct task_struct {......#define KERTSK_SUSPEND 0x100 /* 定义停止标志掩码值 */int kertask_special_flags ;/氺标志位成员变量氺/......}(2)定义停止被调试任务运行接口在linux/kernel/sched. c文件中,增加停止被调试任务函数接口 kertsk_inner_ suspencLtskO,其关键伪代码如下所示int kertsk_inner_suspend_tsk(struct task—struct氺tsk){......tsk- > kertask_special_flags | = KERTSK_SUSPEND ;/* 设置停止标志位 */tsk- > state = TASK_ST0PPED ;/* 设置任务停止状态 */schedule () ;/* 重新调度任务 */
......}(3)定义恢复被调试任务运行接口在linux/kernel/sched. c文件中,增加停止被调试任务函数接口 kertsk_inner_ resumejskO,其关键伪代码如下所示int kertsk_inner_resume_tsk(struct task—struct氺tsk){......tsk- > kertask_special_flags& = KERTASK_SUSPEND ;wake_up_state (tsk, TASK_ST0PPED | TASK_TRACED);____}(4)修改内核任务调度流程在内核任务调度流程中,当调度出来的任务判断被置了 KERTSK_SUSPEND标志,则重新调度一个新的任务。以上操作需要对linux/kemel/sched. c文件中的schedule ()函数进行相应的修改,其关键伪代码如下所示asmlinkage void_sched schedule(void){......reselect_next_kertsk next = pick_next_task (rq, prev) ;/* 选择下一个被调度的任务 *//*被调度的任务被设置了停止标志位*/if (unlikely (next- > ketask_special_flags & KERTASK_SUSPEND)){next- > state = TASK_ST0P ; /* 设置任务停止状态 */deactivate_task(rq, next, 1) ;/* 从任务队列中删除 */goto reselect_next_kertsk ; /*重新选择下一个被调度的任务*/}......}内存访问控制模块320 ;内存访问控制模块,用于在被调试任务停止运行后,获取待访问的内存地址,在所述内存地址为用户态地址时,调用Ptrace系统调用进行内存访问操作;在所述内存地址为内核态地址时,直接进行内存访问操作。本发明所述调试器对被调试任务的内存访问主要分为用户地址空间的内存访问和内核地址空间的内存访问。由于支持双态调试的调试器是一种内核级调试器,运行在内核地址空间,因此,支持双态调试的调试器可以直接对被调试任务的内核地址空间进行读写操作,不需要进行任何地址转换操作。当支持双态调试的调试器对被调试任务的用户地址空间进行访问时,由于用户态地址是一个虚拟地址,需要通过地址转换才能进行访问。对于用户地址空间的访问,类UNIX 操作系统已经提供了相关接口,如access_pr0cess_vm()函数等。因此,访问被调试任务的用户地址空间,支持双态调试的调试器可以调用ptrace系统调用,执行PTRACE_PEEKTEXT 或者PTRACE_POKETEXT操作,来实现对被调试任务用户地址空间的读写操作。如图6所示,为内存访问控制模块320对被调试任务的内存访问流程,包括步骤601、开始;步骤602、获取待访问的内存地址;步骤603、判断待访问的内存地址是否为用户态地址,若是,执行步骤605 ;否则, 执行步骤604 ;步骤604、直接对内核地址空间进行读写操作,执行步骤607 ;步骤605、调用ptrace系统调用;步骤606、执行PTRACE_PEEKTEXT或者PTRACE_POKETEXT操作,对用户地址空间进行读写操作;步骤607、结束。根据图6的流程,支持双态调试的调试器调用sys_ptrace O函数接口,执行 PTRACE_PEEKTEXT或者PTRACE_POKETEXT操作,实现对被调试任务的内存访问。内存访问主要分为用户地址空间的内存访问和内核地址空间的内存访问,需要修改的是linux/mm/ memory, c文件中的access_process_vmQ函数接口,其关键伪代码如下所示int access_process_vm(struct task—struct氺tsk,unsigned long addr, void^buf, intlen, int write) {......if (IS_VALID_KADD(addr)) {/* 访问内核地址空间内存 */if (write) {/* 写内存 */*addr = *buf ;/*直接修改目标地址的值*/}else {/* 读内存 */*buf = *addr ;/*直接读取目标地址的值*/}}else{ /*访问用户地址空间内存*/if (write) {/* 写内存 */copy_to_user_page () ;/*修改用户地址空间内存接口 */}else {/* 读内存 */copy_from_user_page () ;/*读取用记地址空间内存接口 */}}......} 寄存器访问控制模块330 ;
寄存器访问控制模块,用于在被调试任务停止运行后,获取被调试任务信息,若该被调试任务为内核态任务且为中断或异常停止,从内核堆栈上访问被调试任务的寄存器信息;否则,通过调用Ptrace系统调用进行寄存器访问;若被调试任务为用户态任务,则在所述被调试任务停止在用户地址空间时,通过调用Ptrace系统调用进行寄存器访问;在所述被调试任务停止在内核地址空间时(此时用户态被调试任务肯定发生了中断或异常),从内核堆栈上访问被调试任务的寄存器信息。其中,寄存器访问控制模块330在内核堆栈上访问被调试任务的寄存器信息时, 记录下被调试任务发生中断或异常时的栈帧信息,根据栈帧信息和对应的CPU类型的pt_ regs结构体信息,从内核堆栈上相应位置访问pt_regs结构体大小的内存值,这段内存值就对应着被调试任务发生中断或者异常时的寄存器信息。本发明所述调试器对被调试任务的寄存器访问主要分为两种,一种是对内核态被调试任务的寄存器访问,另一种是对用户态被调试任务的寄存器访问。在类UNIX操作系统中,不论是用户态任务还是内核态任务,都在内核中对应着一个taSk_Struct结构体,用于保存与任务相关的一些信息。在taSk_Struct结构体中,还存在一个threacLstruct结构体,用于保存与任务相关的寄存器信息。当内核态任务发生调度时,内核会将一些主要的寄存器信息(包括指令寄存器、 堆栈寄存器、通用寄存器、返回地址寄存器以及参数寄存器)保存到threacLstruct结构体中。当内核态任务发生中断或者异常时,内核会将内核态任务的所有寄存器信息保存到内核堆栈上。因此,支持双态调试的调试器访问内核态被调试任务的寄存器时,首先判断内核态被调试任务是否发生中断或者异常,如果内核态被调试任务发生了中断或者异常,调试器就从内核堆栈上访问被调试任务的寄存器信息;如果被调试任务发生了调度,就可以通过ptrace系统调用,执行PTRACE_PEEKUSER或者PTRACE_POKEUSER操作,来实现对内核态被调试任务的寄存器访问。对于用户态被调试任务的寄存器访问,支持双态调试的调试器首先判断用户态被调试任务停止的地址空间。如果用户态被调试任务停止在用户地址空间,此时内核会将一些主要的寄存器信息保存到threacLstruct结构体中,支持双态调试的调试器就可以通过 ptrace系统调用,执行PTRACE_PEEKUSER或者PTRACE_POKEUSER操作,来实现对用户态被调试任务的寄存器访问;如果用户态被调试任务停止在内核地址空间,此时用户态被调试任务肯定发生了中断或者异常,内核会将被调试任务的所有寄存器信息保存到内核堆栈上, 支持双态调试的调试器从内核堆栈上访问用户态被调试任务的寄存器信息即可。支持双态调试的调试器访问被调试任务的寄存器流程,如图7所示,包括以下步骤步骤701、开始;步骤702、获取被调试任务信息;步骤703、判断是否是内核态被调试任务,若是,执行步骤704,否则,执行步骤 709 ;步骤704、判断被调试任务停止是否是发生中断或者异常,若是,执行步骤707 ;否则,执行步骤705;
步骤705、调用ptrace系统调用;步骤706、执行PTRACE_PEEKUSER或者PTRACE_POKEUSER操作,进行寄存器访问后, 执行步骤714;步骤707、获取栈帧地址;步骤708、从内核堆栈上相应位置访问pt_regs结构体大小的内存值,执行步骤 714 ;步骤709、判断被调试任务是否停止在用户地址空间,若是,执行步骤710 ;否则, 执行步骤712;步骤710、调用ptrace系统调用;步骤711、执行PTRACE_PEEKUSER或者PTRACE_POKEUSER操作,进行寄存器访问后, 执行步骤714;步骤712、获取栈帧地址;步骤713、从内核堆栈上相应位置访问pt_regs结构体大小的内存值,执行步骤 714 ;步骤714、结束。根据图7的流程,支持双态调试的调试器调用sys_ptraCe O函数接口,执行 PTRACE_PEEKUSR或者PTRACE_POKEUSR操作,实现对被调试任务的寄存器访问。寄存器访问主要分为内核态被调试任务的寄存器访问和用户态被调试任务的寄存器访问,每种类型又可以分为从堆栈上访问和从寄存器结构体上访问两种。需要修改的是linuX/arch/i386/ kernel/ptrace. c文件中的getregO和putregO函数接口,其关键伪代码如下所示static unsigned long getreg(struct task—struct氺child,unsigned long regno){......if (child- > mm) { /*用户态被调试任务*/if( ! IS_STOPPED_IN_KERNEL_SPACE(child)) { /* 停止在用户地址空间*/retval = get_usr_reg (chiId, regno) ;/氺从寄存器结构体上获取寄存器信息*/}else {/*停止在内核地址空间*/retval = get_reg_from_stack(child,regno) ;/氺从堆丰曳上获取寄存器信息*/}}else/*内核态被调试任务*/{
if( ! IS_INTERRUPTED(child)) {/* 发生调度 */retval = get_usr_reg(child, regno) ;/氺从寄存器结构体上获取寄存器信息*/}else{/*发生中断或者异常*/retval = get_reg_from_stack(child, regno) ;/氺从堆丰曳上获取寄存器信息*/}}......}static int putreg(struct task—struct氺child, unsigned long regno, unsigned longvalue) {......if (child- > mm) {/*用户态被调试任务*/if( ! IS_STOPPED_IN_KERNEL_SPACE(child)) {/* 停止在用户地址空间*/put_usr_reg(child, regno, value) ;/氺从寄存器结构体上修改寄存器信息*/}else {/*停止在内核地址空间*/put_reg_to_stack(chiId,regno,value) ;/氺从堆栈上修改寄存器信息*/}}else/*内核态被调试任务*/{if( ! IS_INTERRUPTED(child)) {/* 发生调度 */put_usr_reg(chiId,regno,value) ;/氺从寄存器结构体上修改寄存器信息*/}else{/*发生中断或者异常*/put_reg_to_stack(chiId,regno,value) ;/氺从堆栈上修改寄存
断点设置模块;340 ;在调试器需要设置断点时,断点设置模块,获取断点设置的内存地址,若断点为用户地址空间断点,直接修改用户地址空间内存,将内存指令替换为断点指令;若断点为内核地址空间断点,在被调试任务被调度时,修改用户地址空间内存,将内存指令替换为断点指令;并在被调试任务被内核调度出CPU时,将内核地址空间中的断点指令恢复成原内存指令。调试器通常是通过将内存中的指令替换成断点指令进行断点设置的。当被调试任务运行到断点指令时,就会产生断点异常停止下来,并上报事件给调试器,此时调试器就可以查看被调试任务的信息。当被调试任务恢复运行以后,调试器再将原指令恢复回来。支持双态调试的调试器的断点设置主要分为两种,一种是用户地址空间的断点设置,另一种是内核地址空间的断点设置。由于被调试任务的用户地址空间是独立于其他任务,不会被其他任务所访问。因此,处于用户地址空间的断点不会被其他任务所遇到。而被调试任务的内核地址空间是与其他任务所共用的,如果在内核地址空间设置断点,就有可能被其他任务所遇到,造成非法的断点异常。因此,对于支持双态调试的调试器进行断点设置时,如果断点是用户地址空间的断点,可以直接修改用户地址空间的内存,将内存指令替换为断点指令。如果断点是内核地址空间的断点,只有当被调试任务被内核调度进CPU时,才修改内核地址空间的内存,将内存指令替换为断点指令;当被调试任务被内核调度出CPU时,就将内核地址空间中的断点指令恢复成原指令。支持双态调试的调试器的断点设置流程,如图8所示,包括以下步骤步骤801、开始;步骤802、获取断点设置的内存地址;步骤803、判断是否是用户地址空间断点,若是,执行步骤804;否则,执行步骤 805 ;步骤804、直接修改用户地址空间内存,将内存指令替换为断点指令,执行步骤 807 ;步骤805、判断被调试任务是否被调度,若是,执行步骤806 ;否则,执行步骤807 ;步骤806、修改内核地址空间内存,将内存指令替换为断点指令,执行步骤807 ;步骤807、结束。异常处理模块;350 ;异常处理模块,用于在被调试任务被调度时,若发生异常事件,则基于异常类型, 调用异常处理函数执行异常处理,并在上报当前的异常事件,并在当前异常事件为有效事
16件时,触发任务运行控制模块310,停止被调试任务的运行。支持双态调试的调试器涉及到异常处理主要分为断点异常处理和单步异常处理。在类UNIX操作系统中,当用户态任务运行遇到断点指令时,会停止下来进行断点异常处理,并发送SIGTRAP信号给调试器。而内核态任务运行遇到断点指令时,也会进入断点异常处理函数中进行相关的处理操作。同时,在类UNIX操作系统中,ptrace系统调用也已经提供了对被调试任务进行单步操作的功能。ptrace (PTRACE_SINGLESTEP,...)会使内核在被调试任务的每一条指令执行前先将其阻塞,然后发送信号给调试器,并将控制权交给调试器。由于支持双态调试的调试器是一种内核级调试器,不支持对信号的处理。并且不论是用户态被调试任务还是内核态被调试任务,当被调试任务运行遇到断点指令或者执行单步操作时,都会进入到内核的断点异常处理函数或者单步异常处理函数中。因此,可以通过修改内核的断点异常处理函数的方式来实现对被调试任务命中断点操作的处理。在内核的断点异常处理函数和单步异常处理函数中,支持双态调试的调试器除了执行异常处理的相关操作外,还需要执行上报断点命中事件和停止被调试任务运行的操作,其流程如图9和图10所示。如图9所示,为支持双态调试的调试器断点异常处理流程,具体包括步骤901、开始;步骤902、被调试任务命中断点;步骤903、进入内核的断点异常处理函数;步骤904、断点命中相关处理;步骤905、判断是否是有效断点,若是,执行步骤906,否则,执行步骤910;步骤906、上报断点命中事件;步骤907、删除内核地址空间中的断点;步骤908、设置被调试任务的停止标志位;步骤909、将被调试任务从任务运行队列中删除;步骤910、结束。根据图9所述的流程,支持双态调试的调试器的断点设置实际上是将内存中的指令修改为断点指令,其操作流程属于内存访问范畴,修改方法可以是在支持双态调试的调试器的代码中调用内存访问的相关接口进行断点指令的设置。不论是用户态被调试任务还是内核态被调试任务,当遇到断点指令时,都会进入到内核的断点异常处理函数。在内核的断点异常处理函数中,支持双态调试的调试器除了执行断点异常处理的相关操作外,还需要执行上报断点命中事件和停止被调试任务运行的操作。需要修改的是linux/arch/i386/kernel/traps. c文件中的do_trap ()函数接口,其关键伪代码如下所示 vm86,static void_kprobes do_trap(int trapnr, int signr, char氺str, intstruct pt_regs氺regs, long error_code, siginfo_t*info) {......
bp = lookup_breakpoint(regs_ > eip) ;/* 根据断点异常地址查找断点*/if (bp) {/*命中有效断点*/report_hit_breakpoint () ;/* 上报命中断点事件 */kertsk_inner_suspend_task (current) ;/氺停止被i周i式任务运行*/regs- > eflags | = TF_MASK ;/* 设置单步标志 */current- > kertask_special_f lags | = DBG_STEP_0VER ;/* 设置跨断点标志*/return;/*从断点异常中返回*/}......}如图10所示,为支持双态调试的调试器单步异常处理流程,具体包括步骤1001、开始;步骤1002、调用ptrace系统调用设置被调试任务单步标志;步骤1003、进入内核的单步异常处理函数;步骤1004、单步异常相关处理;步骤1005、判断是否是有效单步,若是,执行步骤1006 ;否则,执行步骤1009 ;步骤1006、上报单步异常事件;步骤1007、设置被调试任务的停止标志位;步骤1008、将被调试任务从任务运行队列中删除,执行步骤1009 ;步骤1009、结束。根据图10的流程,对于单步异常处理,支持双态调试的调试器可以调用sys— ptrace ()函数接口,执行PTRACE_SINGLESTEP操作,实现被调试任务的单步操作。为了实现这一功能,需要对内核实现以下几方面的修改。(1)设置单步标志由于支持双态调试的调试器采用设置停止标志位的方式来控制被调试任务的运行,因此,在设置被调试任务的单步标志后,还需要取消停止标志位,恢复被调试任务的运 ^f0 需要修改的是 linux/arch/i386/kernel/ptrace· c 中的 arch—ptrace () 01 Π, 其关键伪代码如下long arch_ptrace(struct task—struct氺child,long request, long addr,long data){......case PTRACE_SINGLESTEP :/* 设置单步标志操作 */......set_singlestep (child) ;/* 设置单步标志 */kertsk_inner_resume_tsk(child) ;/* 恢复被调试任务运行 */
......break ;/*设置单步标志操作结束*/......}(2)单步异常事件处理当被调试任务进入单步异常函数时,支持双态调试的调试器除了执行单步异常处理的相关操作外,还需要执行上报单步异常事件和停止被调试任务运行的操作。需要修改的是linux/arch/i386/kernel/traps. c文件中的do_debug()函数接口,其关键伪代码如下所示fastcall void_kprobes do—debug(struct pt_regs氺regs, long error— code){......regs- > eflags& = TF_MASK ;/* 取消单步标志 */if (is_valid_singstep (regs- > eip)) {/氺有效单步氺/report_singlestep_exception () ;/* 上报单步异常事件 */kertsk_inner_suspend_task (current) ;/氺停止被调试任务运行*/return ;/*从单步异常中返回*/}else if (current- > kertask_special_flags & DBG_STEP_ OVER){/*跨断点操作*/insert_all_breakpoint () ;/* 回插所有的断点 */return ;/*从单步异常中返回*/}......}信号处理模块360 ;信号处理模块,用于在用户态被调试任务从内核地址空间切换到用户地址空间
时,若用户态被调试任务的信号队列不为空,则调用信号处理函数,逐一获取信号队列中待处理信号,上报信号处理事件,并根据信号对应的信号值判定为关心的信号消息时,触发任务运行控制模块310,停止相应被调试任务的运行;若判定为不关心的信号消息时,不做处理,获取下一个信号。在类UNIX操作系统中,当用户态被调试任务从内核地址空间切换到用户地址空间时,需要判断用户态被调试任务的信号队列是否为空。如果存在未处理的信号,需要对信号队列中的信号进行提取并进行相应的处理,同时上报给父进程。由于支持双态调试的调试器是一种内核级调试器,虽然同用户态被调试任务存在父子进程关系,但对用户态被调试任务上报的信号不会进行处理。因此,支持双态调试的调试器要想获取用户态被调试任务信号处理的状态,就必须对内核中信号处理函数进行相应的修改。 当用户态被调试任务从内核地址空间切换到用户地址空间时,如果存在未处理的信号,就会进入内核的信号处理函数。在该函数中,支持双态调试的调试器首先获取用户态被调试任务待处理的信号值,再根据该信号值在预先配置的信号列表中查找相关设置,决定用户态被调试任务对该信号的处理流程,该处理流程具体包括停止被调试任务的运行, 或者当前信号为不关心的信号,不做处理。
支持双态调试的调试器信号处理流程,如图11所示,包括以下步骤 步骤1101、开始;
步骤1102、用户态被调试任务从内核地址空间切换到用户地址空间; 步骤1103、判断是否存在未处理的信号,若是,执行步骤1104;否则,执行步骤
步骤1104、获取待处理信号值; 步骤1105、上报信号处理事件;
步骤1106、根据信号列表设置决定用户态被调试任务处理流程; 步骤1107、结束。
根据图11所述的流程,当用户态被调试任务从内核地址空间切换到用户地址空间时,如果存在未处理的信号,就会进入内核的信号处理函数。在该函数中,支持双态调试的调试器首先获取用户态被调试任务待处理的信号值,再根据信号列表中的相关设置,决定用户态被调试任务对该信号的处理流程。需要修改的是linux/kernel/signal. c文件中的get_Signal_t0_deliver()函数接口,其关键伪代码如下所示 ;/氺从 言号队列中获取一个信号*/
确定信号处理流程*/else if (ret = = HANDL_SIG_ST0P) {/*信号处理需要停止被调试任务*/kertsk_inner_suspend_task (current) ;/氺停止被i周i式任务运
if ( ! signr)
break ;/*没有待处理的信号则退出*/ ret = report_debug_signal (signr) ;/* 上 艮获取信号事件,
if (ret == HANDLE_SIG_IGN) {/* 忽略信号处理 */ continue ;/*获取下一个信号*/
本发明提供的调试方法和调试器,克服了现有的各种调试器不能同时支持对用户地址空间和内核地址空间进行调试功能的问题,达到了能够对被调试程序在用户地址空间和内核地址空间的运行进行跟踪和调试的目的。显然,本领域的技术人员可以对本发明进行各种改动和变型而不脱离本发明的精神和范围。这样,倘若本发明的这些修改和变型属于本发明权利要求及其等同技术的范围之内,则本发明也意图包含这些改动和变型在内。
权利要求
1.一种调试方法,其特征在于,包括调试器在调试关系建立后,运行被调试任务,所述被调试任务运行在内核态或用户态;调试器判断是否停止所述被调试任务,若是,在所述被调试任务中设置停止标志位,停止所述被调试任务的运行后进行任务访问;所述任务访问类型包括访问所述被调试任务的内存信息和/或寄存器信息;其中,所述调制器对于因中断或异常停止的被调试任务,从内核堆栈上访问寄存器信息ο
2.如权利要求1所述的方法,其特征在于,所述调试器判断所述被调试任务是否停止的判断条件包括所述调试器接收到上层发送的停止指令;或者,所述调试器在调试所述被调试任务时发生异常事件;或者,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,捕获到关心的信号消息。
3.如权利要求2所述的方法,其特征在于,所述调试器调试所述被调试任务时发生异常事件的处理过程包括所述调试器调用相应的异常处理函数进行异常处理,并在检测出当前异常事件为有效异常事件时,上报当前异常事件,并停止所述被调试任务的运行;所述异常事件至少包括断点异常事件和单步异常事件。
4.如权利要求3所述的方法,其特征在于,所述断点异常事件中断点的设置方式包括 所述调试器获取断点设置的内存地址,若为用户地址空间断点,直接将内存指令替换为断点指令;若为内核地址空间断点,待所述被调试任务被调度时,将内存指令替换为断点指令,并在所述被调试任务完成调度时,将内核地址空间中的断点指令恢复成原内存指令。
5.如权利要求2所述的方法,其特征在于,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,检测是否存在未处理的信号消息,若是,获取待处理信号消息的信号值,上报信号处理事件,并基于获取的所述信号值得到所关心的信号消息时,停止所述被调试任务。
6.一种调试器,其特征在于,包括调试开启模块,用于建立调试关系,运行被调试任务,所述被调试任务运行在内核态或用户态;任务运行控制模块,用于在判断出需要停止所述被调试任务时,在所述被调试任务中设置停止标志位,停止所述被调试任务的运行;任务访问模块,用于在所述任务运行控制模块停止运行所述被调试任务时,进行任务访问,所述任务访问类型包括访问所述被调试任务的内存信息和/或寄存器信息;其中,所述任务访问模块对于因中断或异常停止的被调试任务,从内核堆栈上获取寄存器信息。
7.如权利要求6所述的调试器,其特征在于,所述任务运行控制模块判断所述被调试任务是否停止的判断条件包括所述调试器接收到上层发送的停止指令;或者,所述调试器在调试所述被调试任务时发生异常事件;或者,所述调试器在用户态被调试任务从内核地址空间切换到用户地址空间时,捕获到关心的信号消息。
8.如权利要求7所述的调试器,其特征在于,所述调试器还包括异常处理模块,用于调用相应的异常处理函数进行异常处理,并在检测出当前异常事件为有效异常事件时,上报当前异常事件,并触发所述任务运行控制模块,停止所述被调试任务的运行;所述异常事件至少包括断点异常事件和单步异常事件。
9.如权利要求6或8所述的调试器,其特征在于,所述调试器还包括断点设置模块,用于获取断点设置的内存地址,若为用户地址空间断点,直接将内存指令替换为断点指令;若为内核地址空间断点,待所述被调试任务被调度时,将内存指令替换为断点指令,并在所述被调试任务完成调度时,将内核地址空间中的断点指令恢复成原内存指令。
10.如权利要求7所述的调试器,其特征在于,所述调试器还包括信号处理模块,用于在用户态被调试任务从内核地址空间切换到用户地址空间时,检测是否存在未处理的信号消息,若是,获取待处理信号消息的信号值,上报信号处理事件, 并基于获取的所述信号值得到所关心的信号消息时,触发所述任务运行控制模块,停止所述被调试任务。
全文摘要
本发明公开了一种调试器及其调试方法,所述方法包括调试器在调试关系建立后,运行被调试任务,所述被调试任务运行在内核态或用户态;调试器在接收到上层发送的停止指令,或者在调试所述被调试任务时命中异常事件,或者在调试用户态被调试任务时捕获到关心的信号消息,停止运行所述被调试任务,所述停止的方式包括在被调试任务中设置停止标志位;调试器在所述被调试任务停止运行后进行任务访问,所述任务访问类型包括访问所述被调试任务的内存信息和/或访问寄存器信息。本发明所述方法实现了对被调试任务在用户地址空间和内核地址空间的运行进行跟踪和调试的目的。
文档编号G06F11/36GK102346708SQ20101024347
公开日2012年2月8日 申请日期2010年8月3日 优先权日2010年8月3日
发明者吴春江 申请人:中兴通讯股份有限公司
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1