16、P-Code形式语义与红黑树验证的研究

P-Code语义与红黑树验证研究

P-Code形式语义与红黑树验证的研究

1. P-Code相关问题与改进建议

P-Code在Ghidra的GUI显示中存在语法不一致的问题。Ghidra的图形用户界面(GUI)中显示的P-Code语法与文档中的语法不同。列表视图中的低级别P-Code使用大写字母表示法,而像图形抽象语法树(AST)等工具则使用常规语法。若统一使用相同的、最好是常规的语法,将大大提高可读性。

P-Code文档也存在诸多问题,需要进行改进:
- 高低级别P-Code文档分离 :当前的P-Code参考手册试图用一个描述涵盖低级别和高级别P-Code的每条指令,然后为高级别P-Code情况添加额外信息。建议将文档分为高级别和低级别P-Code两部分。
- Varnodes的定义修正 :文档中varnodes被描述为包含地址空间、地址和大小三个元素,但实际情况并非如此。常量也被编码为varnodes,其中地址用作常量值。在进行函数调用时,一些地址空间会被保留,而一些则会在调用范围内重置。建议文档采用将地址空间字段作为varnode类型的观点,并列出不同类型的varnodes及其在函数调用等情况下的行为;或者更新CALL和RETURN指令以包含地址空间作用域。
- CALL、CALLIND、RETURN指令说明改进 :文档中称CALL、CALLIND和RETURN分别等同于BRANCH、BRANCHIND和BRANCHIND,但实验结果并非如此。函数参数通过寄存器传递,局部变量会被重置,函数可以返回值,调用后会进行寄存器清理和局部变量恢复。这些指令本身没有问题,主要是说明方式需要改进。同时,CALL、间接调用和返回在低级别和高级别P-Code中的行为完全不同,需要更好的文档说明。
- 消除小的不一致性 :文档中存在许多小的不一致之处,例如语法参考中引入了两种不同的SUBPIECE表示法,但在P-Code操作参考中并未出现,且在实验中也未发现。

Ghidra开发团队对这些发现和建议做出了回应。他们确认了发现的问题并承认了所有问题。对于条件分支问题,他们提到了一个GitHub问题,认为虽然语义上不正确,但不认为这是一个错误,因为条件跳转的目标从低级别到高级别P-Code是保留的,他们认为这比指令本身的正确性更重要。

2. 相关工作

目前利用Ghidra结果的研究较少,以下是几个使用Ghidra和P-Code进行程序分析的有趣项目:
- GhiHorn :一个基于SMT的路径分析工具,使用Ghidra和P-Code。其目标是确定是否存在到达某个程序点的路径,以及如何实例化程序以到达该点。该工具依赖Ghidra的控制流API构建块与块之间的流,对于单个块,使用自定义的从P-Code到Z3表达式的转换器。但该工具的文档有限,似乎没有处理P-Code的一些复杂细节,如phi节点、间接调用和调用/返回机制。
- 将二进制文件反编译为LLVM IR :一个硕士论文项目,将二进制文件加载到Ghidra中,然后将反编译的P-Code转换为LLVM IR。虽然该工作没有为P-Code提供语义,但将LLVM IR的语义与P-Code的语义相关联。在大多数情况下,转换为LLVM IR似乎比较直接,因为调用/返回机制等问题可以直接转移。然而,该工作的一个大限制是不支持P-Code的一些更难的概念,如phi节点、非确定性间接指令、浮点运算和指针计算。并且在条件分支的转换方面,该工作容易受到Ghidra中发现的错误的影响。
- 静态污点分析 :使用Ghidra开发的静态污点分析项目,作者使用包含源和汇的外部列表,并使用污点策略来定义污点的引入和传播。但该项目的源代码已不可用,团队正在开发新版本并暂时撤回了代码。

这些方法的一个问题是都没有进行任何形式的验证或有正式的理论支持。而P-Code语义并不简单,拥有正式的语义有可能改进这些和未来的反编译及二进制分析工作。

3. 红黑树验证的背景与问题

交互式定理证明器通常使用高级代数数据结构(如列表、集合或树)来验证算法的概念正确性。从这些算法生成的代码通常是纯函数式的,效率往往不高,并且很少处理副作用、别名或内存分配,除非显式建模堆的分配和释放。

然而,在验证实际编程语言中的程序时,需要处理所有非原始数据类型都表示为指针结构的问题,并且为了提高效率,经常使用破坏性操作。处理这些问题最流行的概念是使用分离逻辑,它将堆结构的规范纳入逻辑的语义中。许多针对C、Java或Rust程序验证的证明器(如VeriFast或Viper)直接基于分离逻辑。许多交互式定理证明器现在也支持类似的分离逻辑库。

但直接验证用C等语言编写的算法仍然存在问题,概念正确性论证的复杂性与指针别名和副作用问题相互交织。

4. 红黑树验证的方法

提出了一种基于细化的方法,将红黑树实现的验证工作模块化,分为两个独立的部分:
- 代数层面的功能正确性验证 :处理大部分与高级代数证明几乎相同的部分。
- 指针结构操作验证 :使用分离逻辑验证指针结构上的原始操作,这部分相对较小且独立于第一部分。该方法通过合适的模块化结构将两个问题分开,基于通过数据细化链接的顺序程序组件,在定理证明器KIV中得到原生支持。

选择红黑树作为示例,是因为它在搜索、插入和删除操作方面提供了良好的最坏情况保证。目标是验证一个高效的版本,使生成的代码与标准C代码实现相当,因此最终实现使用父指针并避免递归。

5. 定理证明器KIV的背景

使用定理证明器KIV来开发必要的形式规范并证明实现符合这些规范。KIV提供使用显式证明树的交互式验证,规范语言的基本逻辑是高阶逻辑(HOL),最近从单态类型扩展到多态类型。KIV支持具有递归过程和非确定性的命令式编程语言。

过程的参数分为输入、引用和输出参数序列,不支持全局变量,必须将其显式添加为引用参数。

推理顺序程序使用最弱前置条件演算,借用动态逻辑(DL)的符号,包括其两个标准模态:
- [α]ϕ(盒) :表示对于α的每个终止运行,最终状态必须满足ϕ,对应于最弱宽松前置条件wlp(α, ϕ)。
- ⟨α⟩ϕ(菱形) :保证存在α的终止执行,使得ϕ成立。
- ⟨|α|⟩ϕ(强菱形) :表示α的所有运行都以满足ϕ的最终状态终止(最弱前置条件wp(α, ϕ))。

程序α相对于前置/后置条件pre/post的部分和完全正确性分别写为pre →[α] post和pre →⟨|α|⟩post。该演算比标准的Hoare式程序逻辑更具表达力,因为它允许组合和嵌套程序公式,这有助于定义细化的证明义务。

验证程序正确性的主要证明技术是符号执行。每个符号执行步骤从前置条件计算第一个程序语句的最强后置条件。当程序的符号执行完成后,目标被简化为谓词逻辑,通过重写规则和启发式方法实现证明自动化。

6. 代数数据类型的结构化规范

在KIV中,使用结构化代数规范来构建数据类型定义的层次结构。原始数据类型可以自由或非自由生成,规范可以通过附加函数进行扩充,并使用标准的结构化操作(如富集、联合和重命名)进行组合。还可以指定可显式实例化的参数化数据类型。

6.1 代数红黑树定义

证明使用复杂数据结构的算法正确性的标准方法是代数地指定数据结构。红黑树可以定义为多态自由数据类型rbtree(′a),使用常量构造函数SENTINEL(表示树的叶子)和非常量构造函数Node:

rbtree(′a) := SENTINEL | Node(.elem : ′a ; .color : rbcolor ; .left : rbtree(′a) ; .right : rbtree(′a))

节点具有颜色(由枚举类型rbcolor定义为RED或BLACK)、左右子树和通用类型′a的元素。这些字段可以通过后缀选择器函数.elem、.color、.left和.right访问。

为了表达二叉搜索树的属性,使用通用的全序元素类型tord(带有<),得到的树类型写为rbtree(tord)。规范可以根据需要通过合适的类型(如自然数或整数)进行实例化,实例化时KIV会生成证明义务以确保实例化的类型满足假设的属性(在这种情况下,是类型上的全序)。

对于自由数据类型规范,KIV会生成所有必要的公理以及更新函数(如rbt.color := newcol)及其定义。但选择器(和更新)函数并非对所有参数都有公理,例如SENTINEL.color未指定。语义函数在模型中仍然是全函数,SENTINEL.color可以是任何值,但KIV会为函数附加一个域,在程序中调用.color超出其域(这里是使用SENTINEL)会引发异常,因此证明程序中数据类型的正确使用需要证明所有操作的参数都在各自的域内。

6.2 堆和分离逻辑的建模

推理破坏性指针算法需要对堆进行建模,可以隐式地作为公式语义的一部分,也可以显式地作为代数数据类型。在KIV中采用后一种方法,堆被指定为多态非自由数据类型heap(′a)。堆可以看作是一个部分函数,将引用r(类型为ref(′a))映射到通用类型′a的对象obj,引用的分配是显式的,引用类型包含一个特殊元素null,它从不被分配(表示空指针)。

heap(′a)数据类型由常量∅(表示空堆)、分配新引用r(写为h ++ r)或更新已分配位置r的新对象obj(写为h[r := obj])归纳生成。对象类型未进一步指定,因此堆规范可以用于任何具体的对象类型(对于红黑树,类型rbnode表示树的单个节点)。

使用谓词r ∈ h检查引用是否在堆中分配,使用函数h[r]查找堆中的对象(对应于解引用指针)。引用也可以通过函数h – r进行释放。

与自由数据类型的选择器函数类似,构造函数、查找和释放函数都是部分函数,以指定对堆的有效访问:使用空引用访问堆总是未定义的(r ≠ null),只能使用新引用进行分配(¬ r ∈ h),查找、更新和释放需要已分配的引用r ∈ h。

在KIV中,过程的所有参数都是显式的,因此在推理基于指针的程序时,堆必须作为程序的显式参数。为了便于验证此类程序,构建了一个简单的库。

以下是一个简单的流程表示图(mermaid格式),展示红黑树验证的整体流程:

graph LR
    A[开始] --> B[代数层面功能正确性验证]
    B --> C[指针结构操作验证]
    C --> D[结束]

以下是一个表格,总结了P-Code文档改进的几个方面:
|改进方面|具体内容|
| ---- | ---- |
|高低级别P-Code文档分离|将文档分为高级别和低级别P-Code两部分|
|Varnodes的定义修正|采用新观点或更新指令|
|CALL、CALLIND、RETURN指令说明改进|改进说明方式|
|消除小的不一致性|消除文档中的小不一致|

P-Code形式语义与红黑树验证的研究

7. 软件系统的层次化组件分解

可以将软件系统分解为层次化组件,把抽象的系统描述细化为实际的实现。这种分解方式有助于将复杂的系统逐步细化,使得每个组件的功能和职责更加清晰,便于开发和验证。

8. 红黑树实现的拆分

红黑树的实现可以拆分为一个通用部分和可以在指针结构上执行的基本操作。通用部分处理与代数层面相关的逻辑,而基本操作则专注于指针结构的操作,如移除叶子节点或在路径上进行旋转等。这种拆分方式使得验证工作更加模块化,不同部分的验证可以独立进行。

9. 验证的关键属性概述

在红黑树的验证过程中,有一些关键属性需要重点关注:
- 代数层面的属性 :如红黑树的基本性质,包括节点颜色的规则、左右子树的平衡等,这些属性在代数层面进行验证,确保红黑树在抽象层面的正确性。
- 指针结构操作的属性 :指针结构操作的正确性,如内存分配和释放的正确性、指针的引用和更新是否符合预期等,这些属性使用分离逻辑进行验证,确保在实际的指针操作中不会出现错误。

10. 现有方法的比较

与现有的一些方法相比,这种基于细化的红黑树验证方法具有以下优势:
- 模块化验证 :将验证工作分为代数层面的功能正确性验证和指针结构操作验证两个独立的部分,避免了概念正确性论证与指针别名和副作用问题的交织,使得验证更加清晰和高效。
- 高效实现 :目标是验证一个高效的版本,使用父指针并避免递归,使生成的代码与标准C代码实现相当,提高了代码的执行效率。

以下是一个列表,总结了红黑树验证方法的优势:
1. 模块化验证,将两个问题分开处理。
2. 基于数据细化的顺序程序组件,在定理证明器中得到原生支持。
3. 生成的代码高效,与标准C代码实现相当。

以下是一个mermaid格式的流程图,展示了红黑树验证方法与现有方法的比较:

graph LR
    A[现有方法] --> B[概念正确性与指针问题交织]
    A --> C[代码效率可能不高]
    D[基于细化的方法] --> E[模块化验证]
    D --> F[高效代码实现]

综上所述,P-Code的形式语义研究揭示了现有文档和实现中存在的问题,并提出了改进建议,同时相关的程序分析工作也为后续研究提供了参考。而红黑树验证的基于细化的方法通过模块化的方式,有效地解决了验证过程中概念正确性与指针操作的复杂性问题,实现了高效的代码验证和生成。这种方法对于提高软件系统的可靠性和性能具有重要意义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值