首页> 中国专利> 软件故障的反向调试

软件故障的反向调试

摘要

一种调试系统被配置为解决其中写指令被定向到未知目的地地址的存储器别名状况和其中为多个、并发执行的线程收集控制流信息的并发性状况两者。与应用的先前执行对应的记录的状态值和控制流信息二者被获取。

著录项

说明书

背景技术

计算机和对应的软件应用正变得越来越复杂,使得用户能够以各种方式来存储、访问以及操纵数据。例如,计算应用可以被用于执行文字处理、图形涉及、声音/视觉处理、数据分析、电子通信以及更多。

计算机应用包括由计算机系统的一个或多个处理器执行的可执行指令形式的代码。这些指令由开发者在开发环境中创建并且定序在一起。一旦指令被编译、解译和/或构建,对应的应用就可以被部署以供使用。

遗憾的是,计算机系统和应用不总是以所意图或所预期的方式来操作,这可能导致部署之前或之后的应用崩溃。至少出于该原因,调试应用以标识和修复代码中使应用崩溃的指令经常是必需的。

还已经创建了不同的开发人员工具来协助开发者编写、编辑、测试和调试应用的可执行指令,以帮助解决系统故障。这些工具中的一些工具包括程序代码文本编辑器、源代码编辑器、调试器和集成开发环境(IDE),仅举几例。然而,遗憾的是,未被注意的程序错误有时会在开发阶段中持续存留,并且将直到软件应用被部署以供一般或广泛使用之后才会被注意到。在应用已经被部署的情况下,调试应用故障可能会更加困难,因为必须检查更大数量的代码以标识引起问题的指令。

最近,被称为“时间旅行(time travel)”调试的调试过程已经出现为一种流行的技术。利用时间旅行调试,跟踪器程序记录处理器指令中所有的处理器指令的状态、以及在程序的执行期间存在的状态中的许多状态。稍后,当程序被调试时,跟踪器数据被用于重创建在执行期间任何点存在的确切的存储器状态和寄存器状态,以便标识代码中引起错误的特定的指令和位置。该技术已经在收集跟踪数据的高资源使用在某种程度上可以接受的开发环境中对开发人员特别有优势。然而,其仍然是不合期望的,因为将被包括在常规时间旅行调试中的繁重的跟踪中的数据记录日志在计算上是昂贵的。

附加地,应注意,用于时间旅行调试的该类型的繁重的跟踪对大部分部署后场景非常不实用。从而,大多数部署后调试众所周知地困难,因为与在开发期间所获取的、被收集用于时间旅行调试的大量数据相反,开发者已经必须依赖于非常有限的信息(例如,本质上仅有在部署之后被收集的被包括在存储器转储文件(dump)中的信息)。特别是应注意,应用的整个执行历史(例如时间旅行跟踪数据)经常是不可用的,因为对那些系统的高保真度的程序跟踪过于昂贵(即,客户端不想要连同执行应用一起来运行资源密集的调试程序)。该有限的信息可用性是为何调试已经被部署的应用是如此困难的主要因素中的一个主要因素。

已经做出了一些尝试来向开发者提供可以在调试期间被使用的其他数据,诸如从稀疏处理器跟踪所获取的客户端侧调试数据。例如,

尽管有前述内容,已经做出一些尝试来导出、或者更确切地说推断以上所叙述的从使用硬件处理器跟踪所获取的控制流信息中缺失的数据值以及缺失的存储器地址。通常,这些尝试将被包括在存储器转储文件内的状态值与轻量级的硬件跟踪数据(即,控制流)组合,并且然后执行递归的后向分析来得到与每个处理器指令对应的未知状态。尽管这些调试技术中的一些调试技术已经是有帮助的,但是这些技术未被广泛采用,因为它们仅成功出标识处理器指令状态的总数目中的一小部分。

结合该低性能,利用稀疏硬件跟踪数据调试程序的现有尝试已经遇到限制其可伸缩性和实用性的明显问题。例如,一个这样的问题在本文中被称为“存储器别名(memoryaliasing)”。该类型的问题当在递归的后向推断分析期间遇到针对未知目的地地址的写指令时发生,并且调试器无法知道如何处置针对该未知目的地地址的状态值。为解决该问题,一些现有的提议包括做出尝试来推断针对所有未知存储器位置的可能的选项中的所有可能的选项。然而,该类型的方法在实用上是被禁止的,因为调试系统不能够在不抵达资源的严重故障的情况下来跟踪并且解决针对所有分支的所有可能的推断,特别是针对大型和复杂的程序。另一种方法是避免做出针对未知存储器位置和对应的地址值的任何假设。然而,这种朴素的方法是不实用的,因为其实际上阻止了调试软件标识/推断任何大数量的信息,特别是针对复杂的程序。在这方面,前述方法中的两种方法都是不充分的,并且在实际上是不可伸缩的。当使用稀疏/瘦硬件跟踪数据来调试已经执行了大数量的指令(例如,数千个指令、数万个指令、数十万条指令、或者甚至数百万条指令)的程序是,特别如此。

基于稀疏硬件跟踪数据的时间旅行调试具有的另一问题是“并发性”问题,当应用在多个处理器上执行和/或利用被分别跟踪的多个线程执行时,该“并发性”问题存在。该问题具体涉及难以获悉指令序列,因为针对每个跟踪的不同的指令没有各自的时间戳。替代地,在不同的跟踪中的每个跟踪内,指令的块与单个时间戳相关联。使用块时间戳,能够标识每个块的开始时间,但是不可能标识指令块中每个指令的具体时间和相对序列,特别是当并发地与在其他线程上执行的指令一起并发地执行那些指令的块时。由于推断所有可能的定序选项会要求指数式的分支/分析的潜在可能性,现有的调试系统的确不能够处置该并发性问题。

鉴于前述内容,将理解,目前存在需要来标识调试解决方案,特别是可以有效地扩展的解决方案,这些解决方案减轻由于利用稀疏/瘦硬件跟踪的时间旅行调试而导致的存储器别名和/或多线程并发性问题。

本文中所要求保护的主题不限于解决上述任何缺点或仅在上述环境中操作。更确切地说,仅提供该背景来说明在其中可以实践本文中所描述的一些实施例的一个示例性技术领域。

发明内容

所公开的实施例包括被配置为利用稀疏硬件跟踪数据(例如,控制流数据)结合记录的状态值的有限集合来执行调试的计算机系统、方法和硬件存储设备。

在一些实例中,以智能确定何时避免做出可能导致禁止的分析的推断的方式来执行调试,由此减轻时间旅行调试期间的存储器别名和/或多线程并发性问题。

在一些实施例中,所公开的调试过程包括计算机系统,该计算机系统获取与应用的先前执行对应的一组记录的状态值。作为示例,这些记录的状态值可以被包括在存储器转储文件中(例如,来自应用崩溃),或者作为与被调试的代码内的断点或另一指定位置对应的手动触发的程序停止的结果。附加地,从被包括在应用的先前执行的瘦硬件跟踪中的硬件跟踪数据获取控制流信息。该控制流信息描述在应用的先前执行期间被执行的一组处理器指令的序列。随后,执行对该一组处理器指令的迭代的后向和前向分析,以标识与该处理器指令对应的程序状态或处理器状态中的至少一些程序状态或处理器状态。在迭代的后向和前向分析期间,当存储器写指令遇到未知的目的地写地址(由此引起存储器别名状况)时,迭代的后向和前向分析避免推断针对该存储器写指令的未知目的地地址处的未记录的程序状态值。因此,未知目的地地址中的过期值保持未被存储器写指令修改,至少直到随后检测到与未知目的地地址相关联的冲突为止。响应于检测到这样的冲突,如果未知目的地地址中的过期值可修正,则经由错误修正技术来修正该过期值。然而,如果该过期值不可修正,则提供指示来表明该过期值不可修正并且因此将不被用于前向和后向分析期间进一步的推断。因此,在一些实例中,这些过程可以被用于地址存储器别名状况。

还提供了可以被用于帮助解决与多线程实现有关的并发性问题的有关实施例。在这些实施例中,获取与应用的先前执行对应的一组记录的状态值。附加地,标识与应用的先前执行对应的瘦硬件跟踪数据。在此,瘦硬件跟踪数据包括针对在应用的先前执行期间被执行的处理器指令的操作块的控制流信息、以及针对操作块中的每个操作块的对应的操作块时间戳。在这种情况下,操作块中的至少一些操作块由不同的处理器线程执行(由此引起并发性状况)。然后,通过基于其对应的操作块时间戳顺序排序操作块中的每个操作块,来生成操作块的单个合并序列列表。其后,标识针对所标识的处理器指令中的一些处理器指令的程序状态。这通过对被包括在操作块的单个序列列表中的处理器指令的迭代的后向和前向分析而实现。因此,在一些实例中,这些过程可以被用于生成单个合并列表,以解析已知并发性问题中的一些并发性问题。

在一些实施例中,作为以上所描述的顺序对操作块顺序排序的一部分,基于重叠的操作块的对应的操作块时间戳来标识某些重叠的操作块,其中这些重叠的操作块具有重叠的执行时间段。响应于确定重叠的操作块中的至少两个重叠的操作块访问相同的存储器位置、并且该至少两个重叠的操作块中的至少一个操作块包括对该存储器位置的写操作,标记该至少两个重叠的操作块,以防止存储器位置被用于做出推断(例如,在迭代的后向和前向分析期间)。如果重叠的操作块不包括对相同存储器位置的写操作,则来自那些操作块的指令的合并不被标记,并且那些操作块以(相对于彼此的)任意次序而被合并到单个合并指令序列中。在这方面,所公开的实施例可以被用于通过生成来自不同的线程的操作块的单个合并序列来帮助解决多线程并发性问题,其包括以在合并序列的任意排序来合并具有并发操作定时的操作块中的一些操作块,并且同时标识在迭代的后向和前向调试分析期间避免用于做出推断的存储器位置。

提供本发明内容以简化的形式介绍一些概念,这些概念将在一下详细描述中进一步描述。该发明内容并非旨在标识所要求保护的主题的关键特征或必要特征,也不旨在用于协助确定所要求保护的主题的范围。

附加的特征和优点将在以下描述中阐述,并且部分地从该描述中将是明显的,或者可以通过本文中的教导的实践而得知。可以通过在所附权利要求书中特别指出的工具和组合来实现和获取本发明的特征和优点。通过以下描述和所附权利要求书,本发明的特征将变得更加充分明显,或者可以通过以下阐述的本发明的实践来了解本发明的特征。

附图说明

为了描述获得上述以及其他优点和特征的方式,将通过参考在附图中示出的具体实施例来对以上简要描述的主题进行更具体的描述。应当理解,这些附图仅描绘了典型的实施例,因此不应视为对本发明范围的限制,将通过使用附图来对本发明的实施例进行附加的描述和详细说明。

图1图示了可以在其中开发和显示源代码的IDE(集成开发环境)的示例。

图2图示了被编译成二进制/汇编代码、并且后续作为代码块由一个或多个处理器线程执行的源代码的流。

图3图示了与调试相关联的动作的流程图。

图4A和图4B图示了处理器指令的序列和针对处理器指令的对应的控制流,以及作为崩溃或者触发终止处理器指令的序列的事件的结果而被收集的针对处理器指令的最终的记录的状态。

图5A和图5B图示了数据推断图,数据推断图被填充有从对控制流的迭代的后向和前向分析所获取的信息以及图4A和图4B中所引用的与处理器指令相关联的最终的记录的状态信息。

图6图示了与用于选择性地避免在时间旅行调试期间做出推断以减轻存储器别名问题的方法相关联的动作的流程图。

图7图示了与多线程时间旅行调试相关联、并且包括将来自不同线程的操作序列合并在一起的动作的流程图。

图8图示了由不同线程执行的代码块的示例,并且在其中代码块中的一些代码块包括在并发的时间段期间被执行的处理器指令。

图9图示了来自图8的代码块的示例的另一图示,并且在其中代码块被合并为单个合并序列以供调试。

图10图示了与多线程时间旅行调试相关联、并且包括将来自不同线程的操作序列合并在一起的动作的流程图。

图11图示了包括被包括在所公开的实施例内或者可以被用于实现所公开的实施例的计算机系统的计算环境/架构。

具体实施方式

所公开的实施例包括计算机系统、方法和硬件存储设备,被配置为利用稀疏硬件跟踪数据(例如,控制流数据)结合一组有限的记录的状态值以如下方式来执行时间旅行调试:智能地确定何时避免做出可能导致禁止的分析的推断的方式来执行调试,由此减轻存储器别名和/或多线程并发性问题。

在一些实施例中,获取与应用的先前执行对应的一组记录的状态值,诸如从存储器转储文件、或者作为与被调试的代码内的断点或另一指定位置对应的手动触发的程序停止的结果。还从被包括在应用的先前执行的稀疏硬件跟踪中的硬件跟踪数据获取控制流信息,该控制流信息描述在应用的先前执行期间被所执行的一组处理器指令的序列,但是省略针对每个处理器指令的详细的时间戳信息和/或省略与每个处理器指令相关联的存储器地址和/或值。

随后,在对该一组处理器指令的迭代的后向和前向分析期间,标识与处理器指令对应的程序状态或处理器状态中的至少一些状态。该过程迭代,直到不再有较新的状态/值被标识,或者更确切地说,被推断。附加地,在迭代的后向和前向分析期间,当存储器写指令遭遇未知目的地写地址(其可以引起存储器别名状况,针对该存储器写指令的有多个可能的被推断位置)时,迭代的后向和前向分析避免推断针对存储器写指令的未知目的地地址处的未记录的程序状态值。作为结果,未知存储器地址中的过期值保持b不被存储器写指令修改,直到至少在随后检测到与未知目的地地址相关联的冲突为止。响应于在后续的后向/前向分析期间检测到这样的冲突,如果未知目的地地址中的过期值可修正,则经由错误修正技术来修正该过期值。然而,如果该过期值不可修正,则提供指示来表明该过期值不能被修正,并且因此将不被进一步用于前向和后向分析期间的推断。

通过以这种方式来实现时间旅行调试,相比如果在调试分析中忽略所有的未知存储器位置(或者避免其被使用)则会可能的,标识针对处理器指令的更大数量的值是可能的,并且同时仍然提供通过不推断针对未知存储器位置的所有可能的选项而可伸缩的解决方案。这是一种技术益处,用于改进利用稀疏硬件的跟踪时间旅行调试可以被达成的方式,并且可以被用于解决在利用稀疏硬件跟踪的调试期间发生的存储器别名的技术问题。

所公开的实施例还可以被用于解决与针对多线程/处理器场景的时间旅行调试相关联的并发性的技术问题。例如,在一些实施例中,获取与应用的先前执行对应的一组记录的状态值(例如,响应于转储或者开发者所触发的事件)。附加地,包括控制流信息的稀疏硬件跟踪数据与稀疏时间戳数据一起被获取,该控制流信息针对在应用的先前执行期间被执行的处理器指令的操作块,该稀疏时间戳数据指示针对每个操作块的至少一个时间戳,但是不包括针对操作块中的每个处理器指令的时间戳。

然后,通过基于对应的操作块时间戳对操作块中的每个操作块顺序排序,来生成操作块的单个合并序列列表。基于确定这些操作块不包括对未知存储器的任何写指令,以任意次序来定序在并发的时间段期间被执行的操作块中的至少一些操作块。

其后,通过执行对被包括在操作块的单个合并序列列表中的处理器指令的迭代的后向和前向分析,标识针对所标识的处理器指令中的至少一些所标识的处理器指令的程序状态。

当并发执行的操作块被发现时,其中针对该操作块至少一个处理器指令包括对未知位置的写入(如在后向/前向分析期间所发现的),该操作块被标记,以使得其(或者其特定的写操作)不被用于做出针对在操作的合并序列的后向/前向分析期间推断的任何值的推断。

通过以这种方式来实现时间旅行调试,相比如果在分析中忽略所有的未知存储器位置(或者避免其被使用)的可能情况,可以标识更大数量的针对处理器指令的值是,并且同时仍然提供通过不推断针对未知存储器位置的所有可能的选项、并且无需推断针对多线程操作的所有可能的操作序列而可伸缩的解决方案。这是一种技术益处,用于改进利用稀疏硬件的跟踪时间旅行调试可以被实现的方式,并且可以被用于解决在利用稀疏硬件跟踪的调试期间发生的存储器别名和多线程并发性的技术问题,该稀疏硬件跟踪用于利用多个处理器/线程所执行的应用。

注意现在针对图1,图1图示了IDE(集成开发环境)的示例,开发者可以在该IDE中开发和分析可以以各种不同的编程语言来编写的代码,诸如示例代码100。

图2图示了该代码,也被称为源代码200,是如何被编译成二进制或汇编代码205。其后,该汇编代码205作为处理器指令的序列210由一个或多个处理器来执行,以实现代码中所描述的应用的功能性。注意,所图示的代码块中的每个代码块(例如,代码块A–代码块E,以及省略号215表示可以存在任何数目的代码块)可以各自包括多个单独的处理器指令。

在操作期间,利用例如跟踪,处理器指令被记录日志,跟踪反映标识被实现的指令的类型的信息,并且有时是与处理器指令相关联的时间戳和/或存储器位置。这些类型的跟踪可以被记录日志为软件解决方案,以在针对调试的稍后的时间被参考。然而,作为跟踪的部分所收集的信息越多,跟踪就变得越昂贵。在实践中,生成将在应用程序的操作期间被执行的所有处理器指令的每个寄存器的每个状态记录日志的重量级跟踪,在计算上太昂贵。

因此,一些系统避免创建和存储完整的跟踪。代替地,一些系统跟踪执行信息的更有限的集合(例如,如与软件跟踪相对,使用硬件跟踪),并且通过被称为时间旅行调试的过程来推断状态值。在这样的实例中,系统参考程序的已知状态(诸如在崩溃转储期间可以被获取的),并且然后使用该初始的已知信息来外插以前未知的状态值。

例如,这被图示在图3的流程图中。如所示出的,系统可以获取与应用的先前执行对应的一组记录的值(动作305)。该一组记录的值可以包括应用的最终状态,例如,如在未预料的崩溃所导致的核心转储文件中所反映的。在其他一些实施例中,可以响应于有意的用户所选择的收集事件而从程序中获取记录的值,诸如开发者选择针对断点处的程序、或者在已经被执行或正在执行的程序的源代码中的其他实例而存在的当前值/记录的值。

系统还获取针对在应用的先前执行期间被执行的一组处理器指令的控制流信息(动作310)。可以从瘦/稀疏硬件跟踪来获取该控制流信息(例如,诸如

然而,在一些实例中,跟踪(即使是稀疏的)可能不包括针对在最终的记录的状态之前的所有处理器指令的控制流信息的完整集合。例如,在环形缓冲区中对跟踪的记录可以有效地限制指令的数量以及对应的控制流信息可以记录到固定大小的环形缓冲区。在其他实例中,跟踪包括硬件跟踪数据(即,如与传统时间旅行调试中所使用的大量的数据不同的稀疏数据),硬件跟踪数据跨越应用的执行的所有部分或实质部分并且被存储在大型存储库中,使得该跟踪是在程序的最终的记录的状态之前的控制流信息的完整组合。

然后,使用该最终的记录的状态信息、以及从稀疏跟踪所获取的控制流信息,系统通过执行对处理器指令的迭代的后向和前向分析来标识状态信息,该状态信息针对在前的处理器指令中的至少一些在前的处理器指令(动作315)。该过程包括标识程序的以前并且未知的状态(例如,寄存器值、存储器值、存储器地址等)的指令,以外插在该先前的指令的执行之前该状态很可能是什么。以示例的方式,如果针对存储器位置(A)的已知状态值是4(例如,在存储器转储文件或断点的时间处,已知最终状态值是4),但是在该存储器位置(A)处的以前的值是未知的,则通过检查跟踪中的以前的指令来外插、推断或者得到该值是可能的。例如,如果这样的指令是将存储器位置(A)处的值增加1的指令,则可以推断/外插储器位置处的先前状态值是3。在一些情况下,为了推断状态值,迭代的后向和前向分析迭代地向后退1个指令步骤、2个指令步骤、3个指令步骤、4个指令步骤或者更多,并且然后迭代地向前进1、2、3、4(或更多)个指令步骤,以便推断状态值。在这方面,在尝试得到/推断状态值时,迭代的后向和前向分析可以在时间上向后和向前旅行任何数目的处理器步骤。

如以上所指示,一旦处理器指令被遍历(反向)以标识一个、一些或者所有可能的值,然后系统就可以向前遍历处理器指令以推断新状态,该新状态可以是曾经未知的并且现在可以利用所推断的信息而被解析。该后向/前向调试过程可以持续,直到知道所有值和/或直到系统不再能够解决未知值为止。

遗憾的是,如果存在大量数量的未知值(诸如经常发生在利用稀疏硬件跟踪时),则如以前所描述的,存储器别名的问题可以发生。例如,如果指令是针对特定地址(其是未知的)来读或写值,则不可能推断/外插在所有情形中该地址是什么(即使当参考先前指令时)。为解决该问题,一些系统推断并且跟踪所有可能的地址位置。然而,这种指数式分析是不可伸缩的,并且可能在解析到大量数量的未知值之前,使调试器崩溃。其他系统在外插过程期间忽略所有的未知值,并且仅解析那些明显的未知值。然而,大数量的未知值还可以导致系统不可伸缩,因为其实际上不能够做出足够的推断来提供实用的调试解决方案。

当前所公开的实施例中的一些实施例提供了一种解决方案,该解决方案选择性地忽略与写指令对应的一些未知值,同时仍然推断其他未知值,以由此解决存储器别名问题。这将关于图4A-图6来更详细地讨论。

图4A-4B图示了个体处理器指令(A、B、C、D、E、F、G和H)或备选地指令块的序列,其按照在程序的操作期间被处理器执行405的指令序列400进行排序。如该示例中还示出的,处理器指令H的最终执行(或所尝试的执行)与崩溃或者触发事件对应,在该崩溃或者触发事件的点处,程序的最终的记录的状态415(见图4B)被记录日志。该最终的记录的状态415可以包括核心转储文件。备选地,该最终的记录的状态415可以包括与断点或者源代码中由用户指定的其他位置对应、并且在程序的执行期间获取的日志。

指令序列还与由稀疏硬件跟踪标识的控制流420(见图4B)对应。尤其,该稀疏跟踪省略与每个处理器指令(例如,指令A、B、C、D、E、F、G或H)的状态对应的至少一些时间戳和/或存储器位置。该控制流420维持指示源代码在执行期间的分支的信息,并且该信息可能不是如指令A、B、C、D、E、F、G和H的执行405看起来或表现出的简单顺序处理。控制流420还标识被执行的处理器指令的类型,但是那些指令的实际状态值(例如,存储器位置和那些位置中的值)不被记录或包括在控制流420或针对每个指令的记录的状态415中。

为了有效地调试程序,开发者将需要评估引起特定结果(例如,崩溃、要被设置的特定值等)的指令。这可能要求分析源代码中在“特定结果”之前的处理器指令,以及这些指令中的每个指令的产生的状态。然而,当仅依赖于记录的状态415和来自稀疏跟踪的控制流420时,存在许多未知状态。因此,解析尽可能多的未知状态值以使得调试过程有效是必需的。

基于控制流信息和程序的最终的记录的状态来解析未知状态值的一种方式是生成并且解析/填充数据推断图,该数据推断图基于从最终的记录的状态和后向/前向分析所导出的推断,来标识针对在控制流中标识的在前的处理器指令的不同执行时间序列、以及针对那些指令中的每个指令的对应的处理器状态。

图5A图示了数据推断图500A的示例,数据推断图500A被用于标识处理器状态,该处理器状态针对图4B中所示的控制流420和记录的状态415信息、并且与图4A的指令序列400的执行405对应。该推断图包括针对指令序列400中的每个处理器指令而存在的寄存器状态/值。例如,数据推断图500A中的第一行与在相对执行时间T1处的处理器指令A的执行对应。类似地,第二行与在相对执行时间T2处的处理器指令B的执行对应,第三行与在相对执行时间T3处的处理器指令C的执行对应,第四行与在相对执行时间T4处的处理器指令D的执行对应,第五行与在相对执行时间T5处的处理器指令E的执行对应,第六行与在相对执行时间T6处的处理器指令F的执行对应,第七行与在相对执行时间T7处的处理器指令G的执行对应,第八行与在相对执行时间T8处的处理器指令H的执行对应并且包括状态信息,该状态信息从与处理器指令H的最终的记录的状态415相关联执行或尝试执行被获取。

注意,执行时间(T1-T8)是相对时间,因为控制流420(在一些情况下)可能不维持针对每个处理器指令的该实际信息。然而,针对一些实施例(如以下所讨论),时间戳是已知的并且是与不同的代码块相关联的代码块时间戳,诸如,如果指令序列400的所图示的指令(例如,A、B、C、D、E、F、G和H)包括实际代码块。

现在返回到数据推断图500A,注意在该示例场景中存在可以针对每个处理器指令确定的四个不同的信息列。然而,该示例仅是说明性的而非限制性的。因此,将理解,数据推断图500A可以包含许多更多的信息列(或更少的信息列),这些信息列与不同的处理器和存储器位置对应并且在本文中统称为状态值,这些存储器位置与执行程序的处理器的处理器状态相关联。这些状态值可以包括寄存器值(例如,寄存器A、寄存器B处的值等)和各种地址值(例如,与全局变量、缓冲区等对应的地址)。在一些实例中,状态值还可以包括时间戳(未示出)。

图5B图示了基于通过以上描述的迭代的后向510和前向520分析而被实现的演绎/推断,数据推断图500A(图5A)可以被解析或者被填充。

将理解,在该分析期间被推断/被演绎、并且引起图5B的更多被填充的数据推断图500B的新状态数据是迭代的过程,该迭代的过程可以涉及控制流的各种遍历,以仅有在记录的状态415中所获取的实际状态值开始,该实际状态值针对不同的处理器指令中的每个处理器指令,该不同的处理器指令是在记录的状态415中标识的,该记录的状态415是从核心转储文件或其他提取过程所获取的、并且基于控制流指令信息。例如,在一些实例中,调试自始至终向后遍历510控制流信息(例如,按照时间从T8到T1),使用从最终状态值(即在行T8中记录的那些值)提供的初始有限的信息来推断/确定数据推断图中其能够推断/确定的任何可能的新值。

分析还在前向520方向上向前遍历控制流,使用控制流信息和新推断的值来标识更多的新值,这些更多的新值在后向510处理中未被推断/被确定。该过程可以持续地重复,迭代的地在前向分析与后向分析之间交替,直到没有新状态值被发现、和/或直到预确定的时间已经经过、和/或直到预确定数量的迭代已经发生。

在一些实例中,在后向/前向分析期间,调试器标识对未知目的地地址写操作。例如,在当前的示例中,指令D(与执行时间T4对应)可以引起对具有未知地址的缓冲区的写操作。在这样的实例中,调试器忽略或者留下状态为过期值(不管其以前是什么),而不是尝试推断该地址的实际值(和/或跟踪所有可能的选项)。例如,在这种情况下对应的缓冲区目的地地址的过期值被抽象地表示为值(X),该值(X)是在转储文件的时间处(例如,执行的时间T8)处存在的相同的值,但是其是未知目的地地址。

在后向分析510期间,由于(根据当前的示例)指定执行时间T5、T6、T7和T8处的留下该目的地地址未被修改的指令的控制流信息420,该值(X)在数据推断图500B中(作为过期值)向上被传播。除非并且直到在控制流信息中检测到针对该未知目的地地址的写操作为止,该过期值可以被用于做出推断。

然后,响应于在控制流420中检测到用于向该未知目的地地址写入的指令(例如,与代码块D对应,在执行时间T4处向缓冲区写入),针对该目的地地址的值(X)被锁定,并且被防止被用于在迭代的后向和前向分析中做出进一步的推断,除非并且直到稍后发现针对该相同地址位置(X)的冲突为止。这由单元格530中的“????”以及缓冲区列的前三个框反映,因为没有对该目的地地址做出进一步的推断。稍后,然而,如果发现冲突,诸如向相同的未知目的地地址(例如,针对在执行时间T4的缓冲区的目的地地址)写入的另一指令,则系统将解析该冲突并且确定哪个值应当被使用。

包括对处理器指令的附加分析和在控制流420中所标识的其他信息的各种启发式方法可以被用于在两个冲突的值(其中之一是过期值)之间选择以确定哪个值更合适。如果,例如,针对目的地地址的冲突的指令被发现,该冲突的指令指定目的地地址为值Y,并且(由于启发式方法和其他分析)确定了该Y值比过期的X值更正确,则值Y会被用于填充对应的单元格530,因为该未知值被确定为是更正确的。备选地,(在其他情形中)可以确定值是更正确的,因为基于控制流中的其他指令,与Y值相关联的指令可能不是条件性的、并且可以被忽略或者是不确定的。在这些实例中,可以确定值X是正确的并且被解锁以做出进一步的推断。

在一些实例中,过期值是可修正的并且通过从对系统可访问的其他信息(例如,从另一指令所获取的其他控制流信息、其他IDE信息、其他被存储的状态信息、响应于用户输入等)来恢复未知目的地地址而被修正,并且然后使用恢复的未知目的地地址来推断过期值的实际值。

在另一些实例中,没有足够的信息可用以解析针对被锁定的过期值的冲突到阈值程度的确定性,并且使得其保持不可修正。在这样的实例中,该值保持被锁定不做出进一步推断,并且可以在推断表内被标记以防止从该未知值做出进一步推断。在一些实例中,这可以简单地包括忽略存储器写指令。

尽管这可能限制可以针对数据推断图所做出的推断的总数目,但是通过仅在存在针对那些目的地地址未知的被检测到的写操作时,选择性地锁定目的地地址值不被用于做出推断,其仍然允许做出一些推论。该技术还进一步允许,当稍后发现冲突并且响应于解析该冲突,解锁被锁定的值中的一些被锁定的值以做出进一步的推断。

注意参照图6,图6图示了包括与处置存储器别名相关联的动作的各种存储器别名操作的流程图600,该存储器别名可能发生在基于控制流信息和稀疏硬件跟踪数据来执行反向调试时。

如所示出的,首先被图示的动作是在时间旅行调试的迭代的后向和前向分析期间遇到存储器写处理器指令的动作(动作610)。该存储器写指令被包括在从稀疏硬件跟踪中的控制流信息获取的一组处理器指令中,该一组处理器指令在应用的先前执行期间被执行,该应用的先前执行是相对于针对程序所获取的记录的核心转储文件状态信息(或其他提取的记录的状态信息)的定时。写指令还是对未知目的地地址的指令。

在所公开的实施例中,调试系统正在执行对值的迭代的后向和前向分析以填充推断图。然而,响应于检测到对未知目的地地址的存储器写指令(动作610),系统避免用在与该写指令对应的未知目的地地址处的未被记录的程序状态值来做出任何推断(动作620)。这导致未知目的地地址中的过期值在迭代的后向和前向分析期间保持不被该对未知目的地地址的存储器写指令修改,除非并且直到检测到也与该未知目的地地址相关联的冲突为止。

然后,响应于检测到这样的冲突,如果未知目的地地址中的过期值是可修正的,则其被修正,或者备选地,如果其是不可修正的,则其保持未被修正(动作630)。如果控制流中的其他启发式方式或其他信息可以被用于查明该值应当是什么,则该值是可修正的。例如,这可以由检查另一写操作引起,该另一写操作指定针对该未知目的地地址的地址。

注意现在参照图7,其图示了与解决与多线程调试相关联的并发性问题有关的实施例。

图7图示了与并发性操作相关联的动作的流程图700,这些动作与用于生成合并序列列表的实施例有关,该合并序列列表可用于执行对多线程过程的时间旅行调试。这些多线程过程可以包括与在单个处理器上被处理的单个应用的操作相关联的多个线程,或者在多核处理器或者多个处理器上被处理的不同的线程。

当系统崩溃并且提供最终的记录的状态的转储文件时,或者当操作者选择选择点用于获取程序在该选择点处最终的记录的状态时,可以获取在该最终的记录的状态之前的各种硬件跟踪数据,以利用记录的状态信息执行调试。在一些实例中,如以前所描述的,硬件跟踪数据包括瘦/稀疏硬件跟踪数据,其指定针对在应用的先前执行期间被执行的处理器指令的操作块的控制流信息。在一些实例中,诸如多线程过程实施例,控制流信息包括与多个不同的线程相关联的多个不同的操作/代码块。在这样的实例中,控制流信息还包括针对每个块的至少一个操作块时间戳,但是没有包括针对每个块内的每个处理器指令的单独的时间戳。

在一些实例中,当瘦硬件跟踪数据从与远程系统的崩溃对应的远程系统被获取时,该瘦硬件跟踪数据可以被标识(动作710)。在其他实例中,瘦硬件跟踪数据通过IDE被标识(动作710),在该IDE中,操作者选择断点/选择点,从该断点/选择点来开始分析,并且IDE标识在程序的所选择的/最终的记录的状态被标识的选择点处之前的处理器指令。

一旦瘦硬件跟踪数据从一个或多个硬件跟踪被获取(动作710),并且该瘦硬件跟踪数据包括与不同的线程相关联的多个不同的操作块,那些操作块就被合并到单个合并的序列中(动作720)。在一些实例中,在单个合并序列中对操作块的排序是基于与每个操作块相关联的操作时间戳。操作时间戳可以是与每个对应的操作块内被操作的第一个指令对应的时间戳,或者另一时间戳。时间戳还可以是实际时间、循环计数、或者任何其他相对的时间戳测量,这些时间戳可以被用于区分不同的操作块之间的相对操作时间。

一旦生成了单个合并序列列表(动作720),其就被用于执行利用迭代的后向/前向分析的前述时间旅行调试(动作730),并且可以还可以包括选择性地锁定某些值而不被用于在迭代的后向/前向分析期间做出推断。更具体地,该过程包括标识(例如,演绎/推断)执行的对应时间处的特定程序状态,该执行的对应时间处的特定程序状态是针对以下处理器指令中的至少一些处理器指令中的每个处理器指令:这些处理器指令在应用的先前执行期间被执行(如不同的稀疏线程跟踪的单个顺序列表中所标识的,并且以前未被记录在直接从单独的线程跟踪获取的控制流信息中。

图8图示了表示三个不同的跟踪的实施例,该三个不同的跟踪与在应用的操作期间被执行的三个不同的线程对应。第一线程(线程1)与代码块A、代码块B和代码块C的执行相关联。线程2与代码块D和代码块E的执行相关联。线程3与代码块F和代码块G的执行相关联。这些代码块(A-G)中的每个代码块与在单个应用的操作期间被执行的多个单独的处理器指令相关联。在当前的实施例中,这些代码块中的每个代码块(以及其对应的处理器指令)从控制流信息被标识,该控制流信息被记录在以上所描述的瘦/稀疏硬件跟踪中。

在当前的多线程实施例中,诸如应用到当前的示例,在对应的应用的操作期间,代码块由单独的线程执行,或者在相同的处理器上,或者在不同的处理器上。还可以从控制流信息获取与不同的代码块(A-G)向关联的硬件时间戳800(例如,如从稀疏线程跟踪所获取的)。操作块时间戳可以是每个对应的操作/代码块内被处理的第一个指令的时间戳(例如,TS 0针对代码块A,TS 4针对代码块D等)。时间戳还可以是实际时间、循环计数、或者任何其他相对时间戳测量,这些时间戳可以被用于区分不同的操作块之间的相对操作时间。

针对每个代码块(A-G)的至少一个时间戳(并且,有时多个时间戳)可以被标识。在一些实例中,这包括针对被包括在每个代码块内的所有处理器指令的标识一些、但是不是所有的时间戳,使得不可能(具有确定性地)确定当那些代码块并发地被执行时,在代码块内存在的不同的处理器指令的实际执行顺序。以示例的方式,代码块C和代码块G在图8的实施例中并发地被执行,并且代码块C内的哪些处理器指令在被包含在代码块G中的个体处理器指令之前/之后被执行是未知的,因为执行看起来是在相同的时间处开始的(例如,在执行时间TS 22处)。该场景表示并发性问题,因为查明定序信息是不可能的,而该定序信息通常对于利用不同的处理器指令的合并列表来执行推断性时间旅行调试来说是必需的。

尽管有前述内容,但是当前的实施例仍然能够通过以下来构建有用的处理器指令的合并列表:选择性地确定何时任意地排序可以被用于在迭代的后向/前向分析期间做出推断的处理器指令和代码块、以及何时标记指令/代码块以避免将其用于在迭代的后向/前向分析中做出推断。

为了图示前述内容,注意现在参照图9,图9图示了处理器指令的合并序列列表900,该列表基于从针对与图8的代码块(A-G)相关联的线程的稀疏硬件跟踪所获取的排序信息(例如,操作时间戳)。

注意,在当前的实施例中,当确定并发操作的代码块中的一个代码块内与并发操作的代码块中的另一代码块不存在对相同地址位置的写操作时,可以对操作的合并序列900内并发执行的操作/块做出任意排序。例如,代码块A、代码块D和代码块F并发地被操作,具有在时间中重叠的至少一些处理器指令。因为确定这些代码块(代码块A、D或F)中没有任何代码块具有对与由并发地重叠的代码块(代码块A、D或F)中的另一代码块引用的存储器位置相同的存储器位置的指令,所以可以以任意排序将它们写入合并序列900中。在此,代码块D被列出在代码块F之前,尽管与代码块F相关联的至少一个操作时间戳显现为在代码块D之前开始。在这方面,并发执行的操作的排序可以是不相关的。(例如,代码块F可以被定位在代码块A之前,例如)。

在这样的实施例中,当并发执行的块的排序不相关时,针对每个操作块的单独的处理器指令的定序可以被维持在一起作为指令的合并序列900内的指令的单个连续的列表,或者备选地,与来自指令的合并的序列900中的并发执行的操作块中的另一操作块的处理器指令混合(目前未示出)。

在一些实施例中,与任何并发执行的操作块相关联的最早的操作时间戳被用于控制合并列表内的顺序排序,使得代码块F会被定序在代码块D之前,即使该排序是不相关的。在其他一些实施例中,并发执行的操作块的最后的操作时间戳被用于在指令的合并序列列表900内定序处理器指令/块。

同样,当操作/代码块之间不存在重叠或并发性时,根据不同操作块的相对操作时间戳,操作块在指令的合并序列900中被定序。例如,这以在指令的合并序列900代码块B被定位在代码块E之前来示出。

在一些实例中,并发执行的操作块的排序可以是相关的,并且可以触发对操作块的选择性的标记,以避免在利用处理器指令的合并顺序列表的推断性的时间旅行调试分析期间使用。例如,代码块C和代码块G两者都利用它们各自的代码块中单独的处理器指令来访问相同的存储器位置。那些处理器指令中的一个处理器指令还可能包括写指令。在这样的实例中,代码块C和代码G可能是相关的,具有某种互依赖性,并且不能利用简单的操作块时间戳而被解析。

因此,在当前的示例中,代码块C和代码块G被标记,以防止它们在对合并的循序的列表的迭代的后向/前向分析期间,使用在基于其处理器指令的状态值来推断任何值中。更具体地,从线程1和线程3的稀疏硬件跟踪获取的针对代码块C和代码块G的控制流信息在合并顺序列表900的数据推断图的构造期间将被标记,并且将不被用于做出对数据推断图内对应的状态数据的推断,类似于在前述迭代的后向/前向分析期间标记并且忽略使用与对未知地址的写操作相关联的值(例如,如参考图5B的单元格530中的地址所描述的示例)。这将避免在推断性的分析步骤期间必须应用指数地增长的分析的潜在可能性,不然其可能需要被执行,以针对所有潜在的在次序关键操作来考虑所有可能的定序场景。

尽管对针对多线程操作的代码块的控制流信息的该选择性排序和选择性使用可能限制在调试期间可以做出的推断的数量,但是,对控制信息的该类型的选择性排序的使用可以通过以下来提供针对多线程操作的时间旅行调试显著的伸缩性:限制必须被跟踪的未知推断的总数量(否则其可能是指数的并且压垮调试器/使调试器崩溃)并且同时仍然使得能够做出大量数量的推断。

图10图示了并发性操作的流程图1000,诸如以上所描述的,其可以被用于选择性地标识锁定不用于推断性的调试分析的值。

如所示出的,第一个步骤涉及基于操作块的对应的操作块时间戳来标识具有重叠的执行时间段的重叠的操作块(动作1010)。参照图9,这可以包括确定代码块C具有22-24的操作块时间戳,其与代码块G的相同操作块时间戳重叠,例如,这意味着它们在具有并发的时间段的情况下被执行。

接下来,响应于确定重叠的操作块中的至少两个重叠的操作块访问相同的特定存储器位置,并且那两个重叠的操作块中的至少一个操作块包括写指令,则标记来自重叠的操作块的存储器值,以避免其在以上所描述的推断性的后向/前向分析中的使用(动作1020)。将理解,这可以包括标记整个操作块,或者仅标记那些操作块中的具体操作、和/或与该特定存储器位置相关联的值。

接下来,响应于标识已经被标记的那些操作块(动作1030)(例如,具有被标记的存储器值的操作块),做出关于针对那些块的次序是确定的还是不确定的确定(动作1040)。例如,可以基于操作块内的其他属性、或者从针对对应的线程的稀疏硬件跟踪中所获取的控制流信息的其他属性来做出该确定。在一些实例中,例如,操作块中的指令可以指定来自不同的代码块的处理器指令之间的互依赖性,该互依赖性反映处理器块中的每个处理器块内的操作块和/或处理器指令的相对排序。

如果可以从针对不同的操作块的附加的属性和信息(例如,不同于第一个操作块时间戳的信息)确定针对并发操作块(特别是被标记的操作块)的相对的排序信息,则根据该相对的排序信息,被标记的操作块(例如,具有被标记的存储器值的块)在指令的合并序列900内被定序/排序。在一些实例中,这还包括将操作块取消标记(例如,将针对块的存储器值取消标记),一旦那些块被适当地排序,如此它们就可以被考虑用于在较早所描述的迭代的后向/前向分析期间做出推断。

否则,如果在分析其他属性信息(例如,除了仅操作块时间戳以外的信息)之后,被标记的操作块(例如,具有被标记的存储器值的块)的排序仍然是不确定的,则针对这些操作块的存储器访问指令保持被标记,以不用于在迭代的后向/前向分析期间做出推断。如以上所指示,这可以包括标记仅被标记的操作块内与特定存储器位置对应的具体的指令或值,同时使被标记的操作块内的其他指令/值能够用于做出推断。在一些备选的实施例中,这涉及标记整个操作块或者块内的指令的集合,以防止其指令/值中的任何指令/值被用于在后向/前向分析期间做出分析。

从前述内容将理解,所公开的实施例给如何调试应用(特别是已经被部署的应用)和计算机系统如何操作两者都带来许多实质益处。更具体地,实施例改进调试技术,因为它们可以被用于解析存储器别名问题以及并发性问题两者,在存储器别名问题中写指令是针对未知目的地地址(作为在时间旅行调试期间仅具有有限量的状态信息的结果),在并发性问题中为多个不同的线程收集轻量级硬件跟踪数据(例如,控制流信息)。与应用的先前执行对应的记录的状态记录的状态值和控制流信息都被获取。通过实践所公开的原理,开发者在调试已被部署的应用时开发者被提供了实质地更多的信息。此外,客户端/客户将获益,因为他们将不需要安装重量的跟踪应用。

因为向开发者提供了更大量的信息,所以底层计算机系统和应用的操作也将被改进。例如,因为实施例提供更强健的调试数据,所以开发者将能够更准确地找准程序错误,并且解析那些程序错误。因此,这些操作直接并且显著地改进计算机系统如何操作,因为开发者将能够更好地解析应用程序错误和其他错误。

注意现在将针对图11,图11图示了可以被用于支持本文中所描述的操作的计算机系统1100。计算机系统1100可以采取各种不同的形式。例如,在图11中,计算机系统1100可以被体现为平板、台式机、包括与计算机系统1100通信的一个或多个被连接的计算组件/设备的分布式系统、膝上型计算机、移动电话、服务器、数据中心和/或任何其他计算机系统。

在其最基本的配置中,计算机系统1100包括各种不同的组件。例如,图11示出计算机系统1100包括至少一个处理器1105(又名“硬件处理单元”)和存储装置1110。

存储器1210被示出为包括可执行代码/指令1115和跟踪数据1120,其代表较早所讨论的跟踪数据。存储装置1110可以是物理系统存储器,其可以是易失性的、非易失性的或者两者的某种组合。术语“存储器”在本文中还可以被用于指代非易失性大容量存储装置,诸如物理存储介质。如果计算机系统1100是分布式的,则处理、存储器和/或存储功能也可以是分布式的。如本文中所使用的,术语“可执行模块”、“可执行组件”、或者甚至“组件”可以指的是可以在计算机系统1100上被执行的软件对象、例程或方法。本文中所描述的不同的组件、模块、引擎和服务可以被实现为计算机系统1100上执行的对象或处理器(例如,作为单独的线程)。

所公开的实施例可以包括或者利用专用或通用计算机,包括计算机硬件(诸如,例如,一个或多个处理器(诸如处理器1105)和系统存储器(诸如存储装置1110),如下面更详细地所讨论的。实施例还包括用于携带或者存储计算机可执行指令和/或数据结构的物理和其他计算机可读介质。这样的计算机可读介质可以是可以由通用或专用计算机系统访问的任何可用介质。以数据形式存储计算机可执行指令的计算机可读介质是物理计算机存储介质。携带计算机可执行指令的计算机可读介质是传输介质。因此,作为示例而非限制,当前实施例可以包括至少两种明显不同种类的计算机可读介质:计算机存储介质和传输介质。

计算机存储介质是硬件存储设备,诸如RAM、ROM、EEPROM、CD-ROM、基于RAM的固态驱动器(SSD)、闪存、相变存储器(PCM)、或者其他类型的存储器、或者其他光盘存储装置、磁盘存储装备或其他磁性存储设备,或者可以被用于以计算机可执行指令、数据或数据结构的形式来存储所期望的代码手段、并且可以由通用或专用计算机访问的任何其他介质。

计算机系统1100可以通过一个或多个有线或无线的网络1125被连接到(多个)远程系统,该(多个)远程系统被配置为执行关于计算机系统1100所描述的处理中的任何处理。“网络”,如图11中所示的网络1125,被定义为一个或多个数据链路和/或数据交换机,这些数据链路和/或数据交换机使在计算机系统、模块和/或其他电子设备之间传送电子数据成为可能。当通过网络(或者硬连线、无线的,或者硬连线和无线的组合)将信息传送或提供给计算机时,计算机会将连接恰当地视为传输介质。计算机系统1100将包括被用于与网络1125通信的一个或多个通信信道。传输介质包括网络,该网络可以被用于以计算机可执行指令的形式或者数据结构的形式来携带数据或者所期望的程序代码。此外,这些计算机可执行指令可以由通用或专用计算机访问。上述的组合也应被包括在计算机可读介质的范围内。

响应于抵达各种计算机系统组件,计算机可读指令或数据结构形式的程序代码手段可以自动地从传输介质被传输到计算机存储介质(反之亦然)。例如,通过网络或数据链路所接收的计算机可读指令或数据结构可以被缓冲在网络接口模块(例如,网络接口卡或“NIC”)内的RAM中,然后最终被传送到计算机系统RAM和/或计算机系统出较少易失的计算机存储介质中。因此,应理解,计算机存储介质可以被包括在也(或者甚至主要地)利用传输介质的计算机系统组件中。

计算机可执行(或计算机可解译)指令包括例如使通用计算机、专用计算机或专用处理设备执行某个功能或功能的组的指令。计算机可执行指令可以是例如二进制、中间格式指令(诸如汇编语言)、或者甚至源代码。尽管已经用特定于结构化特征和/或方法动作的语言描述了主题,但是应理解,所附权利要求书中所定义的主题不一定限于上述的特征或动作。而是,所描述的特征和动作被公开为实现权利要求的示例形式。

本领域技术人员将理解,可以在具有许多类型的计算机系统配置的网络计算环境中实践实施例,包括个人计算机、台式计算机、膝上型计算机、消息处理器、手持式式设备、多处理器系统、基于微处理器或可编程的消费者电子产品、网络PC、小型计算机、大型计算机、移动电话、PDA、寻呼机、路由器、交换机等。还可以在分布式系统环境中实践实施例,在分布式系统环境中,通过网络而被链接的本地和远程计算机系统(或者通过硬连线数据链路、无线数据链路,或者通过硬连线链路和无线数据链路的组合)各自执行任务(例如云计算、云服务等)。在分布式系统环境中,程序模块可以位于本地和远程存储器存储设备两者。

附加地或备选地,本文描述的功能性可以至少部分由一个或多个硬件逻辑组件(例如,处理器1205)执行。例如但不限于,可以使用的说明性类型的硬件逻辑组件包括现场可编程门阵列(FPGA)、程序特定或应用特定集成电路(ASIC)、程序特定标准产品(ASSP)、片上系统(SOC)、复杂可编程逻辑设备(CPLD)、中央处理单元(CPU)和其他类型的可编程硬件。

在不脱离本发明的精神或特性的情况下,本发明可以以其他特定形式来体现。所描述的实施例在所有方面仅被认为是说明性的而不是限制性的。因此,本发明的范围由所附权利要求书而不是前述说明书指示。在权利要求的等同的意义和范围内的所有改变都包含在其范围之内。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号