Merge pull request #1048 from Samuka007/feat-network-rebuild

fix(net): merge master and make fmt
This commit is contained in:
Samuel Dai 2024-11-15 17:46:21 +08:00 committed by GitHub
commit bd5f713617
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
308 changed files with 33790 additions and 1310 deletions

View File

@ -12,14 +12,14 @@ jobs:
name: Format check ${{ matrix.arch }}
runs-on: ubuntu-latest
continue-on-error: true
container: dragonos/dragonos-dev:v1.4
container: dragonos/dragonos-dev:v1.6
strategy:
matrix:
arch: [x86_64, riscv64]
steps:
- run: echo "Running in dragonos/dragonos-dev:v1.4"
- run: echo "Running in dragonos/dragonos-dev:v1.6"
- uses: actions/checkout@v3
- name: Format check
@ -35,14 +35,14 @@ jobs:
name: Kernel static test ${{ matrix.arch }}
runs-on: ubuntu-latest
continue-on-error: true
container: dragonos/dragonos-dev:v1.4
container: dragonos/dragonos-dev:v1.6
strategy:
matrix:
arch: [x86_64, riscv64]
steps:
- run: echo "Running in dragonos/dragonos-dev:v1.4"
- run: echo "Running in dragonos/dragonos-dev:v1.6"
- uses: actions/checkout@v3
@ -51,15 +51,15 @@ jobs:
env:
ARCH: ${{ matrix.arch }}
HOME: /root
run: bash -c "source /root/.cargo/env && cd kernel && make test"
run: bash -c "source /root/.cargo/env && cd kernel && make test && make test-rbpf"
build-x86_64:
runs-on: ubuntu-latest
container: dragonos/dragonos-dev:v1.4
container: dragonos/dragonos-dev:v1.6
steps:
- run: echo "Running in dragonos/dragonos-dev:v1.4"
- run: echo "Running in dragonos/dragonos-dev:v1.6"
- uses: actions/checkout@v3
- name: build the DragonOS
@ -78,10 +78,10 @@ jobs:
build-riscv64:
runs-on: ubuntu-latest
container: dragonos/dragonos-dev:v1.4
container: dragonos/dragonos-dev:v1.6
steps:
- run: echo "Running in dragonos/dragonos-dev:v1.4"
- run: echo "Running in dragonos/dragonos-dev:v1.6"
- uses: actions/checkout@v3
with:

View File

@ -5,4 +5,4 @@ fmt:
clean:
@cargo clean
check:
@cargo +nightly-2024-07-23 check --workspace $(CARGO_ZBUILD) --message-format=json
@cargo +nightly-2024-11-05 check --workspace $(CARGO_ZBUILD) --message-format=json

View File

@ -36,3 +36,6 @@ help:
.PHONY:
html-multiversion:
rm -rf ./$(BUILDDIR) && CURRENT_GIT_COMMIT_DIRTY=0 sphinx-multiversion $(SPHINXOPTS) "$(SOURCEDIR)" ./$(BUILDDIR)/html && cp -rf ./$(BUILDDIR)/html/master/* ./$(BUILDDIR)/html/
http_server:
python3 -m http.server --directory $(BUILDDIR)/html

View File

@ -9,7 +9,8 @@
.. toctree::
:maxdepth: 1
the-development-process
how-to-contribute <https://community.dragonos.org/contributors/>
c-coding-style
rust-coding-style
conventional-commit

View File

@ -1,155 +0,0 @@
# 开发流程介绍
&emsp;&emsp;作为一名想要参与开发的新人,您可能迫切想要了解如何参与开发,仔细阅读这篇文章将能帮助您了解整个开发流程,以及一些注意事项。
&emsp;&emsp;本文参考了Linux文档中的一些思想、内容非常感谢Linux社区的工作者们的经验
## 1.概要
对于新人而言,参与开发的过程主要包括以下几步:
- **运行DragonOS**:按照文档:{ref}`构建DragonOS <_build_dragonos>`中的教程编译DragonOS并成功运行。在运行过程中如果有任何的疑问欢迎您在交流群或者BBS上进行反馈
- **联系Maintainer**:您可以通过邮箱<longjin@DragonOS.org>或者QQ`184829088`与龙进取得联系或者是对应的模块的开发者进行联系目前您可以通过发行日志上的邮箱与他们建立联系在将来我们将编写一份“Maintainers List”以便于您能快速找到要联系的人
为了节省您的时间,请简要说明:
- 如何称呼您
- 您目前掌握的技术能力
- 您希望为DragonOS贡献什么功能或者进行某个方面的开发亦或者是想要按照社区的需要来参与新功能开发及bug的修复。
- 如果您是来自高校/科研单位/企业/组织的代表希望与社区开展合作研究、开发。那么除使用QQ交流之外还请麻烦您通过您的教师邮箱/学生邮箱/企业邮箱向<contact@DragonOS.org>发送一封相关内容的邮件!这么做的目的是为了确认您是来自您的单位,而不是网络上其他人员冒充您的身份。
- **加入工作群**:在进一步了解,确认您愿意参与开发后,我们将邀请您加入工作群。
- **开始开发**:正式开始代码开发工作。
:::{note}
一些小功能的改进以及Bug修复并不一定需要提前与社区建立正式的联系对于这些patch您可以直接开发然后在Github上进行Pull Request. 这也是可以的。
但是如果您愿意的话与Maintainer联系会是一个更好的选择。
:::
## 2.开发过程是如何运作的?
&emsp;&emsp;如今的DragonOS由于正处于开发的早期阶段开发者数量不超过50人因此现在DragonOS的开发过程是通过比较松散的方式组织起来的。
### 2.1.版本发布周期
&emsp;&emsp;自从2022年11月6日DragonOS发布第一个版本以来版本发布就被定义为15~21天发布一个更新版本。由于开发人员数量仍然较少因此目前这个时间是21天。我们将版本号定义为`x.y.z`的格式每21天发布一个`z`版本. 在积累了23个月后当DragonOS引入了足够的新功能则发布一个`y`版本。请注意,我们仍未定义`x`版本的发行周期。当前,`x`版本号仍然是`0`
&emsp;&emsp;创建没有BUG的、具有尽可能少BUG的代码是每个开发者的目标之一。我们希望在每个`y`版本发布时能够修复已知的问题。然而由于在操作系统中影响问题的变量太多了以至于我们只能尽全力去减少BUG我们无法保证`y`版本不存在bug.
### 2.2.每个补丁的生命周期
&emsp;&emsp;当您向DragonOS的仓库发起一次PR那么这次PR就是一个补丁。我们会对您的补丁进行Review以确保每个补丁都实现了一个希望在主线中进行的更改。并且Maintainer或者感兴趣的小伙伴会对您的补丁提出修改意见。当时机合适时您的代码将被合入主线。
&emsp;&emsp;如果您的补丁的规模比较小,那么,它将会比较快的被合入主线。如果补丁的规模较大,或者存在一些争议,那么我们需要对其进行进一步的讨论及修改、审查,直到它符合要求。
&emsp;&emsp;每个Patch都会经历这么一个过程这只是一个概述详细的描述请看后文
- **设计**:在这个阶段,我们将明确,这个补丁将要实现什么功能,或者是解决什么问题,以及我们将要采用什么样的方式去完成它。通常来说,这项工作是开发者自己完成的。但是,**我们建议您,在设计了这个补丁之后,能够把您的设计方案公开,和大家讨论这项工作。** 闭门造车容易出错,在与大家沟通的过程中,则能及早的发现设计上的错误,从而节省很多的时间。
- **代码编写**:经过了设计阶段,您已经能够明白自己要实现的到底是一个什么样的东西。您在这个阶段进行代码编写、调试。
- **代码审查**当完成代码编写后您可以通过Github发起一个PR然后您的补丁进入了代码审查阶段。在这一阶段开发者们或者是Maintainer会和您进行沟通交流对您的代码进行评论以及对发现的问题提出修改建议。
- **合并主线**:如果一切顺利,那么代码将会被合并入主线。若该补丁在合并主线后,被发现具有较大的问题,那么它可能会被回退。重新进入前面的阶段,进行修改。
- **长期维护**:虽然说,代码被合并之后,原来的开发人员可能会在很久一段时间后,不再理会这些代码,但是这种行为可能会给其他开发者们留下不好的印象。其实,当补丁被合并入主线后,其他开发人员会尝试继续维护这些代码,这样能够很大程度的减轻您的维护负担。但是,如果想要这些代码能够长期的被保留下来,持续的发光发热,那么离不开原始开发人员的支持(不然的话,后来的人可能难以理解、维护这些代码),这是一件很有意义的事情。
&emsp;&emsp;对于没有参与过开源项目的同学来说,他们可能会想当然的,简单的把上述流程简化成**合并主线**这一个步骤,这样是不可取的。因为这样会导致很多问题,包括但不限于“写了代码但是效果很差”、“写的东西由于无法满足项目的需求,而不能被合并”。
### 2.3.开发工具
&emsp;&emsp;从上面的描述可以看出为了让开发人员们能高效地进行协作那么必须使用版本控制工具来管理这个过程。目前DragonOS使用Git来进行源代码管理。它是一个非常优秀的工具这是不必多说的。对于每个开发者而言Git的使用是一项必备的技能哪怕您只是想学习DragonOS的源代码您也需要git来获取、同步最新的代码。虽然Git的使用对于新手来说有些困难但是经过一些时间的学习后还是可以掌握的。
&emsp;&emsp;git的官方网站为[https://git-scm.com/](https://git-scm.com/)
### 2.4.沟通交流
&emsp;&emsp;DragonOS的主要开发工作是通过飞书以及bbs进行的。对于正准备参与的开发者而言您可以加入我们的交流讨论QQ群具体的群号可以在 {ref}`与社区建立联系 <get_contact_with_community>` 一文中找到。
&emsp;&emsp;**何时使用即时通讯软件?** 我们在飞书上创建了工作群,为提高即时沟通的效率,这个群仅供那些真正有意愿、且正在进行或准备进行(能够保证会进行)代码开发的开发者们加入。
&emsp;&emsp;**何时使用BBS** 对于一些正式的、需要大家广泛参与,或者是能够帮助尚未参与开发的同学了解当前的开发进度的主题,请您在[https://bbs.DragonOS.org](https://bbs.DragonOS.org)上使用类似于写信件一样的正式的语言完整地描述清楚您想表达的内容。这样有助于更多的人快速明白您要表达的是什么也能提高整体的沟通效率。并且bbs能够长期保存以往的帖子这样后来者能更方便的了解“当初开发的时候人们究竟是怎么想的”。
&emsp;&emsp;**关于交流讨论会** 除由于法定节假日放假,或特殊情况以外,我们每周末都会召开线上交流讨论会,同步介绍每周的进展。社区成员可以在这里分享自己的方案设计或是一些操作系统相关的知识(分享的话,需要提前跟会议主持人提出,以便妥善安排)。
&emsp;&emsp;**如何提问?** 下面这些建议能够帮助您与他人开展高效率的对话:
- **对于具有主题性的问题在bbs上发帖进行讨论。** 这样能够让讨论更具有目标性。当谈论到新的主题的时候,请开一个新的帖子,并在原来的帖子中,添加对特定的子问题所在的主题的链接。
- **请礼貌的交流。** 文明的语言能够减少不必要的冲突。技术意见上的冲突是思维的碰撞,但是如果涉及到了不文明的语言,或者在非技术层面,对他人进行攻击,这将破坏和谐讨论的氛围,这是我们反对的。如果有人试图激怒你,请忽略他的消息,别理他就好了。
- **在提问之前请确保您已经搜索了bbs以及互联网上的解决方案并描述清楚您的问题的上下文情景、您的思考以及网络上的解决方案。** 一些开发人员会对“明显没有进行认真思考”的问题,表现出不耐烦的态度(因为未经思考的问题会浪费他们大量的时间)。
- **当别人向您提问时**,请您耐心听他人的问题。如果您认为对方的问题过于简单或是未经思考,还请您为对方指个路,告诉对方,在哪些地方,使用什么样的方式搜索,能够得到对解决问题有帮助的资料。有时候,**新手需要的是一个指路人**,他会非常感谢您的!
### 2.5.如何入门开发?
&emsp;&emsp;DragonOS原采用C语言进行开发目前正在用Rust重构原有代码、开发新的模块也就是说除非您要进行对C语言代码的BUG修复否则其余的开发工作我们都建议您通过Rust完成。因为它能从语言层面解决那些让我们头疼的内存安全问题。从长期来看能够提升开发效率以及软件质量。
&emsp;&emsp;如何开发第一个补丁,是一个非常常见的问题。可以理解的是,个人开发者面对这样一个项目,常常会不知道从哪个地方开始入手。这是一件很正常的事情,因此我们建议您通过上文提到的方式,与社区建立联系,了解目前社区正在做什么,以及需要什么。
&emsp;&emsp;对于一个新的参与者来说,我们建议您从这样一个步骤开始:
```text
阅读文档编译、运行DragonOS并且尝试使用它目前已有的功能。
```
&emsp;&emsp;然后您可以通过查看DragonOS的GitHub仓库的project面板看看目前仍有哪些待解决的问题。可以肯定的是永远不会缺少待解决的问题您在解决这些问题的过程中能够获得一些宝贵的经验。
## 3.早期设计
&emsp;&emsp;对于软件开发而言,写代码永远不是第一步。在编写代码之前,进行一些必要的设计(提出架构、技术方案),是项目成功的基础工作。在新的补丁开发的早期,花费一些时间进行计划和沟通,往往能够在接下来的阶段节省更多的时间。
### 3.1.定义我们要解决的问题
&emsp;&emsp;与大多数的工程项目一样在DragonOS中进行开发首先需要清晰的描述要解决的问题。只有精准的定义了问题才能找到正确的解决方案。有时候我们能很轻易的定义问题比如“编写串口驱动程序使得它能把屏幕上打印的字符输出到串口”。
&emsp;&emsp;但是,有时候,**我们容易将真正的问题与某个解决方案相混淆**,并且还没意识到这一点。
&emsp;&emsp;在2022年10月时我发现在真机调试的时候需要频繁的拔插硬盘先连接到电脑待数据拷贝完毕后再连接到测试机。我对这一过程非常的不满因为很浪费时间。我的直觉想法是“有没有一种设备能够一头连接电脑另一头连接测试机的SATA接口。从测试机的角度看来这是一块硬盘测试机对这块磁盘的操作都转换为了对我的电脑上面的一个磁盘镜像文件的操作。”我的想法就是“购买/定制一款设备能够实现上面的这个功能那就能解决频繁拔插硬盘的烦恼了”然后我为了寻找这样的设备询问了一些硬件公司他们的开价都在2万元左右。
&emsp;&emsp;我在上面的这个过程中,就犯了一个错误:将真正的问题与某个解决方案相混淆了。真正的问题是:“解决需要频繁的拔插硬盘”,但是,在我的思考的过程中,不知不觉间,将问题转换成了“如何实现一款具有硬盘模拟功能的设备”。后面这个问题,只是某个解决方案下,需要解决的问题,并不是我们要解决的根本问题。
&emsp;&emsp;对于要解决的根本问题,事实上有更好的解决方案:“制作一个类似于开关一样的转换器,当数据从电脑拷贝到磁盘后,把开关拨向另一边,使得电路与测试机接通”。这个方案的成本估摸着就十几二十块钱。
&emsp;&emsp;上面的这个故事,告诉我们的是,**在早期设计阶段,我们应当关注的是问题本身——而不是特定的解决方案**。
&emsp;&emsp;我们需要关注系统的稳定性、长期的可维护性,解决方案必须考虑到这两点。由于系统比较复杂,因此,请您在开始编码之前,与社区的小伙伴讨论您的设计方案,以便您的方案能充分地,从全局的角度,考虑到系统的稳定性、可维护性。
&emsp;&emsp;**因此,在开发的早期,我们应当对以下三个问题,拥有一个答案**
- 要解决的本质问题是什么?
- 这个问题会影响哪些方面/哪些用户?提出的解决方案应当解决哪些用例、场景?
- DragonOS目前在解决该问题的方面具有哪些不足/问题?
&emsp;&emsp;只有考虑清楚了上面三个问题,讨论的解决方案才是有意义的。这是一个架构设计的过程,需要进行仔细的思考。尽管我们目前提倡敏捷开发,但是前期的架构设计仍然是非常重要的。
### 3.2.早期讨论
&emsp;&emsp;在实施开发之前,与社区的成员们进行讨论是非常有意义的。这能够通过多种方式节省您的时间,并减少许多不必要的麻烦:
- DragonOS可能以您不知道、不理解的方式已经解决了相关的问题。DragonOS里面的一些特性、功能细节不是很明显他们不一定会被写进文档。也许这些细节只是在某个不起眼的注释里面提到了因此您很难注意到这些。这种细节可能只有一些开发人员知道。因此与社区沟通能够避免您进行重复的工作。
- 您提出的解决方案中,可能会有一些东西,由于一些原因(比如方案中的一些设计会在将来造成问题、方案的架构设计具有明显缺陷),无法合入主线。
- 其他的开发人员可能已经考虑过这个问题;他们可能有更好的解决方案,或者是更好的想法。并且,他们可能愿意帮助你一起完善你的解决方案。
&emsp;&emsp;Linux文档中提到闭门造车式的设计和开发所产生的代码总会有问题这些问题只有在发布到社区里面的时候才会暴露出来。因此我们必须吸取前人之鉴通过与社区开发人员进行早期讨论从而避免大量的痛苦和额外的工作。
### 3.3.在何时发布帖子?
&emsp;&emsp;如果可能的话在开发的早期阶段发布您的计划、设计会是一个不错的选择。发帖的时候您可以描述您正在解决的问题以及已经制定的一些计划。包括但不限于如何将设计付诸实施。您在社区内发布帖子不仅能够帮助您获得一些有用的建议也能帮助整个DragonOS社区提供有用的信息使得社区沟通更高效。
&emsp;&emsp;在这个阶段,可能您发布的帖子并不会引来很多评论,这并不一定意味着您做的不好,或者大家对您所做的工作不感兴趣。当然,也不能就此认为您的设计、想法不存在问题。可能只是因为大家比较忙,看了您的帖子之后,了解到了您的工作,但是大家并不一定有时间进行回复。(但是事实上您发布的信息对他人来说是有用的)
&emsp;&emsp;在这种情况下,请不要气馁,您最好的做法就是,继续您的工作,并且不时地在您的帖子下分享您的工作,这样能够让社区的成员们随时了解到您的最新进展。
### 3.4.获得您所在的组织的支持
&emsp;&emsp;如果您对DragonOS的开发工作是在您的公司内完成的。那么很显然在您把计划、代码发布到社区论坛之前您必须取得您的经理或上级的许可。
&emsp;&emsp;同时请注意根据我们的授权许可基于DragonOS操作系统的内核、官方开源的用户库而开发的代码或者为DragonOS操作系统本身而开发的代码根据开源授权许可必须同样以GPLv2协议进行授权发布。如果您所在的组织违背了GPLv2协议中的要求以除GPLv2以外的协议开放源代码或者是进行“闭源使用”那么DragonOS社区对您的公司/组织所授予的使用DragonOS源代码的授权将会被自动撤销。这将会面临一系列的法律问题。因此在这个问题上公司的管理人员、法律人员如果能越早地就公司要在DragonOS中开发的软件项目达成一致将能促进您的公司在该项目上的进展。
&emsp;&emsp;如果您的公司的项目/或者是您研究的项目根据您所在组织的保密规定不能将其进行过早的披露那也没有问题。只要您的公司能够确保这部分代码在其编译而成的二进制产品被发布之时按照GPLv2协议进行开源并向开源社区贡献这部分代码即可。
## 4.如何正确的编写代码
## 5.发起Pull Request
## 6.后期跟进
## 7.另外的一些话题
## 8.更多信息
## 9.结语

View File

@ -8,9 +8,7 @@
社区公共邮箱contact@DragonOS.org
DragonOS社区负责人: 龙进
工作邮箱: longjin@DragonOS.org
社区管理人员信息https://community.dragonos.org/governance/staff-info.html
开发交流QQ群 115763565
@ -37,16 +35,5 @@ DragonOS社区的捐赠信息将按年进行公开。赞助商、赞助者信息
社区管理、财务及法务主体
-------------------------
DragonOS社区的管理、财务及法务主体为灵高计算机系统广州有限公司。
我们是一家开源公司我们坚信开源能为我国将来的IT打下更好的基础。我们也通过其他业务创收投入到DragonOS的研发之中。
公司负责DragonOS社区的运营、财务、法务事项处理工作。
地址:广东省广州市番禺区小谷围街广州大学城华南理工大学大学城校区
邮件contact@DragonOS.org
官网https://ringotek.com.cn
灵高是DragonOS社区为满足相关监管合规要求成立的 **非营利性质** 的单位。详情请见https://ringotek.com.cn

View File

@ -30,8 +30,11 @@
kernel/debug/index
kernel/ktest/index
kernel/cpu_arch/index
kernel/container/index
kernel/libs/index
kernel/net/index
kernel/trace/index
.. toctree::

View File

@ -0,0 +1,13 @@
====================================
容器化
====================================
这里是DragonOS中与容器化相关的说明文档。
主要包括namespaceoverlayfs和cgroup
.. toctree::
:maxdepth: 2
namespaces/index
filesystem/unionfs/index

View File

@ -0,0 +1,14 @@
====================================
名称空间
====================================
DragonOS的namespaces目前支持pid_namespace和mnt_namespace 预计之后会继续完善
namespace是容器化实现过程中的重要组成部分
由于目前os是单用户user_namespace为全局静态
.. toctree::
:maxdepth: 1
pid_namespace
mnt_namespace

View File

@ -0,0 +1,19 @@
# 挂载名称空间
## 底层架构
pcb -> nsproxy -> mnt_namespace
每一个挂载文件系统都有自立独立的挂载点,表现在数据结构上是一个挂载的红黑树,每一个名称空间中挂载是独立的,所以文件系统的挂载和卸载不会影响别的
## 系统调用接口
- clone
- CLONE_NEWNS用于创建一个新的 MNT 命名空间。提供独立的文件系统挂载点
- unshare
- 使用 CLONE_NEWPID 标志调用 unshare() 后,后续创建的所有子进程都将在新的命名空间中运行。
- setns
- 将进程加入到指定的名称空间
- chroot
- 将当前进程的根目录更改为指定的路径,提供文件系统隔离。

View File

@ -0,0 +1,21 @@
# 进程名称空间
:::{note} 本文作者:操丰毅 1553389239@qq.com
2024年10月30日 :::
pid_namespace 是内核中的一种名称空间用于实现进程隔离允许在不同的名称空间中运行的进程有独立的pid试图
## 底层架构
pcb -> nsproxy -> pid_namespace
- pid_namespace 内有独立的一套进程分配器以及孤儿进程回收器独立管理内部的pid
- 不同进程的详细信息都存放在proc文件系统中里面的找到对应的pid号里面的信息都在pid中记录的是pid_namespace中的信息
- pid_namespace等限制由ucount来控制管理
## 系统调用接口
- clone
- CLONE_NEWPID用于创建一个新的 PID 命名空间。使用这个标志时,子进程将在新的 PID 命名空间内运行,进程 ID 从 1 开始。
- unshare
- 使用 CLONE_NEWPID 标志调用 unshare() 后,后续创建的所有子进程都将在新的命名空间中运行。
- getpid
- 在命名空间中调用 getpid() 会返回进程在当前 PID 命名空间中的进程 ID

View File

@ -13,4 +13,5 @@ todo: 由于文件系统模块重构文档暂时不可用预计在2023年4
vfs/index
sysfs
kernfs
unionfs/index

View File

@ -0,0 +1,10 @@
====================================
联合文件系统
====================================
Union Filesystem
OverlayFS 将多个文件系统(称为“层”)合并为一个逻辑文件系统,使用户看到一个统一的目录结构。
.. toctree::
:maxdepth: 1
overlayfs

View File

@ -0,0 +1,26 @@
# overlayfs
OverlayFs是目前使用最多的联合文件系统原理简单方便使用主要用于容器中
在 Docker 中OverlayFS 是默认的存储驱动之一。Docker 为每个容器创建一个独立的上层目录,而所有容器共享同一个下层镜像文件。这样的设计使得容器之间的资源共享更加高效,同时减少了存储需求。
## 架构设计
overlayfs主要有两个层以及一个虚拟的合并层
- Lower Layer下层通常是 只读 文件系统。可以包含多层。
- Upper Layer上层为 可写层,所有的写操作都会在这一层上进行。
- Merged Layer合并层上层和下层的逻辑视图合并后向用户呈现的最终文件系统。
## 工作原理
- 读取操作:
- OverlayFS 会优先从 Upper Layer 读取文件。如果文件不存在于上层,则读取 Lower Layer 中的内容。
- 写入操作:
- 如果一个文件位于 Lower Layer 中,并尝试写入该文件,系统会将其 copy-up 到 Upper Layer 并在上层写入。如果文件已经存在于 Upper Layer则直接在该层写入。
- 删除操作:
- 当删除文件时OverlayFS 会在上层创建一个标记为 whiteout 的条目,这会隐藏下层的文件。
## Copy-up
- 写时拷贝
当一个文件从 下层 被修改时,它会被复制到 上层(称为 copy-up。之后的所有修改都会发生在上层的文件副本上。
## 实现逻辑
通过构建ovlInode来实现indexnode这个trait来代表上层或者下层的inode具体的有关文件文件夹的操作都在

324
docs/kernel/trace/eBPF.md Normal file
View File

@ -0,0 +1,324 @@
# eBPF
> 作者: 陈林峰
>
> Email: chenlinfeng25@outlook.com
## 概述
eBPF 是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序。它用于安全有效地扩展内核的功能,而无需通过更改内核源代码或加载内核模块的方式来实现。
从历史上看,由于内核具有监督和控制整个系统的特权,操作系统一直是实现可观测性、安全性和网络功能的理想场所。同时,由于操作系统内核的核心地位和对稳定性和安全性的高要求,操作系统内核很难快速迭代发展。因此在传统意义上,与在操作系统本身之外实现的功能相比,操作系统级别的创新速度要慢一些。
eBPF 从根本上改变了这个方式。通过允许在操作系统中运行沙盒程序的方式,应用程序开发人员可以运行 eBPF 程序,以便在运行时向操作系统添加额外的功能。然后在 JIT 编译器和验证引擎的帮助下,操作系统确保它像本地编译的程序一样具备安全性和执行效率。这引发了一股基于 eBPF 的项目热潮,它们涵盖了广泛的用例,包括下一代网络实现、可观测性和安全功能等领域。
## eBPF In DragonOS
在一个新的OS上添加eBPF的支持需要了解eBPF的运行过程通常eBPF需要用户态工具和内核相关基础设施配合才能发挥其功能。而新的OS通常会兼容Linux上的应用程序这可以进一步简化对用户态工具的移植工作只要内核实现相关的系统调用和功能就可以配合已有的工具完成eBPF的支持。
## eBPF的运行流程
![image-20240909165945192](./ebpf_flow.png)
如图所示eBPF程序的运行过程分为三个主要步骤
1. 源代码->二进制
1. 用户可以使用python/C/Rust编写eBPF程序并使用相关的工具链编译源代码到二进制程序
2. 这个步骤中用户需要合理使用helper函数丰富eBPF程序功能
2. 加载eBPF程序
1. 用户态的工具库会封装内核提供的系统调用接口以简化用户的工作。用户态工具对eBPF程序经过预处理后发出系统调用请求内核加载eBPF程序。
1. 内核首先会对eBPF程序进行验证检查程序的正确性和合法性同时也会对程序做进一步的处理
1. 内核会根据用户请求将eBPF程序附加到内核的挂载点上(kprobe/uprobe/trace_point)
1. 在内核运行期间,当这些挂载点被特定的事件触发, eBPF程序就会被执行
3. 数据交互
1. eBPF程序可以收集内核的信息用户工具可以选择性的获取这些信息
2. eBPF程序可以直接将信息输出到文件中用户工具通过读取和解析文件中的内容拿到信息
3. eBPF程序通过Map在内核和用户态之间共享和交换数据
## 用户态支持
用户态的eBPF工具库有很多比如C的libbpfpython的bcc, Rust的Aya总体来说这些工具的处理流程都大致相同。DragonOS当前支持[Aya](https://github.com/aya-rs/aya)框架编写的eBPF程序以Aya为例用户态的工具的处理过程如下:
1. 提供eBPF使用的helper函数和Map抽象方便实现eBPF程序
2. 处理编译出来的eBPF程序调用系统调用创建Map获得对应的文件描述符
3. 根据需要更新Map的值(.data)
4. 根据重定位信息对eBPF程序的相关指令做修改
5. 根据内核版本对eBPF程序中的bpf to bpf call进行处理
6. 加载eBPF程序到内核中
7. 对系统调用封装提供大量的函数帮助访问eBPF的信息并与内核交互
DragonOS对Aya 库的支持并不完整。通过对Aya库的删减我们实现了一个较小的[tiny-aya](https://github.com/DragonOS-Community/tiny-aya)。为了确保后期对Aya的兼容tiny-aya只对Aya中的核心工具aya做了修改**其中一些函数被禁用因为这些函数的所需的系统调用或者文件在DragonOS中还未实现**。
### Tokio
Aya需要使用异步运行时通过增加一些系统调用和修复一些错误DragonOS现在已经支持基本的tokio运行时。
### 使用Aya创建eBPF程序
与Aya官方提供的[文档](https://aya-rs.dev/book/start/development/)所述只需要根据其流程安装对应的Rust工具链就可以按照模板创建eBPF项目。以当前实现的`syscall_ebf`为例这个程序的功能是统计系统调用的次数并将其存储在一个HashMap中。
```
├── Cargo.toml
├── README.md
├── syscall_ebpf
├── syscall_ebpf-common
├── syscall_ebpf-ebpf
└── xtask
```
在user/app目录中项目结构如上所示
- `syscall_ebpf-ebpf`是 eBPF代码的实现目录其会被编译到字节码
- `syscall_ebpf-common` 是公共库,方便内核和用户态进行信息交互
- `syscall_ebpf` 是用户态程序其负责加载eBPF程序并获取eBPF程序产生的数据
- `xtask` 是一个命令行工具,方便用户编译和运行用户态程序
为了在DragonOS中运行用户态程序暂时还不能直接使用模板创建的项目
1. 这个项目不符合DragonOS对用户程序的项目结构要求当然这可以通过稍加修改完成
2. 因为DragonOS对tokio运行时的支持还不是完整体需要稍微修改一下使用方式
```
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn Error>> {
```
3. 因为对Aya支持不是完整体因此项目依赖的aya和aya-log需要换成tiny-aya中的实现。
```
[dependencies]
aya = { git = "https://github.com/DragonOS-Community/tiny-aya.git" }
aya-log = { git = "https://github.com/DragonOS-Community/tiny-aya.git" }
```
只需要稍加修改就可以利用Aya现有的工具完成eBPF程序的实现。
## 内核态支持
内核态支持主要为三个部分:
1. kprobe实现位于目录`kernel/crates/kprobe`
2. rbpf运行时位于目录`kernel/crates/rbpf`
3. 系统调用支持
4. helper函数支持
### rbpf
由于rbpf之前只是用于运行一些简单的eBPF程序其需要通过一些修改才能运行更复杂的程序。
1. 增加bpf to bpf call 的支持:通过增加新的栈抽象和保存和恢复必要的寄存器数据
2. 关闭内部不必要的内存检查,这通常由内核的验证器完成
3. 增加带所有权的数据结构避免生命周期的限制
### 系统调用
eBPF相关的系统调用都集中在`bpf()`通过参数cmd来进一步区分功能目前对其支持如下:
```rust
pub fn bpf(cmd: bpf_cmd, attr: &bpf_attr) -> Result<usize> {
let res = match cmd {
// Map related commands
bpf_cmd::BPF_MAP_CREATE => map::bpf_map_create(attr),
bpf_cmd::BPF_MAP_UPDATE_ELEM => map::bpf_map_update_elem(attr),
bpf_cmd::BPF_MAP_LOOKUP_ELEM => map::bpf_lookup_elem(attr),
bpf_cmd::BPF_MAP_GET_NEXT_KEY => map::bpf_map_get_next_key(attr),
bpf_cmd::BPF_MAP_DELETE_ELEM => map::bpf_map_delete_elem(attr),
bpf_cmd::BPF_MAP_LOOKUP_AND_DELETE_ELEM => map::bpf_map_lookup_and_delete_elem(attr),
bpf_cmd::BPF_MAP_LOOKUP_BATCH => map::bpf_map_lookup_batch(attr),
bpf_cmd::BPF_MAP_FREEZE => map::bpf_map_freeze(attr),
// Program related commands
bpf_cmd::BPF_PROG_LOAD => prog::bpf_prog_load(attr),
// Object creation commands
bpf_cmd::BPF_BTF_LOAD => {
error!("bpf cmd {:?} not implemented", cmd);
return Err(SystemError::ENOSYS);
}
ty => {
unimplemented!("bpf cmd {:?} not implemented", ty)
}
};
res
}
```
其中对创建Map命令会再次细分以确定具体的Map类型目前我们对通用的Map基本添加了支持:
```rust
bpf_map_type::BPF_MAP_TYPE_ARRAY
bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY
bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY
bpf_map_type::BPF_MAP_TYPE_HASH
bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH
bpf_map_type::BPF_MAP_TYPE_QUEUE
bpf_map_type::BPF_MAP_TYPE_STACK
bpf_map_type::BPF_MAP_TYPE_LRU_HASH
bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH
bpf_map_type::BPF_MAP_TYPE_CPUMAP
| bpf_map_type::BPF_MAP_TYPE_DEVMAP
| bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH => {
error!("bpf map type {:?} not implemented", map_meta.map_type);
Err(SystemError::EINVAL)?
}
```
所有的Map都会实现定义好的接口这个接口参考Linux的实现定义:
```rust
pub trait BpfMapCommonOps: Send + Sync + Debug + CastFromSync {
/// Lookup an element in the map.
///
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_map_lookup_elem/
fn lookup_elem(&mut self, _key: &[u8]) -> Result<Option<&[u8]>> {
Err(SystemError::ENOSYS)
}
/// Update an element in the map.
///
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_map_update_elem/
fn update_elem(&mut self, _key: &[u8], _value: &[u8], _flags: u64) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// Delete an element from the map.
///
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_map_delete_elem/
fn delete_elem(&mut self, _key: &[u8]) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// For each element in map, call callback_fn function with map,
/// callback_ctx and other map-specific parameters.
///
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_for_each_map_elem/
fn for_each_elem(&mut self, _cb: BpfCallBackFn, _ctx: *const u8, _flags: u64) -> Result<u32> {
Err(SystemError::ENOSYS)
}
/// Look up an element with the given key in the map referred to by the file descriptor fd,
/// and if found, delete the element.
fn lookup_and_delete_elem(&mut self, _key: &[u8], _value: &mut [u8]) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// perform a lookup in percpu map for an entry associated to key on cpu.
fn lookup_percpu_elem(&mut self, _key: &[u8], cpu: u32) -> Result<Option<&[u8]>> {
Err(SystemError::ENOSYS)
}
/// Get the next key in the map. If key is None, get the first key.
///
/// Called from syscall
fn get_next_key(&self, _key: Option<&[u8]>, _next_key: &mut [u8]) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// Push an element value in map.
fn push_elem(&mut self, _value: &[u8], _flags: u64) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// Pop an element value from map.
fn pop_elem(&mut self, _value: &mut [u8]) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// Peek an element value from map.
fn peek_elem(&self, _value: &mut [u8]) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// Freeze the map.
///
/// It's useful for .rodata maps.
fn freeze(&self) -> Result<()> {
Err(SystemError::ENOSYS)
}
/// Get the first value pointer.
fn first_value_ptr(&self) -> *const u8 {
panic!("value_ptr not implemented")
}
}
```
联通eBPF和kprobe的系统调用是[`perf_event_open`](https://man7.org/linux/man-pages/man2/perf_event_open.2.html)这个系统调用在Linux中非常复杂因此Dragon中并没有按照Linux进行实现目前只支持其中两个功能:
```rust
match args.type_ {
// Kprobe
// See /sys/bus/event_source/devices/kprobe/type
perf_type_id::PERF_TYPE_MAX => {
let kprobe_event = kprobe::perf_event_open_kprobe(args);
Box::new(kprobe_event)
}
perf_type_id::PERF_TYPE_SOFTWARE => {
// For bpf prog output
assert_eq!(args.config, perf_sw_ids::PERF_COUNT_SW_BPF_OUTPUT);
assert_eq!(
args.sample_type,
Some(perf_event_sample_format::PERF_SAMPLE_RAW)
);
let bpf_event = bpf::perf_event_open_bpf(args);
Box::new(bpf_event)
}
}
```
- 其中一个`PERF_TYPE_SOFTWARE`是用来创建软件定义的事件,`PERF_COUNT_SW_BPF_OUTPUT` 确保这个事件用来采集bpf的输出。
- `PERF_TYPE_MAX` 通常指示创建kprobe/uprobe事件也就是用户程序使用kprobe的途径之一用户程序可以将eBPF程序绑定在这个事件上
同样的perf不同的事件也实现定义的接口:
```rust
pub trait PerfEventOps: Send + Sync + Debug + CastFromSync + CastFrom {
fn mmap(&self, _start: usize, _len: usize, _offset: usize) -> Result<()> {
panic!("mmap not implemented for PerfEvent");
}
fn set_bpf_prog(&self, _bpf_prog: Arc<File>) -> Result<()> {
panic!("set_bpf_prog not implemented for PerfEvent");
}
fn enable(&self) -> Result<()> {
panic!("enable not implemented");
}
fn disable(&self) -> Result<()> {
panic!("disable not implemented");
}
fn readable(&self) -> bool {
panic!("readable not implemented");
}
}
```
这个接口目前并不稳定。
### helper函数支持
用户态工具通过系统调用和内核进行通信完成eBPF数据的设置、交换。在内核中eBPF程序的运行也需要内核的帮助单独的eBPF程序并没有什么太大的用处因此其会调用内核提供的`helper` 函数完成对内核资源的访问。
目前已经支持的大多数`helper` 函数是与Map操作相关:
```rust
/// Initialize the helper functions.
pub fn init_helper_functions() {
let mut map = BTreeMap::new();
unsafe {
// Map helpers::Generic map helpers
map.insert(1, define_func!(raw_map_lookup_elem));
map.insert(2, define_func!(raw_map_update_elem));
map.insert(3, define_func!(raw_map_delete_elem));
map.insert(164, define_func!(raw_map_for_each_elem));
map.insert(195, define_func!(raw_map_lookup_percpu_elem));
// map.insert(93,define_func!(raw_bpf_spin_lock);
// map.insert(94,define_func!(raw_bpf_spin_unlock);
// Map helpers::Perf event array helpers
map.insert(25, define_func!(raw_perf_event_output));
// Probe and trace helpers::Memory helpers
map.insert(4, define_func!(raw_bpf_probe_read));
// Print helpers
map.insert(6, define_func!(trace_printf));
// Map helpers::Queue and stack helpers
map.insert(87, define_func!(raw_map_push_elem));
map.insert(88, define_func!(raw_map_pop_elem));
map.insert(89, define_func!(raw_map_peek_elem));
}
BPF_HELPER_FUN_SET.init(map);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -0,0 +1,11 @@
内核跟踪机制
====================================
内核跟踪机制由很多功能构成, 比如kprobe/uprobe/tracepoint/ftrace等 以及用于扩展内核可观测性的eBPF内核当前支持kprobe和eBPF 本章将介绍这两种机制。
.. toctree::
:maxdepth: 1
:caption: 目录
eBPF
kprobe

View File

@ -0,0 +1,57 @@
# kprobe
> 作者: 陈林峰
>
> Email: chenlinfeng25@outlook.com
## 概述
Linux kprobes调试技术是内核开发者们专门为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。利用kprobes技术内核开发人员可以在内核的绝大多数指定函数中动态的插入探测点来收集所需的调试状态信息而基本不影响内核原有的执行流程。
kprobes技术依赖硬件架构相关的支持主要包括CPU的异常处理和单步调试机制前者用于让程序的执行流程陷入到用户注册的回调函数中去而后者则用于单步执行被探测点指令。需要注意的是在一些架构上硬件并不支持单步调试机制这可以通过一些软件模拟的方法解决(比如riscv)。
## kprobe工作流程
<img src="./kprobe_flow.png" style="zoom: 67%;" alt="xxx"/>
1. 注册kprobe后注册的每一个kprobe对应一个kprobe结构体该结构中记录着探测点的位置以及该探测点本来对应的指令。
2. 探测点的位置被替换成了一条异常的指令这样当CPU执行到探测点位置时会陷入到异常态在x86_64上指令是int3如果kprobe经过优化后指令是jmp
3. 当执行到异常指令时系统换检查是否是kprobe 安装的异常如果是就执行kprobe的pre_handler,然后利用CPU提供的单步调试single-step功能设置好相应的寄存器将下一条指令设置为插入点处本来的指令从异常态返回
4. 再次陷入异常态。上一步骤中设置了single-step相关的寄存器所以原指令刚一执行便会再次陷入异常态此时将single-step清除并且执行post_handler然后从异常态安全返回.
5. 当卸载kprobe时探测点原来的指令会被恢复回去。
内核目前对x86和riscv64都进行了支持由于 riscv64 没有单步执行模式,因此我们使用 break 异常来进行模拟,在保存探测点指令时,我们会额外填充一条 break 指令这样就可以使得在riscv64架构上在执行完原指令后会再次触发break陷入异常。
## kprobe的接口
```rust
pub fn register_kprobe(kprobe_info: KprobeInfo) -> Result<LockKprobe, SystemError>;
pub fn unregister_kprobe(kprobe: LockKprobe) -> Result<(), SystemError>;
impl KprobeBasic {
pub fn call_pre_handler(&self, trap_frame: &dyn ProbeArgs)
pub fn call_post_handler(&self, trap_frame: &dyn ProbeArgs)
pub fn call_fault_handler(&self, trap_frame: &dyn ProbeArgs)
pub fn call_event_callback(&self, trap_frame: &dyn ProbeArgs)
pub fn update_event_callback(&mut self, callback: Box<dyn CallBackFunc>)
pub fn disable(&mut self)
pub fn enable(&mut self)
pub fn is_enabled(&self) -> bool
pub fn symbol(&self) -> Option<&str>
}
```
- `call_pre_handler` 在探测点指令被执行前调用用户定义的回调函数
- `call_post_handler` 在单步执行完探测点指令后调用用户定义的回调函数
- `call_fault_handler` 在调用前两种回调函数发生失败时调用
- `call_event_callback` 用于调用eBPF相关的回调函数通常与`call_post_handler` 一样在单步执行探测点指令会调用
- `update_event_callback`用于运行过程中更新回调函数
- `disable``enable` 用于动态关闭kprobe`disable`调用后kprobe被触发时不执行回调函数
- `symbol` 返回探测点的函数名称

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -23,7 +23,10 @@ kvm = []
fatfs = []
fatfs-secure = ["fatfs"]
driver_ps2_mouse = []
# kprobe
kprobe_test = []
# 运行时依赖项
[dependencies]
@ -58,8 +61,12 @@ wait_queue_macros = { path = "crates/wait_queue_macros" }
paste = "=1.0.14"
slabmalloc = { path = "crates/rust-slabmalloc" }
log = "0.4.21"
kprobe = { path = "crates/kprobe" }
xarray = "0.1.0"
lru = "0.12.3"
rbpf = { path = "crates/rbpf" }
printf-compat = { version = "0.1.1", default-features = false }
# target为x86_64时使用下面的依赖
[target.'cfg(target_arch = "x86_64")'.dependencies]
mini-backtrace = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/mini-backtrace.git", rev = "e0b1d90940" }

View File

@ -36,12 +36,14 @@ check: ECHO
# @echo "Checking kernel... ARCH=$(ARCH)"
# @exit 1
ifeq ($(ARCH), x86_64)
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-07-23 check --workspace $(CARGO_ZBUILD) --message-format=json --target ./src/$(TARGET_JSON)
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 check --workspace $(CARGO_ZBUILD) --message-format=json --target ./src/$(TARGET_JSON)
else ifeq ($(ARCH), riscv64)
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-07-23 check --workspace $(CARGO_ZBUILD) --message-format=json --target $(TARGET_JSON)
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 check --workspace $(CARGO_ZBUILD) --message-format=json --target $(TARGET_JSON)
endif
test:
# 测试内核库
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-07-23 test --workspace --exclude dragonos_kernel
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 test --workspace --exclude dragonos_kernel rbpf
test-rbpf:
cd crates/rbpf && RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 test --features=std,user,cranelift

View File

@ -13,7 +13,7 @@ pub struct AllocBitmap {
impl AllocBitmap {
pub fn new(elements: usize) -> Self {
let data = vec![0usize; (elements + usize::BITS as usize - 1) / (usize::BITS as usize)];
let data = vec![0usize; elements.div_ceil(usize::BITS as usize)];
Self {
elements,
data,

View File

@ -8,15 +8,15 @@ use crate::{bitmap_core::BitMapCore, traits::BitMapOps};
#[derive(Debug, Clone)]
pub struct StaticBitmap<const N: usize>
where
[(); (N + usize::BITS as usize - 1) / (usize::BITS as usize)]:,
[(); N.div_ceil(usize::BITS as usize)]:,
{
pub data: [usize; (N + usize::BITS as usize - 1) / (usize::BITS as usize)],
pub data: [usize; N.div_ceil(usize::BITS as usize)],
core: BitMapCore<usize>,
}
impl<const N: usize> Default for StaticBitmap<N>
where
[(); (N + usize::BITS as usize - 1) / (usize::BITS as usize)]:,
[(); N.div_ceil(usize::BITS as usize)]:,
{
fn default() -> Self {
Self::new()
@ -25,12 +25,12 @@ where
impl<const N: usize> StaticBitmap<N>
where
[(); (N + usize::BITS as usize - 1) / (usize::BITS as usize)]:,
[(); N.div_ceil(usize::BITS as usize)]:,
{
/// 创建一个新的静态位图
pub const fn new() -> Self {
Self {
data: [0; (N + usize::BITS as usize - 1) / (usize::BITS as usize)],
data: [0; N.div_ceil(usize::BITS as usize)],
core: BitMapCore::new(),
}
}
@ -38,7 +38,7 @@ where
impl<const N: usize> BitMapOps<usize> for StaticBitmap<N>
where
[(); (N + usize::BITS as usize - 1) / (usize::BITS as usize)]:,
[(); N.div_ceil(usize::BITS as usize)]:,
{
#[inline]
fn get(&self, index: usize) -> Option<bool> {

View File

@ -1,6 +1,5 @@
#![cfg_attr(not(test), no_std)]
#![feature(const_for)]
#![feature(const_mut_refs)]
#![feature(const_trait_impl)]
#![allow(clippy::needless_return)]

View File

@ -16,7 +16,7 @@ struct EmptyIdaItemRef<'a> {
_marker: PhantomData<&'a EmptyIdaItem>,
}
impl<'a> Deref for EmptyIdaItemRef<'a> {
impl Deref for EmptyIdaItemRef<'_> {
type Target = EmptyIdaItem;
fn deref(&self) -> &Self::Target {
@ -27,7 +27,10 @@ impl<'a> Deref for EmptyIdaItemRef<'a> {
struct EmptyIdaItem;
unsafe impl kdepends::xarray::ItemEntry for EmptyIdaItem {
type Ref<'a> = EmptyIdaItemRef<'a> where Self: 'a;
type Ref<'a>
= EmptyIdaItemRef<'a>
where
Self: 'a;
fn into_raw(self) -> *const () {
core::ptr::null()
@ -140,6 +143,11 @@ impl IdAllocator {
pub fn used(&self) -> usize {
self.used
}
/// 返回最大id数
pub fn get_max_id(&self) -> usize {
self.max_id
}
}
impl core::fmt::Debug for IdAllocator {

View File

@ -61,7 +61,6 @@ mod item_type;
/// #[derive(std::fmt::Debug)]
/// struct Data;
/// ```
#[proc_macro_attribute]
pub fn cast_to(args: TokenStream, input: TokenStream) -> TokenStream {
match parse::<Targets>(args) {

View File

@ -122,6 +122,7 @@ static CASTER_MAP: once_cell::sync::Lazy<HashMap<(TypeId, TypeId), BoxedCaster,
static mut CASTER_MAP: Option<HashMap<(TypeId, TypeId), BoxedCaster, BuildFastHasher>> = None;
#[cfg(target_os = "none")]
#[allow(static_mut_refs)]
pub fn caster_map() -> &'static HashMap<(TypeId, TypeId), BoxedCaster, BuildFastHasher> {
return unsafe {
CASTER_MAP.as_ref().unwrap_or_else(|| {

View File

@ -1,5 +1,4 @@
#![no_std]
#![feature(const_refs_to_cell)]
#![feature(const_size_of_val)]
#![allow(clippy::needless_return)]

View File

@ -0,0 +1,11 @@
[package]
name = "kprobe"
version = "0.1.0"
edition = "2021"
[dependencies]
log = "0.4.21"
[target.'cfg(target_arch = "x86_64")'.dependencies]
yaxpeax-x86 = { version = "2", default-features = false, features = ["fmt"] }
yaxpeax-arch = { version = "0", default-features = false }

View File

@ -0,0 +1,112 @@
use alloc::sync::Arc;
use core::ops::{Deref, DerefMut};
use crate::{KprobeBasic, KprobeBuilder, KprobeOps};
const BRK_KPROBE_BP: u64 = 10;
const BRK_KPROBE_SSTEPBP: u64 = 11;
const EBREAK_INST: u32 = 0x002a0000;
#[derive(Debug)]
pub struct Kprobe {
basic: KprobeBasic,
point: Arc<LA64KprobePoint>,
}
#[derive(Debug)]
pub struct LA64KprobePoint {
addr: usize,
inst_tmp: [u8; 8],
}
impl Deref for Kprobe {
type Target = KprobeBasic;
fn deref(&self) -> &Self::Target {
&self.basic
}
}
impl DerefMut for Kprobe {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.basic
}
}
impl Kprobe {
pub fn probe_point(&self) -> &Arc<LA64KprobePoint> {
&self.point
}
}
impl Drop for LA64KprobePoint {
fn drop(&mut self) {
let address = self.addr;
let inst_tmp_ptr = self.inst_tmp.as_ptr() as usize;
let inst_32 = unsafe { core::ptr::read(inst_tmp_ptr as *const u32) };
unsafe {
core::ptr::write(address as *mut u32, inst_32);
}
log::trace!(
"Kprobe::uninstall: address: {:#x}, old_instruction: {:?}",
address,
inst_32
);
}
}
impl KprobeBuilder {
pub fn install(self) -> (Kprobe, Arc<LA64KprobePoint>) {
let probe_point = match &self.probe_point {
Some(point) => point.clone(),
None => self.replace_inst(),
};
let kprobe = Kprobe {
basic: KprobeBasic::from(self),
point: probe_point.clone(),
};
(kprobe, probe_point)
}
/// # 安装kprobe
///
/// 不同的架构下需要保存原指令,然后替换为断点指令
fn replace_inst(&self) -> Arc<LA64KprobePoint> {
let address = self.symbol_addr + self.offset;
let point = LA64KprobePoint {
addr: address,
inst_tmp: [0u8; 8],
};
let inst_tmp_ptr = point.inst_tmp.as_ptr() as usize;
let inst_32 = unsafe { core::ptr::read(address as *const u32) };
unsafe {
core::ptr::write(address as *mut u32, EBREAK_INST);
// inst_32 :0-32
// ebreak :32-64
core::ptr::write(inst_tmp_ptr as *mut u32, inst_32);
core::ptr::write((inst_tmp_ptr + 4) as *mut u32, EBREAK_INST);
}
log::trace!(
"Kprobe::install: address: {:#x}, func_name: {:?}, opcode: {:x?}",
address,
self.symbol,
inst_32
);
}
}
impl KprobeOps for LA64KprobePoint {
fn return_address(&self) -> usize {
self.addr + 4
}
fn single_step_address(&self) -> usize {
self.inst_tmp.as_ptr() as usize
}
fn debug_address(&self) -> usize {
self.inst_tmp.as_ptr() as usize + 4
}
fn break_address(&self) -> usize {
self.addr
}
}

View File

@ -0,0 +1,211 @@
use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use core::{any::Any, fmt::Debug};
#[cfg(target_arch = "loongarch64")]
mod loongarch64;
#[cfg(target_arch = "riscv64")]
mod rv64;
#[cfg(target_arch = "x86_64")]
mod x86;
#[cfg(target_arch = "loongarch64")]
pub use loongarch64::*;
#[cfg(target_arch = "riscv64")]
pub use rv64::*;
#[cfg(target_arch = "x86_64")]
pub use x86::*;
#[cfg(target_arch = "x86_64")]
pub type KprobePoint = X86KprobePoint;
#[cfg(target_arch = "riscv64")]
pub type KprobePoint = Rv64KprobePoint;
#[cfg(target_arch = "loongarch64")]
pub type KprobePoint = LA64KprobePoint;
pub trait ProbeArgs: Send {
/// 用于使用者转换到特定架构下的TrapFrame
fn as_any(&self) -> &dyn Any;
/// 返回导致break异常的地址
fn break_address(&self) -> usize;
/// 返回导致单步执行异常的地址
fn debug_address(&self) -> usize;
}
pub trait KprobeOps: Send {
/// # 返回探测点的下一条指令地址
///
/// 执行流需要回到正常的路径中,在执行完探测点的指令后,需要返回到下一条指令
fn return_address(&self) -> usize;
/// # 返回单步执行的指令地址
///
/// 通常探测点的处的原指令被保存在一个数组当中。根据架构的不同, 在保存的指令后面,可能会填充必要的指令。
/// 例如x86架构下支持单步执行的特性 而其它架构下通常没有因此我们使用break异常来进行模拟所以会填充
/// 一条断点指令。
fn single_step_address(&self) -> usize;
/// # 返回单步执行指令触发异常的地址
///
/// 其值等于`single_step_address`的值加上探测点指令的长度
fn debug_address(&self) -> usize;
/// # 返回设置break断点的地址
///
/// 其值与探测点地址相等
fn break_address(&self) -> usize;
}
struct ProbeHandler {
func: fn(&dyn ProbeArgs),
}
impl ProbeHandler {
pub fn new(func: fn(&dyn ProbeArgs)) -> Self {
ProbeHandler { func }
}
/// 调用探测点处理函数
pub fn call(&self, trap_frame: &dyn ProbeArgs) {
(self.func)(trap_frame);
}
}
pub struct KprobeBuilder {
symbol: Option<String>,
symbol_addr: usize,
offset: usize,
pre_handler: ProbeHandler,
post_handler: ProbeHandler,
fault_handler: Option<ProbeHandler>,
event_callback: Option<Box<dyn CallBackFunc>>,
probe_point: Option<Arc<KprobePoint>>,
enable: bool,
}
pub trait EventCallback: Send {
fn call(&self, trap_frame: &dyn ProbeArgs);
}
impl KprobeBuilder {
pub fn new(
symbol: Option<String>,
symbol_addr: usize,
offset: usize,
pre_handler: fn(&dyn ProbeArgs),
post_handler: fn(&dyn ProbeArgs),
enable: bool,
) -> Self {
KprobeBuilder {
symbol,
symbol_addr,
offset,
pre_handler: ProbeHandler::new(pre_handler),
post_handler: ProbeHandler::new(post_handler),
event_callback: None,
fault_handler: None,
probe_point: None,
enable,
}
}
pub fn with_fault_handler(mut self, func: fn(&dyn ProbeArgs)) -> Self {
self.fault_handler = Some(ProbeHandler::new(func));
self
}
pub fn with_probe_point(mut self, point: Arc<KprobePoint>) -> Self {
self.probe_point = Some(point);
self
}
pub fn with_event_callback(mut self, event_callback: Box<dyn CallBackFunc>) -> Self {
self.event_callback = Some(event_callback);
self
}
/// 获取探测点的地址
///
/// 探测点的地址 == break指令的地址
pub fn probe_addr(&self) -> usize {
self.symbol_addr + self.offset
}
}
pub struct KprobeBasic {
symbol: Option<String>,
symbol_addr: usize,
offset: usize,
pre_handler: ProbeHandler,
post_handler: ProbeHandler,
fault_handler: ProbeHandler,
event_callback: Option<Box<dyn CallBackFunc>>,
enable: bool,
}
pub trait CallBackFunc: Send + Sync {
fn call(&self, trap_frame: &dyn ProbeArgs);
}
impl Debug for KprobeBasic {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Kprobe")
.field("symbol", &self.symbol)
.field("symbol_addr", &self.symbol_addr)
.field("offset", &self.offset)
.finish()
}
}
impl KprobeBasic {
pub fn call_pre_handler(&self, trap_frame: &dyn ProbeArgs) {
self.pre_handler.call(trap_frame);
}
pub fn call_post_handler(&self, trap_frame: &dyn ProbeArgs) {
self.post_handler.call(trap_frame);
}
pub fn call_fault_handler(&self, trap_frame: &dyn ProbeArgs) {
self.fault_handler.call(trap_frame);
}
pub fn call_event_callback(&self, trap_frame: &dyn ProbeArgs) {
if let Some(ref call_back) = self.event_callback {
call_back.call(trap_frame);
}
}
pub fn update_event_callback(&mut self, callback: Box<dyn CallBackFunc>) {
self.event_callback = Some(callback);
}
pub fn disable(&mut self) {
self.enable = false;
}
pub fn enable(&mut self) {
self.enable = true;
}
pub fn is_enabled(&self) -> bool {
self.enable
}
/// 返回探测点的函数名称
pub fn symbol(&self) -> Option<&str> {
self.symbol.as_deref()
}
}
impl From<KprobeBuilder> for KprobeBasic {
fn from(value: KprobeBuilder) -> Self {
let fault_handler = value.fault_handler.unwrap_or(ProbeHandler::new(|_| {}));
KprobeBasic {
symbol: value.symbol,
symbol_addr: value.symbol_addr,
offset: value.offset,
pre_handler: value.pre_handler,
post_handler: value.post_handler,
event_callback: value.event_callback,
fault_handler,
enable: value.enable,
}
}
}

View File

@ -0,0 +1,157 @@
use alloc::sync::Arc;
use core::{
arch::riscv64::sfence_vma_all,
fmt::Debug,
ops::{Deref, DerefMut},
};
use crate::{KprobeBasic, KprobeBuilder, KprobeOps};
const EBREAK_INST: u32 = 0x00100073; // ebreak
const C_EBREAK_INST: u32 = 0x9002; // c.ebreak
const INSN_LENGTH_MASK: u16 = 0x3;
const INSN_LENGTH_32: u16 = 0x3;
#[derive(Debug)]
pub struct Kprobe {
basic: KprobeBasic,
point: Arc<Rv64KprobePoint>,
}
#[derive(Debug)]
enum OpcodeTy {
Inst16(u16),
Inst32(u32),
}
#[derive(Debug)]
pub struct Rv64KprobePoint {
addr: usize,
old_instruction: OpcodeTy,
inst_tmp: [u8; 8],
}
impl Deref for Kprobe {
type Target = KprobeBasic;
fn deref(&self) -> &Self::Target {
&self.basic
}
}
impl DerefMut for Kprobe {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.basic
}
}
impl Kprobe {
pub fn probe_point(&self) -> &Arc<Rv64KprobePoint> {
&self.point
}
}
impl Drop for Rv64KprobePoint {
fn drop(&mut self) {
let address = self.addr;
match self.old_instruction {
OpcodeTy::Inst16(inst_16) => unsafe {
core::ptr::write(address as *mut u16, inst_16);
},
OpcodeTy::Inst32(inst_32) => unsafe {
core::ptr::write(address as *mut u32, inst_32);
},
}
unsafe {
sfence_vma_all();
}
log::trace!(
"Kprobe::uninstall: address: {:#x}, old_instruction: {:?}",
address,
self.old_instruction
);
}
}
impl KprobeBuilder {
pub fn install(self) -> (Kprobe, Arc<Rv64KprobePoint>) {
let probe_point = match &self.probe_point {
Some(point) => point.clone(),
None => self.replace_inst(),
};
let kprobe = Kprobe {
basic: KprobeBasic::from(self),
point: probe_point.clone(),
};
(kprobe, probe_point)
}
/// # 安装kprobe
///
/// 不同的架构下需要保存原指令,然后替换为断点指令
fn replace_inst(&self) -> Arc<Rv64KprobePoint> {
let address = self.symbol_addr + self.offset;
let inst_16 = unsafe { core::ptr::read(address as *const u16) };
// See https://elixir.bootlin.com/linux/v6.10.2/source/arch/riscv/kernel/probes/kprobes.c#L68
let is_inst_16 = if (inst_16 & INSN_LENGTH_MASK) == INSN_LENGTH_32 {
false
} else {
true
};
let mut point = Rv64KprobePoint {
old_instruction: OpcodeTy::Inst16(0),
inst_tmp: [0; 8],
addr: address,
};
let inst_tmp_ptr = point.inst_tmp.as_ptr() as usize;
if is_inst_16 {
point.old_instruction = OpcodeTy::Inst16(inst_16);
unsafe {
core::ptr::write(address as *mut u16, C_EBREAK_INST as u16);
// inst_16 :0-16
// c.ebreak:16-32
core::ptr::write(inst_tmp_ptr as *mut u16, inst_16);
core::ptr::write((inst_tmp_ptr + 2) as *mut u16, C_EBREAK_INST as u16);
}
} else {
let inst_32 = unsafe { core::ptr::read(address as *const u32) };
point.old_instruction = OpcodeTy::Inst32(inst_32);
unsafe {
core::ptr::write(address as *mut u32, EBREAK_INST);
// inst_32 :0-32
// ebreak :32-64
core::ptr::write(inst_tmp_ptr as *mut u32, inst_32);
core::ptr::write((inst_tmp_ptr + 4) as *mut u32, EBREAK_INST);
}
}
unsafe {
sfence_vma_all();
}
log::trace!(
"Kprobe::install: address: {:#x}, func_name: {:?}, opcode: {:x?}",
address,
self.symbol,
point.old_instruction
);
Arc::new(point)
}
}
impl KprobeOps for Rv64KprobePoint {
fn return_address(&self) -> usize {
let address = self.addr;
match self.old_instruction {
OpcodeTy::Inst16(_) => address + 2,
OpcodeTy::Inst32(_) => address + 4,
}
}
fn single_step_address(&self) -> usize {
self.inst_tmp.as_ptr() as usize
}
fn debug_address(&self) -> usize {
match self.old_instruction {
OpcodeTy::Inst16(_) => self.inst_tmp.as_ptr() as usize + 2,
OpcodeTy::Inst32(_) => self.inst_tmp.as_ptr() as usize + 4,
}
}
fn break_address(&self) -> usize {
self.addr
}
}

View File

@ -0,0 +1,135 @@
use crate::{KprobeBasic, KprobeBuilder, KprobeOps};
use alloc::string::ToString;
use alloc::sync::Arc;
use core::{
fmt::Debug,
ops::{Deref, DerefMut},
};
use yaxpeax_arch::LengthedInstruction;
const EBREAK_INST: u8 = 0xcc; // x86_64: 0xcc
const MAX_INSTRUCTION_SIZE: usize = 15; // x86_64 max instruction length
pub struct Kprobe {
basic: KprobeBasic,
point: Arc<X86KprobePoint>,
}
#[derive(Debug)]
pub struct X86KprobePoint {
addr: usize,
old_instruction: [u8; MAX_INSTRUCTION_SIZE],
old_instruction_len: usize,
}
impl Drop for X86KprobePoint {
fn drop(&mut self) {
let address = self.addr;
unsafe {
core::ptr::copy(
self.old_instruction.as_ptr(),
address as *mut u8,
self.old_instruction_len,
);
core::arch::x86_64::_mm_mfence();
}
let decoder = yaxpeax_x86::amd64::InstDecoder::default();
let inst = decoder.decode_slice(&self.old_instruction).unwrap();
log::trace!(
"Kprobe::uninstall: address: {:#x}, old_instruction: {:?}",
address,
inst.to_string()
);
}
}
impl Debug for Kprobe {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Kprobe")
.field("basic", &self.basic)
.field("point", &self.point)
.finish()
}
}
impl Deref for Kprobe {
type Target = KprobeBasic;
fn deref(&self) -> &Self::Target {
&self.basic
}
}
impl DerefMut for Kprobe {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.basic
}
}
impl KprobeBuilder {
pub fn install(self) -> (Kprobe, Arc<X86KprobePoint>) {
let probe_point = match &self.probe_point {
Some(point) => point.clone(),
None => self.replace_inst(),
};
let kprobe = Kprobe {
basic: KprobeBasic::from(self),
point: probe_point.clone(),
};
(kprobe, probe_point)
}
/// # 安装kprobe
///
/// 不同的架构下需要保存原指令,然后替换为断点指令
fn replace_inst(&self) -> Arc<X86KprobePoint> {
let address = self.symbol_addr + self.offset;
let mut inst_tmp = [0u8; MAX_INSTRUCTION_SIZE];
unsafe {
core::ptr::copy(
address as *const u8,
inst_tmp.as_mut_ptr(),
MAX_INSTRUCTION_SIZE,
);
}
let decoder = yaxpeax_x86::amd64::InstDecoder::default();
let inst = decoder.decode_slice(&inst_tmp).unwrap();
let len = inst.len().to_const();
log::trace!("inst: {:?}, len: {:?}", inst.to_string(), len);
let point = Arc::new(X86KprobePoint {
addr: address,
old_instruction: inst_tmp,
old_instruction_len: len as usize,
});
unsafe {
core::ptr::write_volatile(address as *mut u8, EBREAK_INST);
core::arch::x86_64::_mm_mfence();
}
log::trace!(
"Kprobe::install: address: {:#x}, func_name: {:?}",
address,
self.symbol
);
point
}
}
impl Kprobe {
pub fn probe_point(&self) -> &Arc<X86KprobePoint> {
&self.point
}
}
impl KprobeOps for X86KprobePoint {
fn return_address(&self) -> usize {
self.addr + self.old_instruction_len
}
fn single_step_address(&self) -> usize {
self.old_instruction.as_ptr() as usize
}
fn debug_address(&self) -> usize {
self.old_instruction.as_ptr() as usize + self.old_instruction_len
}
fn break_address(&self) -> usize {
self.addr
}
}

View File

@ -0,0 +1,7 @@
#![cfg_attr(target_arch = "riscv64", feature(riscv_ext_intrinsics))]
#![no_std]
extern crate alloc;
mod arch;
pub use arch::*;

View File

@ -0,0 +1,21 @@
version: 1.0.{build}
branches:
only:
- main
os:
- Visual Studio 2015
clone_depth: 1
configuration:
- Debug
platform:
- x64
environment:
matrix:
- TOOLCHAIN_VERSION: 14.0
RUST: 1.76.0
- TOOLCHAIN_VERSION: 14.0
RUST: beta
- TOOLCHAIN_VERSION: 14.0
RUST: nightly
build_script: mk/appveyor.bat

2
kernel/crates/rbpf/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
Cargo.lock

View File

@ -0,0 +1,78 @@
[package]
# Project metadata
name = "rbpf"
version = "0.2.0"
authors = ["Quentin <quentin@isovalent.com>"]
# Additional metadata for packaging
description = "Virtual machine and JIT compiler for eBPF programs"
repository = "https://github.com/qmonnet/rbpf"
readme = "README.md"
keywords = ["BPF", "eBPF", "interpreter", "JIT", "filtering"]
license = "Apache-2.0/MIT"
edition = "2021"
# Packaging directives
include = [
"src/**",
"examples/**",
"tests/**",
"bench/**",
"LICENSE*",
"Cargo.toml",
]
[dependencies]
# Default features (std) are disabled so that the dependencies don't pull in the
# standard library when the crate is compiled for no_std
byteorder = { version = "1.2", default-features = false }
log = {version = "0.4.21", default-features = false }
combine = { version = "4.6", default-features = false }
# Optional Dependencies when using the standard library
libc = { version = "0.2", optional = true }
time = { version = "0.2", optional = true }
# Optional Dependencies for the CraneLift JIT
cranelift-codegen = { version = "0.99", optional = true }
cranelift-frontend = { version = "0.99", optional = true }
cranelift-jit = { version = "0.99", optional = true }
cranelift-native = { version = "0.99", optional = true }
cranelift-module = { version = "0.99", optional = true }
[dev-dependencies]
elf = "0.0.10"
json = "0.11"
hex = "0.4.3"
[features]
#default = ["std", "user", "cranelift"]
cargo-clippy = []
std = ["dep:time", "dep:libc", "combine/std"]
cranelift = [
"dep:cranelift-codegen",
"dep:cranelift-frontend",
"dep:cranelift-jit",
"dep:cranelift-native",
"dep:cranelift-module",
]
user = []
# Examples that depend on the standard library should be disabled when
# testing the `no_std` configuration.
[[example]]
name = "disassemble"
required-features = ["std"]
[[example]]
name = "uptime"
required-features = ["std"]
[[example]]
name = "to_json"
[[example]]
name = "rbpf_plugin"

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,25 @@
Copyright (c) 2016 6WIND S.A.
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,743 @@
# rbpf
<picture>
<source media="(prefers-color-scheme: dark)" srcset="misc/rbpf_256_border.png">
<img src="misc/rbpf_256.png">
</picture>
Rust (user-space) virtual machine for eBPF
[![Build Status](https://github.com/qmonnet/rbpf/actions/workflows/test.yaml/badge.svg)](https://github.com/qmonnet/rbpf/actions/workflows/test.yaml)
[![Build status](https://ci.appveyor.com/api/projects/status/ia74coeuhxtrcvsk/branch/main?svg=true)](https://ci.appveyor.com/project/qmonnet/rbpf/branch/main)
[![Coverage Status](https://coveralls.io/repos/github/qmonnet/rbpf/badge.svg?branch=main)](https://coveralls.io/github/qmonnet/rbpf?branch=main)
[![Crates.io](https://img.shields.io/crates/v/rbpf.svg)](https://crates.io/crates/rbpf)
* [Description](#description)
* [Link to the crate](#link-to-the-crate)
* [API](#api)
* [Example uses](#example-uses)
* [Building eBPF programs](#building-ebpf-programs)
* [Build Features](#build-features)
* [Feedback welcome!](#feedback-welcome)
* [Questions / Answers](#questions--answers)
* [Caveats](#caveats)
* [_To do_ list](#to-do-list)
* [License](#license)
* [Inspired by](#inspired-by)
* [Other resources](#other-resources)
## Description
This crate contains a virtual machine for eBPF program execution. BPF, as in
_Berkeley Packet Filter_, is an assembly-like language initially developed for
BSD systems, in order to filter packets in the kernel with tools such as
tcpdump so as to avoid useless copies to user-space. It was ported to Linux,
where it evolved into eBPF (_extended_ BPF), a faster version with more
features. While BPF programs are originally intended to run in the kernel, the
virtual machine of this crate enables running it in user-space applications;
it contains an interpreter, an x86_64 JIT-compiler for eBPF programs, as well as
a disassembler.
It is based on Rich Lane's [uBPF software](https://github.com/iovisor/ubpf/),
which does nearly the same, but is written in C.
The crate is supposed to compile and run on Linux, MacOS X, and Windows,
although the JIT-compiler does not work with Windows at this time.
## Link to the crate
This crate is available from [crates.io](https://crates.io/crates/rbpf), so it
should work out of the box by adding it as a dependency in your `Cargo.toml`
file:
```toml
[dependencies]
rbpf = "0.2.0"
```
You can also use the development version from this GitHub repository. This
should be as simple as putting this inside your `Cargo.toml`:
```toml
[dependencies]
rbpf = { git = "https://github.com/qmonnet/rbpf" }
```
Of course, if you prefer, you can clone it locally, possibly hack the crate,
and then indicate the path of your local version in `Cargo.toml`:
```toml
[dependencies]
rbpf = { path = "path/to/rbpf" }
```
Then indicate in your source code that you want to use the crate:
```rust,ignore
extern crate rbpf;
```
## API
The API is pretty well documented inside the source code. You should also be
able to access [an online version of the documentation from
here](https://docs.rs/rbpf/), automatically generated from the
[crates.io](https://crates.io/crates/rbpf) version (may not be up-to-date with
the main branch). [Examples](../../tree/main/examples) and [unit
tests](../../tree/main/tests) should also prove helpful. Here is a summary of
how to use the crate.
Here are the steps to follow to run an eBPF program with rbpf:
1. Create a virtual machine. There are several kinds of machines, we will come
back on this later. When creating the VM, pass the eBPF program as an
argument to the constructor.
2. If you want to use some helper functions, register them into the virtual
machine.
3. If you want a JIT-compiled program, compile it.
4. Execute your program: either run the interpreter or call the JIT-compiled
function.
eBPF has been initially designed to filter packets (now it has some other hooks
in the Linux kernel, such as kprobes, but this is not covered by rbpf). As a
consequence, most of the load and store instructions of the program are
performed on a memory area representing the packet data. However, in the Linux
kernel, the eBPF program does not immediately access this data area: initially,
it has access to a C `struct sk_buff` instead, which is a buffer containing
metadata about the packet—including memory addresses of the beginning and of
the end of the packet data area. So the program first loads those pointers from
the `sk_buff`, and then can access the packet data.
This behavior can be replicated with rbpf, but it is not mandatory. For this
reason, we have several structs representing different kinds of virtual
machines:
* `struct EbpfVmMbuffer` mimics the kernel. When the program is run, the
address provided to its first eBPF register will be the address of a metadata
buffer provided by the user, and that is expected to contain pointers to the
start and the end of the packet data memory area.
* `struct EbpfVmFixedMbuff` has one purpose: enabling the execution of programs
created to be compatible with the kernel, while saving the effort to manually
handle the metadata buffer for the user. In fact, this struct has a static
internal buffer that is passed to the program. The user has to indicate the
offset values at which the eBPF program expects to find the start and the end
of packet data in the buffer. On calling the function that runs the program
(JITted or not), the struct automatically updates the addresses in this
static buffer, at the appointed offsets, for the start and the end of the
packet data the program is called upon.
* `struct EbpfVmRaw` is for programs that want to run directly on packet data.
No metadata buffer is involved, the eBPF program directly receives the
address of the packet data in its first register. This is the behavior of
uBPF.
* `struct EbpfVmNoData` does not take any data. The eBPF program takes no
argument whatsoever and its return value is deterministic. Not so sure there
is a valid use case for that, but if nothing else, this is very useful for
unit tests.
All these structs implement the same public functions:
```rust,ignore
// called with EbpfVmMbuff:: prefix
pub fn new(prog: &'a [u8]) -> Result<EbpfVmMbuff<'a>, Error>
// called with EbpfVmFixedMbuff:: prefix
pub fn new(prog: &'a [u8],
data_offset: usize,
data_end_offset: usize) -> Result<EbpfVmFixedMbuff<'a>, Error>
// called with EbpfVmRaw:: prefix
pub fn new(prog: &'a [u8]) -> Result<EbpfVmRaw<'a>, Error>
// called with EbpfVmNoData:: prefix
pub fn new(prog: &'a [u8]) -> Result<EbpfVmNoData<'a>, Error>
```
This is used to create a new instance of a VM. The return type is dependent of
the struct from which the function is called. For instance,
`rbpf::EbpfVmRaw::new(Some(my_program))` would return an instance of `struct
rbpf::EbpfVmRaw` (wrapped in a `Result`). When a program is loaded, it is
checked with a very simple verifier (nothing close to the one for Linux
kernel). Users are also able to replace it with a custom verifier.
For `struct EbpfVmFixedMbuff`, two additional arguments must be passed to the
constructor: `data_offset` and `data_end_offset`. They are the offset (byte
number) at which the pointers to the beginning and to the end, respectively, of
the memory area of packet data are to be stored in the internal metadata buffer
each time the program is executed. Other structs do not use this mechanism and
do not need those offsets.
```rust,ignore
// for struct EbpfVmMbuff, struct EbpfVmRaw and struct EbpfVmRawData
pub fn set_program(&mut self, prog: &'a [u8]) -> Result<(), Error>
// for struct EbpfVmFixedMbuff
pub fn set_program(&mut self, prog: &'a [u8],
data_offset: usize,
data_end_offset: usize) -> Result<(), Error>
```
You can use for example `my_vm.set_program(my_program);` to change the loaded
program after the VM instance creation. This program is checked with the
verifier attached to the VM. The verifying function of the VM can be changed at
any moment.
```rust,ignore
pub type Verifier = fn(prog: &[u8]) -> Result<(), Error>;
pub fn set_verifier(&mut self,
verifier: Verifier) -> Result<(), Error>
```
Note that if a program has already been loaded into the VM, setting a new
verifier also immediately runs it on the loaded program. However, the verifier
is not run if no program has been loaded (if `None` was passed to the `new()`
method when creating the VM).
```rust,ignore
pub type Helper = fn (u64, u64, u64, u64, u64) -> u64;
pub fn register_helper(&mut self,
key: u32,
function: Helper) -> Result<(), Error>
```
This function is used to register a helper function. The VM stores its
registers in a hashmap, so the key can be any `u32` value you want. It may be
useful for programs that should be compatible with the Linux kernel and
therefore must use specific helper numbers.
```rust,ignore
// for struct EbpfVmMbuff
pub fn execute_program(&self,
mem: &'a mut [u8],
mbuff: &'a mut [u8]) -> Result<(u64), Error>
// for struct EbpfVmFixedMbuff and struct EbpfVmRaw
pub fn execute_program(&self,
mem: &'a mut [u8]) -> Result<(u64), Error>
// for struct EbpfVmNoData
pub fn execute_program(&self) -> Result<(u64), Error>
```
Interprets the loaded program. The function takes a reference to the packet
data and the metadata buffer, or only to the packet data, or nothing at all,
depending on the kind of the VM used. The value returned is the result of the
eBPF program.
```rust,ignore
pub fn jit_compile(&mut self) -> Result<(), Error>
```
JIT-compile the loaded program, for x86_64 architecture. If the program is to
use helper functions, they must be registered into the VM before this function
is called. The generated assembly function is internally stored in the VM.
```rust,ignore
// for struct EbpfVmMbuff
pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8],
mbuff: &'a mut [u8]) -> Result<(u64), Error>
// for struct EbpfVmFixedMbuff and struct EbpfVmRaw
pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8]) -> Result<(u64), Error>
// for struct EbpfVmNoData
pub unsafe fn execute_program_jit(&self) -> Result<(u64), Error>
```
Calls the JIT-compiled program. The arguments to provide are the same as for
`execute_program()`, again depending on the kind of VM that is used. The result of
the JIT-compiled program should be the same as with the interpreter, but it
should run faster. Note that if errors occur during the program execution, the
JIT-compiled version does not handle it as well as the interpreter, and the
program may crash. For this reason, the functions are marked as `unsafe`.
## Example uses
### Simple example
This comes from the unit test `test_vm_add`.
```rust
extern crate rbpf;
fn main() {
// This is the eBPF program, in the form of bytecode instructions.
let prog = &[
0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov32 r0, 0
0xb4, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov32 r1, 2
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // add32 r0, 1
0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add32 r0, r1
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
];
// Instantiate a struct EbpfVmNoData. This is an eBPF VM for programs that
// takes no packet data in argument.
// The eBPF program is passed to the constructor.
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
// Execute (interpret) the program. No argument required for this VM.
assert_eq!(vm.execute_program().unwrap(), 0x3);
}
```
### With JIT, on packet data
This comes from the unit test `test_jit_ldxh`.
```rust
extern crate rbpf;
fn main() {
let prog = &[
0x71, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxh r0, [r1+2]
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
];
// Let's use some data.
let mem = &mut [
0xaa, 0xbb, 0x11, 0xcc, 0xdd
];
// This is an eBPF VM for programs reading from a given memory area (it
// directly reads from packet data)
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
#[cfg(any(windows, not(feature = "std")))] {
assert_eq!(vm.execute_program(mem).unwrap(), 0x11);
}
#[cfg(all(not(windows), feature = "std"))] {
// This time we JIT-compile the program.
vm.jit_compile().unwrap();
// Then we execute it. For this kind of VM, a reference to the packet
// data must be passed to the function that executes the program.
unsafe { assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x11); }
}
}
```
### Using a metadata buffer
This comes from the unit test `test_jit_mbuff` and derives from the unit test
`test_jit_ldxh`.
```rust
extern crate rbpf;
fn main() {
let prog = &[
// Load mem from mbuff at offset 8 into R1
0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
// ldhx r1[2], r0
0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
let mem = &mut [
0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd
];
// Just for the example we create our metadata buffer from scratch, and
// we store the pointers to packet data start and end in it.
let mut mbuff = &mut [0u8; 32];
unsafe {
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
*data = mem.as_ptr() as u64;
*data_end = mem.as_ptr() as u64 + mem.len() as u64;
}
// This eBPF VM is for program that use a metadata buffer.
let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
#[cfg(any(windows, not(feature = "std")))] {
assert_eq!(vm.execute_program(mem, mbuff).unwrap(), 0x2211);
}
#[cfg(all(not(windows), feature = "std"))] {
// Here again we JIT-compile the program.
vm.jit_compile().unwrap();
// Here we must provide both a reference to the packet data, and to the
// metadata buffer we use.
unsafe {
assert_eq!(vm.execute_program_jit(mem, mbuff).unwrap(), 0x2211);
}
}
}
```
### Loading code from an object file; and using a virtual metadata buffer
This comes from unit test `test_vm_block_port`.
This example requires the following additional crates, you may have to add them
to your `Cargo.toml` file.
```toml
[dependencies]
rbpf = "0.2.0"
elf = "0.0.10"
```
It also uses a kind of VM that uses an internal buffer used to simulate the
`sk_buff` used by eBPF programs in the kernel, without having to manually
create a new buffer for each packet. It may be useful for programs compiled for
the kernel and that assumes the data they receive is a `sk_buff` pointing to
the packet data start and end addresses. So here we just provide the offsets at
which the eBPF program expects to find those pointers, and the VM handles the
buffer update so that we only have to provide a reference to the packet data
for each run of the program.
```rust
extern crate elf;
use std::path::PathBuf;
extern crate rbpf;
use rbpf::helpers;
fn main() {
// Load a program from an ELF file, e.g. compiled from C to eBPF with
// clang/LLVM. Some minor modification to the bytecode may be required.
let filename = "examples/load_elf__block_a_port.elf";
let path = PathBuf::from(filename);
let file = match elf::File::open_path(&path) {
Ok(f) => f,
Err(e) => panic!("Error: {:?}", e),
};
// Here we assume the eBPF program is in the ELF section called
// ".classifier".
let text_scn = match file.get_section(".classifier") {
Some(s) => s,
None => panic!("Failed to look up .classifier section"),
};
let prog = &text_scn.data;
// This is our data: a real packet, starting with Ethernet header
let packet = &mut [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x08, 0x00, // ethertype
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
0xa6, 0xab, 0x40, 0x00,
0x40, 0x06, 0x96, 0x0f,
0x7f, 0x00, 0x00, 0x01,
0x7f, 0x00, 0x00, 0x01,
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
0xd1, 0xe5, 0xc4, 0x9d,
0xd4, 0x30, 0xb5, 0xd2,
0x80, 0x18, 0x01, 0x56,
0xfe, 0x2f, 0x00, 0x00,
0x01, 0x01, 0x08, 0x0a, // start data
0x00, 0x23, 0x75, 0x89,
0x00, 0x23, 0x63, 0x2d,
0x71, 0x64, 0x66, 0x73,
0x64, 0x66, 0x0a
];
// This is an eBPF VM for programs using a virtual metadata buffer, similar
// to the sk_buff that eBPF programs use with tc and in Linux kernel.
// We must provide the offsets at which the pointers to packet data start
// and end must be stored: these are the offsets at which the program will
// load the packet data from the metadata buffer.
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
// We register a helper function, that can be called by the program, into
// the VM. The `bpf_trace_printf` is only available when we have access to
// the standard library.
#[cfg(feature = "std")] {
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX,
helpers::bpf_trace_printf).unwrap();
}
// This kind of VM takes a reference to the packet data, but does not need
// any reference to the metadata buffer: a fixed buffer is handled
// internally by the VM.
let res = vm.execute_program(packet).unwrap();
println!("Program returned: {:?} ({:#x})", res, res);
}
```
## Building eBPF programs
Besides passing the raw hexadecimal codes for building eBPF programs, two other
methods are available.
### Assembler
The first method consists in using the assembler provided by the crate.
```rust
extern crate rbpf;
use rbpf::assembler::assemble;
let prog = assemble("add64 r1, 0x605
mov64 r2, 0x32
mov64 r1, r0
be16 r0
neg64 r2
exit").unwrap();
#[cfg(feature = "std")] {
println!("{:?}", prog);
}
```
The above snippet will produce:
```rust,ignore
Ok([0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
```
Conversely, a disassembler is also available to dump instruction names from
bytecode in a human-friendly format.
```rust
extern crate rbpf;
use rbpf::disassembler::disassemble;
let prog = &[
0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
disassemble(prog);
```
This will produce the following output:
```txt
add64 r1, 0x605
mov64 r2, 0x32
mov64 r1, r0
be16 r0
neg64 r2
exit
```
Please refer to [source code](src/assembler.rs) and [tests](tests/assembler.rs)
for the syntax and the list of instruction names.
### Building API
The other way to build programs is to chain commands from the instruction
builder API. It looks less like assembly, maybe more like high-level functions.
What's sure is that the result is more verbose, but if you prefer to build
programs this way, it works just as well. If we take again the same sample as
above, it would be constructed as follows.
```rust
extern crate rbpf;
use rbpf::insn_builder::*;
let mut program = BpfCode::new();
program.add(Source::Imm, Arch::X64).set_dst(1).set_imm(0x605).push()
.mov(Source::Imm, Arch::X64).set_dst(2).set_imm(0x32).push()
.mov(Source::Reg, Arch::X64).set_src(0).set_dst(1).push()
.swap_bytes(Endian::Big).set_dst(0).set_imm(0x10).push()
.negate(Arch::X64).set_dst(2).push()
.exit().push();
```
Again, please refer to [the source and related tests](src/insn_builder.rs) to
get more information and examples on how to use it.
## Build features
### `no_std`
The `rbpf` crate has a Cargo feature named "std" that is enabled by default. To
use `rbpf` in `no_std` environments this feature needs to be disabled. To do
this, you need to modify your dependency on `rbpf` in Cargo.toml to disable the
enabled-by-default features.
```toml
[dependencies]
rbpf = { version = "1.0", default-features = false }
```
Note that when using this crate in `no_std` environments, the `jit` module
isn't available. This is because it depends on functions provided by `libc`
(`libc::posix_memalign()`, `libc::mprotect()`) which aren't available on
`no_std`.
The `assembler` module is available, albeit with reduced debugging features. It
depends on the `combine` crate providing parser combinators. Under `no_std`
this crate only provides simple parsers which generate less descriptive error
messages.
## Feedback welcome!
This is the author's first try at writing Rust code. He learned a lot in the
process, but there remains a feeling that this crate has a kind of C-ish style
in some places instead of the Rusty look the author would like it to have. So
feedback (or PRs) are welcome, including about ways you might see to take
better advantage of Rust features.
Note that the project expects new commits to be covered by the
[Developer's Certificate of Origin](https://wiki.linuxfoundation.org/dco).
When contributing Pull Requests, please sign off your commits accordingly.
## Questions / Answers
### Why implementing an eBPF virtual machine in Rust?
As of this writing, there is no particular use case for this crate at the best
of the author's knowledge. The author happens to work with BPF on Linux and to
know how uBPF works, and he wanted to learn and experiment with Rust—no more
than that.
### What are the differences with uBPF?
Other than the language, obviously? Well, there are some differences:
* Some constants, such as the maximum length for programs or the length for the
stack, differs between uBPF and rbpf. The latter uses the same values as the
Linux kernel, while uBPF has its own values.
* When an error occurs while a program is run by uBPF, the function running the
program silently returns the maximum value as an error code, while rbpf
returns Rust type `Error`.
* The registration of helper functions, that can be called from within an eBPF
program, is not handled in the same way.
* The distinct structs permitting to run program either on packet data, or with
a metadata buffer (simulated or not) is a specificity of rbpf.
* As for performance: theoretically the JITted programs are expected to run at
the same speed, while the C interpreter of uBPF should go slightly faster
than rbpf. But this has not been asserted yet. Benchmarking both programs
would be an interesting thing to do.
### Can I use it with the “classic” BPF (a.k.a cBPF) version?
No. This crate only works with extended BPF (eBPF) programs. For cBPF programs,
such as used by tcpdump (as of this writing) for example, you may be interested
in the [bpfjit crate](https://crates.io/crates/bpfjit) written by Alexander
Polakov instead.
### What functionalities are implemented?
Running and JIT-compiling eBPF programs work. There is also a mechanism to
register user-defined helper functions. The eBPF implementation of the Linux
kernel comes with [some additional
features](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md):
a high number of helpers, several kinds of maps, tail calls.
* Additional helpers should be easy to add, but very few of the existing Linux
helpers have been replicated in rbpf so far.
* Tail calls (“long jumps” from an eBPF program into another) are not
implemented. This is probably not trivial to design and implement.
* The interaction with maps is done through the use of specific helpers, so
this should not be difficult to add. The maps themselves can reuse the maps
in the kernel (if on Linux), to communicate with in-kernel eBPF programs for
instance; or they can be handled in user space. Rust has arrays and hashmaps,
so their implementation should be pretty straightforward (and may be added to
rbpf in the future).
### What about program validation?
The ”verifier” of this crate is very short and has nothing to do with the
kernel verifier, which means that it accepts programs that may not be safe. On
the other hand, you probably do not run this in a kernel here, so it will not
crash your system. Implementing a verifier similar to the one in the kernel is
not trivial, and we cannot “copy” it since it is under GPL license.
### What about safety then?
Rust has a strong emphasis on safety. Yet to have the eBPF VM work, some
`unsafe` blocks of code are used. The VM, taken as an eBPF interpreter, can
return an error but should not crash. Please file an issue otherwise.
As for the JIT-compiler, it is a different story, since runtime memory checks
are more complicated to implement in assembly. It _will_ crash if your
JIT-compiled program tries to perform unauthorized memory accesses. Usually, it
could be a good idea to test your program with the interpreter first.
Oh, and if your program has infinite loops, even with the interpreter, you're
on your own.
## Caveats
* This crate is **under development** and the API may be subject to change.
* The JIT compiler produces an unsafe program: memory access are not tested at
runtime (yet). Use with caution.
* A small number of eBPF instructions have not been implemented yet. This
should not be a problem for the majority of eBPF programs.
* Beware of turnips. Turnips are disgusting.
## _To do_ list
* Implement some traits (`Clone`, `Drop`, `Debug` are good candidates).
* Provide built-in support for user-space array and hash BPF maps.
* Improve safety of JIT-compiled programs with runtime memory checks.
* Add helpers (some of those supported in the kernel, such as checksum update,
could be helpful).
* Improve verifier. Could we find a way to directly support programs compiled
with clang?
* Maybe one day, tail calls?
* JIT-compilers for other architectures?
* …
## License
Following the effort of the Rust language project itself in order to ease
integration with other projects, the rbpf crate is distributed under the terms
of both the MIT license and the Apache License (Version 2.0).
See
[LICENSE-APACHE](https://github.com/qmonnet/rbpf/blob/main/LICENSE-APACHE)
and [LICENSE-MIT](https://github.com/qmonnet/rbpf/blob/main/LICENSE-MIT) for
details.
## Version
[The last commit](https://github.com/qmonnet/rbpf/commit/fe7021b07b08a43b836743a77796d07ce1f4902e)
## Inspired by
* [uBPF](https://github.com/iovisor/ubpf), a C user-space implementation of an
eBPF virtual machine, with a JIT-compiler and disassembler (and also
including the assembler from the human-readable form of the instructions,
such as in `mov r0, 0x1337`), by Rich Lane for Big Switch Networks (2015)
* [_Building a simple JIT in
Rust_](https://www.sophiajt.com/building-a-simple-jit-in-rust),
by Sophia Turner (2015)
* [bpfjit](https://github.com/polachok/bpfjit) (also [on
crates.io](https://crates.io/crates/bpfjit)), a Rust crate exporting the cBPF
JIT compiler from FreeBSD 10 tree to Rust, by Alexander Polakov (2016)
## Other resources
* Cilium project documentation about BPF: [_BPF and XDP Reference
Guide_](http://docs.cilium.io/en/latest/bpf/)
* [Kernel documentation about BPF](https://docs.kernel.org/bpf/)
* [_Dive into BPF: a list of reading
material_](https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf),
a blog article listing documentation for BPF and related technologies (2016)
* [The Rust programming language](https://www.rust-lang.org)

View File

@ -0,0 +1 @@
doc-valid-idents = ["eBPF", "uBPF"]

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
extern crate rbpf;
use rbpf::disassembler;
// Simply disassemble a program into human-readable instructions.
fn main() {
let prog = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x12, 0x50, 0x00, 0x00, 0x00, 0x00,
0x00, 0x79, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x13, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x2d, 0x23, 0x12, 0x00, 0x00,
0x00, 0x00, 0x00, 0x69, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x10, 0x00,
0x08, 0x00, 0x00, 0x00, 0x71, 0x12, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x0e,
0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x11, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf,
0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
0x15, 0x02, 0x08, 0x00, 0x99, 0x99, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x21, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x18, 0x02, 0x00, 0x00, 0x00,
0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x21, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
];
disassembler::disassemble(prog);
}

View File

@ -0,0 +1,3 @@
fn main() {
rbpf::helpers::show_helper();
}

View File

@ -0,0 +1,115 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
#![allow(clippy::unreadable_literal)]
extern crate elf;
use std::path::PathBuf;
extern crate rbpf;
use rbpf::helpers;
// The following example uses an ELF file that has been compiled from the C program available in
// `load_elf__block_a_port.c` in the same directory.
//
// It was compiled with the following command:
//
// ```bash
// clang -O2 -emit-llvm -c load_elf__block_a_port.c -o - | \
// llc -march=bpf -filetype=obj -o load_elf__block_a_port.o
// ```
//
// Once compiled, this program can be injected into Linux kernel, with tc for instance. Sadly, we
// need to bring some modifications to the generated bytecode in order to run it: the three
// instructions with opcode 0x61 load data from a packet area as 4-byte words, where we need to
// load it as 8-bytes double words (0x79). The kernel does the same kind of translation before
// running the program, but rbpf does not implement this.
//
// In addition, the offset at which the pointer to the packet data is stored must be changed: since
// we use 8 bytes instead of 4 for the start and end addresses of the data packet, we cannot use
// the offsets produced by clang (0x4c and 0x50), the addresses would overlap. Instead we can use,
// for example, 0x40 and 0x50.
//
// These change were applied with the following script:
//
// ```bash
// xxd load_elf__block_a_port.o | sed '
// s/6112 5000 0000 0000/7912 5000 0000 0000/ ;
// s/6111 4c00 0000 0000/7911 4000 0000 0000/ ;
// s/6111 2200 0000 0000/7911 2200 0000 0000/' | xxd -r > load_elf__block_a_port.tmp
// mv load_elf__block_a_port.tmp load_elf__block_a_port.o
// ```
//
// The eBPF program was placed into the `.classifier` ELF section (see C code above), which means
// that you can retrieve the raw bytecode with `readelf -x .classifier load_elf__block_a_port.o` or
// with `objdump -s -j .classifier load_elf__block_a_port.o`.
//
// Once the bytecode has been edited, we can load the bytecode directly from the ELF object file.
fn main() {
let filename = "examples/load_elf__block_a_port.elf";
let path = PathBuf::from(filename);
let file = match elf::File::open_path(path) {
Ok(f) => f,
Err(e) => panic!("Error: {:?}", e),
};
let text_scn = match file.get_section(".classifier") {
Some(s) => s,
None => panic!("Failed to look up .classifier section"),
};
let prog = &text_scn.data;
let packet1 = &mut [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
0x00, // ethertype
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
0x01,
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
];
let packet2 = &mut [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
0x00, // ethertype
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
0x01,
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
0x98, 0x76, 0xc6, 0xcc, // start tcp_hdr
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
];
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
.unwrap();
let res = vm.execute_program(packet1).unwrap();
println!("Packet #1, program returned: {res:?} ({res:#x})");
assert_eq!(res, 0xffffffff);
#[cfg(not(windows))]
{
vm.jit_compile().unwrap();
let res = unsafe { vm.execute_program_jit(packet2).unwrap() };
println!("Packet #2, program returned: {res:?} ({res:#x})");
assert_eq!(res, 0);
}
#[cfg(windows)]
{
let res = vm.execute_program(packet2).unwrap();
println!("Packet #2, program returned: {:?} ({:#x})", res, res);
assert_eq!(res, 0);
}
}

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: (APACHE-2.0 OR MIT)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// Block TCP packets on source or destination port 0x9999.
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/tcp.h>
#include <linux/bpf.h>
#define ETH_ALEN 6
#define ETH_P_IP 0x0008 /* htons(0x0800) */
#define TCP_HDR_LEN 20
#define BLOCKED_TCP_PORT 0x9999
struct eth_hdr {
unsigned char h_dest[ETH_ALEN];
unsigned char h_source[ETH_ALEN];
unsigned short h_proto;
};
#define SEC(NAME) __attribute__((section(NAME), used))
SEC(".classifier")
int handle_ingress(struct __sk_buff *skb)
{
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct eth_hdr *eth = data;
struct iphdr *iph = data + sizeof(*eth);
struct tcphdr *tcp = data + sizeof(*eth) + sizeof(*iph);
/* single length check */
if (data + sizeof(*eth) + sizeof(*iph) + sizeof(*tcp) > data_end)
return 0;
if (eth->h_proto != ETH_P_IP)
return 0;
if (iph->protocol != IPPROTO_TCP)
return 0;
if (tcp->source == BLOCKED_TCP_PORT || tcp->dest == BLOCKED_TCP_PORT)
return -1;
return 0;
}

View File

@ -0,0 +1,126 @@
// Copyright Microsoft Corporation
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Path: examples/rbpf_plugin.rs
use std::io::Read;
// Helper function used by https://github.com/Alan-Jowett/bpf_conformance/blob/main/tests/call_unwind_fail.data
fn _unwind(a: u64, _b: u64, _c: u64, _d: u64, _e: u64) -> u64 {
a
}
// This is a plugin for the bpf_conformance test suite (https://github.com/Alan-Jowett/bpf_conformance)
// It accepts a single argument, the memory contents to pass to the VM.
// It reads the program from stdin.
fn main() {
let mut args: Vec<String> = std::env::args().collect();
#[allow(unused_mut)] // In no_std the jit variable isn't mutated.
let mut jit: bool = false;
let mut cranelift: bool = false;
let mut program_text = String::new();
let mut memory_text = String::new();
args.remove(0);
// Memory is always the first argument.
if !args.is_empty() {
memory_text.clone_from(&args[0]);
// Strip whitespace
memory_text.retain(|c| !c.is_whitespace());
args.remove(0);
}
// Process the rest of the arguments.
while !args.is_empty() {
match args[0].as_str() {
"--help" => {
println!("Usage: rbpf_plugin [memory] < program");
return;
}
"--jit" => {
#[cfg(any(windows, not(feature = "std")))]
{
println!("JIT not supported");
return;
}
#[cfg(all(not(windows), feature = "std"))]
{
jit = true;
}
}
"--cranelift" => {
cranelift = true;
#[cfg(not(feature = "cranelift"))]
{
let _ = cranelift;
println!("Cranelift is not enabled");
return;
}
}
"--program" => {
if args.len() < 2 {
println!("Missing argument to --program");
return;
}
args.remove(0);
if !args.is_empty() {
program_text.clone_from(&args[0]);
args.remove(0);
}
}
_ => panic!("Unknown argument {}", args[0]),
}
args.remove(0);
}
if program_text.is_empty() {
// Read program text from stdin
std::io::stdin().read_to_string(&mut program_text).unwrap();
}
// Strip whitespace
program_text.retain(|c| !c.is_whitespace());
// Convert program from hex to bytecode
let bytecode = hex::decode(program_text).unwrap();
// Convert memory from hex to bytes
let mut memory: Vec<u8> = hex::decode(memory_text).unwrap();
// Create rbpf vm
let mut vm = rbpf::EbpfVmRaw::new(Some(&bytecode)).unwrap();
// Register the helper function used by call_unwind_fail.data test.
vm.register_helper(5, _unwind).unwrap();
let result: u64;
if jit {
#[cfg(any(windows, not(feature = "std")))]
{
println!("JIT not supported");
return;
}
#[cfg(all(not(windows), feature = "std"))]
{
unsafe {
vm.jit_compile().unwrap();
result = vm.execute_program_jit(&mut memory).unwrap();
}
}
} else if cranelift {
#[cfg(not(feature = "cranelift"))]
{
println!("Cranelift is not enabled");
return;
}
#[cfg(feature = "cranelift")]
{
vm.cranelift_compile().unwrap();
result = vm.execute_program_cranelift(&mut memory).unwrap();
}
} else {
result = vm.execute_program(&mut memory).unwrap();
}
println!("{result:x}");
}

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
#[macro_use]
extern crate json;
extern crate elf;
use std::path::PathBuf;
extern crate rbpf;
use rbpf::disassembler;
// Turn a program into a JSON string.
//
// Relies on `json` crate.
//
// You may copy this function and adapt it according to your needs. For instance, you may want to:
//
// * Remove the "desc" (description) attributes from the output.
// * Print integers as integers, and not as strings containing their hexadecimal representation
// (just replace the relevant `format!()` calls by the commented values.
fn to_json(prog: &[u8]) -> String {
// This call returns a high-level representation of the instructions, with the two parts of
// `LD_DW_IMM` instructions merged, and name and descriptions of the instructions.
// If you prefer to use a lower-level representation, use `ebpf::to_insn_vec()` function
// instead.
let insns = disassembler::to_insn_vec(prog);
let mut json_insns = vec![];
for insn in insns {
json_insns.push(object!(
"opc" => format!("{:#x}", insn.opc), // => insn.opc,
"dst" => format!("{:#x}", insn.dst), // => insn.dst,
"src" => format!("{:#x}", insn.src), // => insn.src,
"off" => format!("{:#x}", insn.off), // => insn.off,
// Warning: for imm we use a i64 instead of a i32 (to have correct values for
// `lddw` operation. If we print a number in the JSON this is not a problem, the
// internal i64 has the same value with extended sign on 32 most significant bytes.
// If we print the hexadecimal value as a string however, we want to cast as a i32
// to prevent all other instructions to print spurious `ffffffff` prefix if the
// number is negative. When values takes more than 32 bits with `lddw`, the cast
// has no effect and the complete value is printed anyway.
"imm" => format!("{:#x}", insn.imm as i32), // => insn.imm,
"desc" => insn.desc
));
}
json::stringify_pretty(
object!(
"size" => json_insns.len(),
"insns" => json_insns
),
4,
)
}
// Load a program from an object file, and prints it to standard output as a JSON string.
fn main() {
// Let's reuse this file from `load_elf/example`.
let filename = "examples/load_elf__block_a_port.elf";
let path = PathBuf::from(filename);
let file = match elf::File::open_path(path) {
Ok(f) => f,
Err(e) => panic!("Error: {:?}", e),
};
let text_scn = match file.get_section(".classifier") {
Some(s) => s,
None => panic!("Failed to look up .classifier section"),
};
let prog = &text_scn.data;
println!("{}", to_json(prog));
}

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
extern crate rbpf;
use rbpf::helpers;
// The main objectives of this example is to show:
//
// * the use of EbpfVmNoData function,
// * and the use of a helper.
//
// The two eBPF programs are independent and are not related to one another.
fn main() {
let prog1 = &[
0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov32 r0, 0
0xb4, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov32 r1, 2
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // add32 r0, 1
0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add32 r0, r1
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit and return r0
];
// We use helper `bpf_time_getns()`, which is similar to helper `bpf_ktime_getns()` from Linux
// kernel. Hence rbpf::helpers module provides the index of this in-kernel helper as a
// constant, so that we can remain compatible with programs for the kernel. Here we also cast
// it to a u8 so as to use it directly in program instructions.
let hkey = helpers::BPF_KTIME_GETNS_IDX as u8;
let prog2 = &[
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
0x85, 0x00, 0x00, 0x00, hkey, 0x00, 0x00, 0x00, // call helper <hkey>
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit and return r0
];
// Create a VM: this one takes no data. Load prog1 in it.
let mut vm = rbpf::EbpfVmNoData::new(Some(prog1)).unwrap();
// Execute prog1.
assert_eq!(vm.execute_program().unwrap(), 0x3);
// As struct EbpfVmNoData does not takes any memory area, its return value is mostly
// deterministic. So we know prog1 will always return 3. There is an exception: when it uses
// helpers, the latter may have non-deterministic values, and all calls may not return the same
// value.
//
// In the following example we use a helper to get the elapsed time since boot time: we
// reimplement uptime in eBPF, in Rust. Because why not.
vm.set_program(prog2).unwrap();
vm.register_helper(helpers::BPF_KTIME_GETNS_IDX, helpers::bpf_time_getns)
.unwrap();
let time;
#[cfg(all(not(windows), feature = "std"))]
{
vm.jit_compile().unwrap();
time = unsafe { vm.execute_program_jit().unwrap() };
}
#[cfg(any(windows, not(feature = "std")))]
{
time = vm.execute_program().unwrap();
}
let days = time / 10u64.pow(9) / 60 / 60 / 24;
let hours = (time / 10u64.pow(9) / 60 / 60) % 24;
let minutes = (time / 10u64.pow(9) / 60) % 60;
let seconds = (time / 10u64.pow(9)) % 60;
let nanosec = time % 10u64.pow(9);
println!(
"Uptime: {:#x} ns == {} days {:02}:{:02}:{:02}, {} ns",
time, days, hours, minutes, seconds, nanosec
);
}

View File

@ -0,0 +1,72 @@
echo on
SetLocal EnableDelayedExpansion
REM This is the recommended way to choose the toolchain version, according to
REM Appveyor's documentation.
SET PATH=C:\Program Files (x86)\MSBuild\%TOOLCHAIN_VERSION%\Bin;%PATH%
set VCVARSALL="C:\Program Files (x86)\Microsoft Visual Studio %TOOLCHAIN_VERSION%\VC\vcvarsall.bat"
if [%Platform%] NEQ [x64] goto win32
set TARGET_ARCH=x86_64
set TARGET_PROGRAM_FILES=%ProgramFiles%
call %VCVARSALL% amd64
if %ERRORLEVEL% NEQ 0 exit 1
goto download
:win32
echo on
if [%Platform%] NEQ [Win32] exit 1
set TARGET_ARCH=i686
set TARGET_PROGRAM_FILES=%ProgramFiles(x86)%
call %VCVARSALL% amd64_x86
if %ERRORLEVEL% NEQ 0 exit 1
goto download
:download
REM vcvarsall turns echo off
echo on
mkdir windows_build_tools
mkdir windows_build_tools\
echo Downloading Yasm...
powershell -Command "(New-Object Net.WebClient).DownloadFile('http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe', 'windows_build_tools\yasm.exe')"
if %ERRORLEVEL% NEQ 0 (
echo ...downloading Yasm failed.
exit 1
)
set RUST_URL=https://static.rust-lang.org/dist/rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi
echo Downloading %RUST_URL%...
mkdir build
powershell -Command "(New-Object Net.WebClient).DownloadFile('%RUST_URL%', 'build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi')"
if %ERRORLEVEL% NEQ 0 (
echo ...downloading Rust failed.
exit 1
)
start /wait msiexec /i build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi INSTALLDIR="%TARGET_PROGRAM_FILES%\Rust %RUST%" /quiet /qn /norestart
if %ERRORLEVEL% NEQ 0 exit 1
set PATH="%TARGET_PROGRAM_FILES%\Rust %RUST%\bin";%cd%\windows_build_tools;%PATH%
if [%Configuration%] == [Release] set CARGO_MODE=--release
set
link /?
cl /?
rustc --version
cargo --version
cargo test --all-features -vv %CARGO_MODE%
if %ERRORLEVEL% NEQ 0 exit 1
REM Verify that `cargo build`, independent from `cargo test`, works; i.e.
REM verify that non-test builds aren't trying to use test-only features.
cargo build -vv %CARGO_MODE%
if %ERRORLEVEL% NEQ 0 exit 1
REM Verify that we can build with all features
cargo build --all-features -vv %CARGO_MODE%
if %ERRORLEVEL% NEQ 0 exit 1

View File

@ -0,0 +1,3 @@
group_imports="StdExternalCrate"
reorder_imports=true
imports_granularity="Crate"

View File

@ -0,0 +1,642 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 Rich Lane <lanerl@gmail.com>
// Rust-doc comments were left in the module, but it is no longer publicly exposed from the root
// file of the crate. Do not expect to find those comments in the documentation of the crate.
//! This module parses eBPF assembly language source code.
use alloc::{
string::{String, ToString},
vec::Vec,
};
#[cfg(feature = "std")]
use combine::EasyParser;
use combine::{
attempt, between, eof, many, many1, one_of, optional,
parser::char::{alpha_num, char, digit, hex_digit, spaces, string},
sep_by,
stream::position::{self},
ParseError, Parser, Stream,
};
/// Operand of an instruction.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Operand {
/// Register number.
Register(i64),
/// Jump offset or immediate.
Integer(i64),
/// Register number and offset.
Memory(i64, i64),
/// Used for pattern matching.
Nil,
}
/// Parsed instruction.
#[derive(Debug, PartialEq, Eq)]
pub struct Instruction {
/// Instruction name.
pub name: String,
/// Operands.
pub operands: Vec<Operand>,
}
fn ident<I>() -> impl Parser<I, Output = String>
where
I: Stream<Token = char>,
I::Error: ParseError<I::Token, I::Range, I::Position>,
{
many1(alpha_num())
}
fn integer<I>() -> impl Parser<I, Output = i64>
where
I: Stream<Token = char>,
I::Error: ParseError<I::Token, I::Range, I::Position>,
{
let sign = optional(one_of("-+".chars())).map(|x| match x {
Some('-') => -1,
_ => 1,
});
let hex = string("0x")
.with(many1(hex_digit()))
.map(|x: String| u64::from_str_radix(&x, 16).unwrap() as i64);
let dec = many1(digit()).map(|x: String| x.parse::<i64>().unwrap());
(sign, attempt(hex).or(dec)).map(|(s, x)| s * x)
}
fn register<I>() -> impl Parser<I, Output = i64>
where
I: Stream<Token = char>,
I::Error: ParseError<I::Token, I::Range, I::Position>,
{
char('r')
.with(many1(digit()))
.map(|x: String| x.parse::<i64>().unwrap())
}
fn operand<I>() -> impl Parser<I, Output = Operand>
where
I: Stream<Token = char>,
I::Error: ParseError<I::Token, I::Range, I::Position>,
{
let register_operand = register().map(Operand::Register);
let immediate = integer().map(Operand::Integer);
let memory = between(char('['), char(']'), (register(), optional(integer())))
.map(|t| Operand::Memory(t.0, t.1.unwrap_or(0)));
register_operand.or(immediate).or(memory)
}
fn instruction<I>() -> impl Parser<I, Output = Instruction>
where
I: Stream<Token = char>,
I::Error: ParseError<I::Token, I::Range, I::Position>,
{
let operands = sep_by(operand(), char(',').skip(spaces()));
(ident().skip(spaces()), operands, spaces()).map(|t| Instruction {
name: t.0,
operands: t.1,
})
}
/// Parse a string into a list of instructions.
///
/// The instructions are not validated and may have invalid names and operand types.
pub fn parse(input: &str) -> Result<Vec<Instruction>, String> {
let mut with = spaces().with(many(instruction()).skip(eof()));
#[cfg(feature = "std")]
{
match with.easy_parse(position::Stream::new(input)) {
Ok((insts, _)) => Ok(insts),
Err(err) => Err(err.to_string()),
}
}
#[cfg(not(feature = "std"))]
{
match with.parse(position::Stream::new(input)) {
Ok((insts, _)) => Ok(insts),
Err(err) => Err(err.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use alloc::{string::ToString, vec};
use combine::Parser;
use super::{ident, instruction, integer, operand, parse, register, Instruction, Operand};
// Unit tests for the different kinds of parsers.
#[test]
fn test_ident() {
assert_eq!(ident().parse("nop"), Ok(("nop".to_string(), "")));
assert_eq!(ident().parse("add32"), Ok(("add32".to_string(), "")));
assert_eq!(ident().parse("add32*"), Ok(("add32".to_string(), "*")));
}
#[test]
fn test_integer() {
assert_eq!(integer().parse("0"), Ok((0, "")));
assert_eq!(integer().parse("42"), Ok((42, "")));
assert_eq!(integer().parse("+42"), Ok((42, "")));
assert_eq!(integer().parse("-42"), Ok((-42, "")));
assert_eq!(integer().parse("0x0"), Ok((0, "")));
assert_eq!(
integer().parse("0x123456789abcdef0"),
Ok((0x123456789abcdef0, ""))
);
assert_eq!(integer().parse("-0x1f"), Ok((-31, "")));
}
#[test]
fn test_register() {
assert_eq!(register().parse("r0"), Ok((0, "")));
assert_eq!(register().parse("r15"), Ok((15, "")));
}
#[test]
fn test_operand() {
assert_eq!(operand().parse("r0"), Ok((Operand::Register(0), "")));
assert_eq!(operand().parse("r15"), Ok((Operand::Register(15), "")));
assert_eq!(operand().parse("0"), Ok((Operand::Integer(0), "")));
assert_eq!(operand().parse("42"), Ok((Operand::Integer(42), "")));
assert_eq!(operand().parse("[r1]"), Ok((Operand::Memory(1, 0), "")));
assert_eq!(operand().parse("[r3+5]"), Ok((Operand::Memory(3, 5), "")));
assert_eq!(
operand().parse("[r3+0x1f]"),
Ok((Operand::Memory(3, 31), ""))
);
assert_eq!(
operand().parse("[r3-0x1f]"),
Ok((Operand::Memory(3, -31), ""))
);
}
#[test]
fn test_instruction() {
assert_eq!(
instruction().parse("exit"),
Ok((
Instruction {
name: "exit".to_string(),
operands: vec![],
},
""
))
);
assert_eq!(
instruction().parse("call 2"),
Ok((
Instruction {
name: "call".to_string(),
operands: vec![Operand::Integer(2)],
},
""
))
);
assert_eq!(
instruction().parse("addi r1, 2"),
Ok((
Instruction {
name: "addi".to_string(),
operands: vec![Operand::Register(1), Operand::Integer(2)],
},
""
))
);
assert_eq!(
instruction().parse("ldxb r2, [r1+12]"),
Ok((
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(2), Operand::Memory(1, 12)],
},
""
))
);
assert_eq!(
instruction().parse("lsh r3, 0x8"),
Ok((
Instruction {
name: "lsh".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(8)],
},
""
))
);
assert_eq!(
instruction().parse("jne r3, 0x8, +37"),
Ok((
Instruction {
name: "jne".to_string(),
operands: vec![
Operand::Register(3),
Operand::Integer(8),
Operand::Integer(37)
],
},
""
))
);
// Whitespace between operands is optional.
assert_eq!(
instruction().parse("jne r3,0x8,+37"),
Ok((
Instruction {
name: "jne".to_string(),
operands: vec![
Operand::Register(3),
Operand::Integer(8),
Operand::Integer(37)
],
},
""
))
);
}
// Other unit tests: try to parse various set of instructions.
#[test]
fn test_empty() {
assert_eq!(parse(""), Ok(vec![]));
}
#[test]
fn test_exit() {
// No operands.
assert_eq!(
parse("exit"),
Ok(vec![Instruction {
name: "exit".to_string(),
operands: vec![],
}])
);
}
#[test]
fn test_lsh() {
// Register and immediate operands.
assert_eq!(
parse("lsh r3, 0x20"),
Ok(vec![Instruction {
name: "lsh".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(0x20)],
}])
);
}
#[test]
fn test_ja() {
// Jump offset operand.
assert_eq!(
parse("ja +1"),
Ok(vec![Instruction {
name: "ja".to_string(),
operands: vec![Operand::Integer(1)],
}])
);
}
#[test]
fn test_ldxh() {
// Register and memory operands.
assert_eq!(
parse("ldxh r4, [r1+12]"),
Ok(vec![Instruction {
name: "ldxh".to_string(),
operands: vec![Operand::Register(4), Operand::Memory(1, 12)],
}])
);
}
#[test]
fn test_tcp_sack() {
// Sample program from ubpf.
// We could technically indent the instructions since the parser support white spaces at
// the beginning, but there is another test for that.
let src = "\
ldxb r2, [r1+12]
ldxb r3, [r1+13]
lsh r3, 0x8
or r3, r2
mov r0, 0x0
jne r3, 0x8, +37
ldxb r2, [r1+23]
jne r2, 0x6, +35
ldxb r2, [r1+14]
add r1, 0xe
and r2, 0xf
lsh r2, 0x2
add r1, r2
mov r0, 0x0
ldxh r4, [r1+12]
add r1, 0x14
rsh r4, 0x2
and r4, 0x3c
mov r2, r4
add r2, 0xffffffec
mov r5, 0x15
mov r3, 0x0
jgt r5, r4, +20
mov r5, r3
lsh r5, 0x20
arsh r5, 0x20
mov r4, r1
add r4, r5
ldxb r5, [r4]
jeq r5, 0x1, +4
jeq r5, 0x0, +12
mov r6, r3
jeq r5, 0x5, +9
ja +2
add r3, 0x1
mov r6, r3
ldxb r3, [r4+1]
add r3, r6
lsh r3, 0x20
arsh r3, 0x20
jsgt r2, r3, -18
ja +1
mov r0, 0x1
exit
";
assert_eq!(
parse(src),
Ok(vec![
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(2), Operand::Memory(1, 12)],
},
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(3), Operand::Memory(1, 13)],
},
Instruction {
name: "lsh".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(8)],
},
Instruction {
name: "or".to_string(),
operands: vec![Operand::Register(3), Operand::Register(2)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(0), Operand::Integer(0)],
},
Instruction {
name: "jne".to_string(),
operands: vec![
Operand::Register(3),
Operand::Integer(8),
Operand::Integer(37)
],
},
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(2), Operand::Memory(1, 23)],
},
Instruction {
name: "jne".to_string(),
operands: vec![
Operand::Register(2),
Operand::Integer(6),
Operand::Integer(35)
],
},
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(2), Operand::Memory(1, 14)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(1), Operand::Integer(14)],
},
Instruction {
name: "and".to_string(),
operands: vec![Operand::Register(2), Operand::Integer(15)],
},
Instruction {
name: "lsh".to_string(),
operands: vec![Operand::Register(2), Operand::Integer(2)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(1), Operand::Register(2)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(0), Operand::Integer(0)],
},
Instruction {
name: "ldxh".to_string(),
operands: vec![Operand::Register(4), Operand::Memory(1, 12)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(1), Operand::Integer(20)],
},
Instruction {
name: "rsh".to_string(),
operands: vec![Operand::Register(4), Operand::Integer(2)],
},
Instruction {
name: "and".to_string(),
operands: vec![Operand::Register(4), Operand::Integer(60)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(2), Operand::Register(4)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(2), Operand::Integer(4294967276)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(5), Operand::Integer(21)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(0)],
},
Instruction {
name: "jgt".to_string(),
operands: vec![
Operand::Register(5),
Operand::Register(4),
Operand::Integer(20)
],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(5), Operand::Register(3)],
},
Instruction {
name: "lsh".to_string(),
operands: vec![Operand::Register(5), Operand::Integer(32)],
},
Instruction {
name: "arsh".to_string(),
operands: vec![Operand::Register(5), Operand::Integer(32)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(4), Operand::Register(1)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(4), Operand::Register(5)],
},
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(5), Operand::Memory(4, 0)],
},
Instruction {
name: "jeq".to_string(),
operands: vec![
Operand::Register(5),
Operand::Integer(1),
Operand::Integer(4)
],
},
Instruction {
name: "jeq".to_string(),
operands: vec![
Operand::Register(5),
Operand::Integer(0),
Operand::Integer(12)
],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(6), Operand::Register(3)],
},
Instruction {
name: "jeq".to_string(),
operands: vec![
Operand::Register(5),
Operand::Integer(5),
Operand::Integer(9)
],
},
Instruction {
name: "ja".to_string(),
operands: vec![Operand::Integer(2)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(1)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(6), Operand::Register(3)],
},
Instruction {
name: "ldxb".to_string(),
operands: vec![Operand::Register(3), Operand::Memory(4, 1)],
},
Instruction {
name: "add".to_string(),
operands: vec![Operand::Register(3), Operand::Register(6)],
},
Instruction {
name: "lsh".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(32)],
},
Instruction {
name: "arsh".to_string(),
operands: vec![Operand::Register(3), Operand::Integer(32)],
},
Instruction {
name: "jsgt".to_string(),
operands: vec![
Operand::Register(2),
Operand::Register(3),
Operand::Integer(-18)
],
},
Instruction {
name: "ja".to_string(),
operands: vec![Operand::Integer(1)],
},
Instruction {
name: "mov".to_string(),
operands: vec![Operand::Register(0), Operand::Integer(1)],
},
Instruction {
name: "exit".to_string(),
operands: vec![],
}
])
);
}
/// When running without `std` the `EasyParser` provided by `combine`
/// cannot be used. Because of this we need to use the `Parser` and the
/// error messages are different.
#[test]
fn test_error_eof() {
let expected_error;
#[cfg(feature = "std")]
{
expected_error = Err(
"Parse error at line: 1, column: 6\nUnexpected end of input\nExpected digit\n"
.to_string(),
);
}
#[cfg(not(feature = "std"))]
{
expected_error = Err("unexpected parse".to_string());
}
// Unexpected end of input in a register name.
assert_eq!(parse("lsh r"), expected_error);
}
/// When running without `std` the `EasyParser` provided by `combine`
/// cannot be used. Because of this we need to use the `Parser` and the
/// error messages are different.
#[test]
fn test_error_unexpected_character() {
let expected_error;
#[cfg(feature = "std")]
{
expected_error = Err(
"Parse error at line: 2, column: 1\nUnexpected `^`\nExpected letter or digit, whitespaces, `r`, `-`, `+`, `[` or end of input\n".to_string()
);
}
#[cfg(not(feature = "std"))]
{
expected_error = Err("unexpected parse".to_string());
}
// Unexpected character at end of input.
assert_eq!(parse("exit\n^"), expected_error);
}
#[test]
fn test_initial_whitespace() {
assert_eq!(
parse(
"
exit"
),
Ok(vec![Instruction {
name: "exit".to_string(),
operands: vec![],
}])
);
}
}

View File

@ -0,0 +1,277 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 Rich Lane <lanerl@gmail.com>
//! This module translates eBPF assembly language to binary.
use alloc::{
collections::BTreeMap,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use self::InstructionType::{
AluBinary, AluUnary, Call, Endian, JumpConditional, JumpUnconditional, LoadAbs, LoadImm,
LoadInd, LoadReg, NoOperand, StoreImm, StoreReg,
};
use crate::{
asm_parser::{
parse, Instruction, Operand,
Operand::{Integer, Memory, Nil, Register},
},
ebpf::{self, Insn},
};
#[derive(Clone, Copy, Debug, PartialEq)]
enum InstructionType {
AluBinary,
AluUnary,
LoadImm,
LoadAbs,
LoadInd,
LoadReg,
StoreImm,
StoreReg,
JumpUnconditional,
JumpConditional,
Call,
Endian(i64),
NoOperand,
}
fn make_instruction_map() -> BTreeMap<String, (InstructionType, u8)> {
let mut result = BTreeMap::new();
let alu_binary_ops = [
("add", ebpf::BPF_ADD),
("sub", ebpf::BPF_SUB),
("mul", ebpf::BPF_MUL),
("div", ebpf::BPF_DIV),
("or", ebpf::BPF_OR),
("and", ebpf::BPF_AND),
("lsh", ebpf::BPF_LSH),
("rsh", ebpf::BPF_RSH),
("mod", ebpf::BPF_MOD),
("xor", ebpf::BPF_XOR),
("mov", ebpf::BPF_MOV),
("arsh", ebpf::BPF_ARSH),
];
let mem_sizes = [
("w", ebpf::BPF_W),
("h", ebpf::BPF_H),
("b", ebpf::BPF_B),
("dw", ebpf::BPF_DW),
];
let jump_conditions = [
("jeq", ebpf::BPF_JEQ),
("jgt", ebpf::BPF_JGT),
("jge", ebpf::BPF_JGE),
("jlt", ebpf::BPF_JLT),
("jle", ebpf::BPF_JLE),
("jset", ebpf::BPF_JSET),
("jne", ebpf::BPF_JNE),
("jsgt", ebpf::BPF_JSGT),
("jsge", ebpf::BPF_JSGE),
("jslt", ebpf::BPF_JSLT),
("jsle", ebpf::BPF_JSLE),
];
{
let mut entry = |name: &str, inst_type: InstructionType, opc: u8| {
result.insert(name.to_string(), (inst_type, opc))
};
// Miscellaneous.
entry("exit", NoOperand, ebpf::EXIT);
entry("ja", JumpUnconditional, ebpf::JA);
entry("call", Call, ebpf::CALL);
entry("lddw", LoadImm, ebpf::LD_DW_IMM);
// AluUnary.
entry("neg", AluUnary, ebpf::NEG64);
entry("neg32", AluUnary, ebpf::NEG32);
entry("neg64", AluUnary, ebpf::NEG64);
// AluBinary.
for &(name, opc) in &alu_binary_ops {
entry(name, AluBinary, ebpf::BPF_ALU64 | opc);
entry(&format!("{name}32"), AluBinary, ebpf::BPF_ALU | opc);
entry(&format!("{name}64"), AluBinary, ebpf::BPF_ALU64 | opc);
}
// LoadAbs, LoadInd, LoadReg, StoreImm, and StoreReg.
for &(suffix, size) in &mem_sizes {
entry(
&format!("ldabs{suffix}"),
LoadAbs,
ebpf::BPF_ABS | ebpf::BPF_LD | size,
);
entry(
&format!("ldind{suffix}"),
LoadInd,
ebpf::BPF_IND | ebpf::BPF_LD | size,
);
entry(
&format!("ldx{suffix}"),
LoadReg,
ebpf::BPF_MEM | ebpf::BPF_LDX | size,
);
entry(
&format!("st{suffix}"),
StoreImm,
ebpf::BPF_MEM | ebpf::BPF_ST | size,
);
entry(
&format!("stx{suffix}"),
StoreReg,
ebpf::BPF_MEM | ebpf::BPF_STX | size,
);
}
// JumpConditional.
for &(name, condition) in &jump_conditions {
entry(name, JumpConditional, ebpf::BPF_JMP | condition);
entry(
&format!("{name}32"),
JumpConditional,
ebpf::BPF_JMP32 | condition,
);
}
// Endian.
for &size in &[16, 32, 64] {
entry(&format!("be{size}"), Endian(size), ebpf::BE);
entry(&format!("le{size}"), Endian(size), ebpf::LE);
}
}
result
}
fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result<Insn, String> {
if !(0..16).contains(&dst) {
return Err(format!("Invalid destination register {dst}"));
}
if dst < 0 || src >= 16 {
return Err(format!("Invalid source register {src}"));
}
if !(-32768..32768).contains(&off) {
return Err(format!("Invalid offset {off}"));
}
if !(-2147483648..2147483648).contains(&imm) {
return Err(format!("Invalid immediate {imm}"));
}
Ok(Insn {
opc,
dst: dst as u8,
src: src as u8,
off: off as i16,
imm: imm as i32,
})
}
// TODO Use slice patterns when available and remove this function.
fn operands_tuple(operands: &[Operand]) -> Result<(Operand, Operand, Operand), String> {
match operands.len() {
0 => Ok((Nil, Nil, Nil)),
1 => Ok((operands[0], Nil, Nil)),
2 => Ok((operands[0], operands[1], Nil)),
3 => Ok((operands[0], operands[1], operands[2])),
_ => Err("Too many operands".to_string()),
}
}
fn encode(inst_type: InstructionType, opc: u8, operands: &[Operand]) -> Result<Insn, String> {
let (a, b, c) = (operands_tuple(operands))?;
match (inst_type, a, b, c) {
(AluBinary, Register(dst), Register(src), Nil) => insn(opc | ebpf::BPF_X, dst, src, 0, 0),
(AluBinary, Register(dst), Integer(imm), Nil) => insn(opc | ebpf::BPF_K, dst, 0, 0, imm),
(AluUnary, Register(dst), Nil, Nil) => insn(opc, dst, 0, 0, 0),
(LoadAbs, Integer(imm), Nil, Nil) => insn(opc, 0, 0, 0, imm),
(LoadInd, Register(src), Integer(imm), Nil) => insn(opc, 0, src, 0, imm),
(LoadReg, Register(dst), Memory(src, off), Nil)
| (StoreReg, Memory(dst, off), Register(src), Nil) => insn(opc, dst, src, off, 0),
(StoreImm, Memory(dst, off), Integer(imm), Nil) => insn(opc, dst, 0, off, imm),
(NoOperand, Nil, Nil, Nil) => insn(opc, 0, 0, 0, 0),
(JumpUnconditional, Integer(off), Nil, Nil) => insn(opc, 0, 0, off, 0),
(JumpConditional, Register(dst), Register(src), Integer(off)) => {
insn(opc | ebpf::BPF_X, dst, src, off, 0)
}
(JumpConditional, Register(dst), Integer(imm), Integer(off)) => {
insn(opc | ebpf::BPF_K, dst, 0, off, imm)
}
(Call, Integer(imm), Nil, Nil) => insn(opc, 0, 0, 0, imm),
(Endian(size), Register(dst), Nil, Nil) => insn(opc, dst, 0, 0, size),
(LoadImm, Register(dst), Integer(imm), Nil) => insn(opc, dst, 0, 0, (imm << 32) >> 32),
_ => Err(format!("Unexpected operands: {operands:?}")),
}
}
fn assemble_internal(parsed: &[Instruction]) -> Result<Vec<Insn>, String> {
let instruction_map = make_instruction_map();
let mut result: Vec<Insn> = vec![];
for instruction in parsed {
let name = instruction.name.as_str();
match instruction_map.get(name) {
Some(&(inst_type, opc)) => {
match encode(inst_type, opc, &instruction.operands) {
Ok(insn) => result.push(insn),
Err(msg) => return Err(format!("Failed to encode {name}: {msg}")),
}
// Special case for lddw.
if let LoadImm = inst_type {
if let Integer(imm) = instruction.operands[1] {
result.push(insn(0, 0, 0, 0, imm >> 32).unwrap());
}
}
}
None => return Err(format!("Invalid instruction {name:?}")),
}
}
Ok(result)
}
/// Parse assembly source and translate to binary.
///
/// # Examples
///
/// ```
/// use rbpf::assembler::assemble;
/// let prog = assemble("add64 r1, 0x605
/// mov64 r2, 0x32
/// mov64 r1, r0
/// be16 r0
/// neg64 r2
/// exit");
/// println!("{:?}", prog);
/// # assert_eq!(prog,
/// # Ok(vec![0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
/// # 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
/// # 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// # 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
/// # 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// # 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
/// ```
///
/// This will produce the following output:
///
/// ```test
/// Ok([0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
/// 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
/// 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
/// 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
/// ```
pub fn assemble(src: &str) -> Result<Vec<u8>, String> {
let parsed = (parse(src))?;
let insns = (assemble_internal(&parsed))?;
let mut result: Vec<u8> = vec![];
for insn in insns {
result.extend_from_slice(&insn.to_array());
}
Ok(result)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,807 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
//! Functions in this module are used to handle eBPF programs with a higher level representation,
//! for example to disassemble the code into a human-readable format.
use alloc::{
format,
string::{String, ToString},
vec,
vec::Vec,
};
use log::warn;
use crate::ebpf;
#[inline]
fn alu_imm_str(name: &str, insn: &ebpf::Insn) -> String {
format!("{name} r{}, {:#x}", insn.dst, insn.imm)
}
#[inline]
fn alu_reg_str(name: &str, insn: &ebpf::Insn) -> String {
format!("{name} r{}, r{}", insn.dst, insn.src)
}
#[inline]
fn byteswap_str(name: &str, insn: &ebpf::Insn) -> String {
match insn.imm {
16 | 32 | 64 => {}
_ => warn!("[Disassembler] Warning: Invalid offset value for {name} insn"),
}
format!("{name}{} r{}", insn.imm, insn.dst)
}
#[inline]
fn ld_st_imm_str(name: &str, insn: &ebpf::Insn) -> String {
if insn.off >= 0 {
format!("{name} [r{}+{:#x}], {:#x}", insn.dst, insn.off, insn.imm)
} else {
format!(
"{name} [r{}-{:#x}], {:#x}",
insn.dst,
-(insn.off as isize),
insn.imm
)
}
}
#[inline]
fn ld_reg_str(name: &str, insn: &ebpf::Insn) -> String {
if insn.off >= 0 {
format!("{name} r{}, [r{}+{:#x}]", insn.dst, insn.src, insn.off)
} else {
format!(
"{name} r{}, [r{}-{:#x}]",
insn.dst,
insn.src,
-(insn.off as isize)
)
}
}
#[inline]
fn st_reg_str(name: &str, insn: &ebpf::Insn) -> String {
if insn.off >= 0 {
format!("{name} [r{}+{:#x}], r{}", insn.dst, insn.off, insn.src)
} else {
format!(
"{name} [r{}-{:#x}], r{}",
insn.dst,
-(insn.off as isize),
insn.src
)
}
}
#[inline]
fn ldabs_str(name: &str, insn: &ebpf::Insn) -> String {
format!("{name} {:#x}", insn.imm)
}
#[inline]
fn ldind_str(name: &str, insn: &ebpf::Insn) -> String {
format!("{name} r{}, {:#x}", insn.src, insn.imm)
}
#[inline]
fn jmp_imm_str(name: &str, insn: &ebpf::Insn) -> String {
if insn.off >= 0 {
format!("{name} r{}, {:#x}, +{:#x}", insn.dst, insn.imm, insn.off)
} else {
format!(
"{name} r{}, {:#x}, -{:#x}",
insn.dst,
insn.imm,
-(insn.off as isize)
)
}
}
#[inline]
fn jmp_reg_str(name: &str, insn: &ebpf::Insn) -> String {
if insn.off >= 0 {
format!("{name} r{}, r{}, +{:#x}", insn.dst, insn.src, insn.off)
} else {
format!(
"{name} r{}, r{}, -{:#x}",
insn.dst,
insn.src,
-(insn.off as isize)
)
}
}
/// High-level representation of an eBPF instruction.
///
/// In addition to standard operation code and various operand, this struct has the following
/// properties:
///
/// * It stores a name, corresponding to a mnemonic for the operation code.
/// * It also stores a description, which is a mnemonic for the full instruction, using the actual
/// values of the relevant operands, and that can be used for disassembling the eBPF program for
/// example.
/// * Immediate values are stored in an `i64` instead of a traditional i32, in order to merge the
/// two parts of (otherwise double-length) `LD_DW_IMM` instructions.
///
/// See <https://www.kernel.org/doc/Documentation/networking/filter.txt> for the Linux kernel
/// documentation about eBPF, or <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md> for a
/// more concise version.
#[derive(Debug, PartialEq, Eq)]
pub struct HLInsn {
/// Operation code.
pub opc: u8,
/// Name (mnemonic). This name is not canon.
pub name: String,
/// Description of the instruction. This is not canon.
pub desc: String,
/// Destination register operand.
pub dst: u8,
/// Source register operand.
pub src: u8,
/// Offset operand.
pub off: i16,
/// Immediate value operand. For `LD_DW_IMM` instructions, contains the whole value merged from
/// the two 8-bytes parts of the instruction.
pub imm: i64,
}
/// Return a vector of `struct HLInsn` built from an eBPF program.
///
/// This is made public to provide a way to manipulate a program as a vector of instructions, in a
/// high-level format, for example for dumping the program instruction after instruction with a
/// custom format.
///
/// Note that the two parts of `LD_DW_IMM` instructions (that have the size of two standard
/// instructions) are considered as making a single immediate value. As a consequence, the number
/// of instructions stored in the vector may not be equal to the size in bytes of the program
/// divided by the length of an instructions.
///
/// To do so, the immediate value operand is stored as an `i64` instead as an i32, so be careful
/// when you use it (see example `examples/to_json.rs`).
///
/// This is to oppose to `ebpf::to_insn_vec()` function, that treats instructions on a low-level
/// ground and do not merge the parts of `LD_DW_IMM`. Also, the version in `ebpf` module does not
/// use names or descriptions when storing the instructions.
///
/// # Examples
///
/// ```
/// use rbpf::disassembler;
///
/// let prog = &[
/// 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55,
/// 0x00, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
/// ];
///
/// let v = disassembler::to_insn_vec(prog);
/// assert_eq!(v, vec![
/// disassembler::HLInsn {
/// opc: 0x18,
/// name: "lddw".to_string(),
/// desc: "lddw r0, 0x1122334455667788".to_string(),
/// dst: 0,
/// src: 0,
/// off: 0,
/// imm: 0x1122334455667788
/// },
/// disassembler::HLInsn {
/// opc: 0x95,
/// name: "exit".to_string(),
/// desc: "exit".to_string(),
/// dst: 0,
/// src: 0,
/// off: 0,
/// imm: 0
/// },
/// ]);
/// ```
pub fn to_insn_vec(prog: &[u8]) -> Vec<HLInsn> {
if prog.len() % ebpf::INSN_SIZE != 0 {
panic!(
"[Disassembler] Error: eBPF program length must be a multiple of {:?} octets",
ebpf::INSN_SIZE
);
}
if prog.is_empty() {
return vec![];
}
let mut res = vec![];
let mut insn_ptr: usize = 0;
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
let insn = ebpf::get_insn(prog, insn_ptr);
let name;
let desc;
let mut imm = insn.imm as i64;
match insn.opc {
// BPF_LD class
ebpf::LD_ABS_B => {
name = "ldabsb";
desc = ldabs_str(name, &insn);
}
ebpf::LD_ABS_H => {
name = "ldabsh";
desc = ldabs_str(name, &insn);
}
ebpf::LD_ABS_W => {
name = "ldabsw";
desc = ldabs_str(name, &insn);
}
ebpf::LD_ABS_DW => {
name = "ldabsdw";
desc = ldabs_str(name, &insn);
}
ebpf::LD_IND_B => {
name = "ldindb";
desc = ldind_str(name, &insn);
}
ebpf::LD_IND_H => {
name = "ldindh";
desc = ldind_str(name, &insn);
}
ebpf::LD_IND_W => {
name = "ldindw";
desc = ldind_str(name, &insn);
}
ebpf::LD_IND_DW => {
name = "ldinddw";
desc = ldind_str(name, &insn);
}
ebpf::LD_DW_IMM => {
insn_ptr += 1;
let next_insn = ebpf::get_insn(prog, insn_ptr);
imm = ((insn.imm as u32) as u64 + ((next_insn.imm as u64) << 32)) as i64;
name = "lddw";
desc = format!("{name} r{:}, {imm:#x}", insn.dst);
}
// BPF_LDX class
ebpf::LD_B_REG => {
name = "ldxb";
desc = ld_reg_str(name, &insn);
}
ebpf::LD_H_REG => {
name = "ldxh";
desc = ld_reg_str(name, &insn);
}
ebpf::LD_W_REG => {
name = "ldxw";
desc = ld_reg_str(name, &insn);
}
ebpf::LD_DW_REG => {
name = "ldxdw";
desc = ld_reg_str(name, &insn);
}
// BPF_ST class
ebpf::ST_B_IMM => {
name = "stb";
desc = ld_st_imm_str(name, &insn);
}
ebpf::ST_H_IMM => {
name = "sth";
desc = ld_st_imm_str(name, &insn);
}
ebpf::ST_W_IMM => {
name = "stw";
desc = ld_st_imm_str(name, &insn);
}
ebpf::ST_DW_IMM => {
name = "stdw";
desc = ld_st_imm_str(name, &insn);
}
// BPF_STX class
ebpf::ST_B_REG => {
name = "stxb";
desc = st_reg_str(name, &insn);
}
ebpf::ST_H_REG => {
name = "stxh";
desc = st_reg_str(name, &insn);
}
ebpf::ST_W_REG => {
name = "stxw";
desc = st_reg_str(name, &insn);
}
ebpf::ST_DW_REG => {
name = "stxdw";
desc = st_reg_str(name, &insn);
}
ebpf::ST_W_XADD => {
name = "stxxaddw";
desc = st_reg_str(name, &insn);
}
ebpf::ST_DW_XADD => {
name = "stxxadddw";
desc = st_reg_str(name, &insn);
}
// BPF_ALU class
ebpf::ADD32_IMM => {
name = "add32";
desc = alu_imm_str(name, &insn);
}
ebpf::ADD32_REG => {
name = "add32";
desc = alu_reg_str(name, &insn);
}
ebpf::SUB32_IMM => {
name = "sub32";
desc = alu_imm_str(name, &insn);
}
ebpf::SUB32_REG => {
name = "sub32";
desc = alu_reg_str(name, &insn);
}
ebpf::MUL32_IMM => {
name = "mul32";
desc = alu_imm_str(name, &insn);
}
ebpf::MUL32_REG => {
name = "mul32";
desc = alu_reg_str(name, &insn);
}
ebpf::DIV32_IMM => {
name = "div32";
desc = alu_imm_str(name, &insn);
}
ebpf::DIV32_REG => {
name = "div32";
desc = alu_reg_str(name, &insn);
}
ebpf::OR32_IMM => {
name = "or32";
desc = alu_imm_str(name, &insn);
}
ebpf::OR32_REG => {
name = "or32";
desc = alu_reg_str(name, &insn);
}
ebpf::AND32_IMM => {
name = "and32";
desc = alu_imm_str(name, &insn);
}
ebpf::AND32_REG => {
name = "and32";
desc = alu_reg_str(name, &insn);
}
ebpf::LSH32_IMM => {
name = "lsh32";
desc = alu_imm_str(name, &insn);
}
ebpf::LSH32_REG => {
name = "lsh32";
desc = alu_reg_str(name, &insn);
}
ebpf::RSH32_IMM => {
name = "rsh32";
desc = alu_imm_str(name, &insn);
}
ebpf::RSH32_REG => {
name = "rsh32";
desc = alu_reg_str(name, &insn);
}
ebpf::NEG32 => {
name = "neg32";
desc = format!("{name} r{:}", insn.dst);
}
ebpf::MOD32_IMM => {
name = "mod32";
desc = alu_imm_str(name, &insn);
}
ebpf::MOD32_REG => {
name = "mod32";
desc = alu_reg_str(name, &insn);
}
ebpf::XOR32_IMM => {
name = "xor32";
desc = alu_imm_str(name, &insn);
}
ebpf::XOR32_REG => {
name = "xor32";
desc = alu_reg_str(name, &insn);
}
ebpf::MOV32_IMM => {
name = "mov32";
desc = alu_imm_str(name, &insn);
}
ebpf::MOV32_REG => {
name = "mov32";
desc = alu_reg_str(name, &insn);
}
ebpf::ARSH32_IMM => {
name = "arsh32";
desc = alu_imm_str(name, &insn);
}
ebpf::ARSH32_REG => {
name = "arsh32";
desc = alu_reg_str(name, &insn);
}
ebpf::LE => {
name = "le";
desc = byteswap_str(name, &insn);
}
ebpf::BE => {
name = "be";
desc = byteswap_str(name, &insn);
}
// BPF_ALU64 class
ebpf::ADD64_IMM => {
name = "add64";
desc = alu_imm_str(name, &insn);
}
ebpf::ADD64_REG => {
name = "add64";
desc = alu_reg_str(name, &insn);
}
ebpf::SUB64_IMM => {
name = "sub64";
desc = alu_imm_str(name, &insn);
}
ebpf::SUB64_REG => {
name = "sub64";
desc = alu_reg_str(name, &insn);
}
ebpf::MUL64_IMM => {
name = "mul64";
desc = alu_imm_str(name, &insn);
}
ebpf::MUL64_REG => {
name = "mul64";
desc = alu_reg_str(name, &insn);
}
ebpf::DIV64_IMM => {
name = "div64";
desc = alu_imm_str(name, &insn);
}
ebpf::DIV64_REG => {
name = "div64";
desc = alu_reg_str(name, &insn);
}
ebpf::OR64_IMM => {
name = "or64";
desc = alu_imm_str(name, &insn);
}
ebpf::OR64_REG => {
name = "or64";
desc = alu_reg_str(name, &insn);
}
ebpf::AND64_IMM => {
name = "and64";
desc = alu_imm_str(name, &insn);
}
ebpf::AND64_REG => {
name = "and64";
desc = alu_reg_str(name, &insn);
}
ebpf::LSH64_IMM => {
name = "lsh64";
desc = alu_imm_str(name, &insn);
}
ebpf::LSH64_REG => {
name = "lsh64";
desc = alu_reg_str(name, &insn);
}
ebpf::RSH64_IMM => {
name = "rsh64";
desc = alu_imm_str(name, &insn);
}
ebpf::RSH64_REG => {
name = "rsh64";
desc = alu_reg_str(name, &insn);
}
ebpf::NEG64 => {
name = "neg64";
desc = format!("{name} r{:}", insn.dst);
}
ebpf::MOD64_IMM => {
name = "mod64";
desc = alu_imm_str(name, &insn);
}
ebpf::MOD64_REG => {
name = "mod64";
desc = alu_reg_str(name, &insn);
}
ebpf::XOR64_IMM => {
name = "xor64";
desc = alu_imm_str(name, &insn);
}
ebpf::XOR64_REG => {
name = "xor64";
desc = alu_reg_str(name, &insn);
}
ebpf::MOV64_IMM => {
name = "mov64";
desc = alu_imm_str(name, &insn);
}
ebpf::MOV64_REG => {
name = "mov64";
desc = alu_reg_str(name, &insn);
}
ebpf::ARSH64_IMM => {
name = "arsh64";
desc = alu_imm_str(name, &insn);
}
ebpf::ARSH64_REG => {
name = "arsh64";
desc = alu_reg_str(name, &insn);
}
// BPF_JMP class
ebpf::JA => {
name = "ja";
desc = if insn.off >= 0 {
format!("{name} +{:#x}", insn.off)
} else {
format!("{name} -{:#x}", -insn.off)
}
}
ebpf::JEQ_IMM => {
name = "jeq";
desc = jmp_imm_str(name, &insn);
}
ebpf::JEQ_REG => {
name = "jeq";
desc = jmp_reg_str(name, &insn);
}
ebpf::JGT_IMM => {
name = "jgt";
desc = jmp_imm_str(name, &insn);
}
ebpf::JGT_REG => {
name = "jgt";
desc = jmp_reg_str(name, &insn);
}
ebpf::JGE_IMM => {
name = "jge";
desc = jmp_imm_str(name, &insn);
}
ebpf::JGE_REG => {
name = "jge";
desc = jmp_reg_str(name, &insn);
}
ebpf::JLT_IMM => {
name = "jlt";
desc = jmp_imm_str(name, &insn);
}
ebpf::JLT_REG => {
name = "jlt";
desc = jmp_reg_str(name, &insn);
}
ebpf::JLE_IMM => {
name = "jle";
desc = jmp_imm_str(name, &insn);
}
ebpf::JLE_REG => {
name = "jle";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSET_IMM => {
name = "jset";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSET_REG => {
name = "jset";
desc = jmp_reg_str(name, &insn);
}
ebpf::JNE_IMM => {
name = "jne";
desc = jmp_imm_str(name, &insn);
}
ebpf::JNE_REG => {
name = "jne";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSGT_IMM => {
name = "jsgt";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSGT_REG => {
name = "jsgt";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSGE_IMM => {
name = "jsge";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSGE_REG => {
name = "jsge";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSLT_IMM => {
name = "jslt";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSLT_REG => {
name = "jslt";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSLE_IMM => {
name = "jsle";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSLE_REG => {
name = "jsle";
desc = jmp_reg_str(name, &insn);
}
ebpf::CALL => {
name = "call";
desc = format!("{name} {:#x}", insn.imm);
}
ebpf::TAIL_CALL => {
name = "tail_call";
desc = name.to_string();
}
ebpf::EXIT => {
name = "exit";
desc = name.to_string();
}
// BPF_JMP32 class
ebpf::JEQ_IMM32 => {
name = "jeq32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JEQ_REG32 => {
name = "jeq32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JGT_IMM32 => {
name = "jgt32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JGT_REG32 => {
name = "jgt32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JGE_IMM32 => {
name = "jge32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JGE_REG32 => {
name = "jge32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JLT_IMM32 => {
name = "jlt32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JLT_REG32 => {
name = "jlt32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JLE_IMM32 => {
name = "jle32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JLE_REG32 => {
name = "jle32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSET_IMM32 => {
name = "jset32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSET_REG32 => {
name = "jset32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JNE_IMM32 => {
name = "jne32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JNE_REG32 => {
name = "jne32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSGT_IMM32 => {
name = "jsgt32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSGT_REG32 => {
name = "jsgt32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSGE_IMM32 => {
name = "jsge32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSGE_REG32 => {
name = "jsge32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSLT_IMM32 => {
name = "jslt32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSLT_REG32 => {
name = "jslt32";
desc = jmp_reg_str(name, &insn);
}
ebpf::JSLE_IMM32 => {
name = "jsle32";
desc = jmp_imm_str(name, &insn);
}
ebpf::JSLE_REG32 => {
name = "jsle32";
desc = jmp_reg_str(name, &insn);
}
_ => {
panic!(
"[Disassembler] Error: unknown eBPF opcode {:#2x} (insn #{:?})",
insn.opc, insn_ptr
);
}
};
let hl_insn = HLInsn {
opc: insn.opc,
name: name.to_string(),
desc,
dst: insn.dst,
src: insn.src,
off: insn.off,
imm,
};
res.push(hl_insn);
insn_ptr += 1;
}
res
}
/// Disassemble an eBPF program into human-readable instructions and prints it to standard output.
///
/// The program is not checked for errors or inconsistencies.
///
/// # Examples
///
/// ```
/// use rbpf::disassembler;
/// let prog = &[
/// 0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
/// 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
/// 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
/// 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
/// ];
/// disassembler::disassemble(prog);
/// # // "\nadd64 r1, 0x605\nmov64 r2, 0x32\nmov64 r1, r0\nbe16 r0\nneg64 r2\nexit"
/// ```
///
/// This will produce the following output:
///
/// ```test
/// add64 r1, 0x605
/// mov64 r2, 0x32
/// mov64 r1, r0
/// be16 r0
/// neg64 r2
/// exit
/// ```
pub fn disassemble(prog: &[u8]) {
#[cfg(feature = "std")]
{
for insn in to_insn_vec(prog) {
println!("{}", insn.desc);
}
}
#[cfg(not(feature = "std"))]
{
for insn in to_insn_vec(prog) {
log::info!("{}", insn.desc);
}
}
}

View File

@ -0,0 +1,635 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
//! This module contains all the definitions related to eBPF, and some functions permitting to
//! manipulate eBPF instructions.
//!
//! The number of bytes in an instruction, the maximum number of instructions in a program, and
//! also all operation codes are defined here as constants.
//!
//! The structure for an instruction used by this crate, as well as the function to extract it from
//! a program, is also defined in the module.
//!
//! To learn more about these instructions, see the Linux kernel documentation:
//! <https://www.kernel.org/doc/Documentation/networking/filter.txt>, or for a shorter version of
//! the list of the operation codes: <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md>
use alloc::{vec, vec::Vec};
use byteorder::{ByteOrder, LittleEndian};
/// The maximum call depth is 8
pub const RBPF_MAX_CALL_DEPTH: usize = 8;
/// Maximum number of instructions in an eBPF program.
pub const PROG_MAX_INSNS: usize = 1000000;
/// Size of an eBPF instructions, in bytes.
pub const INSN_SIZE: usize = 8;
/// Maximum size of an eBPF program, in bytes.
pub const PROG_MAX_SIZE: usize = PROG_MAX_INSNS * INSN_SIZE;
/// Stack for the eBPF stack, in bytes.
pub const STACK_SIZE: usize = 512;
// eBPF op codes.
// See also https://www.kernel.org/doc/Documentation/networking/filter.txt
// Three least significant bits are operation class:
/// BPF operation class: load from immediate.
pub const BPF_LD: u8 = 0x00;
/// BPF operation class: load from register.
pub const BPF_LDX: u8 = 0x01;
/// BPF operation class: store immediate.
pub const BPF_ST: u8 = 0x02;
/// BPF operation class: store value from register.
pub const BPF_STX: u8 = 0x03;
/// BPF operation class: 32 bits arithmetic operation.
pub const BPF_ALU: u8 = 0x04;
/// BPF operation class: jump (64-bit wide operands for comparisons).
pub const BPF_JMP: u8 = 0x05;
/// BPF operation class: jump (32-bit wide operands for comparisons).
pub const BPF_JMP32: u8 = 0x06;
// [ class 6 unused, reserved for future use ]
/// BPF operation class: 64 bits arithmetic operation.
pub const BPF_ALU64: u8 = 0x07;
// For load and store instructions:
// +------------+--------+------------+
// | 3 bits | 2 bits | 3 bits |
// | mode | size | insn class |
// +------------+--------+------------+
// (MSB) (LSB)
// Size modifiers:
/// BPF size modifier: word (4 bytes).
pub const BPF_W: u8 = 0x00;
/// BPF size modifier: half-word (2 bytes).
pub const BPF_H: u8 = 0x08;
/// BPF size modifier: byte (1 byte).
pub const BPF_B: u8 = 0x10;
/// BPF size modifier: double word (8 bytes).
pub const BPF_DW: u8 = 0x18;
// Mode modifiers:
/// BPF mode modifier: immediate value.
pub const BPF_IMM: u8 = 0x00;
/// BPF mode modifier: absolute load.
pub const BPF_ABS: u8 = 0x20;
/// BPF mode modifier: indirect load.
pub const BPF_IND: u8 = 0x40;
/// BPF mode modifier: load from / store to memory.
pub const BPF_MEM: u8 = 0x60;
// [ 0x80 reserved ]
// [ 0xa0 reserved ]
/// BPF mode modifier: exclusive add.
pub const BPF_XADD: u8 = 0xc0;
// For arithmetic (BPF_ALU/BPF_ALU64) and jump (BPF_JMP) instructions:
// +----------------+--------+--------+
// | 4 bits |1 b.| 3 bits |
// | operation code | src| insn class |
// +----------------+----+------------+
// (MSB) (LSB)
// Source modifiers:
/// BPF source operand modifier: 32-bit immediate value.
pub const BPF_K: u8 = 0x00;
/// BPF source operand modifier: `src` register.
pub const BPF_X: u8 = 0x08;
// Operation codes -- BPF_ALU or BPF_ALU64 classes:
/// BPF ALU/ALU64 operation code: addition.
pub const BPF_ADD: u8 = 0x00;
/// BPF ALU/ALU64 operation code: subtraction.
pub const BPF_SUB: u8 = 0x10;
/// BPF ALU/ALU64 operation code: multiplication.
pub const BPF_MUL: u8 = 0x20;
/// BPF ALU/ALU64 operation code: division.
pub const BPF_DIV: u8 = 0x30;
/// BPF ALU/ALU64 operation code: or.
pub const BPF_OR: u8 = 0x40;
/// BPF ALU/ALU64 operation code: and.
pub const BPF_AND: u8 = 0x50;
/// BPF ALU/ALU64 operation code: left shift.
pub const BPF_LSH: u8 = 0x60;
/// BPF ALU/ALU64 operation code: right shift.
pub const BPF_RSH: u8 = 0x70;
/// BPF ALU/ALU64 operation code: negation.
pub const BPF_NEG: u8 = 0x80;
/// BPF ALU/ALU64 operation code: modulus.
pub const BPF_MOD: u8 = 0x90;
/// BPF ALU/ALU64 operation code: exclusive or.
pub const BPF_XOR: u8 = 0xa0;
/// BPF ALU/ALU64 operation code: move.
pub const BPF_MOV: u8 = 0xb0;
/// BPF ALU/ALU64 operation code: sign extending right shift.
pub const BPF_ARSH: u8 = 0xc0;
/// BPF ALU/ALU64 operation code: endianness conversion.
pub const BPF_END: u8 = 0xd0;
// Operation codes -- BPF_JMP or BPF_JMP32 classes:
/// BPF JMP operation code: jump.
pub const BPF_JA: u8 = 0x00;
/// BPF JMP operation code: jump if equal.
pub const BPF_JEQ: u8 = 0x10;
/// BPF JMP operation code: jump if greater than.
pub const BPF_JGT: u8 = 0x20;
/// BPF JMP operation code: jump if greater or equal.
pub const BPF_JGE: u8 = 0x30;
/// BPF JMP operation code: jump if `src` & `reg`.
pub const BPF_JSET: u8 = 0x40;
/// BPF JMP operation code: jump if not equal.
pub const BPF_JNE: u8 = 0x50;
/// BPF JMP operation code: jump if greater than (signed).
pub const BPF_JSGT: u8 = 0x60;
/// BPF JMP operation code: jump if greater or equal (signed).
pub const BPF_JSGE: u8 = 0x70;
/// BPF JMP operation code: helper function call.
pub const BPF_CALL: u8 = 0x80;
/// BPF JMP operation code: return from program.
pub const BPF_EXIT: u8 = 0x90;
/// BPF JMP operation code: jump if lower than.
pub const BPF_JLT: u8 = 0xa0;
/// BPF JMP operation code: jump if lower or equal.
pub const BPF_JLE: u8 = 0xb0;
/// BPF JMP operation code: jump if lower than (signed).
pub const BPF_JSLT: u8 = 0xc0;
/// BPF JMP operation code: jump if lower or equal (signed).
pub const BPF_JSLE: u8 = 0xd0;
// Op codes
// (Following operation names are not “official”, but may be proper to rbpf; Linux kernel only
// combines above flags and does not attribute a name per operation.)
/// BPF opcode: `ldabsb src, dst, imm`.
pub const LD_ABS_B: u8 = BPF_LD | BPF_ABS | BPF_B;
/// BPF opcode: `ldabsh src, dst, imm`.
pub const LD_ABS_H: u8 = BPF_LD | BPF_ABS | BPF_H;
/// BPF opcode: `ldabsw src, dst, imm`.
pub const LD_ABS_W: u8 = BPF_LD | BPF_ABS | BPF_W;
/// BPF opcode: `ldabsdw src, dst, imm`.
pub const LD_ABS_DW: u8 = BPF_LD | BPF_ABS | BPF_DW;
/// BPF opcode: `ldindb src, dst, imm`.
pub const LD_IND_B: u8 = BPF_LD | BPF_IND | BPF_B;
/// BPF opcode: `ldindh src, dst, imm`.
pub const LD_IND_H: u8 = BPF_LD | BPF_IND | BPF_H;
/// BPF opcode: `ldindw src, dst, imm`.
pub const LD_IND_W: u8 = BPF_LD | BPF_IND | BPF_W;
/// BPF opcode: `ldinddw src, dst, imm`.
pub const LD_IND_DW: u8 = BPF_LD | BPF_IND | BPF_DW;
#[allow(unknown_lints)]
#[allow(clippy::eq_op)]
/// BPF opcode: `lddw dst, imm` /// `dst = imm`.
pub const LD_DW_IMM: u8 = BPF_LD | BPF_IMM | BPF_DW;
/// BPF opcode: `ldxb dst, [src + off]` /// `dst = (src + off) as u8`.
pub const LD_B_REG: u8 = BPF_LDX | BPF_MEM | BPF_B;
/// BPF opcode: `ldxh dst, [src + off]` /// `dst = (src + off) as u16`.
pub const LD_H_REG: u8 = BPF_LDX | BPF_MEM | BPF_H;
/// BPF opcode: `ldxw dst, [src + off]` /// `dst = (src + off) as u32`.
pub const LD_W_REG: u8 = BPF_LDX | BPF_MEM | BPF_W;
/// BPF opcode: `ldxdw dst, [src + off]` /// `dst = (src + off) as u64`.
pub const LD_DW_REG: u8 = BPF_LDX | BPF_MEM | BPF_DW;
/// BPF opcode: `stb [dst + off], imm` /// `(dst + offset) as u8 = imm`.
pub const ST_B_IMM: u8 = BPF_ST | BPF_MEM | BPF_B;
/// BPF opcode: `sth [dst + off], imm` /// `(dst + offset) as u16 = imm`.
pub const ST_H_IMM: u8 = BPF_ST | BPF_MEM | BPF_H;
/// BPF opcode: `stw [dst + off], imm` /// `(dst + offset) as u32 = imm`.
pub const ST_W_IMM: u8 = BPF_ST | BPF_MEM | BPF_W;
/// BPF opcode: `stdw [dst + off], imm` /// `(dst + offset) as u64 = imm`.
pub const ST_DW_IMM: u8 = BPF_ST | BPF_MEM | BPF_DW;
/// BPF opcode: `stxb [dst + off], src` /// `(dst + offset) as u8 = src`.
pub const ST_B_REG: u8 = BPF_STX | BPF_MEM | BPF_B;
/// BPF opcode: `stxh [dst + off], src` /// `(dst + offset) as u16 = src`.
pub const ST_H_REG: u8 = BPF_STX | BPF_MEM | BPF_H;
/// BPF opcode: `stxw [dst + off], src` /// `(dst + offset) as u32 = src`.
pub const ST_W_REG: u8 = BPF_STX | BPF_MEM | BPF_W;
/// BPF opcode: `stxdw [dst + off], src` /// `(dst + offset) as u64 = src`.
pub const ST_DW_REG: u8 = BPF_STX | BPF_MEM | BPF_DW;
/// BPF opcode: `stxxaddw [dst + off], src`.
pub const ST_W_XADD: u8 = BPF_STX | BPF_XADD | BPF_W;
/// BPF opcode: `stxxadddw [dst + off], src`.
pub const ST_DW_XADD: u8 = BPF_STX | BPF_XADD | BPF_DW;
/// BPF opcode: `add32 dst, imm` /// `dst += imm`.
pub const ADD32_IMM: u8 = BPF_ALU | BPF_K | BPF_ADD;
/// BPF opcode: `add32 dst, src` /// `dst += src`.
pub const ADD32_REG: u8 = BPF_ALU | BPF_X | BPF_ADD;
/// BPF opcode: `sub32 dst, imm` /// `dst -= imm`.
pub const SUB32_IMM: u8 = BPF_ALU | BPF_K | BPF_SUB;
/// BPF opcode: `sub32 dst, src` /// `dst -= src`.
pub const SUB32_REG: u8 = BPF_ALU | BPF_X | BPF_SUB;
/// BPF opcode: `mul32 dst, imm` /// `dst *= imm`.
pub const MUL32_IMM: u8 = BPF_ALU | BPF_K | BPF_MUL;
/// BPF opcode: `mul32 dst, src` /// `dst *= src`.
pub const MUL32_REG: u8 = BPF_ALU | BPF_X | BPF_MUL;
/// BPF opcode: `div32 dst, imm` /// `dst /= imm`.
pub const DIV32_IMM: u8 = BPF_ALU | BPF_K | BPF_DIV;
/// BPF opcode: `div32 dst, src` /// `dst /= src`.
pub const DIV32_REG: u8 = BPF_ALU | BPF_X | BPF_DIV;
/// BPF opcode: `or32 dst, imm` /// `dst |= imm`.
pub const OR32_IMM: u8 = BPF_ALU | BPF_K | BPF_OR;
/// BPF opcode: `or32 dst, src` /// `dst |= src`.
pub const OR32_REG: u8 = BPF_ALU | BPF_X | BPF_OR;
/// BPF opcode: `and32 dst, imm` /// `dst &= imm`.
pub const AND32_IMM: u8 = BPF_ALU | BPF_K | BPF_AND;
/// BPF opcode: `and32 dst, src` /// `dst &= src`.
pub const AND32_REG: u8 = BPF_ALU | BPF_X | BPF_AND;
/// BPF opcode: `lsh32 dst, imm` /// `dst <<= imm`.
pub const LSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_LSH;
/// BPF opcode: `lsh32 dst, src` /// `dst <<= src`.
pub const LSH32_REG: u8 = BPF_ALU | BPF_X | BPF_LSH;
/// BPF opcode: `rsh32 dst, imm` /// `dst >>= imm`.
pub const RSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_RSH;
/// BPF opcode: `rsh32 dst, src` /// `dst >>= src`.
pub const RSH32_REG: u8 = BPF_ALU | BPF_X | BPF_RSH;
/// BPF opcode: `neg32 dst` /// `dst = -dst`.
pub const NEG32: u8 = BPF_ALU | BPF_NEG;
/// BPF opcode: `mod32 dst, imm` /// `dst %= imm`.
pub const MOD32_IMM: u8 = BPF_ALU | BPF_K | BPF_MOD;
/// BPF opcode: `mod32 dst, src` /// `dst %= src`.
pub const MOD32_REG: u8 = BPF_ALU | BPF_X | BPF_MOD;
/// BPF opcode: `xor32 dst, imm` /// `dst ^= imm`.
pub const XOR32_IMM: u8 = BPF_ALU | BPF_K | BPF_XOR;
/// BPF opcode: `xor32 dst, src` /// `dst ^= src`.
pub const XOR32_REG: u8 = BPF_ALU | BPF_X | BPF_XOR;
/// BPF opcode: `mov32 dst, imm` /// `dst = imm`.
pub const MOV32_IMM: u8 = BPF_ALU | BPF_K | BPF_MOV;
/// BPF opcode: `mov32 dst, src` /// `dst = src`.
pub const MOV32_REG: u8 = BPF_ALU | BPF_X | BPF_MOV;
/// BPF opcode: `arsh32 dst, imm` /// `dst >>= imm (arithmetic)`.
///
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
pub const ARSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_ARSH;
/// BPF opcode: `arsh32 dst, src` /// `dst >>= src (arithmetic)`.
///
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
pub const ARSH32_REG: u8 = BPF_ALU | BPF_X | BPF_ARSH;
/// BPF opcode: `le dst` /// `dst = htole<imm>(dst), with imm in {16, 32, 64}`.
pub const LE: u8 = BPF_ALU | BPF_K | BPF_END;
/// BPF opcode: `be dst` /// `dst = htobe<imm>(dst), with imm in {16, 32, 64}`.
pub const BE: u8 = BPF_ALU | BPF_X | BPF_END;
/// BPF opcode: `add64 dst, imm` /// `dst += imm`.
pub const ADD64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ADD;
/// BPF opcode: `add64 dst, src` /// `dst += src`.
pub const ADD64_REG: u8 = BPF_ALU64 | BPF_X | BPF_ADD;
/// BPF opcode: `sub64 dst, imm` /// `dst -= imm`.
pub const SUB64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_SUB;
/// BPF opcode: `sub64 dst, src` /// `dst -= src`.
pub const SUB64_REG: u8 = BPF_ALU64 | BPF_X | BPF_SUB;
/// BPF opcode: `div64 dst, imm` /// `dst /= imm`.
pub const MUL64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MUL;
/// BPF opcode: `div64 dst, src` /// `dst /= src`.
pub const MUL64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MUL;
/// BPF opcode: `div64 dst, imm` /// `dst /= imm`.
pub const DIV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_DIV;
/// BPF opcode: `div64 dst, src` /// `dst /= src`.
pub const DIV64_REG: u8 = BPF_ALU64 | BPF_X | BPF_DIV;
/// BPF opcode: `or64 dst, imm` /// `dst |= imm`.
pub const OR64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_OR;
/// BPF opcode: `or64 dst, src` /// `dst |= src`.
pub const OR64_REG: u8 = BPF_ALU64 | BPF_X | BPF_OR;
/// BPF opcode: `and64 dst, imm` /// `dst &= imm`.
pub const AND64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_AND;
/// BPF opcode: `and64 dst, src` /// `dst &= src`.
pub const AND64_REG: u8 = BPF_ALU64 | BPF_X | BPF_AND;
/// BPF opcode: `lsh64 dst, imm` /// `dst <<= imm`.
pub const LSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_LSH;
/// BPF opcode: `lsh64 dst, src` /// `dst <<= src`.
pub const LSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_LSH;
/// BPF opcode: `rsh64 dst, imm` /// `dst >>= imm`.
pub const RSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_RSH;
/// BPF opcode: `rsh64 dst, src` /// `dst >>= src`.
pub const RSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_RSH;
/// BPF opcode: `neg64 dst, imm` /// `dst = -dst`.
pub const NEG64: u8 = BPF_ALU64 | BPF_NEG;
/// BPF opcode: `mod64 dst, imm` /// `dst %= imm`.
pub const MOD64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MOD;
/// BPF opcode: `mod64 dst, src` /// `dst %= src`.
pub const MOD64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MOD;
/// BPF opcode: `xor64 dst, imm` /// `dst ^= imm`.
pub const XOR64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_XOR;
/// BPF opcode: `xor64 dst, src` /// `dst ^= src`.
pub const XOR64_REG: u8 = BPF_ALU64 | BPF_X | BPF_XOR;
/// BPF opcode: `mov64 dst, imm` /// `dst = imm`.
pub const MOV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MOV;
/// BPF opcode: `mov64 dst, src` /// `dst = src`.
pub const MOV64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MOV;
/// BPF opcode: `arsh64 dst, imm` /// `dst >>= imm (arithmetic)`.
///
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
pub const ARSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ARSH;
/// BPF opcode: `arsh64 dst, src` /// `dst >>= src (arithmetic)`.
///
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
pub const ARSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_ARSH;
/// BPF opcode: `ja +off` /// `PC += off`.
pub const JA: u8 = BPF_JMP | BPF_JA;
/// BPF opcode: `jeq dst, imm, +off` /// `PC += off if dst == imm`.
pub const JEQ_IMM: u8 = BPF_JMP | BPF_K | BPF_JEQ;
/// BPF opcode: `jeq dst, src, +off` /// `PC += off if dst == src`.
pub const JEQ_REG: u8 = BPF_JMP | BPF_X | BPF_JEQ;
/// BPF opcode: `jgt dst, imm, +off` /// `PC += off if dst > imm`.
pub const JGT_IMM: u8 = BPF_JMP | BPF_K | BPF_JGT;
/// BPF opcode: `jgt dst, src, +off` /// `PC += off if dst > src`.
pub const JGT_REG: u8 = BPF_JMP | BPF_X | BPF_JGT;
/// BPF opcode: `jge dst, imm, +off` /// `PC += off if dst >= imm`.
pub const JGE_IMM: u8 = BPF_JMP | BPF_K | BPF_JGE;
/// BPF opcode: `jge dst, src, +off` /// `PC += off if dst >= src`.
pub const JGE_REG: u8 = BPF_JMP | BPF_X | BPF_JGE;
/// BPF opcode: `jlt dst, imm, +off` /// `PC += off if dst < imm`.
pub const JLT_IMM: u8 = BPF_JMP | BPF_K | BPF_JLT;
/// BPF opcode: `jlt dst, src, +off` /// `PC += off if dst < src`.
pub const JLT_REG: u8 = BPF_JMP | BPF_X | BPF_JLT;
/// BPF opcode: `jle dst, imm, +off` /// `PC += off if dst <= imm`.
pub const JLE_IMM: u8 = BPF_JMP | BPF_K | BPF_JLE;
/// BPF opcode: `jle dst, src, +off` /// `PC += off if dst <= src`.
pub const JLE_REG: u8 = BPF_JMP | BPF_X | BPF_JLE;
/// BPF opcode: `jset dst, imm, +off` /// `PC += off if dst & imm`.
pub const JSET_IMM: u8 = BPF_JMP | BPF_K | BPF_JSET;
/// BPF opcode: `jset dst, src, +off` /// `PC += off if dst & src`.
pub const JSET_REG: u8 = BPF_JMP | BPF_X | BPF_JSET;
/// BPF opcode: `jne dst, imm, +off` /// `PC += off if dst != imm`.
pub const JNE_IMM: u8 = BPF_JMP | BPF_K | BPF_JNE;
/// BPF opcode: `jne dst, src, +off` /// `PC += off if dst != src`.
pub const JNE_REG: u8 = BPF_JMP | BPF_X | BPF_JNE;
/// BPF opcode: `jsgt dst, imm, +off` /// `PC += off if dst > imm (signed)`.
pub const JSGT_IMM: u8 = BPF_JMP | BPF_K | BPF_JSGT;
/// BPF opcode: `jsgt dst, src, +off` /// `PC += off if dst > src (signed)`.
pub const JSGT_REG: u8 = BPF_JMP | BPF_X | BPF_JSGT;
/// BPF opcode: `jsge dst, imm, +off` /// `PC += off if dst >= imm (signed)`.
pub const JSGE_IMM: u8 = BPF_JMP | BPF_K | BPF_JSGE;
/// BPF opcode: `jsge dst, src, +off` /// `PC += off if dst >= src (signed)`.
pub const JSGE_REG: u8 = BPF_JMP | BPF_X | BPF_JSGE;
/// BPF opcode: `jslt dst, imm, +off` /// `PC += off if dst < imm (signed)`.
pub const JSLT_IMM: u8 = BPF_JMP | BPF_K | BPF_JSLT;
/// BPF opcode: `jslt dst, src, +off` /// `PC += off if dst < src (signed)`.
pub const JSLT_REG: u8 = BPF_JMP | BPF_X | BPF_JSLT;
/// BPF opcode: `jsle dst, imm, +off` /// `PC += off if dst <= imm (signed)`.
pub const JSLE_IMM: u8 = BPF_JMP | BPF_K | BPF_JSLE;
/// BPF opcode: `jsle dst, src, +off` /// `PC += off if dst <= src (signed)`.
pub const JSLE_REG: u8 = BPF_JMP | BPF_X | BPF_JSLE;
/// BPF opcode: `jeq dst, imm, +off` /// `PC += off if (dst as u32) == imm`.
pub const JEQ_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JEQ;
/// BPF opcode: `jeq dst, src, +off` /// `PC += off if (dst as u32) == (src as u32)`.
pub const JEQ_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JEQ;
/// BPF opcode: `jgt dst, imm, +off` /// `PC += off if (dst as u32) > imm`.
pub const JGT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JGT;
/// BPF opcode: `jgt dst, src, +off` /// `PC += off if (dst as u32) > (src as u32)`.
pub const JGT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JGT;
/// BPF opcode: `jge dst, imm, +off` /// `PC += off if (dst as u32) >= imm`.
pub const JGE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JGE;
/// BPF opcode: `jge dst, src, +off` /// `PC += off if (dst as u32) >= (src as u32)`.
pub const JGE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JGE;
/// BPF opcode: `jlt dst, imm, +off` /// `PC += off if (dst as u32) < imm`.
pub const JLT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JLT;
/// BPF opcode: `jlt dst, src, +off` /// `PC += off if (dst as u32) < (src as u32)`.
pub const JLT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JLT;
/// BPF opcode: `jle dst, imm, +off` /// `PC += off if (dst as u32) <= imm`.
pub const JLE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JLE;
/// BPF opcode: `jle dst, src, +off` /// `PC += off if (dst as u32) <= (src as u32)`.
pub const JLE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JLE;
/// BPF opcode: `jset dst, imm, +off` /// `PC += off if (dst as u32) & imm`.
pub const JSET_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSET;
/// BPF opcode: `jset dst, src, +off` /// `PC += off if (dst as u32) & (src as u32)`.
pub const JSET_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSET;
/// BPF opcode: `jne dst, imm, +off` /// `PC += off if (dst as u32) != imm`.
pub const JNE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JNE;
/// BPF opcode: `jne dst, src, +off` /// `PC += off if (dst as u32) != (src as u32)`.
pub const JNE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JNE;
/// BPF opcode: `jsgt dst, imm, +off` /// `PC += off if (dst as i32) > imm (signed)`.
pub const JSGT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSGT;
/// BPF opcode: `jsgt dst, src, +off` /// `PC += off if (dst as i32) > (src as i32) (signed)`.
pub const JSGT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSGT;
/// BPF opcode: `jsge dst, imm, +off` /// `PC += off if (dst as i32) >= imm (signed)`.
pub const JSGE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSGE;
/// BPF opcode: `jsge dst, src, +off` /// `PC += off if (dst as i32) >= (src as i32) (signed)`.
pub const JSGE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSGE;
/// BPF opcode: `jslt dst, imm, +off` /// `PC += off if (dst as i32) < imm (signed)`.
pub const JSLT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSLT;
/// BPF opcode: `jslt dst, src, +off` /// `PC += off if (dst as i32) < (src as i32) (signed)`.
pub const JSLT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSLT;
/// BPF opcode: `jsle dst, imm, +off` /// `PC += off if (dst as i32) <= imm (signed)`.
pub const JSLE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSLE;
/// BPF opcode: `jsle dst, src, +off` /// `PC += off if (dst as i32) <= (src as i32) (signed)`.
pub const JSLE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSLE;
/// BPF opcode: `call imm` /// helper function call to helper with key `imm`.
pub const CALL: u8 = BPF_JMP | BPF_CALL;
/// BPF opcode: tail call.
pub const TAIL_CALL: u8 = BPF_JMP | BPF_X | BPF_CALL;
/// BPF opcode: `exit` /// `return r0`.
pub const EXIT: u8 = BPF_JMP | BPF_EXIT;
// Used in JIT
/// Mask to extract the operation class from an operation code.
pub const BPF_CLS_MASK: u8 = 0x07;
/// Mask to extract the arithmetic operation code from an instruction operation code.
pub const BPF_ALU_OP_MASK: u8 = 0xf0;
/// Prototype of an eBPF helper function.
pub type Helper = fn(u64, u64, u64, u64, u64) -> u64;
/// An eBPF instruction.
///
/// See <https://www.kernel.org/doc/Documentation/networking/filter.txt> for the Linux kernel
/// documentation about eBPF, or <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md> for a
/// more concise version.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Insn {
/// Operation code.
pub opc: u8,
/// Destination register operand.
pub dst: u8,
/// Source register operand.
pub src: u8,
/// Offset operand.
pub off: i16,
/// Immediate value operand.
pub imm: i32,
}
impl Insn {
/// Turn an `Insn` back into an array of bytes.
///
/// # Examples
///
/// ```
/// use rbpf::ebpf;
///
/// let prog: &[u8] = &[
/// 0xb7, 0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78,
/// ];
/// let insn = ebpf::Insn {
/// opc: 0xb7,
/// dst: 2,
/// src: 1,
/// off: 0x3456,
/// imm: 0x789abcde
/// };
/// assert_eq!(insn.to_array(), prog);
/// ```
pub fn to_array(&self) -> [u8; INSN_SIZE] {
[
self.opc,
self.src.wrapping_shl(4) | self.dst,
(self.off & 0xff) as u8,
self.off.wrapping_shr(8) as u8,
(self.imm & 0xff) as u8,
(self.imm & 0xff_00).wrapping_shr(8) as u8,
(self.imm as u32 & 0xff_00_00).wrapping_shr(16) as u8,
(self.imm as u32 & 0xff_00_00_00).wrapping_shr(24) as u8,
]
}
/// Turn an `Insn` into an vector of bytes.
///
/// # Examples
///
/// ```
/// use rbpf::ebpf;
///
/// let prog: Vec<u8> = vec![
/// 0xb7, 0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78,
/// ];
/// let insn = ebpf::Insn {
/// opc: 0xb7,
/// dst: 2,
/// src: 1,
/// off: 0x3456,
/// imm: 0x789abcde
/// };
/// assert_eq!(insn.to_vec(), prog);
/// ```
pub fn to_vec(&self) -> Vec<u8> {
vec![
self.opc,
self.src.wrapping_shl(4) | self.dst,
(self.off & 0xff) as u8,
self.off.wrapping_shr(8) as u8,
(self.imm & 0xff) as u8,
(self.imm & 0xff_00).wrapping_shr(8) as u8,
(self.imm as u32 & 0xff_00_00).wrapping_shr(16) as u8,
(self.imm as u32 & 0xff_00_00_00).wrapping_shr(24) as u8,
]
}
}
/// Get the instruction at `idx` of an eBPF program. `idx` is the index (number) of the
/// instruction (not a byte offset). The first instruction has index 0.
///
/// # Panics
///
/// Panics if it is not possible to get the instruction (if idx is too high, or last instruction is
/// incomplete).
///
/// # Examples
///
/// ```
/// use rbpf::ebpf;
///
/// let prog = &[
/// 0xb7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
/// ];
/// let insn = ebpf::get_insn(prog, 1);
/// assert_eq!(insn.opc, 0x95);
/// ```
///
/// The example below will panic, since the last instruction is not complete and cannot be loaded.
///
/// ```rust,should_panic
/// use rbpf::ebpf;
///
/// let prog = &[
/// 0xb7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00 // two bytes missing
/// ];
/// let insn = ebpf::get_insn(prog, 1);
/// ```
pub fn get_insn(prog: &[u8], idx: usize) -> Insn {
// This guard should not be needed in most cases, since the verifier already checks the program
// size, and indexes should be fine in the interpreter/JIT. But this function is publicly
// available and user can call it with any `idx`, so we have to check anyway.
if (idx + 1) * INSN_SIZE > prog.len() {
panic!(
"Error: cannot reach instruction at index {:?} in program containing {:?} bytes",
idx,
prog.len()
);
}
Insn {
opc: prog[INSN_SIZE * idx],
dst: prog[INSN_SIZE * idx + 1] & 0x0f,
src: (prog[INSN_SIZE * idx + 1] & 0xf0) >> 4,
off: LittleEndian::read_i16(&prog[(INSN_SIZE * idx + 2)..]),
imm: LittleEndian::read_i32(&prog[(INSN_SIZE * idx + 4)..]),
}
}
/// Return a vector of `struct Insn` built from a program.
///
/// This is provided as a convenience for users wishing to manipulate a vector of instructions, for
/// example for dumping the program instruction after instruction with a custom format.
///
/// Note that the two parts of `LD_DW_IMM` instructions (spanning on 64 bits) are considered as two
/// distinct instructions.
///
/// # Examples
///
/// ```
/// use rbpf::ebpf;
///
/// let prog = &[
/// 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55,
/// 0x00, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
/// ];
///
/// let v = ebpf::to_insn_vec(prog);
/// assert_eq!(v, vec![
/// ebpf::Insn {
/// opc: 0x18,
/// dst: 0,
/// src: 0,
/// off: 0,
/// imm: 0x55667788
/// },
/// ebpf::Insn {
/// opc: 0,
/// dst: 0,
/// src: 0,
/// off: 0,
/// imm: 0x11223344
/// },
/// ebpf::Insn {
/// opc: 0x95,
/// dst: 0,
/// src: 0,
/// off: 0,
/// imm: 0
/// },
/// ]);
/// ```
pub fn to_insn_vec(prog: &[u8]) -> Vec<Insn> {
if prog.len() % INSN_SIZE != 0 {
panic!(
"Error: eBPF program length must be a multiple of {:?} octets",
INSN_SIZE
);
}
let mut res = vec![];
let mut insn_ptr: usize = 0;
while insn_ptr * INSN_SIZE < prog.len() {
let insn = get_insn(prog, insn_ptr);
res.push(insn);
insn_ptr += 1;
}
res
}

View File

@ -0,0 +1,488 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2015 Big Switch Networks, Inc
// (Algorithms for uBPF helpers, originally in C)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// (Translation to Rust, other helpers)
//! This module implements some built-in helpers that can be called from within an eBPF program.
//!
//! These helpers may originate from several places:
//!
//! * Some of them mimic the helpers available in the Linux kernel.
//! * Some of them were proposed as example helpers in uBPF and they were adapted here.
//! * Other helpers may be specific to rbpf.
//!
//! The prototype for helpers is always the same: five `u64` as arguments, and a `u64` as a return
//! value. Hence some helpers have unused arguments, or return a 0 value in all cases, in order to
//! respect this convention.
// Helpers associated to kernel helpers
// See also linux/include/uapi/linux/bpf.h in Linux kernel sources.
// bpf_ktime_getns()
/// Index of helper `bpf_ktime_getns()`, equivalent to `bpf_time_getns()`, in Linux kernel, see
/// <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/bpf.h>.
pub const BPF_KTIME_GETNS_IDX: u32 = 5;
/// Get monotonic time (since boot time) in nanoseconds. All arguments are unused.
///
/// # Examples
///
/// ```
/// use rbpf::helpers;
///
/// let t = helpers::bpf_time_getns(0, 0, 0, 0, 0);
/// let d = t / 10u64.pow(9) / 60 / 60 / 24;
/// let h = (t / 10u64.pow(9) / 60 / 60) % 24;
/// let m = (t / 10u64.pow(9) / 60 ) % 60;
/// let s = (t / 10u64.pow(9)) % 60;
/// let ns = t % 10u64.pow(9);
/// println!("Uptime: {:#x} == {} days {}:{}:{}, {} ns", t, d, h, m, s, ns);
/// ```
#[allow(dead_code)]
#[allow(unused_variables)]
#[allow(deprecated)]
#[cfg(feature = "std")]
pub fn bpf_time_getns(unused1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
time::precise_time_ns()
}
// bpf_trace_printk()
/// Index of helper `bpf_trace_printk()`, equivalent to `bpf_trace_printf()`, in Linux kernel, see
/// <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/bpf.h>.
pub const BPF_TRACE_PRINTK_IDX: u32 = 6;
/// Prints its **last three** arguments to standard output. The **first two** arguments are
/// **unused**. Returns the number of bytes written.
///
/// By ignoring the first two arguments, it creates a helper that will have a behavior similar to
/// the one of the equivalent helper `bpf_trace_printk()` from Linux kernel.
///
/// # Examples
///
/// ```
/// use rbpf::helpers;
///
/// let res = helpers::bpf_trace_printf(0, 0, 1, 15, 32);
/// assert_eq!(res as usize, "bpf_trace_printf: 0x1, 0xf, 0x20\n".len());
/// ```
///
/// This will print `bpf_trace_printf: 0x1, 0xf, 0x20`.
///
/// The eBPF code needed to perform the call in this example would be nearly identical to the code
/// obtained by compiling the following code from C to eBPF with clang:
///
/// ```c
/// #include <linux/bpf.h>
/// #include "path/to/linux/samples/bpf/bpf_helpers.h"
///
/// int main(struct __sk_buff *skb)
/// {
/// // Only %d %u %x %ld %lu %lx %lld %llu %llx %p %s conversion specifiers allowed.
/// // See <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/kernel/trace/bpf_trace.c>.
/// char *fmt = "bpf_trace_printk %llx, %llx, %llx\n";
/// return bpf_trace_printk(fmt, sizeof(fmt), 1, 15, 32);
/// }
/// ```
///
/// This would equally print the three numbers in `/sys/kernel/debug/tracing` file each time the
/// program is run.
#[allow(dead_code)]
#[allow(unused_variables)]
#[cfg(feature = "std")]
pub fn bpf_trace_printf(unused1: u64, unused2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
println!("bpf_trace_printf: {arg3:#x}, {arg4:#x}, {arg5:#x}");
let size_arg = |x| {
if x == 0 {
1
} else {
(x as f64).log(16.0).floor() as u64 + 1
}
};
"bpf_trace_printf: 0x, 0x, 0x\n".len() as u64 + size_arg(arg3) + size_arg(arg4) + size_arg(arg5)
}
// Helpers coming from uBPF <https://github.com/iovisor/ubpf/blob/master/vm/test.c>
/// The idea is to assemble five bytes into a single `u64`. For compatibility with the helpers API,
/// each argument must be a `u64`.
///
/// # Examples
///
/// ```
/// use rbpf::helpers;
///
/// let gathered = helpers::gather_bytes(0x11, 0x22, 0x33, 0x44, 0x55);
/// assert_eq!(gathered, 0x1122334455);
/// ```
pub fn gather_bytes(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
arg1.wrapping_shl(32)
| arg2.wrapping_shl(24)
| arg3.wrapping_shl(16)
| arg4.wrapping_shl(8)
| arg5
}
/// Same as `void *memfrob(void *s, size_t n);` in `string.h` in C. See the GNU manual page (in
/// section 3) for `memfrob`. The memory is directly modified, and the helper returns 0 in all
/// cases. Arguments 3 to 5 are unused.
///
/// # Examples
///
/// ```
/// use rbpf::helpers;
///
/// let val: u64 = 0x112233;
/// let val_ptr = &val as *const u64;
///
/// helpers::memfrob(val_ptr as u64, 8, 0, 0, 0);
/// assert_eq!(val, 0x2a2a2a2a2a3b0819);
/// helpers::memfrob(val_ptr as u64, 8, 0, 0, 0);
/// assert_eq!(val, 0x112233);
/// ```
#[allow(unused_variables)]
pub fn memfrob(ptr: u64, len: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
for i in 0..len {
unsafe {
let mut p = (ptr + i) as *mut u8;
*p ^= 0b101010;
}
}
0
}
// TODO: Try again when asm!() is available in stable Rust.
// #![feature(asm)]
// #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
// #[allow(unused_variables)]
// pub fn memfrob (ptr: u64, len: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
// unsafe {
// asm!(
// "mov $0xf0, %rax"
// ::: "mov $0xf1, %rcx"
// ::: "mov $0xf2, %rdx"
// ::: "mov $0xf3, %rsi"
// ::: "mov $0xf4, %rdi"
// ::: "mov $0xf5, %r8"
// ::: "mov $0xf6, %r9"
// ::: "mov $0xf7, %r10"
// ::: "mov $0xf8, %r11"
// );
// }
// 0
// }
/// Compute and return the square root of argument 1, cast as a float. Arguments 2 to 5 are
/// unused.
///
/// # Examples
///
/// ```
/// use rbpf::helpers;
///
/// let x = helpers::sqrti(9, 0, 0, 0, 0);
/// assert_eq!(x, 3);
/// ```
#[allow(dead_code)]
#[allow(unused_variables)]
#[cfg(feature = "std")] // sqrt is only available when using `std`
pub fn sqrti(arg1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
(arg1 as f64).sqrt() as u64
}
/// C-like `strcmp`, return 0 if the strings are equal, and a non-null value otherwise.
///
/// # Examples
///
/// ```
/// use rbpf::helpers;
///
/// let foo = "This is a string.\0".as_ptr() as u64;
/// let bar = "This is another sting.\0".as_ptr() as u64;
///
/// assert!(helpers::strcmp(foo, foo, 0, 0, 0) == 0);
/// assert!(helpers::strcmp(foo, bar, 0, 0, 0) != 0);
/// ```
#[allow(dead_code)]
#[allow(unused_variables)]
pub fn strcmp(arg1: u64, arg2: u64, arg3: u64, unused4: u64, unused5: u64) -> u64 {
// C-like strcmp, maybe shorter than converting the bytes to string and comparing?
if arg1 == 0 || arg2 == 0 {
return u64::MAX;
}
let mut a = arg1;
let mut b = arg2;
unsafe {
let mut a_val = *(a as *const u8);
let mut b_val = *(b as *const u8);
while a_val == b_val && a_val != 0 && b_val != 0 {
a += 1;
b += 1;
a_val = *(a as *const u8);
b_val = *(b as *const u8);
}
if a_val >= b_val {
(a_val - b_val) as u64
} else {
(b_val - a_val) as u64
}
}
}
// Some additional helpers
/// Returns a random u64 value comprised between `min` and `max` values (inclusive). Arguments 3 to
/// 5 are unused.
///
/// Relies on `rand()` function from libc, so `libc::srand()` should be called once before this
/// helper is used.
///
/// # Examples
///
/// ```
/// extern crate libc;
/// extern crate rbpf;
/// extern crate time;
///
/// unsafe {
/// libc::srand(time::precise_time_ns() as u32)
/// }
///
/// let n = rbpf::helpers::rand(3, 6, 0, 0, 0);
/// assert!(3 <= n && n <= 6);
/// ```
#[allow(dead_code)]
#[allow(unused_variables)]
#[cfg(feature = "std")]
pub fn rand(min: u64, max: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
let mut n = unsafe { (libc::rand() as u64).wrapping_shl(32) + libc::rand() as u64 };
if min < max {
n = n % (max + 1 - min) + min;
};
n
}
/// Prints the helper functions name and it's index.
#[cfg(feature = "std")]
pub fn show_helper() {
for (index, name) in BPF_FUNC_MAPPER.iter().enumerate() {
println!("{}:{}", index, name);
}
}
/// See https://github.com/torvalds/linux/blob/master/include/uapi/linux/bpf.h
pub const BPF_FUNC_MAPPER: &[&str] = &[
"unspec",
"map_lookup_elem",
"map_update_elem",
"map_delete_elem",
"probe_read",
"ktime_get_ns",
"trace_printk",
"get_prandom_u32",
"get_smp_processor_id",
"skb_store_bytes",
"l3_csum_replace",
"l4_csum_replace",
"tail_call",
"clone_redirect",
"get_current_pid_tgid",
"get_current_uid_gid",
"get_current_comm",
"get_cgroup_classid",
"skb_vlan_push",
"skb_vlan_pop",
"skb_get_tunnel_key",
"skb_set_tunnel_key",
"perf_event_read",
"redirect",
"get_route_realm",
"perf_event_output",
"skb_load_bytes",
"get_stackid",
"csum_diff",
"skb_get_tunnel_opt",
"skb_set_tunnel_opt",
"skb_change_proto",
"skb_change_type",
"skb_under_cgroup",
"get_hash_recalc",
"get_current_task",
"probe_write_user",
"current_task_under_cgroup",
"skb_change_tail",
"skb_pull_data",
"csum_update",
"set_hash_invalid",
"get_numa_node_id",
"skb_change_head",
"xdp_adjust_head",
"probe_read_str",
"get_socket_cookie",
"get_socket_uid",
"set_hash",
"setsockopt",
"skb_adjust_room",
"redirect_map",
"sk_redirect_map",
"sock_map_update",
"xdp_adjust_meta",
"perf_event_read_value",
"perf_prog_read_value",
"getsockopt",
"override_return",
"sock_ops_cb_flags_set",
"msg_redirect_map",
"msg_apply_bytes",
"msg_cork_bytes",
"msg_pull_data",
"bind",
"xdp_adjust_tail",
"skb_get_xfrm_state",
"get_stack",
"skb_load_bytes_relative",
"fib_lookup",
"sock_hash_update",
"msg_redirect_hash",
"sk_redirect_hash",
"lwt_push_encap",
"lwt_seg6_store_bytes",
"lwt_seg6_adjust_srh",
"lwt_seg6_action",
"rc_repeat",
"rc_keydown",
"skb_cgroup_id",
"get_current_cgroup_id",
"get_local_storage",
"sk_select_reuseport",
"skb_ancestor_cgroup_id",
"sk_lookup_tcp",
"sk_lookup_udp",
"sk_release",
"map_push_elem",
"map_pop_elem",
"map_peek_elem",
"msg_push_data",
"msg_pop_data",
"rc_pointer_rel",
"spin_lock",
"spin_unlock",
"sk_fullsock",
"tcp_sock",
"skb_ecn_set_ce",
"get_listener_sock",
"skc_lookup_tcp",
"tcp_check_syncookie",
"sysctl_get_name",
"sysctl_get_current_value",
"sysctl_get_new_value",
"sysctl_set_new_value",
"strtol",
"strtoul",
"sk_storage_get",
"sk_storage_delete",
"send_signal",
"tcp_gen_syncookie",
"skb_output",
"probe_read_user",
"probe_read_kernel",
"probe_read_user_str",
"probe_read_kernel_str",
"tcp_send_ack",
"send_signal_thread",
"jiffies64",
"read_branch_records",
"get_ns_current_pid_tgid",
"xdp_output",
"get_netns_cookie",
"get_current_ancestor_cgroup_id",
"sk_assign",
"ktime_get_boot_ns",
"seq_printf",
"seq_write",
"sk_cgroup_id",
"sk_ancestor_cgroup_id",
"ringbuf_output",
"ringbuf_reserve",
"ringbuf_submit",
"ringbuf_discard",
"ringbuf_query",
"csum_level",
"skc_to_tcp6_sock",
"skc_to_tcp_sock",
"skc_to_tcp_timewait_sock",
"skc_to_tcp_request_sock",
"skc_to_udp6_sock",
"get_task_stack",
"load_hdr_opt",
"store_hdr_opt",
"reserve_hdr_opt",
"inode_storage_get",
"inode_storage_delete",
"d_path",
"copy_from_user",
"snprintf_btf",
"seq_printf_btf",
"skb_cgroup_classid",
"redirect_neigh",
"per_cpu_ptr",
"this_cpu_ptr",
"redirect_peer",
"task_storage_get",
"task_storage_delete",
"get_current_task_btf",
"bprm_opts_set",
"ktime_get_coarse_ns",
"ima_inode_hash",
"sock_from_file",
"check_mtu",
"for_each_map_elem",
"snprintf",
"sys_bpf",
"btf_find_by_name_kind",
"sys_close",
"timer_init",
"timer_set_callback",
"timer_start",
"timer_cancel",
"get_func_ip",
"get_attach_cookie",
"task_pt_regs",
"get_branch_snapshot",
"trace_vprintk",
"skc_to_unix_sock",
"kallsyms_lookup_name",
"find_vma",
"loop",
"strncmp",
"get_func_arg",
"get_func_ret",
"get_func_arg_cnt",
"get_retval",
"set_retval",
"xdp_get_buff_len",
"xdp_load_bytes",
"xdp_store_bytes",
"copy_from_user_task",
"skb_set_tstamp",
"ima_file_hash",
"kptr_xchg",
"map_lookup_percpu_elem",
"skc_to_mptcp_sock",
"dynptr_from_mem",
"ringbuf_reserve_dynptr",
"ringbuf_submit_dynptr",
"ringbuf_discard_dynptr",
"dynptr_read",
"dynptr_write",
"dynptr_data",
"tcp_raw_gen_syncookie_ipv4",
"tcp_raw_gen_syncookie_ipv6",
"tcp_raw_check_syncookie_ipv4",
"tcp_raw_check_syncookie_ipv6",
"ktime_get_tai_ns",
"user_ringbuf_drain",
"cgrp_storage_get",
"cgrp_storage_delete",
];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,708 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Derived from uBPF <https://github.com/iovisor/ubpf>
// Copyright 2015 Big Switch Networks, Inc
// (uBPF: VM architecture, parts of the interpreter, originally in C)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// (Translation to Rust, MetaBuff/multiple classes addition, hashmaps for helpers)
use crate::{
ebpf::{self, Insn},
helpers::BPF_FUNC_MAPPER,
stack::StackFrame,
*,
};
#[cfg(not(feature = "user"))]
#[allow(unused)]
fn check_mem(
addr: u64,
len: usize,
access_type: &str,
insn_ptr: usize,
mbuff: &[u8],
mem: &[u8],
stack: &[u8],
) -> Result<(), Error> {
log::trace!(
"check_mem: addr {:#x}, len {}, access_type {}, insn_ptr {}",
addr,
len,
access_type,
insn_ptr
);
log::trace!(
"check_mem: mbuff: {:#x}/{:#x}, mem: {:#x}/{:#x}, stack: {:#x}/{:#x}",
mbuff.as_ptr() as u64,
mbuff.len(),
mem.as_ptr() as u64,
mem.len(),
stack.as_ptr() as u64,
stack.len()
);
Ok(())
}
#[cfg(feature = "user")]
fn check_mem(
addr: u64,
len: usize,
access_type: &str,
insn_ptr: usize,
mbuff: &[u8],
mem: &[u8],
stack: &[u8],
) -> Result<(), Error> {
if let Some(addr_end) = addr.checked_add(len as u64) {
if mbuff.as_ptr() as u64 <= addr && addr_end <= mbuff.as_ptr() as u64 + mbuff.len() as u64 {
return Ok(());
}
if mem.as_ptr() as u64 <= addr && addr_end <= mem.as_ptr() as u64 + mem.len() as u64 {
return Ok(());
}
if stack.as_ptr() as u64 <= addr && addr_end <= stack.as_ptr() as u64 + stack.len() as u64 {
return Ok(());
}
}
Err(Error::new(ErrorKind::Other, format!(
"Error: out of bounds memory {} (insn #{:?}), addr {:#x}, size {:?}\nmbuff: {:#x}/{:#x}, mem: {:#x}/{:#x}, stack: {:#x}/{:#x}",
access_type, insn_ptr, addr, len,
mbuff.as_ptr() as u64, mbuff.len(),
mem.as_ptr() as u64, mem.len(),
stack.as_ptr() as u64, stack.len()
)))
}
#[inline]
fn do_jump(insn_ptr: &mut usize, insn: &Insn) {
*insn_ptr = (*insn_ptr as i16 + insn.off) as usize;
}
#[allow(unknown_lints)]
#[allow(cyclomatic_complexity)]
pub fn execute_program(
prog_: Option<&[u8]>,
mem: &[u8],
mbuff: &[u8],
helpers: &HashMap<u32, ebpf::Helper>,
) -> Result<u64, Error> {
const U32MAX: u64 = u32::MAX as u64;
const SHIFT_MASK_64: u64 = 0x3f;
let prog = match prog_ {
Some(prog) => prog,
None => Err(Error::new(
ErrorKind::Other,
"Error: No program set, call prog_set() to load one",
))?,
};
let mut stacks = Vec::new();
let stack = StackFrame::new();
// R1 points to beginning of memory area, R10 to stack
let mut reg: [u64; 11] = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
stack.as_ptr() as u64 + stack.len() as u64,
];
stacks.push(stack);
if !mbuff.is_empty() {
reg[1] = mbuff.as_ptr() as u64;
} else if !mem.is_empty() {
reg[1] = mem.as_ptr() as u64;
}
let check_mem_load =
|stack: &[u8], addr: u64, len: usize, insn_ptr: usize| -> Result<(), Error> {
check_mem(addr, len, "load", insn_ptr, mbuff, mem, stack)
};
let check_mem_store =
|stack: &[u8], addr: u64, len: usize, insn_ptr: usize| -> Result<(), Error> {
check_mem(addr, len, "store", insn_ptr, mbuff, mem, stack)
};
// Loop on instructions
let mut insn_ptr: usize = 0;
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
let insn = ebpf::get_insn(prog, insn_ptr);
insn_ptr += 1;
let _dst = insn.dst as usize;
let _src = insn.src as usize;
match insn.opc {
// BPF_LD class
// LD_ABS_* and LD_IND_* are supposed to load pointer to data from metadata buffer.
// Since this pointer is constant, and since we already know it (mem), do not
// bother re-fetching it, just use mem already.
ebpf::LD_ABS_B => {
reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u8;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_ABS_H => {
reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u16;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_ABS_W => {
reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u32;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_ABS_DW => {
log::info!("executing LD_ABS_DW, set reg[{}] to {:#x}", _dst, insn.imm);
reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u64;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned()
}
}
ebpf::LD_IND_B => {
reg[0] = unsafe {
let x =
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u8;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_IND_H => {
reg[0] = unsafe {
let x =
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u16;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_IND_W => {
reg[0] = unsafe {
let x =
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u32;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_IND_DW => {
reg[0] = unsafe {
let x =
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u64;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned()
}
}
ebpf::LD_DW_IMM => {
let next_insn = ebpf::get_insn(prog, insn_ptr);
insn_ptr += 1;
// log::warn!(
// "executing LD_DW_IMM, set reg[{}] to {:#x}",
// _dst,
// ((insn.imm as u32) as u64) + ((next_insn.imm as u64) << 32)
// );
reg[_dst] = ((insn.imm as u32) as u64) + ((next_insn.imm as u64) << 32);
}
// BPF_LDX class
ebpf::LD_B_REG => {
reg[_dst] = unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize);
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 1, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_H_REG => {
reg[_dst] = unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u16;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 2, insn_ptr)?;
x.read_unaligned() as u64
}
}
ebpf::LD_W_REG => {
reg[_dst] = unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u32;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 4, insn_ptr)?;
// log::warn!(
// "executing LD_W_REG, the ptr is REG:{} -> [{:#x}] + {:#x}",
// _src,
// reg[_src],
// insn.off
// );
x.read_unaligned() as u64
}
}
ebpf::LD_DW_REG => {
reg[_dst] = unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u64;
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.read_unaligned()
}
}
// BPF_ST class
ebpf::ST_B_IMM => unsafe {
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u8;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 1, insn_ptr)?;
x.write_unaligned(insn.imm as u8);
},
ebpf::ST_H_IMM => unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u16;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 2, insn_ptr)?;
x.write_unaligned(insn.imm as u16);
},
ebpf::ST_W_IMM => unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u32;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 4, insn_ptr)?;
x.write_unaligned(insn.imm as u32);
},
ebpf::ST_DW_IMM => unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u64;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.write_unaligned(insn.imm as u64);
},
// BPF_STX class
ebpf::ST_B_REG => unsafe {
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u8;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 1, insn_ptr)?;
x.write_unaligned(reg[_src] as u8);
},
ebpf::ST_H_REG => unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u16;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 2, insn_ptr)?;
x.write_unaligned(reg[_src] as u16);
},
ebpf::ST_W_REG => unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u32;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 4, insn_ptr)?;
x.write_unaligned(reg[_src] as u32);
},
ebpf::ST_DW_REG => unsafe {
#[allow(clippy::cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u64;
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
x.write_unaligned(reg[_src]);
},
ebpf::ST_W_XADD => unimplemented!(),
ebpf::ST_DW_XADD => unimplemented!(),
// BPF_ALU class
// TODO Check how overflow works in kernel. Should we &= U32MAX all src register value
// before we do the operation?
// Cf ((0x11 << 32) - (0x1 << 32)) as u32 VS ((0x11 << 32) as u32 - (0x1 << 32) as u32
ebpf::ADD32_IMM => reg[_dst] = (reg[_dst] as i32).wrapping_add(insn.imm) as u64, //((reg[_dst] & U32MAX) + insn.imm as u64) & U32MAX,
ebpf::ADD32_REG => reg[_dst] = (reg[_dst] as i32).wrapping_add(reg[_src] as i32) as u64, //((reg[_dst] & U32MAX) + (reg[_src] & U32MAX)) & U32MAX,
ebpf::SUB32_IMM => reg[_dst] = (reg[_dst] as i32).wrapping_sub(insn.imm) as u64,
ebpf::SUB32_REG => reg[_dst] = (reg[_dst] as i32).wrapping_sub(reg[_src] as i32) as u64,
ebpf::MUL32_IMM => reg[_dst] = (reg[_dst] as i32).wrapping_mul(insn.imm) as u64,
ebpf::MUL32_REG => reg[_dst] = (reg[_dst] as i32).wrapping_mul(reg[_src] as i32) as u64,
ebpf::DIV32_IMM if insn.imm as u32 == 0 => reg[_dst] = 0,
ebpf::DIV32_IMM => reg[_dst] = (reg[_dst] as u32 / insn.imm as u32) as u64,
ebpf::DIV32_REG if reg[_src] as u32 == 0 => reg[_dst] = 0,
ebpf::DIV32_REG => reg[_dst] = (reg[_dst] as u32 / reg[_src] as u32) as u64,
ebpf::OR32_IMM => reg[_dst] = (reg[_dst] as u32 | insn.imm as u32) as u64,
ebpf::OR32_REG => reg[_dst] = (reg[_dst] as u32 | reg[_src] as u32) as u64,
ebpf::AND32_IMM => reg[_dst] = (reg[_dst] as u32 & insn.imm as u32) as u64,
ebpf::AND32_REG => reg[_dst] = (reg[_dst] as u32 & reg[_src] as u32) as u64,
// As for the 64-bit version, we should mask the number of bits to shift with
// 0x1f, but .wrappping_shr() already takes care of it for us.
ebpf::LSH32_IMM => reg[_dst] = (reg[_dst] as u32).wrapping_shl(insn.imm as u32) as u64,
ebpf::LSH32_REG => reg[_dst] = (reg[_dst] as u32).wrapping_shl(reg[_src] as u32) as u64,
ebpf::RSH32_IMM => reg[_dst] = (reg[_dst] as u32).wrapping_shr(insn.imm as u32) as u64,
ebpf::RSH32_REG => reg[_dst] = (reg[_dst] as u32).wrapping_shr(reg[_src] as u32) as u64,
ebpf::NEG32 => {
reg[_dst] = (reg[_dst] as i32).wrapping_neg() as u64;
reg[_dst] &= U32MAX;
}
ebpf::MOD32_IMM if insn.imm as u32 == 0 => (),
ebpf::MOD32_IMM => reg[_dst] = (reg[_dst] as u32 % insn.imm as u32) as u64,
ebpf::MOD32_REG if reg[_src] as u32 == 0 => (),
ebpf::MOD32_REG => reg[_dst] = (reg[_dst] as u32 % reg[_src] as u32) as u64,
ebpf::XOR32_IMM => reg[_dst] = (reg[_dst] as u32 ^ insn.imm as u32) as u64,
ebpf::XOR32_REG => reg[_dst] = (reg[_dst] as u32 ^ reg[_src] as u32) as u64,
ebpf::MOV32_IMM => reg[_dst] = insn.imm as u32 as u64,
ebpf::MOV32_REG => reg[_dst] = (reg[_src] as u32) as u64,
// As for the 64-bit version, we should mask the number of bits to shift with
// 0x1f, but .wrappping_shr() already takes care of it for us.
ebpf::ARSH32_IMM => {
reg[_dst] = (reg[_dst] as i32).wrapping_shr(insn.imm as u32) as u64;
reg[_dst] &= U32MAX;
}
ebpf::ARSH32_REG => {
reg[_dst] = (reg[_dst] as i32).wrapping_shr(reg[_src] as u32) as u64;
reg[_dst] &= U32MAX;
}
ebpf::LE => {
reg[_dst] = match insn.imm {
16 => (reg[_dst] as u16).to_le() as u64,
32 => (reg[_dst] as u32).to_le() as u64,
64 => reg[_dst].to_le(),
_ => unreachable!(),
};
}
ebpf::BE => {
reg[_dst] = match insn.imm {
16 => (reg[_dst] as u16).to_be() as u64,
32 => (reg[_dst] as u32).to_be() as u64,
64 => reg[_dst].to_be(),
_ => unreachable!(),
};
}
// BPF_ALU64 class
ebpf::ADD64_IMM => reg[_dst] = reg[_dst].wrapping_add(insn.imm as u64),
ebpf::ADD64_REG => reg[_dst] = reg[_dst].wrapping_add(reg[_src]),
ebpf::SUB64_IMM => reg[_dst] = reg[_dst].wrapping_sub(insn.imm as u64),
ebpf::SUB64_REG => reg[_dst] = reg[_dst].wrapping_sub(reg[_src]),
ebpf::MUL64_IMM => reg[_dst] = reg[_dst].wrapping_mul(insn.imm as u64),
ebpf::MUL64_REG => reg[_dst] = reg[_dst].wrapping_mul(reg[_src]),
ebpf::DIV64_IMM if insn.imm == 0 => reg[_dst] = 0,
ebpf::DIV64_IMM => reg[_dst] /= insn.imm as u64,
ebpf::DIV64_REG if reg[_src] == 0 => reg[_dst] = 0,
ebpf::DIV64_REG => reg[_dst] /= reg[_src],
ebpf::OR64_IMM => reg[_dst] |= insn.imm as u64,
ebpf::OR64_REG => reg[_dst] |= reg[_src],
ebpf::AND64_IMM => reg[_dst] &= insn.imm as u64,
ebpf::AND64_REG => reg[_dst] &= reg[_src],
ebpf::LSH64_IMM => reg[_dst] <<= insn.imm as u64 & SHIFT_MASK_64,
ebpf::LSH64_REG => reg[_dst] <<= reg[_src] & SHIFT_MASK_64,
ebpf::RSH64_IMM => reg[_dst] >>= insn.imm as u64 & SHIFT_MASK_64,
ebpf::RSH64_REG => reg[_dst] >>= reg[_src] & SHIFT_MASK_64,
ebpf::NEG64 => reg[_dst] = -(reg[_dst] as i64) as u64,
ebpf::MOD64_IMM if insn.imm == 0 => (),
ebpf::MOD64_IMM => reg[_dst] %= insn.imm as u64,
ebpf::MOD64_REG if reg[_src] == 0 => (),
ebpf::MOD64_REG => reg[_dst] %= reg[_src],
ebpf::XOR64_IMM => reg[_dst] ^= insn.imm as u64,
ebpf::XOR64_REG => reg[_dst] ^= reg[_src],
ebpf::MOV64_IMM => reg[_dst] = insn.imm as u64,
ebpf::MOV64_REG => reg[_dst] = reg[_src],
ebpf::ARSH64_IMM => {
reg[_dst] = (reg[_dst] as i64 >> (insn.imm as u64 & SHIFT_MASK_64)) as u64
}
ebpf::ARSH64_REG => {
reg[_dst] = (reg[_dst] as i64 >> (reg[_src] as u64 & SHIFT_MASK_64)) as u64
}
// BPF_JMP class
// TODO: check this actually works as expected for signed / unsigned ops
ebpf::JA => do_jump(&mut insn_ptr, &insn),
ebpf::JEQ_IMM => {
if reg[_dst] == insn.imm as u64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JEQ_REG => {
if reg[_dst] == reg[_src] {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGT_IMM => {
if reg[_dst] > insn.imm as u64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGT_REG => {
if reg[_dst] > reg[_src] {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGE_IMM => {
if reg[_dst] >= insn.imm as u64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGE_REG => {
if reg[_dst] >= reg[_src] {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLT_IMM => {
if reg[_dst] < insn.imm as u64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLT_REG => {
if reg[_dst] < reg[_src] {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLE_IMM => {
if reg[_dst] <= insn.imm as u64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLE_REG => {
if reg[_dst] <= reg[_src] {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSET_IMM => {
if reg[_dst] & insn.imm as u64 != 0 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSET_REG => {
if reg[_dst] & reg[_src] != 0 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JNE_IMM => {
if reg[_dst] != insn.imm as u64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JNE_REG => {
if reg[_dst] != reg[_src] {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGT_IMM => {
if reg[_dst] as i64 > insn.imm as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGT_REG => {
if reg[_dst] as i64 > reg[_src] as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGE_IMM => {
if reg[_dst] as i64 >= insn.imm as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGE_REG => {
if reg[_dst] as i64 >= reg[_src] as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLT_IMM => {
if (reg[_dst] as i64) < insn.imm as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLT_REG => {
if (reg[_dst] as i64) < reg[_src] as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLE_IMM => {
if reg[_dst] as i64 <= insn.imm as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLE_REG => {
if reg[_dst] as i64 <= reg[_src] as i64 {
do_jump(&mut insn_ptr, &insn);
}
}
// BPF_JMP32 class
ebpf::JEQ_IMM32 => {
if reg[_dst] as u32 == insn.imm as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JEQ_REG32 => {
if reg[_dst] as u32 == reg[_src] as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGT_IMM32 => {
if reg[_dst] as u32 > insn.imm as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGT_REG32 => {
if reg[_dst] as u32 > reg[_src] as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGE_IMM32 => {
if reg[_dst] as u32 >= insn.imm as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JGE_REG32 => {
if reg[_dst] as u32 >= reg[_src] as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLT_IMM32 => {
if (reg[_dst] as u32) < insn.imm as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLT_REG32 => {
if (reg[_dst] as u32) < reg[_src] as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLE_IMM32 => {
if reg[_dst] as u32 <= insn.imm as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JLE_REG32 => {
if reg[_dst] as u32 <= reg[_src] as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSET_IMM32 => {
if reg[_dst] as u32 & insn.imm as u32 != 0 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSET_REG32 => {
if reg[_dst] as u32 & reg[_src] as u32 != 0 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JNE_IMM32 => {
if reg[_dst] as u32 != insn.imm as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JNE_REG32 => {
if reg[_dst] as u32 != reg[_src] as u32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGT_IMM32 => {
if reg[_dst] as i32 > insn.imm {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGT_REG32 => {
if reg[_dst] as i32 > reg[_src] as i32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGE_IMM32 => {
if reg[_dst] as i32 >= insn.imm {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSGE_REG32 => {
if reg[_dst] as i32 >= reg[_src] as i32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLT_IMM32 => {
if (reg[_dst] as i32) < insn.imm {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLT_REG32 => {
if (reg[_dst] as i32) < reg[_src] as i32 {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLE_IMM32 => {
if reg[_dst] as i32 <= insn.imm {
do_jump(&mut insn_ptr, &insn);
}
}
ebpf::JSLE_REG32 => {
if reg[_dst] as i32 <= reg[_src] as i32 {
do_jump(&mut insn_ptr, &insn);
}
}
// Do not delegate the check to the verifier, since registered functions can be
// changed after the program has been verified.
ebpf::CALL => {
// See https://www.kernel.org/doc/html/latest/bpf/standardization/instruction-set.html#id16
let src_reg = _src;
let call_func_res = match src_reg {
0 => {
// Handle call by address to external function.
if let Some(function) = helpers.get(&(insn.imm as u32)) {
reg[0] = function(reg[1], reg[2], reg[3], reg[4], reg[5]);
Ok(())
}else {
Err(format!(
"Error: unknown helper function (id: {:#x}) [{}], (instruction #{})",
insn.imm as u32,BPF_FUNC_MAPPER[insn.imm as usize],insn_ptr
))
}
}
1 => {
// bpf to bpf call
// The function is in the same program, so we can just jump to the address
if stacks.len() >= ebpf::RBPF_MAX_CALL_DEPTH{
Err(format!(
"Error: bpf to bpf call stack limit reached (instruction #{}) max depth: {}",
insn_ptr, ebpf::RBPF_MAX_CALL_DEPTH
))
}else {
let mut pre_stack = stacks.last_mut().unwrap();
// Save the callee saved registers
pre_stack.save_registers(&reg[6..=9]);
// Save the return address
pre_stack.save_return_address(insn_ptr as u16);
// save the stack pointer
pre_stack.save_sp(reg[10] as u16);
let mut stack = StackFrame::new();
log::trace!("BPF TO BPF CALL: new pc: {} + {} = {}",insn_ptr ,insn.imm,insn_ptr + insn.imm as usize);
reg[10] = stack.as_ptr() as u64 + stack.len() as u64;
stacks.push(stack);
insn_ptr += insn.imm as usize;
Ok(())
}
}
_ =>{
Err(format!(
"Error: the function call type (id: {:#x}) [{}], (instruction #{}) not supported",
insn.imm as u32,BPF_FUNC_MAPPER[insn.imm as usize],insn_ptr
))
}
};
if let Err(e) = call_func_res {
Err(Error::new(ErrorKind::Other, e))?;
}
}
ebpf::TAIL_CALL => unimplemented!(),
ebpf::EXIT => {
if stacks.len() == 1 {
return Ok(reg[0]);
} else {
// Pop the stack
stacks.pop();
let stack = stacks.last().unwrap();
// Restore the callee saved registers
reg[6..=9].copy_from_slice(&stack.get_registers());
// Restore the return address
insn_ptr = stack.get_return_address() as usize;
// Restore the stack pointer
reg[10] = stack.get_sp() as u64;
log::trace!("EXIT: new pc: {}", insn_ptr);
}
}
_ => unreachable!(),
}
}
unreachable!()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//! This module provides a simple implementation of the Error struct that is
//! used as a drop-in replacement for `std::io::Error` when using `rbpf` in `no_std`.
use alloc::string::String;
/// Implementation of Error for no_std applications.
/// Ensures that the existing code can use it with the same interface
/// as the Error from std::io::Error.
#[derive(Debug)]
pub struct Error {
#[allow(dead_code)]
kind: ErrorKind,
#[allow(dead_code)]
error: String,
}
impl Error {
/// New function exposing the same signature as `std::io::Error::new`.
#[allow(dead_code)]
pub fn new<S: Into<String>>(kind: ErrorKind, error: S) -> Error {
Error {
kind,
error: error.into(),
}
}
}
/// The current version of `rbpf` only uses the [`Other`](ErrorKind::Other) variant
/// from the [std::io::ErrorKind] enum. If a dependency on other variants were
/// introduced in the future, this enum needs to be updated accordingly to maintain
/// compatibility with the real `ErrorKind`. The reason all available variants
/// aren't included in the first place is that [std::io::ErrorKind] exposes
/// 40 variants, and not all of them are meaningful under `no_std`.
#[derive(Debug)]
pub enum ErrorKind {
/// The no_std code only uses this variant.
#[allow(dead_code)]
Other,
}

View File

@ -0,0 +1,75 @@
use crate::{ebpf::STACK_SIZE, vec, Vec};
pub struct StackFrame {
return_address: u16,
saved_registers: [u64; 4],
sp: u16,
frame: Vec<u8>,
}
impl StackFrame {
/// Create a new stack frame
///
/// The stack frame is created with a capacity of `STACK_SIZE` == 512 bytes
pub fn new() -> Self {
Self {
sp: 0,
return_address: 0,
saved_registers: [0; 4],
frame: vec![0; STACK_SIZE],
}
}
/// Create a new stack frame with a given capacity
#[allow(unused)]
pub fn with_capacity(capacity: usize) -> Self {
Self {
sp: 0,
return_address: 0,
saved_registers: [0; 4],
frame: vec![0; capacity],
}
}
/// The capacity of the stack frame
pub fn len(&self) -> usize {
self.frame.len()
}
pub fn as_ptr(&self) -> *const u8 {
self.frame.as_ptr()
}
pub fn as_slice(&self) -> &[u8] {
self.frame.as_slice()
}
/// Save the callee-saved registers
pub fn save_registers(&mut self, regs: &[u64]) {
self.saved_registers.copy_from_slice(regs);
}
/// Get the callee-saved registers
pub fn get_registers(&self) -> [u64; 4] {
self.saved_registers
}
/// Save the return address
pub fn save_return_address(&mut self, address: u16) {
self.return_address = address;
}
/// Get the return address
pub fn get_return_address(&self) -> u16 {
self.return_address
}
/// Save the stack pointer
pub fn save_sp(&mut self, sp: u16) {
self.sp = sp;
}
/// Get the stack pointer
pub fn get_sp(&self) -> u16 {
self.sp
}
}

View File

@ -0,0 +1,386 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Derived from uBPF <https://github.com/iovisor/ubpf>
// Copyright 2015 Big Switch Networks, Inc
// (uBPF: safety checks, originally in C)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// (Translation to Rust)
// This “verifier” performs simple checks when the eBPF program is loaded into the VM (before it is
// interpreted or JIT-compiled). It has nothing to do with the much more elaborated verifier inside
// Linux kernel. There is no verification regarding the program flow control (should be a Direct
// Acyclic Graph) or the consistency for registers usage (the verifier of the kernel assigns types
// to the registers and is much stricter).
//
// On the other hand, rbpf is not expected to run in kernel space.
//
// Improving the verifier would be nice, but this is not trivial (and Linux kernel is under GPL
// license, so we cannot copy it).
//
// Contrary to the verifier of the Linux kernel, this one does not modify the bytecode at all.
use alloc::format;
use crate::{ebpf, Error, ErrorKind};
fn reject<S: AsRef<str>>(msg: S) -> Result<(), Error> {
let full_msg = format!("[Verifier] Error: {}", msg.as_ref());
Err(Error::new(ErrorKind::Other, full_msg))
}
fn check_prog_len(prog: &[u8]) -> Result<(), Error> {
if prog.len() % ebpf::INSN_SIZE != 0 {
reject(format!(
"eBPF program length must be a multiple of {:?} octets",
ebpf::INSN_SIZE
))?;
}
if prog.len() > ebpf::PROG_MAX_SIZE {
reject(format!(
"eBPF program length limited to {:?}, here {:?}",
ebpf::PROG_MAX_INSNS,
prog.len() / ebpf::INSN_SIZE
))?;
}
if prog.is_empty() {
reject("no program set, call set_program() to load one")?;
}
let last_opc = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1).opc;
if last_opc & ebpf::BPF_CLS_MASK != ebpf::BPF_JMP {
reject("program does not end with “EXIT” instruction")?;
}
Ok(())
}
fn check_imm_endian(insn: &ebpf::Insn, insn_ptr: usize) -> Result<(), Error> {
match insn.imm {
16 | 32 | 64 => Ok(()),
_ => reject(format!(
"unsupported argument for LE/BE (insn #{insn_ptr:?})"
)),
}
}
fn check_load_dw(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
// We know we can reach next insn since we enforce an EXIT insn at the end of program, while
// this function should be called only for LD_DW insn, that cannot be last in program.
let next_insn = ebpf::get_insn(prog, insn_ptr + 1);
if next_insn.opc != 0 {
reject(format!("incomplete LD_DW instruction (insn #{insn_ptr:?})"))?;
}
Ok(())
}
fn check_jmp_offset(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
let insn = ebpf::get_insn(prog, insn_ptr);
if insn.off == -1 {
reject(format!("infinite loop (insn #{insn_ptr:?})"))?;
}
let dst_insn_ptr = insn_ptr as isize + 1 + insn.off as isize;
if dst_insn_ptr < 0 || dst_insn_ptr as usize >= (prog.len() / ebpf::INSN_SIZE) {
reject(format!(
"jump out of code to #{dst_insn_ptr:?} (insn #{insn_ptr:?})"
))?;
}
let dst_insn = ebpf::get_insn(prog, dst_insn_ptr as usize);
if dst_insn.opc == 0 {
reject(format!(
"jump to middle of LD_DW at #{dst_insn_ptr:?} (insn #{insn_ptr:?})"
))?;
}
Ok(())
}
fn check_registers(insn: &ebpf::Insn, store: bool, insn_ptr: usize) -> Result<(), Error> {
if insn.src > 10 {
reject(format!("invalid source register (insn #{insn_ptr:?})"))?;
}
match (insn.dst, store) {
(0..=9, _) | (10, true) => Ok(()),
(10, false) => reject(format!(
"cannot write into register r10 (insn #{insn_ptr:?})"
)),
(_, _) => reject(format!("invalid destination register (insn #{insn_ptr:?})")),
}
}
pub fn check(prog: &[u8]) -> Result<(), Error> {
check_prog_len(prog)?;
let mut insn_ptr: usize = 0;
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
let insn = ebpf::get_insn(prog, insn_ptr);
let mut store = false;
match insn.opc {
// BPF_LD class
ebpf::LD_ABS_B => {}
ebpf::LD_ABS_H => {}
ebpf::LD_ABS_W => {}
ebpf::LD_ABS_DW => {}
ebpf::LD_IND_B => {}
ebpf::LD_IND_H => {}
ebpf::LD_IND_W => {}
ebpf::LD_IND_DW => {}
ebpf::LD_DW_IMM => {
store = true;
check_load_dw(prog, insn_ptr)?;
insn_ptr += 1;
}
// BPF_LDX class
ebpf::LD_B_REG => {}
ebpf::LD_H_REG => {}
ebpf::LD_W_REG => {}
ebpf::LD_DW_REG => {}
// BPF_ST class
ebpf::ST_B_IMM => store = true,
ebpf::ST_H_IMM => store = true,
ebpf::ST_W_IMM => store = true,
ebpf::ST_DW_IMM => store = true,
// BPF_STX class
ebpf::ST_B_REG => store = true,
ebpf::ST_H_REG => store = true,
ebpf::ST_W_REG => store = true,
ebpf::ST_DW_REG => store = true,
ebpf::ST_W_XADD => {
unimplemented!();
}
ebpf::ST_DW_XADD => {
unimplemented!();
}
// BPF_ALU class
ebpf::ADD32_IMM => {}
ebpf::ADD32_REG => {}
ebpf::SUB32_IMM => {}
ebpf::SUB32_REG => {}
ebpf::MUL32_IMM => {}
ebpf::MUL32_REG => {}
ebpf::DIV32_IMM => {}
ebpf::DIV32_REG => {}
ebpf::OR32_IMM => {}
ebpf::OR32_REG => {}
ebpf::AND32_IMM => {}
ebpf::AND32_REG => {}
ebpf::LSH32_IMM => {}
ebpf::LSH32_REG => {}
ebpf::RSH32_IMM => {}
ebpf::RSH32_REG => {}
ebpf::NEG32 => {}
ebpf::MOD32_IMM => {}
ebpf::MOD32_REG => {}
ebpf::XOR32_IMM => {}
ebpf::XOR32_REG => {}
ebpf::MOV32_IMM => {}
ebpf::MOV32_REG => {}
ebpf::ARSH32_IMM => {}
ebpf::ARSH32_REG => {}
ebpf::LE => {
check_imm_endian(&insn, insn_ptr)?;
}
ebpf::BE => {
check_imm_endian(&insn, insn_ptr)?;
}
// BPF_ALU64 class
ebpf::ADD64_IMM => {}
ebpf::ADD64_REG => {}
ebpf::SUB64_IMM => {}
ebpf::SUB64_REG => {}
ebpf::MUL64_IMM => {}
ebpf::MUL64_REG => {}
ebpf::DIV64_IMM => {}
ebpf::DIV64_REG => {}
ebpf::OR64_IMM => {}
ebpf::OR64_REG => {}
ebpf::AND64_IMM => {}
ebpf::AND64_REG => {}
ebpf::LSH64_IMM => {}
ebpf::LSH64_REG => {}
ebpf::RSH64_IMM => {}
ebpf::RSH64_REG => {}
ebpf::NEG64 => {}
ebpf::MOD64_IMM => {}
ebpf::MOD64_REG => {}
ebpf::XOR64_IMM => {}
ebpf::XOR64_REG => {}
ebpf::MOV64_IMM => {}
ebpf::MOV64_REG => {}
ebpf::ARSH64_IMM => {}
ebpf::ARSH64_REG => {}
// BPF_JMP class
ebpf::JA => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JEQ_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JEQ_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGT_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGT_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGE_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGE_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLT_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLT_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLE_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLE_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSET_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSET_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JNE_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JNE_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGT_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGT_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGE_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGE_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLT_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLT_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLE_IMM => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLE_REG => {
check_jmp_offset(prog, insn_ptr)?;
}
// BPF_JMP32 class
ebpf::JEQ_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JEQ_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGT_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGT_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGE_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JGE_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLT_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLT_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLE_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JLE_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSET_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSET_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JNE_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JNE_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGT_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGT_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGE_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSGE_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLT_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLT_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLE_IMM32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::JSLE_REG32 => {
check_jmp_offset(prog, insn_ptr)?;
}
ebpf::CALL => {}
ebpf::TAIL_CALL => {
unimplemented!()
}
ebpf::EXIT => {}
_ => {
reject(format!(
"unknown eBPF opcode {:#2x} (insn #{insn_ptr:?})",
insn.opc
))?;
}
}
check_registers(&insn, store, insn_ptr)?;
insn_ptr += 1;
}
// insn_ptr should now be equal to number of instructions.
if insn_ptr != prog.len() / ebpf::INSN_SIZE {
reject(format!("jumped out of code to #{insn_ptr:?}"))?;
}
Ok(())
}

View File

@ -0,0 +1,655 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 Rich Lane <lanerl@gmail.com>
#![allow(clippy::unreadable_literal)]
extern crate rbpf;
mod common;
use common::{TCP_SACK_ASM, TCP_SACK_BIN};
use rbpf::{assembler::assemble, ebpf};
fn asm(src: &str) -> Result<Vec<ebpf::Insn>, String> {
Ok(ebpf::to_insn_vec(&(assemble(src))?))
}
fn insn(opc: u8, dst: u8, src: u8, off: i16, imm: i32) -> ebpf::Insn {
ebpf::Insn {
opc,
dst,
src,
off,
imm,
}
}
#[test]
fn test_empty() {
assert_eq!(asm(""), Ok(vec![]));
}
// Example for InstructionType::NoOperand.
#[test]
fn test_exit() {
assert_eq!(asm("exit"), Ok(vec![insn(ebpf::EXIT, 0, 0, 0, 0)]));
}
// Example for InstructionType::AluBinary.
#[test]
fn test_add64() {
assert_eq!(
asm("add64 r1, r3"),
Ok(vec![insn(ebpf::ADD64_REG, 1, 3, 0, 0)])
);
assert_eq!(
asm("add64 r1, 5"),
Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 5)])
);
}
// Example for InstructionType::AluUnary.
#[test]
fn test_neg64() {
assert_eq!(asm("neg64 r1"), Ok(vec![insn(ebpf::NEG64, 1, 0, 0, 0)]));
}
// Example for InstructionType::LoadReg.
#[test]
fn test_ldxw() {
assert_eq!(
asm("ldxw r1, [r2+5]"),
Ok(vec![insn(ebpf::LD_W_REG, 1, 2, 5, 0)])
);
}
// Example for InstructionType::StoreImm.
#[test]
fn test_stw() {
assert_eq!(
asm("stw [r2+5], 7"),
Ok(vec![insn(ebpf::ST_W_IMM, 2, 0, 5, 7)])
);
}
// Example for InstructionType::StoreReg.
#[test]
fn test_stxw() {
assert_eq!(
asm("stxw [r2+5], r8"),
Ok(vec![insn(ebpf::ST_W_REG, 2, 8, 5, 0)])
);
}
// Example for InstructionType::JumpUnconditional.
#[test]
fn test_ja() {
assert_eq!(asm("ja +8"), Ok(vec![insn(ebpf::JA, 0, 0, 8, 0)]));
assert_eq!(asm("ja -3"), Ok(vec![insn(ebpf::JA, 0, 0, -3, 0)]));
}
// Example for InstructionType::JumpConditional.
#[test]
fn test_jeq() {
assert_eq!(
asm("jeq r1, 4, +8"),
Ok(vec![insn(ebpf::JEQ_IMM, 1, 0, 8, 4)])
);
assert_eq!(
asm("jeq r1, r3, +8"),
Ok(vec![insn(ebpf::JEQ_REG, 1, 3, 8, 0)])
);
}
// Example for InstructionType::Call.
#[test]
fn test_call() {
assert_eq!(asm("call 300"), Ok(vec![insn(ebpf::CALL, 0, 0, 0, 300)]));
}
// Example for InstructionType::Endian.
#[test]
fn test_be32() {
assert_eq!(asm("be32 r1"), Ok(vec![insn(ebpf::BE, 1, 0, 0, 32)]));
}
// Example for InstructionType::LoadImm.
#[test]
fn test_lddw() {
assert_eq!(
asm("lddw r1, 0x1234abcd5678eeff"),
Ok(vec![
insn(ebpf::LD_DW_IMM, 1, 0, 0, 0x5678eeff),
insn(0, 0, 0, 0, 0x1234abcd)
])
);
assert_eq!(
asm("lddw r1, 0xff11ee22dd33cc44"),
Ok(vec![
insn(ebpf::LD_DW_IMM, 1, 0, 0, 0xdd33cc44u32 as i32),
insn(0, 0, 0, 0, 0xff11ee22u32 as i32)
])
);
}
// Example for InstructionType::LoadAbs.
#[test]
fn test_ldabsw() {
assert_eq!(asm("ldabsw 1"), Ok(vec![insn(ebpf::LD_ABS_W, 0, 0, 0, 1)]));
}
// Example for InstructionType::LoadInd.
#[test]
fn test_ldindw() {
assert_eq!(
asm("ldindw r1, 2"),
Ok(vec![insn(ebpf::LD_IND_W, 0, 1, 0, 2)])
);
}
// Example for InstructionType::LoadReg.
#[test]
fn test_ldxdw() {
assert_eq!(
asm("ldxdw r1, [r2+3]"),
Ok(vec![insn(ebpf::LD_DW_REG, 1, 2, 3, 0)])
);
}
// Example for InstructionType::StoreImm.
#[test]
fn test_sth() {
assert_eq!(
asm("sth [r1+2], 3"),
Ok(vec![insn(ebpf::ST_H_IMM, 1, 0, 2, 3)])
);
}
// Example for InstructionType::StoreReg.
#[test]
fn test_stxh() {
assert_eq!(
asm("stxh [r1+2], r3"),
Ok(vec![insn(ebpf::ST_H_REG, 1, 3, 2, 0)])
);
}
// Test all supported AluBinary mnemonics.
#[test]
fn test_alu_binary() {
assert_eq!(
asm("add r1, r2
sub r1, r2
mul r1, r2
div r1, r2
or r1, r2
and r1, r2
lsh r1, r2
rsh r1, r2
mod r1, r2
xor r1, r2
mov r1, r2
arsh r1, r2"),
Ok(vec![
insn(ebpf::ADD64_REG, 1, 2, 0, 0),
insn(ebpf::SUB64_REG, 1, 2, 0, 0),
insn(ebpf::MUL64_REG, 1, 2, 0, 0),
insn(ebpf::DIV64_REG, 1, 2, 0, 0),
insn(ebpf::OR64_REG, 1, 2, 0, 0),
insn(ebpf::AND64_REG, 1, 2, 0, 0),
insn(ebpf::LSH64_REG, 1, 2, 0, 0),
insn(ebpf::RSH64_REG, 1, 2, 0, 0),
insn(ebpf::MOD64_REG, 1, 2, 0, 0),
insn(ebpf::XOR64_REG, 1, 2, 0, 0),
insn(ebpf::MOV64_REG, 1, 2, 0, 0),
insn(ebpf::ARSH64_REG, 1, 2, 0, 0)
])
);
assert_eq!(
asm("add r1, 2
sub r1, 2
mul r1, 2
div r1, 2
or r1, 2
and r1, 2
lsh r1, 2
rsh r1, 2
mod r1, 2
xor r1, 2
mov r1, 2
arsh r1, 2"),
Ok(vec![
insn(ebpf::ADD64_IMM, 1, 0, 0, 2),
insn(ebpf::SUB64_IMM, 1, 0, 0, 2),
insn(ebpf::MUL64_IMM, 1, 0, 0, 2),
insn(ebpf::DIV64_IMM, 1, 0, 0, 2),
insn(ebpf::OR64_IMM, 1, 0, 0, 2),
insn(ebpf::AND64_IMM, 1, 0, 0, 2),
insn(ebpf::LSH64_IMM, 1, 0, 0, 2),
insn(ebpf::RSH64_IMM, 1, 0, 0, 2),
insn(ebpf::MOD64_IMM, 1, 0, 0, 2),
insn(ebpf::XOR64_IMM, 1, 0, 0, 2),
insn(ebpf::MOV64_IMM, 1, 0, 0, 2),
insn(ebpf::ARSH64_IMM, 1, 0, 0, 2)
])
);
assert_eq!(
asm("add64 r1, r2
sub64 r1, r2
mul64 r1, r2
div64 r1, r2
or64 r1, r2
and64 r1, r2
lsh64 r1, r2
rsh64 r1, r2
mod64 r1, r2
xor64 r1, r2
mov64 r1, r2
arsh64 r1, r2"),
Ok(vec![
insn(ebpf::ADD64_REG, 1, 2, 0, 0),
insn(ebpf::SUB64_REG, 1, 2, 0, 0),
insn(ebpf::MUL64_REG, 1, 2, 0, 0),
insn(ebpf::DIV64_REG, 1, 2, 0, 0),
insn(ebpf::OR64_REG, 1, 2, 0, 0),
insn(ebpf::AND64_REG, 1, 2, 0, 0),
insn(ebpf::LSH64_REG, 1, 2, 0, 0),
insn(ebpf::RSH64_REG, 1, 2, 0, 0),
insn(ebpf::MOD64_REG, 1, 2, 0, 0),
insn(ebpf::XOR64_REG, 1, 2, 0, 0),
insn(ebpf::MOV64_REG, 1, 2, 0, 0),
insn(ebpf::ARSH64_REG, 1, 2, 0, 0)
])
);
assert_eq!(
asm("add64 r1, 2
sub64 r1, 2
mul64 r1, 2
div64 r1, 2
or64 r1, 2
and64 r1, 2
lsh64 r1, 2
rsh64 r1, 2
mod64 r1, 2
xor64 r1, 2
mov64 r1, 2
arsh64 r1, 2"),
Ok(vec![
insn(ebpf::ADD64_IMM, 1, 0, 0, 2),
insn(ebpf::SUB64_IMM, 1, 0, 0, 2),
insn(ebpf::MUL64_IMM, 1, 0, 0, 2),
insn(ebpf::DIV64_IMM, 1, 0, 0, 2),
insn(ebpf::OR64_IMM, 1, 0, 0, 2),
insn(ebpf::AND64_IMM, 1, 0, 0, 2),
insn(ebpf::LSH64_IMM, 1, 0, 0, 2),
insn(ebpf::RSH64_IMM, 1, 0, 0, 2),
insn(ebpf::MOD64_IMM, 1, 0, 0, 2),
insn(ebpf::XOR64_IMM, 1, 0, 0, 2),
insn(ebpf::MOV64_IMM, 1, 0, 0, 2),
insn(ebpf::ARSH64_IMM, 1, 0, 0, 2)
])
);
assert_eq!(
asm("add32 r1, r2
sub32 r1, r2
mul32 r1, r2
div32 r1, r2
or32 r1, r2
and32 r1, r2
lsh32 r1, r2
rsh32 r1, r2
mod32 r1, r2
xor32 r1, r2
mov32 r1, r2
arsh32 r1, r2"),
Ok(vec![
insn(ebpf::ADD32_REG, 1, 2, 0, 0),
insn(ebpf::SUB32_REG, 1, 2, 0, 0),
insn(ebpf::MUL32_REG, 1, 2, 0, 0),
insn(ebpf::DIV32_REG, 1, 2, 0, 0),
insn(ebpf::OR32_REG, 1, 2, 0, 0),
insn(ebpf::AND32_REG, 1, 2, 0, 0),
insn(ebpf::LSH32_REG, 1, 2, 0, 0),
insn(ebpf::RSH32_REG, 1, 2, 0, 0),
insn(ebpf::MOD32_REG, 1, 2, 0, 0),
insn(ebpf::XOR32_REG, 1, 2, 0, 0),
insn(ebpf::MOV32_REG, 1, 2, 0, 0),
insn(ebpf::ARSH32_REG, 1, 2, 0, 0)
])
);
assert_eq!(
asm("add32 r1, 2
sub32 r1, 2
mul32 r1, 2
div32 r1, 2
or32 r1, 2
and32 r1, 2
lsh32 r1, 2
rsh32 r1, 2
mod32 r1, 2
xor32 r1, 2
mov32 r1, 2
arsh32 r1, 2"),
Ok(vec![
insn(ebpf::ADD32_IMM, 1, 0, 0, 2),
insn(ebpf::SUB32_IMM, 1, 0, 0, 2),
insn(ebpf::MUL32_IMM, 1, 0, 0, 2),
insn(ebpf::DIV32_IMM, 1, 0, 0, 2),
insn(ebpf::OR32_IMM, 1, 0, 0, 2),
insn(ebpf::AND32_IMM, 1, 0, 0, 2),
insn(ebpf::LSH32_IMM, 1, 0, 0, 2),
insn(ebpf::RSH32_IMM, 1, 0, 0, 2),
insn(ebpf::MOD32_IMM, 1, 0, 0, 2),
insn(ebpf::XOR32_IMM, 1, 0, 0, 2),
insn(ebpf::MOV32_IMM, 1, 0, 0, 2),
insn(ebpf::ARSH32_IMM, 1, 0, 0, 2)
])
);
}
// Test all supported AluUnary mnemonics.
#[test]
fn test_alu_unary() {
assert_eq!(
asm("neg r1
neg64 r1
neg32 r1"),
Ok(vec![
insn(ebpf::NEG64, 1, 0, 0, 0),
insn(ebpf::NEG64, 1, 0, 0, 0),
insn(ebpf::NEG32, 1, 0, 0, 0)
])
);
}
// Test all supported LoadAbs mnemonics.
#[test]
fn test_load_abs() {
assert_eq!(
asm("ldabsw 1
ldabsh 1
ldabsb 1
ldabsdw 1"),
Ok(vec![
insn(ebpf::LD_ABS_W, 0, 0, 0, 1),
insn(ebpf::LD_ABS_H, 0, 0, 0, 1),
insn(ebpf::LD_ABS_B, 0, 0, 0, 1),
insn(ebpf::LD_ABS_DW, 0, 0, 0, 1)
])
);
}
// Test all supported LoadInd mnemonics.
#[test]
fn test_load_ind() {
assert_eq!(
asm("ldindw r1, 2
ldindh r1, 2
ldindb r1, 2
ldinddw r1, 2"),
Ok(vec![
insn(ebpf::LD_IND_W, 0, 1, 0, 2),
insn(ebpf::LD_IND_H, 0, 1, 0, 2),
insn(ebpf::LD_IND_B, 0, 1, 0, 2),
insn(ebpf::LD_IND_DW, 0, 1, 0, 2)
])
);
}
// Test all supported LoadReg mnemonics.
#[test]
fn test_load_reg() {
assert_eq!(
asm("ldxw r1, [r2+3]
ldxh r1, [r2+3]
ldxb r1, [r2+3]
ldxdw r1, [r2+3]"),
Ok(vec![
insn(ebpf::LD_W_REG, 1, 2, 3, 0),
insn(ebpf::LD_H_REG, 1, 2, 3, 0),
insn(ebpf::LD_B_REG, 1, 2, 3, 0),
insn(ebpf::LD_DW_REG, 1, 2, 3, 0)
])
);
}
// Test all supported StoreImm mnemonics.
#[test]
fn test_store_imm() {
assert_eq!(
asm("stw [r1+2], 3
sth [r1+2], 3
stb [r1+2], 3
stdw [r1+2], 3"),
Ok(vec![
insn(ebpf::ST_W_IMM, 1, 0, 2, 3),
insn(ebpf::ST_H_IMM, 1, 0, 2, 3),
insn(ebpf::ST_B_IMM, 1, 0, 2, 3),
insn(ebpf::ST_DW_IMM, 1, 0, 2, 3)
])
);
}
// Test all supported StoreReg mnemonics.
#[test]
fn test_store_reg() {
assert_eq!(
asm("stxw [r1+2], r3
stxh [r1+2], r3
stxb [r1+2], r3
stxdw [r1+2], r3"),
Ok(vec![
insn(ebpf::ST_W_REG, 1, 3, 2, 0),
insn(ebpf::ST_H_REG, 1, 3, 2, 0),
insn(ebpf::ST_B_REG, 1, 3, 2, 0),
insn(ebpf::ST_DW_REG, 1, 3, 2, 0)
])
);
}
// Test all supported JumpConditional mnemonics.
#[test]
fn test_jump_conditional() {
assert_eq!(
asm("jeq r1, r2, +3
jgt r1, r2, +3
jge r1, r2, +3
jlt r1, r2, +3
jle r1, r2, +3
jset r1, r2, +3
jne r1, r2, +3
jsgt r1, r2, +3
jsge r1, r2, +3
jslt r1, r2, +3
jsle r1, r2, +3"),
Ok(vec![
insn(ebpf::JEQ_REG, 1, 2, 3, 0),
insn(ebpf::JGT_REG, 1, 2, 3, 0),
insn(ebpf::JGE_REG, 1, 2, 3, 0),
insn(ebpf::JLT_REG, 1, 2, 3, 0),
insn(ebpf::JLE_REG, 1, 2, 3, 0),
insn(ebpf::JSET_REG, 1, 2, 3, 0),
insn(ebpf::JNE_REG, 1, 2, 3, 0),
insn(ebpf::JSGT_REG, 1, 2, 3, 0),
insn(ebpf::JSGE_REG, 1, 2, 3, 0),
insn(ebpf::JSLT_REG, 1, 2, 3, 0),
insn(ebpf::JSLE_REG, 1, 2, 3, 0)
])
);
assert_eq!(
asm("jeq r1, 2, +3
jgt r1, 2, +3
jge r1, 2, +3
jlt r1, 2, +3
jle r1, 2, +3
jset r1, 2, +3
jne r1, 2, +3
jsgt r1, 2, +3
jsge r1, 2, +3
jslt r1, 2, +3
jsle r1, 2, +3"),
Ok(vec![
insn(ebpf::JEQ_IMM, 1, 0, 3, 2),
insn(ebpf::JGT_IMM, 1, 0, 3, 2),
insn(ebpf::JGE_IMM, 1, 0, 3, 2),
insn(ebpf::JLT_IMM, 1, 0, 3, 2),
insn(ebpf::JLE_IMM, 1, 0, 3, 2),
insn(ebpf::JSET_IMM, 1, 0, 3, 2),
insn(ebpf::JNE_IMM, 1, 0, 3, 2),
insn(ebpf::JSGT_IMM, 1, 0, 3, 2),
insn(ebpf::JSGE_IMM, 1, 0, 3, 2),
insn(ebpf::JSLT_IMM, 1, 0, 3, 2),
insn(ebpf::JSLE_IMM, 1, 0, 3, 2)
])
);
assert_eq!(
asm("jeq32 r1, r2, +3
jgt32 r1, r2, +3
jge32 r1, r2, +3
jlt32 r1, r2, +3
jle32 r1, r2, +3
jset32 r1, r2, +3
jne32 r1, r2, +3
jsgt32 r1, r2, +3
jsge32 r1, r2, +3
jslt32 r1, r2, +3
jsle32 r1, r2, +3"),
Ok(vec![
insn(ebpf::JEQ_REG32, 1, 2, 3, 0),
insn(ebpf::JGT_REG32, 1, 2, 3, 0),
insn(ebpf::JGE_REG32, 1, 2, 3, 0),
insn(ebpf::JLT_REG32, 1, 2, 3, 0),
insn(ebpf::JLE_REG32, 1, 2, 3, 0),
insn(ebpf::JSET_REG32, 1, 2, 3, 0),
insn(ebpf::JNE_REG32, 1, 2, 3, 0),
insn(ebpf::JSGT_REG32, 1, 2, 3, 0),
insn(ebpf::JSGE_REG32, 1, 2, 3, 0),
insn(ebpf::JSLT_REG32, 1, 2, 3, 0),
insn(ebpf::JSLE_REG32, 1, 2, 3, 0)
])
);
assert_eq!(
asm("jeq32 r1, 2, +3
jgt32 r1, 2, +3
jge32 r1, 2, +3
jlt32 r1, 2, +3
jle32 r1, 2, +3
jset32 r1, 2, +3
jne32 r1, 2, +3
jsgt32 r1, 2, +3
jsge32 r1, 2, +3
jslt32 r1, 2, +3
jsle32 r1, 2, +3"),
Ok(vec![
insn(ebpf::JEQ_IMM32, 1, 0, 3, 2),
insn(ebpf::JGT_IMM32, 1, 0, 3, 2),
insn(ebpf::JGE_IMM32, 1, 0, 3, 2),
insn(ebpf::JLT_IMM32, 1, 0, 3, 2),
insn(ebpf::JLE_IMM32, 1, 0, 3, 2),
insn(ebpf::JSET_IMM32, 1, 0, 3, 2),
insn(ebpf::JNE_IMM32, 1, 0, 3, 2),
insn(ebpf::JSGT_IMM32, 1, 0, 3, 2),
insn(ebpf::JSGE_IMM32, 1, 0, 3, 2),
insn(ebpf::JSLT_IMM32, 1, 0, 3, 2),
insn(ebpf::JSLE_IMM32, 1, 0, 3, 2)
])
);
}
// Test all supported Endian mnemonics.
#[test]
fn test_endian() {
assert_eq!(
asm("be16 r1
be32 r1
be64 r1
le16 r1
le32 r1
le64 r1"),
Ok(vec![
insn(ebpf::BE, 1, 0, 0, 16),
insn(ebpf::BE, 1, 0, 0, 32),
insn(ebpf::BE, 1, 0, 0, 64),
insn(ebpf::LE, 1, 0, 0, 16),
insn(ebpf::LE, 1, 0, 0, 32),
insn(ebpf::LE, 1, 0, 0, 64)
])
);
}
#[test]
fn test_large_immediate() {
assert_eq!(
asm("add64 r1, 2147483647"),
Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 2147483647)])
);
assert_eq!(
asm("add64 r1, -2147483648"),
Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, -2147483648)])
);
}
#[test]
fn test_tcp_sack() {
assert_eq!(assemble(TCP_SACK_ASM), Ok(TCP_SACK_BIN.to_vec()));
}
#[test]
fn test_error_invalid_instruction() {
assert_eq!(asm("abcd"), Err("Invalid instruction \"abcd\"".to_string()));
}
#[test]
fn test_error_unexpected_operands() {
assert_eq!(
asm("add 1, 2"),
Err("Failed to encode add: Unexpected operands: [Integer(1), Integer(2)]".to_string())
);
}
#[test]
fn test_error_too_many_operands() {
assert_eq!(
asm("add 1, 2, 3, 4"),
Err("Failed to encode add: Too many operands".to_string())
);
}
#[test]
fn test_error_operands_out_of_range() {
assert_eq!(
asm("add r16, r2"),
Err("Failed to encode add: Invalid destination register 16".to_string())
);
assert_eq!(
asm("add r1, r16"),
Err("Failed to encode add: Invalid source register 16".to_string())
);
assert_eq!(
asm("ja -32769"),
Err("Failed to encode ja: Invalid offset -32769".to_string())
);
assert_eq!(
asm("ja 32768"),
Err("Failed to encode ja: Invalid offset 32768".to_string())
);
assert_eq!(
asm("add r1, 4294967296"),
Err("Failed to encode add: Invalid immediate 4294967296".to_string())
);
assert_eq!(
asm("add r1, 2147483648"),
Err("Failed to encode add: Invalid immediate 2147483648".to_string())
);
assert_eq!(
asm("add r1, -2147483649"),
Err("Failed to encode add: Invalid immediate -2147483649".to_string())
);
}

View File

@ -0,0 +1,97 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Converted from the tests for uBPF <https://github.com/iovisor/ubpf>
// Copyright 2015 Big Switch Networks, Inc
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// Assembly code and data for tcp_sack testcases.
#[allow(dead_code)]
pub const TCP_SACK_ASM: &str = "
ldxb r2, [r1+12]
ldxb r3, [r1+13]
lsh r3, 0x8
or r3, r2
mov r0, 0x0
jne r3, 0x8, +37
ldxb r2, [r1+23]
jne r2, 0x6, +35
ldxb r2, [r1+14]
add r1, 0xe
and r2, 0xf
lsh r2, 0x2
add r1, r2
mov r0, 0x0
ldxh r4, [r1+12]
add r1, 0x14
rsh r4, 0x2
and r4, 0x3c
mov r2, r4
add r2, -20
mov r5, 0x15
mov r3, 0x0
jgt r5, r4, +20
mov r5, r3
lsh r5, 0x20
arsh r5, 0x20
mov r4, r1
add r4, r5
ldxb r5, [r4]
jeq r5, 0x1, +4
jeq r5, 0x0, +12
mov r6, r3
jeq r5, 0x5, +9
ja +2
add r3, 0x1
mov r6, r3
ldxb r3, [r4+1]
add r3, r6
lsh r3, 0x20
arsh r3, 0x20
jsgt r2, r3, -18
ja +1
mov r0, 0x1
exit";
#[allow(dead_code)]
pub const TCP_SACK_BIN: [u8; 352] = [
0x71, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x13, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x25, 0x00, 0x08, 0x00, 0x00, 0x00,
0x71, 0x12, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x23, 0x00, 0x06, 0x00, 0x00, 0x00,
0x71, 0x12, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
0x57, 0x02, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x67, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x0f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x69, 0x14, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x77, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x57, 0x04, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
0xbf, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00, 0x00, 0xec, 0xff, 0xff, 0xff,
0xb7, 0x05, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2d, 0x45, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xc7, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0xbf, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x71, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x05, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
0x15, 0x05, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x15, 0x05, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x71, 0x43, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x03, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xc7, 0x03, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x6d, 0x32, 0xee, 0xff, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
#[allow(dead_code)]
pub const TCP_SACK_MATCH: [u8; 78] = [
0x00, 0x26, 0x62, 0x2f, 0x47, 0x87, 0x00, 0x1d, 0x60, 0xb3, 0x01, 0x84, 0x08, 0x00, 0x45, 0x00,
0x00, 0x40, 0xa8, 0xde, 0x40, 0x00, 0x40, 0x06, 0x9d, 0x58, 0xc0, 0xa8, 0x01, 0x03, 0x3f, 0x74,
0xf3, 0x61, 0xe5, 0xc0, 0x00, 0x50, 0xe5, 0x94, 0x3f, 0x77, 0xa3, 0xc4, 0xc4, 0x80, 0xb0, 0x10,
0x01, 0x3e, 0x34, 0xb6, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x00, 0x17, 0x95, 0x6f, 0x8d, 0x9d,
0x9e, 0x27, 0x01, 0x01, 0x05, 0x0a, 0xa3, 0xc4, 0xca, 0x28, 0xa3, 0xc4, 0xcf, 0xd0,
];
#[allow(dead_code)]
pub const TCP_SACK_NOMATCH: [u8; 66] = [
0x00, 0x26, 0x62, 0x2f, 0x47, 0x87, 0x00, 0x1d, 0x60, 0xb3, 0x01, 0x84, 0x08, 0x00, 0x45, 0x00,
0x00, 0x40, 0xa8, 0xde, 0x40, 0x00, 0x40, 0x06, 0x9d, 0x58, 0xc0, 0xa8, 0x01, 0x03, 0x3f, 0x74,
0xf3, 0x61, 0xe5, 0xc0, 0x00, 0x50, 0xe5, 0x94, 0x3f, 0x77, 0xa3, 0xc4, 0xc4, 0x80, 0x80, 0x10,
0x01, 0x3e, 0x34, 0xb6, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x00, 0x17, 0x95, 0x6f, 0x8d, 0x9d,
0x9e, 0x27,
];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,377 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2017 Jan-Erik Rediger <badboy@archlinux.us>
//
// Adopted from tests in `tests/assembler.rs`
extern crate rbpf;
mod common;
use rbpf::{assembler::assemble, disassembler::to_insn_vec};
// Using a macro to keep actual line numbers in failure output
macro_rules! disasm {
($src:expr) => {{
let src = $src;
let asm = assemble(src).expect("Can't assemble from string");
let insn = to_insn_vec(&asm);
let reasm = insn
.into_iter()
.map(|ins| ins.desc)
.collect::<Vec<_>>()
.join("\n");
assert_eq!(src, reasm);
}};
}
#[test]
fn test_empty() {
disasm!("");
}
// Example for InstructionType::NoOperand.
#[test]
fn test_exit() {
disasm!("exit");
}
// Example for InstructionType::AluBinary.
#[test]
fn test_add64() {
disasm!("add64 r1, r3");
disasm!("add64 r1, 0x5");
}
// Example for InstructionType::AluUnary.
#[test]
fn test_neg64() {
disasm!("neg64 r1");
}
// Example for InstructionType::LoadReg.
#[test]
fn test_ldxw() {
disasm!("ldxw r1, [r2+0x5]");
}
// Example for InstructionType::StoreImm.
#[test]
fn test_stw() {
disasm!("stw [r2+0x5], 0x7");
}
// Example for InstructionType::StoreReg.
#[test]
fn test_stxw() {
disasm!("stxw [r2+0x5], r8");
}
// Example for InstructionType::JumpUnconditional.
#[test]
fn test_ja() {
disasm!("ja +0x8");
}
// Example for InstructionType::JumpConditional.
#[test]
fn test_jeq() {
disasm!("jeq r1, 0x4, +0x8");
disasm!("jeq r1, r3, +0x8");
}
// Example for InstructionType::Call.
#[test]
fn test_call() {
disasm!("call 0x3");
}
// Example for InstructionType::Endian.
#[test]
fn test_be32() {
disasm!("be32 r1");
}
// Example for InstructionType::LoadImm.
#[test]
fn test_lddw() {
disasm!("lddw r1, 0x1234abcd5678eeff");
disasm!("lddw r1, 0xff11ee22dd33cc44");
}
// Example for InstructionType::LoadAbs.
#[test]
fn test_ldabsw() {
disasm!("ldabsw 0x1");
}
// Example for InstructionType::LoadInd.
#[test]
fn test_ldindw() {
disasm!("ldindw r1, 0x2");
}
// Example for InstructionType::LoadReg.
#[test]
fn test_ldxdw() {
disasm!("ldxdw r1, [r2+0x3]");
}
// Example for InstructionType::StoreImm.
#[test]
fn test_sth() {
disasm!("sth [r1+0x2], 0x3");
}
// Example for InstructionType::StoreReg.
#[test]
fn test_stxh() {
disasm!("stxh [r1+0x2], r3");
}
// Test all supported AluBinary mnemonics.
#[test]
fn test_alu_binary() {
disasm!(
"add64 r1, r2
sub64 r1, r2
mul64 r1, r2
div64 r1, r2
or64 r1, r2
and64 r1, r2
lsh64 r1, r2
rsh64 r1, r2
mod64 r1, r2
xor64 r1, r2
mov64 r1, r2
arsh64 r1, r2"
);
disasm!(
"add64 r1, 0x2
sub64 r1, 0x2
mul64 r1, 0x2
div64 r1, 0x2
or64 r1, 0x2
and64 r1, 0x2
lsh64 r1, 0x2
rsh64 r1, 0x2
mod64 r1, 0x2
xor64 r1, 0x2
mov64 r1, 0x2
arsh64 r1, 0x2"
);
disasm!(
"add32 r1, r2
sub32 r1, r2
mul32 r1, r2
div32 r1, r2
or32 r1, r2
and32 r1, r2
lsh32 r1, r2
rsh32 r1, r2
mod32 r1, r2
xor32 r1, r2
mov32 r1, r2
arsh32 r1, r2"
);
disasm!(
"add32 r1, 0x2
sub32 r1, 0x2
mul32 r1, 0x2
div32 r1, 0x2
or32 r1, 0x2
and32 r1, 0x2
lsh32 r1, 0x2
rsh32 r1, 0x2
mod32 r1, 0x2
xor32 r1, 0x2
mov32 r1, 0x2
arsh32 r1, 0x2"
);
}
// Test all supported AluUnary mnemonics.
#[test]
fn test_alu_unary() {
disasm!(
"neg64 r1
neg32 r1"
);
}
// Test all supported LoadAbs mnemonics.
#[test]
fn test_load_abs() {
disasm!(
"ldabsw 0x1
ldabsh 0x1
ldabsb 0x1
ldabsdw 0x1"
);
}
// Test all supported LoadInd mnemonics.
#[test]
fn test_load_ind() {
disasm!(
"ldindw r1, 0x2
ldindh r1, 0x2
ldindb r1, 0x2
ldinddw r1, 0x2"
);
}
// Test all supported LoadReg mnemonics.
#[test]
fn test_load_reg() {
disasm!(
r"ldxw r1, [r2+0x3]
ldxh r1, [r2+0x3]
ldxb r1, [r2+0x3]
ldxdw r1, [r2+0x3]"
);
}
// Test all supported StoreImm mnemonics.
#[test]
fn test_store_imm() {
disasm!(
"stw [r1+0x2], 0x3
sth [r1+0x2], 0x3
stb [r1+0x2], 0x3
stdw [r1+0x2], 0x3"
);
}
// Test all supported StoreReg mnemonics.
#[test]
fn test_store_reg() {
disasm!(
"stxw [r1+0x2], r3
stxh [r1+0x2], r3
stxb [r1+0x2], r3
stxdw [r1+0x2], r3"
);
}
// Test all supported JumpConditional mnemonics.
#[test]
fn test_jump_conditional() {
disasm!(
"jeq r1, r2, +0x3
jgt r1, r2, +0x3
jge r1, r2, +0x3
jlt r1, r2, +0x3
jle r1, r2, +0x3
jset r1, r2, +0x3
jne r1, r2, +0x3
jsgt r1, r2, +0x3
jsge r1, r2, -0x3
jslt r1, r2, +0x3
jsle r1, r2, -0x3"
);
disasm!(
"jeq r1, 0x2, +0x3
jgt r1, 0x2, +0x3
jge r1, 0x2, +0x3
jlt r1, 0x2, +0x3
jle r1, 0x2, +0x3
jset r1, 0x2, +0x3
jne r1, 0x2, +0x3
jsgt r1, 0x2, +0x3
jsge r1, 0x2, -0x3
jslt r1, 0x2, +0x3
jsle r1, 0x2, -0x3"
);
disasm!(
"jeq32 r1, r2, +0x3
jgt32 r1, r2, +0x3
jge32 r1, r2, +0x3
jlt32 r1, r2, +0x3
jle32 r1, r2, +0x3
jset32 r1, r2, +0x3
jne32 r1, r2, +0x3
jsgt32 r1, r2, +0x3
jsge32 r1, r2, -0x3
jslt32 r1, r2, +0x3
jsle32 r1, r2, -0x3"
);
disasm!(
"jeq32 r1, 0x2, +0x3
jgt32 r1, 0x2, +0x3
jge32 r1, 0x2, +0x3
jlt32 r1, 0x2, +0x3
jle32 r1, 0x2, +0x3
jset32 r1, 0x2, +0x3
jne32 r1, 0x2, +0x3
jsgt32 r1, 0x2, +0x3
jsge32 r1, 0x2, -0x3
jslt32 r1, 0x2, +0x3
jsle32 r1, 0x2, -0x3"
);
}
// Test all supported Endian mnemonics.
#[test]
fn test_endian() {
disasm!(
"be16 r1
be32 r1
be64 r1
le16 r1
le32 r1
le64 r1"
);
}
#[test]
fn test_large_immediate() {
disasm!("add64 r1, 0x7fffffff");
disasm!("add64 r1, 0x7fffffff");
}
// Non-regression tests for overflow when trying to negate offset 0x8000i16.
#[test]
fn test_offset_overflow() {
let insns = [
0x62, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // stw
0x6a, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // sth
0x72, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // stb
0x7a, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // stdw
0x61, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxw
0x69, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxh
0x71, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxb
0x79, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxdw
0x15, 0x01, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, // jeq (imm)
0x1d, 0x21, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // jeq (reg)
0x16, 0x01, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, // jeq32 (imm)
0x1e, 0x21, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // jeq32 (reg)
];
let expected_output = "stw [r1-0x8000], 0x1
sth [r1-0x8000], 0x1
stb [r1-0x8000], 0x1
stdw [r1-0x8000], 0x1
ldxw r1, [r0-0x8000]
ldxh r1, [r0-0x8000]
ldxb r1, [r0-0x8000]
ldxdw r1, [r0-0x8000]
jeq r1, 0x2, -0x8000
jeq r1, r2, -0x8000
jeq32 r1, 0x2, -0x8000
jeq32 r1, r2, -0x8000";
let prog = to_insn_vec(&insns);
let asm = prog
.into_iter()
.map(|ins| ins.desc)
.collect::<Vec<_>>()
.join("\n");
assert_eq!(asm, expected_output);
}

View File

@ -0,0 +1,571 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// There are unused mut warnings due to unsafe code.
#![allow(unused_mut)]
#![allow(clippy::unreadable_literal)]
// This crate would be needed to load bytecode from a BPF-compiled object file. Since the crate
// is not used anywhere else in the library, it is deactivated: we do not want to load and compile
// it just for the tests. If you want to use it, do not forget to add the following
// dependency to your Cargo.toml file:
//
// ---
// elf = "0.0.10"
// ---
//
// extern crate elf;
// use std::path::PathBuf;
extern crate rbpf;
#[cfg(feature = "std")]
use rbpf::helpers;
use rbpf::{assembler::assemble, Error, ErrorKind};
// The following two examples have been compiled from C with the following command:
//
// ```bash
// clang -O2 -emit-llvm -c <file.c> -o - | llc -march=bpf -filetype=obj -o <file.o>
// ```
//
// The C source code was the following:
//
// ```c
// #include <linux/ip.h>
// #include <linux/in.h>
// #include <linux/tcp.h>
// #include <linux/bpf.h>
//
// #define ETH_ALEN 6
// #define ETH_P_IP 0x0008 /* htons(0x0800) */
// #define TCP_HDR_LEN 20
//
// #define BLOCKED_TCP_PORT 0x9999
//
// struct eth_hdr {
// unsigned char h_dest[ETH_ALEN];
// unsigned char h_source[ETH_ALEN];
// unsigned short h_proto;
// };
//
// #define SEC(NAME) __attribute__((section(NAME), used))
// SEC(".classifier")
// int handle_ingress(struct __sk_buff *skb)
// {
// void *data = (void *)(long)skb->data;
// void *data_end = (void *)(long)skb->data_end;
// struct eth_hdr *eth = data;
// struct iphdr *iph = data + sizeof(*eth);
// struct tcphdr *tcp = data + sizeof(*eth) + sizeof(*iph);
//
// /* single length check */
// if (data + sizeof(*eth) + sizeof(*iph) + sizeof(*tcp) > data_end)
// return 0;
// if (eth->h_proto != ETH_P_IP)
// return 0;
// if (iph->protocol != IPPROTO_TCP)
// return 0;
// if (tcp->source == BLOCKED_TCP_PORT || tcp->dest == BLOCKED_TCP_PORT)
// return -1;
// return 0;
// }
// char _license[] SEC(".license") = "GPL";
// ```
//
// This program, once compiled, can be injected into Linux kernel, with tc for instance. Sadly, we
// need to bring some modifications to the generated bytecode in order to run it: the three
// instructions with opcode 0x61 load data from a packet area as 4-byte words, where we need to
// load it as 8-bytes double words (0x79). The kernel does the same kind of translation before
// running the program, but rbpf does not implement this.
//
// In addition, the offset at which the pointer to the packet data is stored must be changed: since
// we use 8 bytes instead of 4 for the start and end addresses of the data packet, we cannot use
// the offsets produced by clang (0x4c and 0x50), the addresses would overlap. Instead we can use,
// for example, 0x40 and 0x50. See comments on the bytecode below to see the modifications.
//
// Once the bytecode has been (manually, in our case) edited, we can load the bytecode directly
// from the ELF object file. This is easy to do, but requires the addition of two crates in the
// Cargo.toml file (see comments above), so here we use just the hardcoded bytecode instructions
// instead.
#[test]
#[cfg(feature = "std")]
fn test_vm_block_port() {
// To load the bytecode from an object file instead of using the hardcoded instructions,
// use the additional crates commented at the beginning of this file (and also add them to your
// Cargo.toml). See comments above.
//
// ---
// let filename = "my_ebpf_object_file.o";
//
// let path = PathBuf::from(filename);
// let file = match elf::File::open_path(&path) {
// Ok(f) => f,
// Err(e) => panic!("Error: {:?}", e),
// };
//
// let text_scn = match file.get_section(".classifier") {
// Some(s) => s,
// None => panic!("Failed to look up .classifier section"),
// };
//
// let prog = &text_scn.data;
// ---
let prog = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x12, 0x50, 0x00, 0x00, 0x00, 0x00,
0x00, // 0x79 instead of 0x61
0x79, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, // 0x79 instead of 0x61, 0x40 i.o. 0x4c
0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x36, 0x00, 0x00,
0x00, 0x2d, 0x23, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x12, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x55, 0x02, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x71, 0x12, 0x17, 0x00, 0x00,
0x00, 0x00, 0x00, 0x55, 0x02, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x11, 0x22,
0x00, 0x00, 0x00, 0x00, 0x00, // 0x79 instead of 0x61
0xbf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00,
0x00, 0x15, 0x02, 0x08, 0x00, 0x99, 0x99, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x21, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x18, 0x02, 0x00, 0x00,
0x00, 0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x21, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let packet = &mut [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
0x00, // ethertype
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
0x01,
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
];
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
.unwrap();
let res = vm.execute_program(packet).unwrap();
println!("Program returned: {res:?} ({res:#x})");
assert_eq!(res, 0xffffffff);
}
#[test]
#[cfg(all(not(windows), feature = "std"))]
fn test_jit_block_port() {
// To load the bytecode from an object file instead of using the hardcoded instructions,
// use the additional crates commented at the beginning of this file (and also add them to your
// Cargo.toml). See comments above.
//
// ---
// let filename = "my_ebpf_object_file.o";
//
// let path = PathBuf::from(filename);
// let file = match elf::File::open_path(&path) {
// Ok(f) => f,
// Err(e) => panic!("Error: {:?}", e),
// };
//
// let text_scn = match file.get_section(".classifier") {
// Some(s) => s,
// None => panic!("Failed to look up .classifier section"),
// };
//
// let prog = &text_scn.data;
// ---
let prog = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x12, 0x50, 0x00, 0x00, 0x00, 0x00,
0x00, // 0x79 instead of 0x61
0x79, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, // 0x79 instead of 0x61, 0x40 i.o. 0x4c
0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x36, 0x00, 0x00,
0x00, 0x2d, 0x23, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x12, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x55, 0x02, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x71, 0x12, 0x17, 0x00, 0x00,
0x00, 0x00, 0x00, 0x55, 0x02, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x11, 0x22,
0x00, 0x00, 0x00, 0x00, 0x00, // 0x79 instead of 0x61
0xbf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00,
0x00, 0x15, 0x02, 0x08, 0x00, 0x99, 0x99, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x21, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x18, 0x02, 0x00, 0x00,
0x00, 0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x21, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let packet = &mut [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
0x00, // ethertype
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
0x01,
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
];
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
.unwrap();
vm.jit_compile().unwrap();
unsafe {
let res = vm.execute_program_jit(packet).unwrap();
println!("Program returned: {res:?} ({res:#x})");
assert_eq!(res, 0xffffffff);
}
}
// Program and memory come from uBPF test ldxh.
#[test]
fn test_vm_mbuff() {
let prog = &[
// Load mem from mbuff into R1
0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // ldhx r1[2], r0
0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &[0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
let mbuff = [0u8; 32];
unsafe {
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
data.write_unaligned(mem.as_ptr() as u64);
data_end.write_unaligned(mem.as_ptr() as u64 + mem.len() as u64);
}
let vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem, &mbuff).unwrap(), 0x2211);
}
// Program and memory come from uBPF test ldxh.
#[test]
fn test_vm_mbuff_with_rust_api() {
use rbpf::insn_builder::*;
let mut program = BpfCode::new();
program
.load_x(MemSize::DoubleWord)
.set_dst(0x01)
.set_src(0x01)
.set_off(0x00_08)
.push()
.load_x(MemSize::HalfWord)
.set_dst(0x00)
.set_src(0x01)
.set_off(0x00_02)
.push()
.exit()
.push();
let mem = &[0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
let mbuff = [0u8; 32];
unsafe {
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
*data = mem.as_ptr() as u64;
*data_end = mem.as_ptr() as u64 + mem.len() as u64;
}
let vm = rbpf::EbpfVmMbuff::new(Some(program.into_bytes())).unwrap();
assert_eq!(vm.execute_program(mem, &mbuff).unwrap(), 0x2211);
}
// Program and memory come from uBPF test ldxh.
#[test]
#[cfg(all(not(windows), feature = "std"))]
fn test_jit_mbuff() {
let prog = &[
// Load mem from mbuff into R1
0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // ldhx r1[2], r0
0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &mut [0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
let mut mbuff = [0u8; 32];
unsafe {
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
*data = mem.as_ptr() as u64;
*data_end = mem.as_ptr() as u64 + mem.len() as u64;
}
unsafe {
let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
vm.jit_compile().unwrap();
assert_eq!(vm.execute_program_jit(mem, &mut mbuff).unwrap(), 0x2211);
}
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldabsb() {
let prog = &[
0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0x33);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x33);
};
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldabsh() {
let prog = &[
0x28, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0x4433);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x4433);
};
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldabsw() {
let prog = &[
0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0x66554433);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x66554433);
};
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldabsdw() {
let prog = &[
0x38, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0xaa99887766554433);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xaa99887766554433);
};
}
#[test]
#[should_panic(expected = "Error: out of bounds memory load (insn #1),")]
fn test_vm_err_ldabsb_oob() {
let prog = &[
0x38, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
vm.execute_program(mem).unwrap();
// Memory check not implemented for JIT yet.
}
#[test]
#[should_panic(expected = "Error: out of bounds memory load (insn #1),")]
fn test_vm_err_ldabsb_nomem() {
let prog = &[
0x38, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
vm.execute_program().unwrap();
// Memory check not implemented for JIT yet.
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldindb() {
let prog = &[
0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0x88);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x88);
};
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldindh() {
let prog = &[
0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x48, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0x9988);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x9988);
};
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldindw() {
let prog = &[
0xb7, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0x88776655);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x88776655);
};
}
#[cfg(all(not(windows), feature = "std"))]
#[test]
fn test_vm_jit_ldinddw() {
let prog = &[
0xb7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x58, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
assert_eq!(vm.execute_program(mem).unwrap(), 0xccbbaa9988776655);
vm.jit_compile().unwrap();
unsafe {
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xccbbaa9988776655);
};
}
#[test]
#[should_panic(expected = "Error: out of bounds memory load (insn #2),")]
fn test_vm_err_ldindb_oob() {
let prog = &[
0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x38, 0x10, 0x00, 0x00, 0x33, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let mem = &mut [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff,
];
let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
vm.execute_program(mem).unwrap();
// Memory check not implemented for JIT yet.
}
#[test]
#[should_panic(expected = "Error: out of bounds memory load (insn #2),")]
fn test_vm_err_ldindb_nomem() {
let prog = &[
0xb7, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
vm.execute_program().unwrap();
// Memory check not implemented for JIT yet.
}
#[test]
#[should_panic(expected = "Error: No program set, call prog_set() to load one")]
fn test_vm_exec_no_program() {
let vm = rbpf::EbpfVmNoData::new(None).unwrap();
assert_eq!(vm.execute_program().unwrap(), 0xBEE);
}
fn verifier_success(_prog: &[u8]) -> Result<(), Error> {
Ok(())
}
fn verifier_fail(_prog: &[u8]) -> Result<(), Error> {
Err(Error::new(ErrorKind::Other, "Gaggablaghblagh!"))
}
#[test]
fn test_verifier_success() {
let prog = assemble(
"mov32 r0, 0xBEE
exit",
)
.unwrap();
let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();
vm.set_verifier(verifier_success).unwrap();
vm.set_program(&prog).unwrap();
assert_eq!(vm.execute_program().unwrap(), 0xBEE);
}
#[test]
#[should_panic(expected = "Gaggablaghblagh!")]
fn test_verifier_fail() {
let prog = assemble(
"mov32 r0, 0xBEE
exit",
)
.unwrap();
let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();
vm.set_verifier(verifier_fail).unwrap();
vm.set_program(&prog).unwrap();
assert_eq!(vm.execute_program().unwrap(), 0xBEE);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Converted from the tests for uBPF <https://github.com/iovisor/ubpf>
// Copyright 2015 Big Switch Networks, Inc
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
// The tests contained in this file are extracted from the unit tests of uBPF software. Each test
// in this file has a name in the form `test_verifier_<name>`, and corresponds to the
// (human-readable) code in `ubpf/tree/master/tests/<name>`, available at
// <https://github.com/iovisor/ubpf/tree/master/tests> (hyphen had to be replaced with underscores
// as Rust will not accept them in function names). It is strongly advised to refer to the uBPF
// version to understand what these program do.
//
// Each program was assembled from the uBPF version with the assembler provided by uBPF itself, and
// available at <https://github.com/iovisor/ubpf/tree/master/ubpf>.
// The very few modifications that have been realized should be indicated.
// These are unit tests for the eBPF “verifier”.
extern crate rbpf;
use rbpf::{assembler::assemble, ebpf};
#[test]
#[should_panic(expected = "[Verifier] Error: unsupported argument for LE/BE (insn #0)")]
fn test_verifier_err_endian_size() {
let prog = &[
0xdc, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: incomplete LD_DW instruction (insn #0)")]
fn test_verifier_err_incomplete_lddw() {
// Note: ubpf has test-err-incomplete-lddw2, which is the same
let prog = &[
0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: infinite loop")]
fn test_verifier_err_infinite_loop() {
let prog = assemble(
"
ja -1
exit",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: invalid destination register (insn #0)")]
fn test_verifier_err_invalid_reg_dst() {
let prog = assemble(
"
mov r11, 1
exit",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: invalid source register (insn #0)")]
fn test_verifier_err_invalid_reg_src() {
let prog = assemble(
"
mov r0, r11
exit",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: jump to middle of LD_DW at #2 (insn #0)")]
fn test_verifier_err_jmp_lddw() {
let prog = assemble(
"
ja +1
lddw r0, 0x1122334455667788
exit",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: jump out of code to #3 (insn #0)")]
fn test_verifier_err_jmp_out() {
let prog = assemble(
"
ja +2
exit",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: program does not end with “EXIT” instruction")]
fn test_verifier_err_no_exit() {
let prog = assemble(
"
mov32 r0, 0",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
fn test_verifier_err_no_exit_backward_jump() {
let prog = assemble(
"
ja +1
exit
ja -2",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: eBPF program length limited to 1000000, here 1000001")]
fn test_verifier_err_too_many_instructions() {
// uBPF uses 65637 instructions, because it sets its limit at 65636.
// We use the classic 4096 limit from kernel, so no need to produce as many instructions.
let mut prog = (0..(1_000_000 * ebpf::INSN_SIZE))
.map(|x| match x % 8 {
0 => 0xb7,
1 => 0x01,
_ => 0,
})
.collect::<Vec<u8>>();
prog.append(&mut vec![0x95, 0, 0, 0, 0, 0, 0, 0]);
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: unknown eBPF opcode 0x6 (insn #0)")]
fn test_verifier_err_unknown_opcode() {
let prog = &[
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
vm.execute_program().unwrap();
}
#[test]
#[should_panic(expected = "[Verifier] Error: cannot write into register r10 (insn #0)")]
fn test_verifier_err_write_r10() {
let prog = assemble(
"
mov r10, 1
exit",
)
.unwrap();
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
vm.execute_program().unwrap();
}

File diff suppressed because it is too large Load Diff

View File

@ -18,11 +18,9 @@
//! # Implementing GlobalAlloc
//! See the [global alloc](https://github.com/gz/rust-slabmalloc/tree/master/examples/global_alloc.rs) example.
#![allow(unused_features)]
#![cfg_attr(feature = "unstable", feature(const_mut_refs))]
#![no_std]
#![crate_name = "slabmalloc"]
#![crate_type = "lib"]
#![feature(new_uninit)]
#![feature(maybe_uninit_as_bytes)]
extern crate alloc;
@ -65,6 +63,8 @@ pub enum AllocationError {
/// Needs to adhere to safety requirements of a rust allocator (see GlobalAlloc et. al.).
pub unsafe trait Allocator<'a> {
fn allocate(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocationError>;
/// # Safety
/// The caller must ensure that the memory is valid and that the layout is correct.
unsafe fn deallocate(
&mut self,
ptr: NonNull<u8>,
@ -85,5 +85,7 @@ pub unsafe trait Allocator<'a> {
/// 将slab_page归还Buddy的回调函数
pub trait CallBack: Send + Sync {
/// # Safety
/// The caller must ensure that the memory is valid and that the size is correct.
unsafe fn free_slab_page(&self, _: *mut u8, _: usize) {}
}

View File

@ -303,10 +303,10 @@ impl<'a> ObjectPage<'a> {
}
// These needs some more work to be really safe...
unsafe impl<'a> Send for ObjectPage<'a> {}
unsafe impl<'a> Sync for ObjectPage<'a> {}
unsafe impl Send for ObjectPage<'_> {}
unsafe impl Sync for ObjectPage<'_> {}
impl<'a> AllocablePage for ObjectPage<'a> {
impl AllocablePage for ObjectPage<'_> {
const SIZE: usize = OBJECT_PAGE_SIZE;
fn bitfield(&self) -> &[AtomicU64; 8] {
@ -331,7 +331,7 @@ impl<'a> Default for ObjectPage<'a> {
}
}
impl<'a> fmt::Debug for ObjectPage<'a> {
impl fmt::Debug for ObjectPage<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ObjectPage")
}

View File

@ -314,6 +314,9 @@ impl<'a, P: AllocablePage> SCAllocator<'a, P> {
/// May return an error in case an invalid `layout` is provided.
/// The function may also move internal slab pages between lists partial -> empty
/// or full -> partial lists.
///
/// # Safety
/// The caller must ensure that the `layout` is valid.
pub unsafe fn deallocate(
&mut self,
ptr: NonNull<u8>,

View File

@ -6,7 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
kdepends = { path = "../kdepends" }
num-traits = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/num-traits.git", rev="1597c1c", default-features = false }
num = { version = "0.4.0", default-features = false }
num-derive = "0.3"

View File

@ -1,7 +1,7 @@
#![no_std]
#![allow(clippy::needless_return)]
#![allow(clippy::upper_case_acronyms)]
#![allow(non_local_definitions)]
use num_derive::{FromPrimitive, ToPrimitive};
#[repr(i32)]

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2024-07-23"
channel = "nightly-2024-11-05"
components = ["rust-src", "clippy"]

View File

@ -40,7 +40,7 @@ kernel_subdirs := debug
kernel_rust:
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-07-23 $(CARGO_ZBUILD) build --release --target $(TARGET_JSON)
RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 $(CARGO_ZBUILD) build --release --target $(TARGET_JSON)
all: kernel

View File

@ -4,7 +4,6 @@ use crate::arch::{
interrupt::TrapFrame,
};
use asm_macros::{restore_from_x6_to_x31, save_from_x6_to_x31};
use core::arch::asm;
use kdepends::memoffset::offset_of;
/// Riscv64中断处理入口
@ -12,7 +11,7 @@ use kdepends::memoffset::offset_of;
#[no_mangle]
#[repr(align(4))]
pub unsafe extern "C" fn handle_exception() -> ! {
asm!(
core::arch::naked_asm!(
concat!("
/*
* If coming from userspace, preserve the user thread pointer and load
@ -27,15 +26,14 @@ pub unsafe extern "C" fn handle_exception() -> ! {
j {_restore_kernel_tpsp}
"),
csr_scratch = const CSR_SSCRATCH,
_restore_kernel_tpsp = sym _restore_kernel_tpsp,
options(noreturn),
_restore_kernel_tpsp = sym _restore_kernel_tpsp
)
}
#[naked]
#[no_mangle]
unsafe extern "C" fn _restore_kernel_tpsp() -> ! {
asm!(
core::arch::naked_asm!(
concat!("
// 这次是从内核态进入中断
// 从sscratch寄存器加载当前cpu的上下文
@ -48,16 +46,14 @@ unsafe extern "C" fn _restore_kernel_tpsp() -> ! {
"),
csr_scratch = const CSR_SSCRATCH,
lc_off_kernel_sp = const offset_of!(LocalContext, kernel_sp),
_save_context = sym _save_context,
options(noreturn),
_save_context = sym _save_context
)
}
#[naked]
#[no_mangle]
unsafe extern "C" fn _save_context() -> ! {
asm!(
core::arch::naked_asm!(
concat!("
@ -164,15 +160,14 @@ unsafe extern "C" fn _save_context() -> ! {
csr_epc = const CSR_SEPC,
csr_tval = const CSR_STVAL,
csr_cause = const CSR_SCAUSE,
csr_scratch = const CSR_SSCRATCH,
options(noreturn),
csr_scratch = const CSR_SSCRATCH
)
}
#[naked]
#[no_mangle]
pub unsafe extern "C" fn ret_from_exception() -> ! {
asm!(
core::arch::naked_asm!(
concat!("
ld s0, {off_status}(sp)
andi s0, s0, {sr_spp}
@ -249,8 +244,6 @@ pub unsafe extern "C" fn ret_from_exception() -> ! {
off_t6 = const offset_of!(TrapFrame, t6),
off_sp = const offset_of!(TrapFrame, sp),
off_tp = const offset_of!(TrapFrame, tp),
off_epc = const offset_of!(TrapFrame, epc),
options(noreturn),
off_epc = const offset_of!(TrapFrame, epc)
)
}

View File

@ -3,12 +3,12 @@
//! 架构相关的处理逻辑参考: https://code.dragonos.org.cn/xref/linux-6.6.21/arch/riscv/kernel/traps.c
use core::hint::spin_loop;
use log::error;
use log::{error, trace};
use system_error::SystemError;
use crate::{arch::syscall::syscall_handler, driver::irqchip::riscv_intc::riscv_intc_irq};
use super::TrapFrame;
use crate::exception::ebreak::EBreak;
use crate::{arch::syscall::syscall_handler, driver::irqchip::riscv_intc::riscv_intc_irq};
type ExceptionHandler = fn(&mut TrapFrame) -> Result<(), SystemError>;
@ -93,11 +93,10 @@ fn do_trap_insn_illegal(_trap_frame: &mut TrapFrame) -> Result<(), SystemError>
}
/// 处理断点异常 #3
fn do_trap_break(_trap_frame: &mut TrapFrame) -> Result<(), SystemError> {
error!("riscv64_do_irq: do_trap_break");
loop {
spin_loop();
}
fn do_trap_break(trap_frame: &mut TrapFrame) -> Result<(), SystemError> {
trace!("riscv64_do_irq: do_trap_break");
// handle breakpoint
EBreak::handle(trap_frame)
}
/// 处理加载地址不对齐异常 #4

View File

@ -1,3 +1,5 @@
use core::any::Any;
use kprobe::ProbeArgs;
use riscv::register::{scause::Scause, sstatus::Sstatus};
use system_error::SystemError;
@ -160,4 +162,21 @@ impl TrapFrame {
pub fn set_return_value(&mut self, value: usize) {
self.a0 = value;
}
/// 设置当前的程序计数器
pub fn set_pc(&mut self, pc: usize) {
self.epc = pc;
}
}
impl ProbeArgs for TrapFrame {
fn as_any(&self) -> &dyn Any {
self
}
fn break_address(&self) -> usize {
self.epc
}
fn debug_address(&self) -> usize {
self.epc
}
}

View File

@ -0,0 +1,85 @@
use crate::arch::interrupt::TrapFrame;
pub fn setup_single_step(frame: &mut TrapFrame, step_addr: usize) {
frame.set_pc(step_addr);
}
pub fn clear_single_step(frame: &mut TrapFrame, return_addr: usize) {
frame.set_pc(return_addr);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct KProbeContext {
pub pc: usize,
pub ra: usize,
pub sp: usize,
pub gp: usize,
pub tp: usize,
pub t0: usize,
pub t1: usize,
pub t2: usize,
pub s0: usize,
pub s1: usize,
pub a0: usize,
pub a1: usize,
pub a2: usize,
pub a3: usize,
pub a4: usize,
pub a5: usize,
pub a6: usize,
pub a7: usize,
pub s2: usize,
pub s3: usize,
pub s4: usize,
pub s5: usize,
pub s6: usize,
pub s7: usize,
pub s8: usize,
pub s9: usize,
pub s10: usize,
pub s11: usize,
pub t3: usize,
pub t4: usize,
pub t5: usize,
pub t6: usize,
}
impl From<&TrapFrame> for KProbeContext {
fn from(trap_frame: &TrapFrame) -> Self {
Self {
pc: trap_frame.epc,
ra: trap_frame.ra,
sp: trap_frame.sp,
gp: trap_frame.gp,
tp: trap_frame.tp,
t0: trap_frame.t0,
t1: trap_frame.t1,
t2: trap_frame.t2,
s0: trap_frame.s0,
s1: trap_frame.s1,
a0: trap_frame.a0,
a1: trap_frame.a1,
a2: trap_frame.a2,
a3: trap_frame.a3,
a4: trap_frame.a4,
a5: trap_frame.a5,
a6: trap_frame.a6,
a7: trap_frame.a7,
s2: trap_frame.s2,
s3: trap_frame.s3,
s4: trap_frame.s4,
s5: trap_frame.s5,
s6: trap_frame.s6,
s7: trap_frame.s7,
s8: trap_frame.s8,
s9: trap_frame.s9,
s10: trap_frame.s10,
s11: trap_frame.s11,
t3: trap_frame.t3,
t4: trap_frame.t4,
t5: trap_frame.t5,
t6: trap_frame.t6,
}
}
}

View File

@ -5,6 +5,7 @@ pub mod elf;
pub mod init;
pub mod interrupt;
pub mod ipc;
pub mod kprobe;
mod kvm;
pub mod mm;
pub mod msi;

View File

@ -66,7 +66,7 @@ impl KernelThreadMechanism {
pub(super) unsafe extern "C" fn kernel_thread_bootstrap_stage1() {
// 这个函数要是naked的只是因为现在还没有实现而naked func不能打`unimplemented!()`
// 所以先写成了普通函数
asm!(concat!(
core::arch::naked_asm!(concat!(
"
ld x3, {off_gp}(sp)
ld x5, {off_t0}(sp)
@ -111,8 +111,7 @@ pub(super) unsafe extern "C" fn kernel_thread_bootstrap_stage1() {
off_t4 = const offset_of!(TrapFrame, t4),
off_t5 = const offset_of!(TrapFrame, t5),
off_t6 = const offset_of!(TrapFrame, t6),
stage2_func = sym jump_to_stage2,
options(noreturn),
stage2_func = sym jump_to_stage2
);
}

View File

@ -78,9 +78,8 @@ pub unsafe fn arch_switch_to_user(trap_frame: TrapFrame) -> ! {
#[naked]
unsafe extern "C" fn ready_to_switch_to_user(trap_frame: usize, new_pc: usize) -> ! {
asm!(
concat!(
"
core::arch::naked_asm!(concat!(
"
// 设置trap frame
mv sp, a0
// 设置返回地址
@ -88,9 +87,7 @@ unsafe extern "C" fn ready_to_switch_to_user(trap_frame: usize, new_pc: usize) -
jr a1
"
),
options(noreturn)
);
));
}
impl ProcessManager {
@ -104,7 +101,7 @@ impl ProcessManager {
pub fn copy_thread(
current_pcb: &Arc<ProcessControlBlock>,
new_pcb: &Arc<ProcessControlBlock>,
clone_args: KernelCloneArgs,
clone_args: &KernelCloneArgs,
current_trapframe: &TrapFrame,
) -> Result<(), SystemError> {
let clone_flags = clone_args.flags;
@ -227,7 +224,7 @@ impl ProcessManager {
/// 参考 https://code.dragonos.org.cn/xref/linux-6.6.21/arch/riscv/kernel/entry.S#233
#[naked]
unsafe extern "C" fn switch_to_inner(prev: *mut ArchPCBInfo, next: *mut ArchPCBInfo) {
core::arch::asm!(concat!(
core::arch::naked_asm!(concat!(
"
sd ra, {off_ra}(a0)
sd sp, {off_sp}(a0)
@ -304,8 +301,7 @@ unsafe extern "C" fn switch_to_inner(prev: *mut ArchPCBInfo, next: *mut ArchPCBI
off_s9 = const(offset_of!(ArchPCBInfo, s9)),
off_s10 = const(offset_of!(ArchPCBInfo, s10)),
off_s11 = const(offset_of!(ArchPCBInfo, s11)),
before_switch_finish_hook = sym before_switch_finish_hook,
options(noreturn));
before_switch_finish_hook = sym before_switch_finish_hook);
}
/// 在切换上下文完成后的钩子函数(必须在这里加一个跳转函数否则会出现relocation truncated to fit: R_RISCV_JAL错误)

View File

@ -1,96 +1,21 @@
use alloc::{ffi::CString, string::String, vec::Vec};
use riscv::register::sstatus::{FS, SPP};
use system_error::SystemError;
use crate::{
arch::{interrupt::TrapFrame, CurrentIrqArch},
exception::InterruptArch,
mm::ucontext::AddressSpace,
process::{
exec::{load_binary_file, ExecParam, ExecParamFlags},
ProcessManager,
},
arch::interrupt::TrapFrame,
mm::VirtAddr,
process::exec::{BinaryLoaderResult, ExecParam},
syscall::Syscall,
};
impl Syscall {
pub fn do_execve(
path: String,
argv: Vec<CString>,
envp: Vec<CString>,
pub fn arch_do_execve(
regs: &mut TrapFrame,
param: &ExecParam,
load_result: &BinaryLoaderResult,
user_sp: VirtAddr,
argv_ptr: VirtAddr,
) -> Result<(), SystemError> {
// 关中断,防止在设置地址空间的时候,发生中断,然后进调度器,出现错误。
let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
let pcb = ProcessManager::current_pcb();
// crate::debug!(
// "pid: {:?} do_execve: path: {:?}, argv: {:?}, envp: {:?}\n",
// pcb.pid(),
// path,
// argv,
// envp
// );
let mut basic_info = pcb.basic_mut();
// 暂存原本的用户地址空间的引用(因为如果在切换页表之前释放了它可能会造成内存use after free)
let old_address_space = basic_info.user_vm();
// 在pcb中原来的用户地址空间
unsafe {
basic_info.set_user_vm(None);
}
// 创建新的地址空间并设置为当前地址空间
let address_space = AddressSpace::new(true).expect("Failed to create new address space");
unsafe {
basic_info.set_user_vm(Some(address_space.clone()));
}
// to avoid deadlock
drop(basic_info);
assert!(
AddressSpace::is_current(&address_space),
"Failed to set address space"
);
// debug!("Switch to new address space");
// 切换到新的用户地址空间
unsafe { address_space.read().user_mapper.utable.make_current() };
drop(old_address_space);
drop(irq_guard);
// debug!("to load binary file");
let mut param = ExecParam::new(path.as_str(), address_space.clone(), ExecParamFlags::EXEC)?;
// 加载可执行文件
let load_result = load_binary_file(&mut param)?;
// debug!("load binary file done");
// debug!("argv: {:?}, envp: {:?}", argv, envp);
param.init_info_mut().args = argv;
param.init_info_mut().envs = envp;
// 把proc_init_info写到用户栈上
let mut ustack_message = unsafe {
address_space
.write()
.user_stack_mut()
.expect("No user stack found")
.clone_info_only()
};
let (user_sp, argv_ptr) = unsafe {
param
.init_info()
.push_at(
// address_space
// .write()
// .user_stack_mut()
// .expect("No user stack found"),
&mut ustack_message,
)
.expect("Failed to push proc_init_info to user stack")
};
address_space.write().user_stack = Some(ustack_message);
// debug!("write proc_init_info to user stack done");
regs.a0 = param.init_info().args.len();
@ -104,8 +29,6 @@ impl Syscall {
regs.status.update_fs(FS::Clean);
regs.status.update_sum(true);
drop(param);
return Ok(());
}

View File

@ -54,7 +54,6 @@ Restore_all:
popq %rax
addq $0x10, %rsp // FUNCerrcode
sti
iretq
ret_from_exception:

View File

@ -45,7 +45,7 @@ macro_rules! interrupt_handler {
#[naked]
#[no_mangle]
unsafe extern "C" fn [<irq_handler $name>]() {
core::arch::asm!(
core::arch::naked_asm!(
concat!(
"
push 0x0
@ -60,8 +60,7 @@ macro_rules! interrupt_handler {
jmp x86_64_do_irq
"
),
irqnum = const($name),
options(noreturn)
irqnum = const($name)
);
}
}

View File

@ -4,11 +4,12 @@ pub mod ipi;
pub mod msi;
pub mod trap;
use core::any::Any;
use core::{
arch::asm,
sync::atomic::{compiler_fence, Ordering},
};
use kprobe::ProbeArgs;
use log::error;
use system_error::SystemError;
@ -177,4 +178,21 @@ impl TrapFrame {
pub fn is_from_user(&self) -> bool {
return (self.cs & 0x3) != 0;
}
/// 设置当前的程序计数器
pub fn set_pc(&mut self, pc: usize) {
self.rip = pc as u64;
}
}
impl ProbeArgs for TrapFrame {
fn as_any(&self) -> &dyn Any {
self
}
fn break_address(&self) -> usize {
(self.rip - 1) as usize
}
fn debug_address(&self) -> usize {
self.rip as usize
}
}

View File

@ -1,6 +1,12 @@
use log::{error, warn};
use log::{error, trace, warn};
use system_error::SystemError;
use super::{
entry::{set_intr_gate, set_system_trap_gate},
TrapFrame,
};
use crate::exception::debug::DebugException;
use crate::exception::ebreak::EBreak;
use crate::{
arch::{CurrentIrqArch, MMArch},
exception::InterruptArch,
@ -9,11 +15,6 @@ use crate::{
smp::core::smp_get_processor_id,
};
use super::{
entry::{set_intr_gate, set_system_trap_gate},
TrapFrame,
};
extern "C" {
fn trap_divide_error();
fn trap_debug();
@ -125,8 +126,8 @@ unsafe extern "C" fn do_divide_error(regs: &'static TrapFrame, error_code: u64)
/// 处理调试异常 1 #DB
#[no_mangle]
unsafe extern "C" fn do_debug(regs: &'static TrapFrame, error_code: u64) {
error!(
unsafe extern "C" fn do_debug(regs: &'static mut TrapFrame, error_code: u64) {
trace!(
"do_debug(1), \tError code: {:#x},\trsp: {:#x},\trip: {:#x},\t CPU: {}, \tpid: {:?}",
error_code,
regs.rsp,
@ -134,7 +135,7 @@ unsafe extern "C" fn do_debug(regs: &'static TrapFrame, error_code: u64) {
smp_get_processor_id().data(),
ProcessManager::current_pid()
);
panic!("Debug Exception");
DebugException::handle(regs).unwrap();
}
/// 处理NMI中断 2 NMI
@ -153,8 +154,8 @@ unsafe extern "C" fn do_nmi(regs: &'static TrapFrame, error_code: u64) {
/// 处理断点异常 3 #BP
#[no_mangle]
unsafe extern "C" fn do_int3(regs: &'static TrapFrame, error_code: u64) {
error!(
unsafe extern "C" fn do_int3(regs: &'static mut TrapFrame, error_code: u64) {
trace!(
"do_int3(3), \tError code: {:#x},\trsp: {:#x},\trip: {:#x},\t CPU: {}, \tpid: {:?}",
error_code,
regs.rsp,
@ -162,7 +163,7 @@ unsafe extern "C" fn do_int3(regs: &'static TrapFrame, error_code: u64) {
smp_get_processor_id().data(),
ProcessManager::current_pid()
);
panic!("Int3");
EBreak::handle(regs).unwrap();
}
/// 处理溢出异常 4 #OF

View File

@ -431,8 +431,6 @@ impl SignalArch for X86_64SignalArch {
return;
}
let pcb = ProcessManager::current_pcb();
let mut sig_number: Signal;
let mut info: Option<SigInfo>;
let mut sigaction: Sigaction;
@ -483,9 +481,13 @@ impl SignalArch for X86_64SignalArch {
//避免死锁
drop(siginfo_mut_guard);
drop(sig_guard);
drop(pcb);
// 做完上面的检查后,开中断
CurrentIrqArch::interrupt_enable();
// 注意由于handle_signal里面可能会退出进程
// 因此这里需要检查清楚上面所有的锁、arc指针都被释放了。否则会产生资源泄露的问题
let res: Result<i32, SystemError> =
handle_signal(sig_number, &mut sigaction, &info.unwrap(), &oldset, frame);
if res.is_err() {

View File

@ -0,0 +1,65 @@
use crate::arch::interrupt::TrapFrame;
pub fn setup_single_step(frame: &mut TrapFrame, step_addr: usize) {
frame.rflags |= 0x100;
frame.set_pc(step_addr);
}
pub fn clear_single_step(frame: &mut TrapFrame, return_addr: usize) {
frame.rflags &= !0x100;
frame.set_pc(return_addr);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct KProbeContext {
pub r15: ::core::ffi::c_ulong,
pub r14: ::core::ffi::c_ulong,
pub r13: ::core::ffi::c_ulong,
pub r12: ::core::ffi::c_ulong,
pub rbp: ::core::ffi::c_ulong,
pub rbx: ::core::ffi::c_ulong,
pub r11: ::core::ffi::c_ulong,
pub r10: ::core::ffi::c_ulong,
pub r9: ::core::ffi::c_ulong,
pub r8: ::core::ffi::c_ulong,
pub rax: ::core::ffi::c_ulong,
pub rcx: ::core::ffi::c_ulong,
pub rdx: ::core::ffi::c_ulong,
pub rsi: ::core::ffi::c_ulong,
pub rdi: ::core::ffi::c_ulong,
pub orig_rax: ::core::ffi::c_ulong,
pub rip: ::core::ffi::c_ulong,
pub cs: ::core::ffi::c_ulong,
pub eflags: ::core::ffi::c_ulong,
pub rsp: ::core::ffi::c_ulong,
pub ss: ::core::ffi::c_ulong,
}
impl From<&TrapFrame> for KProbeContext {
fn from(trap_frame: &TrapFrame) -> Self {
Self {
r15: trap_frame.r15,
r14: trap_frame.r14,
r13: trap_frame.r13,
r12: trap_frame.r12,
rbp: trap_frame.rbp,
rbx: trap_frame.rbx,
r11: trap_frame.r11,
r10: trap_frame.r10,
r9: trap_frame.r9,
r8: trap_frame.r8,
rax: trap_frame.rax,
rcx: trap_frame.rcx,
rdx: trap_frame.rdx,
rsi: trap_frame.rsi,
rdi: trap_frame.rdi,
orig_rax: 0,
rip: trap_frame.rip,
cs: trap_frame.cs,
eflags: trap_frame.rflags,
rsp: trap_frame.rsp,
ss: trap_frame.ss,
}
}
}

View File

@ -500,7 +500,7 @@ unsafe fn allocator_init() {
for i in 0..total_num {
let area = mem_block_manager().get_initial_memory_region(i).unwrap();
// debug!("area: base={:?}, size={:#x}, end={:?}", area.base, area.size, area.base + area.size);
for i in 0..((area.size + MMArch::PAGE_SIZE - 1) / MMArch::PAGE_SIZE) {
for i in 0..area.size.div_ceil(MMArch::PAGE_SIZE) {
let paddr = area.base.add(i * MMArch::PAGE_SIZE);
let vaddr = unsafe { MMArch::phys_2_virt(paddr) }.unwrap();
let flags = kernel_page_flags::<MMArch>(vaddr);

View File

@ -8,6 +8,7 @@ pub mod fpu;
pub mod init;
pub mod interrupt;
pub mod ipc;
pub mod kprobe;
pub mod kvm;
pub mod libs;
pub mod mm;

View File

@ -1,5 +1,3 @@
use core::arch::asm;
use alloc::sync::Arc;
use system_error::SystemError;
@ -61,7 +59,7 @@ impl KernelThreadMechanism {
/// 跳转之后指向Box<KernelThreadClosure>的指针将传入到stage2的函数
#[naked]
pub(super) unsafe extern "sysv64" fn kernel_thread_bootstrap_stage1() {
asm!(
core::arch::naked_asm!(
concat!(
"
@ -92,6 +90,5 @@ pub(super) unsafe extern "sysv64" fn kernel_thread_bootstrap_stage1() {
"
),
stage2_func = sym kernel_thread_bootstrap_stage2,
options(noreturn)
)
}

View File

@ -299,7 +299,7 @@ impl ProcessManager {
pub fn copy_thread(
current_pcb: &Arc<ProcessControlBlock>,
new_pcb: &Arc<ProcessControlBlock>,
clone_args: KernelCloneArgs,
clone_args: &KernelCloneArgs,
current_trapframe: &TrapFrame,
) -> Result<(), SystemError> {
let clone_flags = clone_args.flags;
@ -425,7 +425,7 @@ impl ProcessManager {
/// 保存上下文然后切换进程接着jmp到`switch_finish_hook`钩子函数
#[naked]
unsafe extern "sysv64" fn switch_to_inner(prev: *mut ArchPCBInfo, next: *mut ArchPCBInfo) {
asm!(
core::arch::naked_asm!(
// As a quick reminder for those who are unfamiliar with the System V ABI (extern "C"):
//
// - the current parameters are passed in the registers `rdi`, `rsi`,
@ -498,13 +498,12 @@ unsafe extern "sysv64" fn switch_to_inner(prev: *mut ArchPCBInfo, next: *mut Arc
off_gs = const(offset_of!(ArchPCBInfo, gs)),
switch_hook = sym crate::process::switch_finish_hook,
options(noreturn),
);
}
#[naked]
unsafe extern "sysv64" fn switch_back() -> ! {
asm!("ret", options(noreturn));
core::arch::naked_asm!("ret");
}
pub unsafe fn arch_switch_to_user(trap_frame: TrapFrame) -> ! {

View File

@ -1,99 +1,27 @@
use alloc::{ffi::CString, string::String, sync::Arc, vec::Vec};
use alloc::sync::Arc;
use system_error::SystemError;
use crate::{
arch::{
interrupt::TrapFrame,
process::table::{USER_CS, USER_DS},
CurrentIrqArch,
},
exception::InterruptArch,
mm::ucontext::AddressSpace,
mm::VirtAddr,
process::{
exec::{load_binary_file, ExecParam, ExecParamFlags},
exec::{BinaryLoaderResult, ExecParam},
ProcessControlBlock, ProcessManager,
},
syscall::{user_access::UserBufferWriter, Syscall},
};
impl Syscall {
pub fn do_execve(
path: String,
argv: Vec<CString>,
envp: Vec<CString>,
pub fn arch_do_execve(
regs: &mut TrapFrame,
param: &ExecParam,
load_result: &BinaryLoaderResult,
user_sp: VirtAddr,
argv_ptr: VirtAddr,
) -> Result<(), SystemError> {
// 关中断,防止在设置地址空间的时候,发生中断,然后进调度器,出现错误。
let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
let pcb = ProcessManager::current_pcb();
// log::debug!(
// "pid: {:?} do_execve: path: {:?}, argv: {:?}, envp: {:?}\n",
// pcb.pid(),
// path,
// argv,
// envp
// );
let mut basic_info = pcb.basic_mut();
// 暂存原本的用户地址空间的引用(因为如果在切换页表之前释放了它可能会造成内存use after free)
let old_address_space = basic_info.user_vm();
// 在pcb中原来的用户地址空间
unsafe {
basic_info.set_user_vm(None);
}
// 创建新的地址空间并设置为当前地址空间
let address_space = AddressSpace::new(true).expect("Failed to create new address space");
unsafe {
basic_info.set_user_vm(Some(address_space.clone()));
}
// to avoid deadlock
drop(basic_info);
assert!(
AddressSpace::is_current(&address_space),
"Failed to set address space"
);
// debug!("Switch to new address space");
// 切换到新的用户地址空间
unsafe { address_space.read().user_mapper.utable.make_current() };
drop(old_address_space);
drop(irq_guard);
// debug!("to load binary file");
let mut param = ExecParam::new(path.as_str(), address_space.clone(), ExecParamFlags::EXEC)?;
// 加载可执行文件
let load_result = load_binary_file(&mut param)?;
// debug!("load binary file done");
// debug!("argv: {:?}, envp: {:?}", argv, envp);
param.init_info_mut().args = argv;
param.init_info_mut().envs = envp;
// 把proc_init_info写到用户栈上
let mut ustack_message = unsafe {
address_space
.write()
.user_stack_mut()
.expect("No user stack found")
.clone_info_only()
};
let (user_sp, argv_ptr) = unsafe {
param
.init_info()
.push_at(
// address_space
// .write()
// .user_stack_mut()
// .expect("No user stack found"),
&mut ustack_message,
)
.expect("Failed to push proc_init_info to user stack")
};
address_space.write().user_stack = Some(ustack_message);
// debug!("write proc_init_info to user stack done");
// 兼容旧版libc把argv的指针写到寄存器内
@ -114,8 +42,6 @@ impl Syscall {
regs.rflags = 0x200;
regs.rax = 1;
drop(param);
// debug!("regs: {:?}\n", regs);
// crate::debug!(

View File

@ -1,5 +1,4 @@
use core::{
arch::asm,
hint::spin_loop,
sync::atomic::{compiler_fence, fence, AtomicBool, Ordering},
};
@ -65,14 +64,13 @@ unsafe extern "C" fn smp_ap_start() -> ! {
#[naked]
unsafe extern "sysv64" fn smp_init_switch_stack(st: &ApStartStackInfo) -> ! {
asm!(concat!("
core::arch::naked_asm!(concat!("
mov rsp, [rdi + {off_rsp}]
mov rbp, [rdi + {off_rsp}]
jmp {stage1}
"),
off_rsp = const(offset_of!(ApStartStackInfo, vaddr)),
stage1 = sym smp_ap_start_stage1,
options(noreturn));
stage1 = sym smp_ap_start_stage1);
}
unsafe extern "C" fn smp_ap_start_stage1() -> ! {

View File

@ -104,15 +104,16 @@ pub extern "sysv64" fn syscall_handler(frame: &mut TrapFrame) {
];
mfence();
let pid = ProcessManager::current_pcb().pid();
let mut show = (syscall_num != SYS_SCHED) && (pid.data() >= 7);
// let mut show = true;
let mut show =
(syscall_num != SYS_SCHED) && (pid.data() >= 7);
// false;
let to_print = SysCall::try_from(syscall_num);
if let Ok(to_print) = to_print {
use SysCall::*;
match to_print {
SYS_ACCEPT | SYS_ACCEPT4 | SYS_BIND | SYS_CONNECT | SYS_SHUTDOWN | SYS_LISTEN => {
// show &= false;
show &= false;
}
SYS_RECVFROM | SYS_SENDTO | SYS_SENDMSG | SYS_RECVMSG => {
show &= false;

View File

@ -732,4 +732,4 @@ impl From<SysCall> for usize {
fn from(value: SysCall) -> Self {
<SysCall as ToPrimitive>::to_usize(&value).unwrap()
}
}
}

Some files were not shown because too many files have changed in this diff Show More