一种基于安卓操作系统的一体化免源码调试方法与流程

文档序号:29046945发布日期:2022-02-25 22:21阅读:79来源:国知局
一种基于安卓操作系统的一体化免源码调试方法与流程

1.本发明涉及一种基于安卓操作系统的一体化免源码调试方法。


背景技术:

2.自2008年android问世以来,其市场占有率一直在稳步提升,并直接导致了symbian、windows phone等移动操作系统退出历史舞台。然而,这样一个主流移动操作系统,多年来一直没有一个较为好用的针对android程序的调试工具。目前,android免源码调试这一领域依然处于比较原始简陋的状态。从一台手机、一台电脑,到能真正开始进行调试,中间有着各种繁琐的步骤。从adb的连接,到调试端口的开启,调试模式的启动,待调试程序的安装等,每一步都在耗费调试者的精力和时间。即便是正式开始了调试,在无源码调试的情况下,缺少变量类型信息将会使得调试举步维艰。现有的技术方案不仅需要调试者事先自行做好各种准备工作,并且调试过程中没有图形化界面,每一个调试操作都需要调试者直接在终端输入对应的调试命令,而调试输出也仅能在终端展现,不能多窗口交互,各种各样的因素导致了调试效率低。
3.因此,寻求一种能自动化搭建出研究人员所需要的调试环境,并在调试过程中提供各种重要辅助功能的调试方法很有必要。


技术实现要素:

4.有鉴于此,本发明目的是提供一种以adb为媒介,在只有软件包而没有软件源码的情况下,能全自动地实现软件的安装、激活调试模式、启动并附加到待调试程序、断点设置、断点变量识别等,并在人工调试过程中,还具有支持断点代码执行、改写变量内容等功能的自动化调试方法。
5.为了解决上述技术问题,本发明的技术方案是:
6.一种基于安卓操作系统的一体化免源码调试方法,包括以下步骤:
7.1)待调试程序的智能安装与更新;
8.计算出待调试程序的hash值,并且和系统中已有的程序的hash值作比较,当且仅当这两个hash值不相等,亦即待调试程序的内容发生改变,新的待调试程序才会取代旧有的程序,安装到目标设备上;
9.2)激活调试模式;
10.通过直接修改系统中的环境变量ro.debuggable,在系统级别把调试开关打开,这样处理可以避免对软件包进行任何改动,保证了软件包的完整性,同时又能成功地对该程序进行调试;
11.3)待调试程序的启动及其配置;
12.当发现调试器成功附加后,再等待数百毫秒,并假定在这段时间中与调试相关的配置已经完成,解除对程序的挂起,根据用户事先在界面中所作的设定,在这个短暂的窗口通过jdwp进行断点设置等操作,确保时机不会被耽误;
13.4)变量类型识别;
14.要对函数进行分块,使得每一块代码在除去抛出异常的情况下,一旦程序执行到该块,那么整个块的代码都将会被执行而没有遗漏,在划分好函数块后,还要把这些块按照执行流程的转移方向连接起来,构建起块与块之间的关系;分块完毕后,就要进行静态单赋值的处理,ssa一般使用下标的方式表示新变量,每当dalvik层面上的变量被赋值,这个寄存器的下标计数器就会加一,并将当前计数器数值用于新ssa变量的下标,dalvik字节码形式的控制流,其寄存器和函数中的变量不对等,变量和寄存器之间是一个动态的映射关系;在构建函数ssa形式的过程中,ssa引入一个名为phi的函数,它的作用是表示从这两个不同的寄存器中作出选择,并引入一个新的ssa变量,当phi函数充足并合理地加入到ssa图表当中,ssa的构建就完成了,给定一个函数偏移和一个寄存器,可以查找出此时这个寄存器的值所对应的变量类型,在调试的过程中,可以随意设置断点,并在断点触发后实时分析出各寄存器的变量类型,配合调试协议实时读取出各个寄存器的有效数值;
15.5)基于反编译的源码级调试;
16.配合反编译模块,可以实现基于反编译结果的源码级调试,只要有dalvik控制流和反编译源码之间的映射关系,就可以实现源码级调试,并能在源码级调试和dalvik指令级调试的无缝切换。
17.本发明技术效果主要体现在以下方面:本发明提供了一整套的自动化调试流程,以adb为媒介,在只有软件包而没有软件源码的情况下,能全自动地实现软件的安装、激活调试模式、启动并附加到待调试程序、断点设置、断点变量识别等,并在人工调试过程中,还具有支持断点代码执行、改写变量内容等功能。依据本方案所设计出来的调试器,其表现优于市面上各种产品,能自动化搭建出研究人员所需要的调试环境,并在调试过程中提供各种重要的辅助功能。
附图说明
18.图1为本发明一种基于安卓操作系统的一体化免源码调试方法的流程图。
具体实施方式
19.以下结合附图1,对本发明的具体实施方式作进一步详述,以使本发明技术方案更易于理解和掌握。
20.实施例
21.android(安卓)是现今主流手机操作系统之一,android debug bridge(adb)是android操作系统中最为常用的调试套件,它提供了软件安装与卸载、shell操作、端口转发等各种各样的实用功能,android所支持的名为dalvik的字节码,是参照java虚拟机(jvm)的字节码,将栈机结构改成基于寄存器的一套指令集,因此,dalvik和jvm在很多地方上有共通的地方,java虚拟机所使用的调试协议java debug wire protocol(jdwp)也被android所采用,哪怕随着android的发展,dalvik虚拟机被android运行时(art)所取代,dalvik作为一套指令集依然支撑着每一个android程序,而jdwp也从未退出舞台。
22.一种基于安卓操作系统的一体化免源码调试方法,包括以下步骤:
23.1)待调试程序的智能安装与更新
24.待调试的程序,其内容并非固定不变。某些场合下,调试人员可能会对被调试的程序打补丁。这种情况下,补丁后的程序需要重新安装到android设备上。然而,程序也可能一直不变。如果每次调试前都要重新安装一次,动辄上分钟的安装时间将会令调试效率大大降低。本方案会计算出待调试程序的hash值,并且和系统中已有的程序的hash值作比较。当且仅当这两个hash值不相等,亦即待调试程序的内容发生改变,新的待调试程序才会取代旧有的程序,安装到目标设备上。这样既节省了不必要的安装所带来的时间开销,也能确保调试人员能调试到最新的版本。
25.2)激活调试模式
26.一般而言,发行版的软件包,其manifest.xml文件中,一般不会将自己标识为debuggable(可调试)。如果一个android程序没有被标记为debuggable,那么就不能对这个程序进行调试。针对这种情况,一般而言有两种解决方法。一是重新打包。这种方式的好处是兼容性好,对于任何的软件包都能通过对manifest.xml打补丁而激活调试模式。然而,此法在现实中有很大的限制。android要求所有软件包都需要签名,而签名和软件包的完整性挂钩,因此打补丁意味着整个软件包需要重新签名。市面上有不少软件在运行的过程中会校验自己的软件包的签名以确保自己不被篡改,而这种重打包的方式会触发这种检测。本解决方案采用的是另外一个方式,通过直接修改系统中的环境变量ro.debuggable,在系统级别把调试开关打开。这样处理可以避免对软件包进行任何改动,保证了软件包的完整性,同时又能成功地对该程序进行调试。
27.3)待调试程序的启动及其配置
28.表面上看,要启动一个程序,只需要简单地通过adb发送一个指令,就可以把目标程序启动起来。然而,从调试的角度看,待调试的程序从启动一直到用户可调试的状态,中间有不少繁琐的步骤。一般而言,调试人员会希望在尽可能早的地方使程序中断下来,以防待调试的代码片段在程序运行起来的瞬间就已经跑完。这种情况下,就需要通过adb以暂停模式启动待调试程序。在这种模式下启动的程序处于挂起状态,并等待调试器的附加。然而光等待附加并不足够,即使附加上去了,如果没有任何断点,程序也是直接跑飞。另外,受到调试协议的限制,android设备这一侧并没有任何手段得知调试人员已做好所有针对调试的准备工作。因此,在设备这边,当发现调试器成功附加后,只会再等待数百毫秒,并假定在这段时间中与调试相关的配置已经完成,解除对程序的挂起。本方案会根据用户事先在界面中所作的设定,在这个短暂的窗口通过jdwp进行断点设置等操作,确保时机不会被耽误。
29.4)变量类型识别
30.dalvik程序调试的难点在于确定变量类型。在调试基于dalvik字节码的程序时,若要通过调试协议获取一个寄存器的内容,则需要由调试者提供该变量的变量类型。常规开发中编译出来的带调试信息的程序附带了这些信息,因此在调试过程中可以顺利显示出各种变量。然而,由于各种原因,调试者不一定拥有这些调试信息。在这种情况下,如要获取一个寄存器,就只剩下两个方案。
31.方案一不考虑程序上下文,直接将变量类型指定为原始数据类型(primitive type)。这种做法虽然是能获取这一寄存器中的数值,但获取到的信息很可能没有意义。比如,这个寄存器实际上存放了一个物件(object),但调试者通过原始类型int去进行读取,那么得到的寄存器值,是一个指向上述物件的一个指针。这对于研究者来说没有意义,因为
研究者想要的必然是它指向的物件,而不是这个指针本身。
32.另一个方案则是先分析理解程序上下文并得知这一寄存器此时此刻的变量类型,再按这一变量类型通过调试协议去获取寄存器的内容。这种做法能正确返回研究者所希望获取的内容。然而,这种做法的人力成本很高。虽然某函数内单一位置单一寄存器的变量类型可以依靠人力分析解决,但调试是动态的,程序所执行到的位置随时在变化,变量类型也随之改变。更何况寄存器有多个,即使能忍受高强度人力分析,也难保分析过程中不会出现人为错误。
33.倘若直接将变量类型指定为物件,那么在遇到寄存器在事实上存放原始类型的时候,调试协议的服务端一侧将会把这个寄存器的值当作物件指针以读取物件内容。由于这个寄存器的值本身就不是一个合法的指针,绝大多数场合下会导致非法内存操作而导致被调试程序直接崩溃。即使在极低的概率下,这个寄存器的值碰巧在事实上是个有效指针,读出来的物件内容也是错误的。因此,这个方案不成立。
34.分析的第一步,是要对函数进行分块,使得每一块代码在除去抛出异常的情况下,一旦程序执行到该块,那么整个块的代码都将会被执行而没有遗漏。dalvik中主要有三种可以改变程序执行流程的情况。一是条件跳转,如if-eqz指令,若源寄存器的值为零,则跳转到目标地址。第二种情况是switch指令。dalvik中的packed-switch和sparse-switch可根据源寄存器的值,通过查找跳转表确定跳转的目标地址。第三种情况则是异常的捕获。如果函数内部某处注册了某一异常的捕获(即try块),并且这种异常在此处被抛出,那么函数的执行就将直接从抛出异常的地方转入到对应的异常处理代码中。以上数种情况,在发生函数执行流程改变的时候所到达的地址,即是一个代码块的入口。而当这个代码块执行到下一个代码块的入口,则意味着此处为这个代码块的结束。在划分好函数块后,还要把这些块按照执行流程的转移方向连接起来,构建起块与块之间的关系。
35.分块完毕后,就要进行静态单赋值(static single assignment,ssa)的处理。在ssa概念中,一个变量会且只会被赋值一次,并且每个变量在使用前都必须被赋值。ssa一般使用下标的方式表示新变量。每当dalvik层面上的变量(即寄存器)被赋值,这个寄存器的下标计数器就会加一,并将当前计数器数值用于新ssa变量的下标。比如,”x=10;x=20;“就会变成"x1=10;x2=20"。
36.dalvik字节码形式的控制流,其寄存器和函数中的变量不对等。从编译的角度看,开发人员理论上可以定义无限个变量,但事实上有效寄存器的数量是有限的。也就是说,变量和寄存器之间是一个动态的映射关系。比如,当dalvik编译器发现函数中的某个变量在执行到某处之后便不再被使用,那么即使在源码中这个变量的生命周期仍未结束,编译器的优化流程会将这个事实上更短的生命周期识别出来,并且在这个变量实际生命结束的地方,把对应的寄存器标记为空闲。这个变量这一阶段的实际生命周期,亦即从这个变量最近一次赋值一直到它生命结束,是可以映射到存放它的寄存器。此后如需要定义新变量,哪怕旧变量从源码的角度看依然存活,程序依然会占用这个寄存器。
37.在构建函数ssa形式的过程中,有一个极其核心的概念,那就是phi函数。假如一个函数内没有任何跳转,那么ssa形式的构建就很简单。每当一个变量被重新赋值,那么在ssa中这个变量的生命周期结束,并产生新的变量。然而,如果函数内有跳转有分支,那么函数内就有合流的地方。假如一个寄存器在两个分支中被分别赋值,然后这两个分支合流并被
使用,那么在这个合流的地方,并不能确定这个寄存器的值是来自于哪一个分支。这种情况下,ssa引入一个名为phi的函数。它的作用是表示从这两个不同的寄存器中作出选择,并引入一个新的ssa变量。
38.对于简单的控制流图表,需要添加phi函数的地方显而易见。然而,当控制流有一定的复杂度,寻找需要插入phi函数的地方就会变得困难。要解决这个问题,就需要图论中的dominance frontier(df)。df是一个节点集合。给定一个节点,它的df表明了,程序若要运行到某个df节点的直接支配节点(immediate dominator,idom),那么程序必定先经过给定的这一节点。df的这个特性,正好和上文所提及的phi函数匹配。假如节点n定义了一个变量v,那么查询节点n的df。如节点n对应的df不为空,则对每个df中的节点插入phi函数。df的计算可参照《efficiently computing static single assignment form and the control dependence graph》(cytron et al.,1991)。
39.当phi函数充足并合理地加入到ssa图表当中,ssa的构建就完成了。现阶段的目标是,给定一个函数偏移及一个寄存器,找到此时此刻这个寄存器内容被定义的地方。由于该寄存器在这个位置被使用,因此能直接得知此处所使用的寄存器在ssa中的变量下标,并通过这个下标从ssa中查找定义。
40.当变量的定义与使用都能通过ssa查找出引用关系,下一步便正式开始涉及变量类型的处理。通过ssa找到定义后,在赋值语句中,等号右侧的内容存在两种情况,类型可确定或类型不可确定。对于前者,比如变量定义来源于dalvik指令const,那么这个变量的类型就是原生整数类型。若是来源于move-result-object或new-instance等指令,那么这个变量的类型就是物件而不是原生变量类型。至于类型不可确定的情况,赋值语句右侧是个phi函数。这种情况下,就需要递归处理phi函数中的参数,对参数中出现的各个变量再次进行定义查询。这种情况下,最终查找得到的定义会有多个。比如,源码中使用了switch,并且在各case块中分别向同一变量进行赋值,并在整个switch结束后使用了这个变量。那么在ssa中,switch所对应的各个case节点的共同后继节点中,存在着一个phi函数,其参数所包含的各个变量,分别来源于这些case节点中的赋值语句。如果这些变量的定义都使用了同一种变量类型,那么就可以确定phi所返回的变量,其类型也和它各个参数的类型相一致。理论上,当phi函数参数的各个变量类型不统一时,就无法确定phi所返回的变量的类型。然而,事实上编译器本身也需要确定一个变量的类型,否则程序将出现崩溃的可能性。因此,在正常情况下,这种递归查找得到的各个定义,它们的类型应该是一致的。
41.至此,整个分析处理流程结束。给定一个函数偏移和一个寄存器,通过上述流程可以查找出此时这个寄存器的值所对应的变量类型。在调试的过程中,可以随意设置断点,并在断点触发后实时分析出各寄存器的变量类型,配合调试协议实时读取出各个寄存器的有效数值,有效地提升了调试效率。
42.5)基于反编译的源码级调试
43.配合另外一个配套的反编译模块,本方案可以实现基于反编译结果的源码级调试。只要有dalvik控制流和反编译源码之间的映射关系,本方案就可以实现源码级调试,并能在源码级调试和dalvik指令级调试的无缝切换。
44.对于本领域技术人员而言,显然本发明不限于上述示范性实施例的细节,而且在不背离本发明的精神或基本特征的情况下,能够以其他的具体形式实现本发明。因此,无论
从哪一点来看,均应将实施例看作是示范性的,而且是非限制性的,本发明的范围由所附权利要求而不是上述说明限定,因此旨在将落在权利要求的等同要件的含义和范围内的所有变化囊括在本发明内。
45.此外,应当理解,虽然本说明书按照实施方式加以描述,但并非每个实施方式仅包含一个独立的技术方案,说明书的这种叙述方式仅仅是为清楚起见,本领域技术人员应当将说明书作为一个整体,各实施例中的技术方案也可以经适当组合,形成本领域技术人员可以理解的其他实施方式。
当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1