@@ -44,4 +44,4 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOY_S3_SECRET_KEY }}
run: |
- aws s3 sync ./_build/html s3://dragonos-docs --delete
+ aws s3 sync ./_build/html s3://dragonos-docs --delete --exclude "p/*"
@@ -2,96 +2,97 @@ name: Build Check
on:
push:
- branches: [ "master", "feat-*", "fix-*"]
+ branches: ["master", "feat-*", "fix-*"]
pull_request:
jobs:
-
format-check:
name: Format check ${{ matrix.arch }}
runs-on: ubuntu-latest
continue-on-error: true
- container: dragonos/dragonos-dev:v1.4
+ container: dragonos/dragonos-dev:v1.7
strategy:
matrix:
arch: [x86_64, riscv64]
steps:
- - run: echo "Running in dragonos/dragonos-dev:v1.4"
- - uses: actions/checkout@v3
+ - run: echo "Running in dragonos/dragonos-dev:v1.7"
+ - uses: actions/checkout@v3
- - name: Format check
- env:
+ - name: Format check
+ env:
ARCH: ${{ matrix.arch }}
HOME: /root
- shell: bash -ileo pipefail {0}
- run: |
- printf "\n" >> kernel/src/include/bindings/bindings.rs
- FMT_CHECK=1 make fmt
+ shell: bash -ileo pipefail {0}
+ run: |
+ printf "\n" >> kernel/src/include/bindings/bindings.rs
+ sed -i 's/arch = ".*"/arch = "${{ matrix.arch }}"/' dadk-manifest.toml
+ FMT_CHECK=1 make fmt
+
kernel-static-test:
name: Kernel static test ${{ matrix.arch }}
- - name: Run kernel static test
+ - name: Run kernel static test
- 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:
- - name: build the DragonOS
+ - name: build the DragonOS
ARCH: x86_64
- source ~/.bashrc
- source ~/.cargo/env
- export DragonOS_GCC=$HOME/opt/dragonos-gcc/gcc-x86_64-unknown-none/bin
- make -j $(nproc)
+ source ~/.bashrc
+ source ~/.cargo/env
+ export DragonOS_GCC=$HOME/opt/dragonos-gcc/gcc-x86_64-unknown-none/bin
+ sed -i 's/arch = ".*"/arch = "${{ env.ARCH }}"/' dadk-manifest.toml
- build-riscv64:
+ make all -j $(nproc)
+ build-riscv64:
- with:
- submodules: 'recursive'
+ with:
+ submodules: "recursive"
ARCH: riscv64
- run: source ~/.bashrc && source ~/.cargo/env && make kernel -j $(nproc)
+ source ~/.bashrc && source ~/.cargo/env
+ make kernel -j $(nproc)
@@ -34,19 +34,22 @@ else
FMT_CHECK=
endif
+# Check if ARCH matches the arch field in dadk-manifest.toml
+check_arch:
+ @bash tools/check_arch.sh
.PHONY: all
all: kernel user
.PHONY: kernel
-kernel:
+kernel: check_arch
mkdir -p bin/kernel/
$(MAKE) -C ./kernel all ARCH=$(ARCH) || (sh -c "echo 内核编译失败" && exit 1)
.PHONY: user
-user:
+user: check_arch
$(MAKE) -C ./user all ARCH=$(ARCH) || (sh -c "echo 用户程序编译失败" && exit 1)
.PHONY: clean
@@ -61,11 +64,6 @@ clean:
ECHO:
@echo "$@"
-cppcheck-xml:
- cppcheck kernel user --platform=unix64 --std=c11 -I user/libs/ -I=kernel/ --force -j $(NPROCS) --xml 2> cppcheck.xml
-cppcheck:
- cppcheck kernel user --platform=unix64 --std=c11 -I user/libs/ -I=kernel/ --force -j $(NPROCS)
docs: ECHO
bash -c "cd docs && make html && cd .."
@@ -81,74 +79,74 @@ else
# 写入磁盘镜像
-write_diskimage:
+write_diskimage: check_arch
@echo "write_diskimage arch=$(ARCH)"
- bash -c "export ARCH=$(ARCH); cd tools && bash grub_auto_install.sh && sudo ARCH=$(ARCH) bash $(ROOT_PATH)/tools/write_disk_image.sh --bios=legacy && cd .."
+ bash -c "export ARCH=$(ARCH); cd tools && bash grub_auto_install.sh && sudo DADK=$(DADK) ARCH=$(ARCH) bash $(ROOT_PATH)/tools/write_disk_image.sh --bios=legacy && cd .."
# 写入磁盘镜像(uefi)
-write_diskimage-uefi:
- bash -c "export ARCH=$(ARCH); cd tools && bash grub_auto_install.sh && sudo ARCH=$(ARCH) bash $(ROOT_PATH)/tools/write_disk_image.sh --bios=uefi && cd .."
+write_diskimage-uefi: check_arch
+ bash -c "export ARCH=$(ARCH); cd tools && bash grub_auto_install.sh && sudo DADK=$(DADK) ARCH=$(ARCH) bash $(ROOT_PATH)/tools/write_disk_image.sh --bios=uefi && cd .."
# 不编译,直接启动QEMU
-qemu:
+qemu: check_arch
sh -c "cd tools && bash run-qemu.sh --bios=legacy --display=window && cd .."
# 不编译,直接启动QEMU,不显示图像
-qemu-nographic:
+qemu-nographic: check_arch
sh -c "cd tools && bash run-qemu.sh --bios=legacy --display=nographic && cd .."
# 不编译,直接启动QEMU(UEFI)
-qemu-uefi:
+qemu-uefi: check_arch
sh -c "cd tools && bash run-qemu.sh --bios=uefi --display=window && cd .."
# 不编译,直接启动QEMU,使用VNC Display作为图像输出
-qemu-vnc:
+qemu-vnc: check_arch
sh -c "cd tools && bash run-qemu.sh --bios=legacy --display=vnc && cd .."
# 不编译,直接启动QEMU(UEFI),使用VNC Display作为图像输出
-qemu-uefi-vnc:
+qemu-uefi-vnc: check_arch
sh -c "cd tools && bash run-qemu.sh --bios=uefi --display=vnc && cd .."
# 编译并写入磁盘镜像
-build:
+build: check_arch
$(MAKE) all -j $(NPROCS)
$(MAKE) write_diskimage || exit 1
# 在docker中编译,并写入磁盘镜像
-docker:
+docker: check_arch
@echo "使用docker构建"
sudo bash tools/build_in_docker.sh || exit 1
# uefi方式启动
-run-uefi:
+run-uefi: check_arch
$(MAKE) write_diskimage-uefi || exit 1
$(MAKE) qemu-uefi
# 编译并启动QEMU
-run:
+run: check_arch
$(MAKE) qemu
# uefi方式启动,使用VNC Display作为图像输出
-run-uefi-vnc:
+run-uefi-vnc: check_arch
$(MAKE) qemu-uefi-vnc
# 编译并启动QEMU,使用VNC Display作为图像输出
-run-vnc:
+run-vnc: check_arch
$(MAKE) qemu-vnc
# 在docker中编译,并启动QEMU
-run-docker:
+run-docker: check_arch
@echo "使用docker构建并运行"
-fmt:
+fmt: check_arch
@echo "格式化代码"
FMT_CHECK=$(FMT_CHECK) $(MAKE) fmt -C kernel
FMT_CHECK=$(FMT_CHECK) $(MAKE) fmt -C user
@@ -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
@@ -0,0 +1,14 @@
+[metadata]
+# Filesystem type (options: `fat32`)
+fs_type = "fat32"
+# Size of the rootfs disk image (eg, `1G`, `1024M`)
+size = "1G"
+[partition]
+# Partition type (options: "none", "mbr", "gpt")
+#
+# If "none" is specified, no partition table will be created,
+# and the entire disk will be treated as a single partition.
+# Note that the "none" option is incompatible with GRUB boot.
+type = "mbr"
@@ -0,0 +1,24 @@
+# DADK 总控文件
+# Target architecture. Options: x86_64, riscv64
+arch = "x86_64"
+# Hypervisor config path
+hypervisor-config = "config/hypervisor.toml"
+# RootFS config path
+rootfs-config = "config/rootfs.toml"
+# Boot config path
+boot-config = "config/boot.toml"
+# System root directory folder (DADK will copy the files in this directory to the root directory of the disk image)
+sysroot-dir = "bin/sysroot"
+# DADK Root Cache directory path
+cache-root-dir = "bin/dadk_cache"
+# User configuration directory path
+# 这个字段只是临时用于兼容旧版本,v0.2版本重构完成后会删除
+user-config-dir = "user/dadk/config"
@@ -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
@@ -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
@@ -1,155 +0,0 @@
-# 开发流程介绍
-  作为一名想要参与开发的新人,您可能迫切想要了解如何参与开发,仔细阅读这篇文章将能帮助您了解整个开发流程,以及一些注意事项。
-  注:本文参考了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.开发过程是如何运作的?
-  如今的DragonOS由于正处于开发的早期阶段,开发者数量不超过50人,因此,现在DragonOS的开发过程是通过比较松散的方式组织起来的。
-### 2.1.版本发布周期
-  自从2022年11月6日,DragonOS发布第一个版本以来,版本发布就被定义为15~21天发布一个更新版本。由于开发人员数量仍然较少,因此,目前这个时间是21天。我们将版本号定义为`x.y.z`的格式,每21天发布一个`z`版本. 在积累了2~3个月后,当DragonOS引入了足够的新功能,则发布一个`y`版本。请注意,我们仍未定义`x`版本的发行周期。当前,`x`版本号仍然是`0`。
-  创建没有BUG的、具有尽可能少BUG的代码,是每个开发者的目标之一。我们希望在每个`y`版本发布时,能够修复已知的问题。然而,由于在操作系统中,影响问题的变量太多了,以至于我们只能尽全力去减少BUG,我们无法保证`y`版本不存在bug.
-### 2.2.每个补丁的生命周期
-  当您向DragonOS的仓库发起一次PR,那么这次PR就是一个补丁。我们会对您的补丁进行Review,以确保每个补丁都实现了一个希望在主线中进行的更改。并且,Maintainer或者感兴趣的小伙伴会对您的补丁提出修改意见。当时机合适时,您的代码将被合入主线。
-  如果您的补丁的规模比较小,那么,它将会比较快的被合入主线。如果补丁的规模较大,或者存在一些争议,那么我们需要对其进行进一步的讨论及修改、审查,直到它符合要求。
-  每个Patch都会经历这么一个过程(这只是一个概述,详细的描述请看后文):
-- **设计**:在这个阶段,我们将明确,这个补丁将要实现什么功能,或者是解决什么问题,以及我们将要采用什么样的方式去完成它。通常来说,这项工作是开发者自己完成的。但是,**我们建议您,在设计了这个补丁之后,能够把您的设计方案公开,和大家讨论这项工作。** 闭门造车容易出错,在与大家沟通的过程中,则能及早的发现设计上的错误,从而节省很多的时间。
-- **代码编写**:经过了设计阶段,您已经能够明白自己要实现的到底是一个什么样的东西。您在这个阶段进行代码编写、调试。
-- **代码审查**:当完成代码编写后,您可以通过Github发起一个PR,然后您的补丁进入了代码审查阶段。在这一阶段,开发者们,或者是Maintainer会和您进行沟通交流,对您的代码进行评论,以及对发现的问题提出修改建议。
-- **合并主线**:如果一切顺利,那么代码将会被合并入主线。若该补丁在合并主线后,被发现具有较大的问题,那么它可能会被回退。重新进入前面的阶段,进行修改。
-- **长期维护**:虽然说,代码被合并之后,原来的开发人员可能会在很久一段时间后,不再理会这些代码,但是这种行为可能会给其他开发者们留下不好的印象。其实,当补丁被合并入主线后,其他开发人员会尝试继续维护这些代码,这样能够很大程度的减轻您的维护负担。但是,如果想要这些代码能够长期的被保留下来,持续的发光发热,那么离不开原始开发人员的支持(不然的话,后来的人可能难以理解、维护这些代码),这是一件很有意义的事情。
-  对于没有参与过开源项目的同学来说,他们可能会想当然的,简单的把上述流程简化成**合并主线**这一个步骤,这样是不可取的。因为这样会导致很多问题,包括但不限于“写了代码但是效果很差”、“写的东西由于无法满足项目的需求,而不能被合并”。
-### 2.3.开发工具
-  从上面的描述可以看出,为了让开发人员们能高效地进行协作,那么必须使用版本控制工具来管理这个过程。目前,DragonOS使用Git来进行源代码管理。它是一个非常优秀的工具,这是不必多说的。对于每个开发者而言,Git的使用是一项必备的技能;哪怕您只是想学习DragonOS的源代码,您也需要git来获取、同步最新的代码。虽然Git的使用,对于新手来说,有些困难,但是经过一些时间的学习后,还是可以掌握的。
-  git的官方网站为[https://git-scm.com/](https://git-scm.com/)
-### 2.4.沟通交流
-  DragonOS的主要开发工作是通过飞书以及bbs进行的。对于正准备参与的开发者而言,您可以加入我们的交流讨论QQ群,具体的群号可以在 {ref}`与社区建立联系 <get_contact_with_community>` 一文中找到。
-  **何时使用即时通讯软件?** 我们在飞书上创建了工作群,为提高即时沟通的效率,这个群仅供那些真正有意愿、且正在进行或准备进行(能够保证会进行)代码开发的开发者们加入。
-  **何时使用BBS?** 对于一些正式的、需要大家广泛参与,或者是能够帮助尚未参与开发的同学了解当前的开发进度的主题,请您在[https://bbs.DragonOS.org](https://bbs.DragonOS.org)上,使用类似于写信件一样的,正式的语言,完整地描述清楚您想表达的内容。这样有助于更多的人快速明白您要表达的是什么,也能提高整体的沟通效率。并且,bbs能够长期保存以往的帖子,这样后来者能更方便的了解“当初开发的时候,人们究竟是怎么想的”。
-  **关于交流讨论会** 除由于法定节假日放假,或特殊情况以外,我们每周末都会召开线上交流讨论会,同步介绍每周的进展。社区成员可以在这里分享自己的方案设计或是一些操作系统相关的知识(分享的话,需要提前跟会议主持人提出,以便妥善安排)。
-  **如何提问?** 下面这些建议能够帮助您与他人开展高效率的对话:
-- **对于具有主题性的问题,在bbs上发帖进行讨论。** 这样能够让讨论更具有目标性。当谈论到新的主题的时候,请开一个新的帖子,并在原来的帖子中,添加对特定的子问题所在的主题的链接。
-- **请礼貌的交流。** 文明的语言能够减少不必要的冲突。技术意见上的冲突是思维的碰撞,但是如果涉及到了不文明的语言,或者在非技术层面,对他人进行攻击,这将破坏和谐讨论的氛围,这是我们反对的。如果有人试图激怒你,请忽略他的消息,别理他就好了。
-- **在提问之前,请确保您已经搜索了bbs以及互联网上的解决方案,并描述清楚您的问题的上下文情景、您的思考以及网络上的解决方案。** 一些开发人员会对“明显没有进行认真思考”的问题,表现出不耐烦的态度(因为未经思考的问题会浪费他们大量的时间)。
-- **当别人向您提问时**,请您耐心听他人的问题。如果您认为对方的问题过于简单或是未经思考,还请您为对方指个路,告诉对方,在哪些地方,使用什么样的方式搜索,能够得到对解决问题有帮助的资料。有时候,**新手需要的是一个指路人**,他会非常感谢您的!
-### 2.5.如何入门开发?
-  DragonOS原采用C语言进行开发,目前正在用Rust重构原有代码、开发新的模块,也就是说,除非您要进行对C语言代码的BUG修复,否则,其余的开发工作,我们都建议您通过Rust完成。因为,它能从语言层面解决那些让我们头疼的内存安全问题。从长期来看,能够提升开发效率以及软件质量。
-  如何开发第一个补丁,是一个非常常见的问题。可以理解的是,个人开发者面对这样一个项目,常常会不知道从哪个地方开始入手。这是一件很正常的事情,因此我们建议您通过上文提到的方式,与社区建立联系,了解目前社区正在做什么,以及需要什么。
-  对于一个新的参与者来说,我们建议您从这样一个步骤开始:
-```text
-阅读文档,编译、运行DragonOS,并且尝试使用它目前已有的功能。
-```
-  然后,您可以通过查看DragonOS的GitHub仓库的project面板,看看目前仍有哪些待解决的问题。可以肯定的是,永远不会缺少待解决的问题,您在解决这些问题的过程中,能够获得一些宝贵的经验。
-## 3.早期设计
-  对于软件开发而言,写代码永远不是第一步。在编写代码之前,进行一些必要的设计(提出架构、技术方案),是项目成功的基础工作。在新的补丁开发的早期,花费一些时间进行计划和沟通,往往能够在接下来的阶段节省更多的时间。
-### 3.1.定义我们要解决的问题
-  与大多数的工程项目一样,在DragonOS中进行开发,首先需要清晰的描述要解决的问题。只有精准的定义了问题,才能找到正确的解决方案。有时候,我们能很轻易的定义问题,比如“编写串口驱动程序,使得它能把屏幕上打印的字符,输出到串口”。
-  但是,有时候,**我们容易将真正的问题与某个解决方案相混淆**,并且还没意识到这一点。
-  在2022年10月时,我发现,在真机调试的时候,需要频繁的拔插硬盘(先连接到电脑,待数据拷贝完毕后,再连接到测试机)。我对这一过程非常的不满,因为很浪费时间。我的直觉想法是:“有没有一种设备,能够一头连接电脑,另一头连接测试机的SATA接口。从测试机的角度看来,这是一块硬盘;测试机对这块磁盘的操作,都转换为了对我的电脑上面的一个磁盘镜像文件的操作。”我的想法就是:“购买/定制一款设备,能够实现上面的这个功能,那就能解决频繁拔插硬盘的烦恼了!”然后我为了寻找这样的设备,询问了一些硬件公司,他们的开价都在2万元左右。
-  我在上面的这个过程中,就犯了一个错误:将真正的问题与某个解决方案相混淆了。真正的问题是:“解决需要频繁的拔插硬盘”,但是,在我的思考的过程中,不知不觉间,将问题转换成了“如何实现一款具有硬盘模拟功能的设备”。后面这个问题,只是某个解决方案下,需要解决的问题,并不是我们要解决的根本问题。
-  对于要解决的根本问题,事实上有更好的解决方案:“制作一个类似于开关一样的转换器,当数据从电脑拷贝到磁盘后,把开关拨向另一边,使得电路与测试机接通”。这个方案的成本估摸着就十几二十块钱。
-  上面的这个故事,告诉我们的是,**在早期设计阶段,我们应当关注的是问题本身——而不是特定的解决方案**。
-  我们需要关注系统的稳定性、长期的可维护性,解决方案必须考虑到这两点。由于系统比较复杂,因此,请您在开始编码之前,与社区的小伙伴讨论您的设计方案,以便您的方案能充分地,从全局的角度,考虑到系统的稳定性、可维护性。
-  **因此,在开发的早期,我们应当对以下三个问题,拥有一个答案**:
-- 要解决的本质问题是什么?
-- 这个问题会影响哪些方面/哪些用户?提出的解决方案应当解决哪些用例、场景?
-- DragonOS目前在解决该问题的方面,具有哪些不足/问题?
-  只有考虑清楚了上面三个问题,讨论的解决方案才是有意义的。这是一个架构设计的过程,需要进行仔细的思考。尽管我们目前提倡敏捷开发,但是前期的架构设计仍然是非常重要的。
-### 3.2.早期讨论
-  在实施开发之前,与社区的成员们进行讨论是非常有意义的。这能够通过多种方式节省您的时间,并减少许多不必要的麻烦:
-- DragonOS可能以您不知道、不理解的方式,已经解决了相关的问题。DragonOS里面的一些特性、功能细节不是很明显,他们不一定会被写进文档。也许这些细节只是在某个不起眼的注释里面提到了,因此您很难注意到这些。这种细节可能只有一些开发人员知道。因此,与社区沟通能够避免您进行重复的工作。
-- 您提出的解决方案中,可能会有一些东西,由于一些原因(比如方案中的一些设计会在将来造成问题、方案的架构设计具有明显缺陷),无法合入主线。
-- 其他的开发人员可能已经考虑过这个问题;他们可能有更好的解决方案,或者是更好的想法。并且,他们可能愿意帮助你一起完善你的解决方案。
-  Linux文档中提到:闭门造车式的设计和开发,所产生的代码总会有问题,这些问题只有在发布到社区里面的时候才会暴露出来。因此,我们必须吸取前人之鉴,通过与社区开发人员进行早期讨论,从而避免大量的痛苦和额外的工作。
-### 3.3.在何时发布帖子?
-  如果可能的话,在开发的早期阶段发布您的计划、设计,会是一个不错的选择。发帖的时候,您可以描述您正在解决的问题,以及已经制定的一些计划。包括但不限于:如何将设计付诸实施。您在社区内发布帖子,不仅能够帮助您获得一些有用的建议,也能帮助整个DragonOS社区提供有用的信息,使得社区沟通更高效。
-  在这个阶段,可能您发布的帖子并不会引来很多评论,这并不一定意味着您做的不好,或者大家对您所做的工作不感兴趣。当然,也不能就此认为您的设计、想法不存在问题。可能只是因为大家比较忙,看了您的帖子之后,了解到了您的工作,但是大家并不一定有时间进行回复。(但是事实上您发布的信息对他人来说是有用的)
-  在这种情况下,请不要气馁,您最好的做法就是,继续您的工作,并且不时地在您的帖子下分享您的工作,这样能够让社区的成员们随时了解到您的最新进展。
-### 3.4.获得您所在的组织的支持
-  如果您对DragonOS的开发工作,是在您的公司内完成的。那么,很显然,在您把计划、代码发布到社区论坛之前,您必须取得您的经理或上级的许可。
-  同时,请注意,根据我们的授权许可,基于DragonOS操作系统的内核、官方开源的用户库而开发的代码,或者为DragonOS操作系统本身而开发的代码,根据开源授权许可,必须同样以GPLv2协议进行授权发布。如果您所在的组织,违背了GPLv2协议中的要求,以除GPLv2以外的协议开放源代码,或者是进行“闭源使用”,那么DragonOS社区对您的公司/组织所授予的使用DragonOS源代码的授权,将会被自动撤销。这将会面临一系列的法律问题。因此,在这个问题上,公司的管理人员、法律人员如果能越早地就公司要在DragonOS中开发的软件项目达成一致,将能促进您的公司在该项目上的进展。
-  如果您的公司的项目/或者是您研究的项目根据您所在组织的保密规定,不能将其进行过早的披露,那也没有问题。只要您的公司能够确保这部分代码,在其编译而成的二进制产品被发布之时,按照GPLv2协议进行开源,并向开源社区贡献这部分代码即可。
-## 4.如何正确的编写代码
-## 5.发起Pull Request
-## 6.后期跟进
-## 7.另外的一些话题
-## 8.更多信息
-## 9.结语
@@ -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
@@ -30,7 +30,10 @@
kernel/debug/index
kernel/ktest/index
kernel/cpu_arch/index
+ kernel/container/index
kernel/libs/index
+ kernel/trace/index
@@ -0,0 +1,109 @@
+# 内核启动命令行参数
+:::{note}
+本文作者:
+- 龙进 <longjin@DragonOS.org>
+:::
+## 概述
+  DragonOS内核启动命令行参数解析模块旨在提供类似Linux的内核启动命令行参数解析支持,以便更灵活地让内核执行不同的行为。该模块允许内核在启动时接收并解析命令行参数,根据参数的不同类型执行相应的回调函数或设置环境变量。
+暂时不支持设置回调函数
+## 设计方案
+### 参数类型
+内核启动命令行参数分为三种类型:
+- Arg类型
+- KV类型
+- EarlyKV类型
+#### Arg类型
+Arg类型的参数在命令行中只有名称,没有值。分为以下两种类型:
+- ArgNormal:默认值为`false`,如果命令行中包含这个参数,则会设置为`true`。
+- ArgInv:默认值为`true`,如果命令行中包含这个参数,则会设置为`false`。
+#### KV类型
+KV类型的参数在命令行中表现为`name=value`,`value`按照逗号分隔。内核模块可提供参数的默认值。
+#### EarlyKV类型
+EarlyKV类型的参数与KV类型类似,但它们在内存管理初始化之前被解析。
+### Module标志
+Module标志类似于`usbprobe.xxxx`。
+### 参数声明
+提供宏来声明内核命令行参数。
+### procfs支持
+TODO: 在`/proc/cmdline`下显示当前内核的启动命令行参数。
+## 声明内核启动命令行参数的宏
+### Arg类型参数声明
+```rust
+kernel_cmdline_param_arg!(varname, name, default_bool, inv);
+```
+- `varname`:参数的变量名
+- `name`:参数的名称
+- `default_bool`:默认值
+- `inv`:是否反转
+### KV类型参数声明
+kernel_cmdline_param_kv!(varname, name, default_str);
+- `default_str`:默认值
+### 内存管理初始化之前的KV类型参数声明
+kernel_cmdline_param_early_kv!(varname, name, default_str);
+## 示例
+以下示例展示了如何声明和使用KV类型参数:
+kernel_cmdline_param_kv!(ROOTFS_PATH_PARAM, root, "");
+if let Some(rootfs_dev_path) = ROOTFS_PATH_PARAM.value_str() {
+ .......
+} else {
+};
+### 使用方式
+1. 在内核代码中,使用`kernel_cmdline_param_kv!`宏声明所需的KV类型参数。
+2. 在内核初始化过程中,通过参数的`value_str()`或者`value_bool()`方法获取参数值。
+3. 根据参数值执行相应的操作。
+通过以上步骤,开发者可以灵活地使用内核启动命令行参数来控制内核行为。
+## TODO
+- 支持在`/proc/cmdline`下显示当前内核的启动命令行参数。(需要在procfs重构后)
+- 支持设置回调函数,调用回调函数来设置参数值
@@ -1,10 +1,9 @@
引导加载
====================================
- DragonOS采用GRUB2作为其引导加载程序,支持Multiboot2协议引导。目前仅支持GRUB2.06版本。
:caption: 目录
bootloader
+ cmdline
@@ -0,0 +1,13 @@
+====================================
+容器化
+ 这里是DragonOS中,与容器化相关的说明文档。
+ 主要包括namespace,overlayfs和cgroup
+.. toctree::
+ :maxdepth: 2
+ namespaces/index
+ ../filesystem/unionfs/index
+命名空间
+DragonOS的namespaces目前支持pid_namespace和mnt_namespace 预计之后会继续完善
+namespace是容器化实现过程中的重要组成部分
+由于目前os是单用户,user_namespace为全局静态
+ :maxdepth: 1
+ pid_namespace
+ mnt_namespace
@@ -0,0 +1,19 @@
+# 挂载命名空间
+## 底层架构
+pcb -> nsproxy -> mnt_namespace
+每一个挂载文件系统都有自立独立的挂载点,表现在数据结构上是一个挂载的红黑树,每一个命名空间中挂载是独立的,所以文件系统的挂载和卸载不会影响别的
+## 系统调用接口
+- clone
+ - CLONE_NEWNS用于创建一个新的 MNT 命名空间。提供独立的文件系统挂载点
+- unshare
+ - 使用 CLONE_NEWPID 标志调用 unshare() 后,后续创建的所有子进程都将在新的命名空间中运行。
+- setns
+ - 将进程加入到指定的命名空间
+- chroot
+ - 将当前进程的根目录更改为指定的路径,提供文件系统隔离。
@@ -0,0 +1,23 @@
+# 进程命名空间
+:::{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_NEWPID用于创建一个新的 PID 命名空间。使用这个标志时,子进程将在新的 PID 命名空间内运行,进程 ID 从 1 开始。
+- getpid
+ - 在命名空间中调用 getpid() 会返回进程在当前 PID 命名空间中的进程 ID
@@ -9,3 +9,4 @@
traceback
debug-kernel-with-gdb
+ profiling-kernel-with-dadk
@@ -0,0 +1,95 @@
+# 使用DADK对内核进行性能分析
+## 1. 概述
+本文将教你使用DADK,对DragonOS内核进行性能分析,以识别和解决潜在的性能瓶颈。
+### 1.1 准备工作
+::: {note}
+在开始之前,请确保你已经安装了DADK,并且已经配置好了DragonOS内核的编译环境。
+### 1.2 什么是火焰图?
+如果你没有听说过火焰图,可以先阅读这篇文章:[《如何读懂火焰图?- 阮一峰》](https://www.ruanyifeng.com/blog/2017/09/flame-graph.html)
+简单的说,火焰图是基于性能采样结果产生的 SVG 图片,用来展示 CPU 的调用栈。
+
+x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
+火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
+颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。
+## 2. 配置DragonOS内核
+由于性能分析需要详尽的符号表数据,因此我们需要在编译内核时,需要进行以下配置:
+在`kernel/Cargo.toml`中的`[profile.release]`部分,设置以下两项:
+```toml
+[profile.release]
+debug = true
+opt-level = 1
+这样,编译出来的内核就会包含符号表数据,方便我们进行性能分析。
+## 3. 使用DADK进行性能分析
+### 3.1 启动内核
+首先,我们需要启动DragonOS内核。
+```shell
+# 使用你喜欢的方式启动内核,例如:
+make run
+# 或者
+make build && make qemu-nographic
+### 3.2 运行你的工作负载
+在启动内核后,我们需要运行一些工作负载,以便进行性能分析。
+这可以是一个应用程序,也可以是别的东西。甚至你可以什么都不运行,只是单纯看看DragonOS内核在空闲时的调用栈情况。
+### 3.3 启动DADK进行性能分析
+在DragonOS项目目录下,运行以下命令:
+dadk profile sample --format flamegraph --output flame.svg --interval 200ms --duration 20s --cpu-mask 0x1
+上面的命令,将会对DragonOS内核进行性能分析,并生成一个火焰图。
+详细解释:
+- `--format flamegraph`:指定输出格式为火焰图。
+- `--output flame.svg`:指定输出文件名为`flame.svg`。
+- `--interval 200ms`:指定采样间隔为200ms。
+- `--duration 20s`:指定采样时间为20s。
+- `--cpu-mask 0x1`:指定采样的CPU为0号CPU。(这是个按位掩码,也就是说,如果要采样0和1号CPU,那么cpu-mask为0x3)
+*更多参数请参考`dadk profile sample --help`.*
+由于采样时会暂停vCPU,因此采样时间不宜过短,否则会影响系统的正常运行。
+经过一段时间的等待,你将会得到一个`flame.svg`文件。
+### 3.4 分析火焰图
+使用浏览器打开`flame.svg`文件,你将会看到一个火焰图。
+你可以通过点击火焰图中的某个函数,来查看它的调用栈。
+**你可以右键下面的图片,在新的标签页打开,体验交互效果。**
@@ -13,4 +13,5 @@ todo: 由于文件系统模块重构,文档暂时不可用,预计在2023年4
vfs/index
sysfs
kernfs
+ unionfs/index
@@ -0,0 +1,10 @@
+联合文件系统
+Union Filesystem:
+OverlayFS 将多个文件系统(称为“层”)合并为一个逻辑文件系统,使用户看到一个统一的目录结构。
+ overlayfs
@@ -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,具体的有关文件文件夹的操作都在
@@ -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的运行流程
+
+如图所示,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的libbpf,python的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来进一步区分功能,目前对其支持如下:
+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基本添加了支持:
+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的实现定义:
+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<()> {
+ /// 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<()> {
+ /// 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> {
+ /// 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<()> {
+ /// 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]>> {
+ /// 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<()> {
+ /// Push an element value in map.
+ fn push_elem(&mut self, _value: &[u8], _flags: u64) -> Result<()> {
+ /// Pop an element value from map.
+ fn pop_elem(&mut self, _value: &mut [u8]) -> Result<()> {
+ /// Peek an element value from map.
+ fn peek_elem(&self, _value: &mut [u8]) -> Result<()> {
+ /// Freeze the map.
+ /// It's useful for .rodata maps.
+ fn freeze(&self) -> Result<()> {
+ /// 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进行实现,目前只支持其中两个功能:
+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不同的事件也实现定义的接口:
+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操作相关:
+/// 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);
@@ -0,0 +1,11 @@
+内核跟踪机制
+ 内核跟踪机制由很多功能构成, 比如kprobe/uprobe/tracepoint/ftrace等, 以及用于扩展内核可观测性的eBPF,内核当前支持kprobe和eBPF, 本章将介绍这两种机制。
+ :caption: 目录
+ eBPF
+ kprobe
@@ -0,0 +1,57 @@
+# kprobe
+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的接口
+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` 返回探测点的函数名称
@@ -13,3 +13,6 @@ musl-gcc -static -o hello hello.c
在移植现有程序时,可能需要配置`CFLAGS`和`LDFLAGS`,以及`CPPFLAGS`,以便正确地编译,具体请以实际为准。
+## 配置dadk
+请参考:[Quick Start | DADK](https://docs.dragonos.org.cn/p/dadk/user-manual/quickstart.html)
@@ -8,3 +8,5 @@
rust-quick-start
c-cpp-quick-start
+ 快速使用DADK打包一个应用到DragonOS <https://docs.dragonos.org.cn/p/dadk/user-manual/quickstart.html>
+ DADK完整文档 <https://docs.dragonos.org.cn/p/dadk/>
@@ -2,19 +2,14 @@
## 编译环境
-  DragonOS与Linux具有部分二进制兼容性,因此可以使用Linux的Rust编译器进行编译,但是需要进行一些配置:
-您可以参考DragonOS的`tools/bootstrap.sh`中,`initialize_userland_musl_toolchain()`函数的实现,进行配置。
-或者,只要运行一下bootstrap.sh就可以了。
-主要是因为DragonOS还不支持动态链接,但是默认的工具链里面,包含了动态链接解释器相关的代码,因此像脚本内那样,进行替换就能运行。
+  DragonOS与Linux具有部分二进制兼容性,因此可以使用Linux的Rust编译器进行编译。
## 配置项目
### 从模板创建
:::{note}
-该功能需要dadk 0.1.4及以上版本方能支持
+该功能需要dadk 0.2.0及以上版本方能支持。旧版的请参考历史版本的DragonOS文档。
:::
1. 使用DragonOS的tools目录下的`bootstrap.sh`脚本初始化环境
@@ -30,7 +25,7 @@ cargo generate --git https://git.mirrors.dragonos.org/DragonOS-Community/Rust-Ap
```
4. 使用`cargo run`来运行项目
-5. 在DragonOS的`user/dadk/config`目录下,使用`dadk new`命令,创建编译配置,安装到DragonOS的`/`目录下。
+5. 在DragonOS的`user/dadk/config`目录下,参考模版[userapp_config.toml](https://github.com/DragonOS-Community/DADK/blob/main/dadk-config/templates/config/userapp_config.toml),创建编译配置,安装到DragonOS的`/`目录下。
(在dadk的编译命令选项处,请使用Makefile里面的`make install`配置进行编译、安装)
6. 编译DragonOS即可安装
@@ -8,3 +8,6 @@ endif
ifeq ($(EMULATOR), )
export EMULATOR=__NO_EMULATION__
+export DADK?=$(shell which dadk)
@@ -15,15 +15,19 @@ members = [
]
[features]
-default = ["backtrace", "kvm", "fatfs", "fatfs-secure"]
+default = ["backtrace", "kvm", "fatfs", "fatfs-secure", "static_keys_test"]
# 内核栈回溯
-backtrace = []
+backtrace = ["dep:unwinding"]
# kvm
kvm = []
fatfs = []
fatfs-secure = ["fatfs"]
+driver_ps2_mouse = []
+kprobe_test = []
+static_keys_test = []
# 运行时依赖项
[dependencies]
@@ -41,6 +45,7 @@ fdt = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/fdt", rev
hashbrown = "=0.13.2"
ida = { path = "crates/ida" }
intertrait = { path = "crates/intertrait" }
+kcmdline_macros = { path = "crates/kcmdline_macros" }
kdepends = { path = "crates/kdepends" }
klog_types = { path = "crates/klog_types" }
linkme = "=0.3.27"
@@ -57,11 +62,23 @@ 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 }
+static-keys = "=0.6.1"
+unwinding = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/unwinding", rev = "4eb845da62", default-features = false, optional = true, features = [
+ "unwinder",
+ "fde-gnu-eh-frame-hdr",
+ "panic",
+ "personality"
+]}
# 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" }
multiboot2 = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/multiboot2", rev = "05739aab40" }
raw-cpuid = "11.0.1"
x86 = "=0.52.0"
@@ -6,7 +6,7 @@ include ./env.mk
ifeq ($(ARCH), x86_64)
export TARGET_JSON=arch/x86_64/x86_64-unknown-none.json
else ifeq ($(ARCH), riscv64)
- export TARGET_JSON=riscv64gc-unknown-none-elf
+ export TARGET_JSON=arch/riscv64/riscv64gc-unknown-none-elf.json
export CARGO_ZBUILD=-Z build-std=core,alloc,compiler_builtins -Z build-std-features=compiler-builtins-mem
@@ -27,21 +27,17 @@ clean:
fmt:
RUSTFLAGS="$(RUSTFLAGS)" cargo fmt --all $(FMT_CHECK)
- RUSTFLAGS="$(RUSTFLAGS)" cargo clippy --all-features
+ RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 clippy --all-features
.PHONY: check
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)
-else ifeq ($(ARCH), riscv64)
- RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-07-23 check --workspace $(CARGO_ZBUILD) --message-format=json --target $(TARGET_JSON)
-endif
+ $(MAKE) -C src check ARCH=$(ARCH)
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
@@ -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,
@@ -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>
fn default() -> Self {
Self::new()
@@ -25,12 +25,12 @@ where
impl<const N: usize> StaticBitmap<N>
/// 创建一个新的静态位图
pub const fn new() -> 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>
#[inline]
fn get(&self, index: usize) -> Option<bool> {
@@ -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)]
@@ -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 {
@@ -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) {
@@ -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(|| {
@@ -0,0 +1,6 @@
+[package]
+name = "kcmdline_macros"
+version = "0.1.0"
+edition = "2021"
@@ -0,0 +1,74 @@
+#![no_std]
+#![deny(clippy::all)]
+#![allow(clippy::crate_in_macro_def)]
+/// 定义一个bool类型的参数
+///
+/// # 参数
+/// - `$varname`: 参数的变量名
+/// - `$name`: 参数的名称
+/// - `$default_bool`: 默认值
+/// - `$inv`: 是否反转
+#[macro_export]
+macro_rules! kernel_cmdline_param_arg {
+ ($varname:ident, $name:ident, $default_bool:expr, $inv:expr) => {
+ #[::linkme::distributed_slice(crate::init::cmdline::KCMDLINE_PARAM_ARG)]
+ static $varname: crate::init::cmdline::KernelCmdlineParameter =
+ crate::init::cmdline::KernelCmdlineParamBuilder::new(
+ stringify!($name),
+ crate::init::cmdline::KCmdlineParamType::Arg,
+ )
+ .default_bool($default_bool)
+ .inv($inv)
+ .build()
+ .unwrap();
+/// 定义一个key-value类型的参数
+/// - `$default_str`: 默认值
+macro_rules! kernel_cmdline_param_kv {
+ ($varname:ident, $name:ident, $default_str:expr) => {
+ #[::linkme::distributed_slice(crate::init::cmdline::KCMDLINE_PARAM_KV)]
+ crate::init::cmdline::KCmdlineParamType::KV,
+ .default_str($default_str)
+/// 定义一个内存管理初始化之前就要设置的key-value类型的参数
+macro_rules! kernel_cmdline_param_early_kv {
+ #[::linkme::distributed_slice(crate::init::cmdline::KCMDLINE_PARAM_EARLY_KV)]
+ static $varname: crate::init::cmdline::KernelCmdlineParameter = {
+ static ___KV: crate::init::cmdline::KernelCmdlineEarlyKV = {
+ const { assert!($default_str.len() < KernelCmdlineEarlyKV::VALUE_MAX_LEN) };
+ crate::init::cmdline::KCmdlineParamType::EarlyKV,
+ .build_early_kv()
+ .unwrap()
+ crate::init::cmdline::KernelCmdlineParameter::EarlyKV(&___KV)
@@ -1,5 +1,4 @@
#![no_std]
-#![feature(const_refs_to_cell)]
#![feature(const_size_of_val)]
+name = "kprobe"
+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 }
@@ -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>,
+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) };
+ 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) };
+ 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);
+ "Kprobe::install: address: {:#x}, func_name: {:?}, opcode: {:x?}",
+ self.symbol,
+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
@@ -0,0 +1,211 @@
+use alloc::boxed::Box;
+use alloc::string::String;
+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;
+pub use loongarch64::*;
+pub use rv64::*;
+pub use x86::*;
+pub type KprobePoint = X86KprobePoint;
+pub type KprobePoint = Rv64KprobePoint;
+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`的值加上探测点指令的长度
+ /// # 返回设置break断点的地址
+ /// 其值与探测点地址相等
+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);
+ pub fn new(
+ pre_handler: fn(&dyn ProbeArgs),
+ post_handler: fn(&dyn ProbeArgs),
+ ) -> 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);
+ pub fn with_event_callback(mut self, event_callback: Box<dyn CallBackFunc>) -> Self {
+ self.event_callback = Some(event_callback);
+ /// 获取探测点的地址
+ /// 探测点的地址 == break指令的地址
+ pub fn probe_addr(&self) -> usize {
+ self.symbol_addr + self.offset
+pub struct KprobeBasic {
+ fault_handler: ProbeHandler,
+pub trait CallBackFunc: Send + Sync {
+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()
+ 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,
@@ -0,0 +1,157 @@
+use core::{
+ arch::riscv64::sfence_vma_all,
+ fmt::Debug,
+ ops::{Deref, DerefMut},
+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;
+ point: Arc<Rv64KprobePoint>,
+enum OpcodeTy {
+ Inst16(u16),
+ Inst32(u32),
+pub struct Rv64KprobePoint {
+ old_instruction: OpcodeTy,
+ pub fn probe_point(&self) -> &Arc<Rv64KprobePoint> {
+impl Drop for Rv64KprobePoint {
+ match self.old_instruction {
+ OpcodeTy::Inst16(inst_16) => unsafe {
+ core::ptr::write(address as *mut u16, inst_16);
+ },
+ OpcodeTy::Inst32(inst_32) => unsafe {
+ sfence_vma_all();
+ self.old_instruction
+ pub fn install(self) -> (Kprobe, Arc<Rv64KprobePoint>) {
+ fn replace_inst(&self) -> Arc<Rv64KprobePoint> {
+ 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],
+ if is_inst_16 {
+ point.old_instruction = OpcodeTy::Inst16(inst_16);
+ 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);
+ point.old_instruction = OpcodeTy::Inst32(inst_32);
+ point.old_instruction
+ Arc::new(point)
+impl KprobeOps for Rv64KprobePoint {
+ OpcodeTy::Inst16(_) => address + 2,
+ OpcodeTy::Inst32(_) => address + 4,
+ OpcodeTy::Inst16(_) => self.inst_tmp.as_ptr() as usize + 2,
+ OpcodeTy::Inst32(_) => self.inst_tmp.as_ptr() as usize + 4,
@@ -0,0 +1,135 @@
+use alloc::string::ToString;
+use yaxpeax_arch::LengthedInstruction;
+const EBREAK_INST: u8 = 0xcc; // x86_64: 0xcc
+const MAX_INSTRUCTION_SIZE: usize = 15; // x86_64 max instruction length
+ point: Arc<X86KprobePoint>,
+pub struct X86KprobePoint {
+ old_instruction: [u8; MAX_INSTRUCTION_SIZE],
+ old_instruction_len: usize,
+impl Drop for X86KprobePoint {
+ 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();
+ inst.to_string()
+impl Debug for Kprobe {
+ .field("basic", &self.basic)
+ .field("point", &self.point)
+ pub fn install(self) -> (Kprobe, Arc<X86KprobePoint>) {
+ fn replace_inst(&self) -> Arc<X86KprobePoint> {
+ let mut inst_tmp = [0u8; MAX_INSTRUCTION_SIZE];
+ address as *const u8,
+ inst_tmp.as_mut_ptr(),
+ MAX_INSTRUCTION_SIZE,
+ 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 {
+ old_instruction: inst_tmp,
+ old_instruction_len: len as usize,
+ });
+ core::ptr::write_volatile(address as *mut u8, EBREAK_INST);
+ "Kprobe::install: address: {:#x}, func_name: {:?}",
+ self.symbol
+ point
+ pub fn probe_point(&self) -> &Arc<X86KprobePoint> {
+impl KprobeOps for X86KprobePoint {
+ self.addr + self.old_instruction_len
+ self.old_instruction.as_ptr() as usize
+ self.old_instruction.as_ptr() as usize + self.old_instruction_len
@@ -0,0 +1,7 @@
+#![cfg_attr(target_arch = "riscv64", feature(riscv_ext_intrinsics))]
+extern crate alloc;
+mod arch;
+pub use arch::*;
@@ -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
+ RUST: beta
+ RUST: nightly
+build_script: mk/appveyor.bat
@@ -0,0 +1,2 @@
+target
+Cargo.lock
@@ -0,0 +1,78 @@
+# 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"
+# Packaging directives
+include = [
+ "src/**",
+ "examples/**",
+ "tests/**",
+ "bench/**",
+ "LICENSE*",
+ "Cargo.toml",
+]
+# 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"]
+name = "uptime"
+name = "to_json"
+name = "rbpf_plugin"
@@ -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
+ (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.
@@ -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.
@@ -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
+[](https://github.com/qmonnet/rbpf/actions/workflows/test.yaml)
+[](https://ci.appveyor.com/project/qmonnet/rbpf/branch/main)
+[](https://coveralls.io/github/qmonnet/rbpf?branch=main)
+[](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:
+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`:
+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`:
+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:
+// 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.
+// 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_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.
+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).
+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.
+// 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
+ 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.
+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.
+pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8],
+pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8]) -> Result<(u64), Error>
+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`.
+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`.
+ 0x71, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxh r0, [r1+2]
+ // 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`.
+ // 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
+ 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];
+ 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();
+ assert_eq!(vm.execute_program(mem, mbuff).unwrap(), 0x2211);
+ // Here again we JIT-compile the program.
+ // Here we must provide both a reference to the packet data, and to the
+ // metadata buffer we use.
+ 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.
+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.
+extern crate elf;
+use std::path::PathBuf;
+use rbpf::helpers;
+ // 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,
+ 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.
+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:
+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.
+use rbpf::disassembler::disassemble;
+let prog = &[
+ 0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 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.
+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.
+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)
@@ -0,0 +1 @@
+doc-valid-idents = ["eBPF", "uBPF"]
+// SPDX-License-Identifier: (Apache-2.0 OR MIT)
+// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
+use rbpf::disassembler;
+// Simply disassemble a program into human-readable instructions.
+ 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);
@@ -0,0 +1,3 @@
+ rbpf::helpers::show_helper();
@@ -0,0 +1,115 @@
+// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
+#![allow(clippy::unreadable_literal)]
+// 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:
+// 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.
+ let file = match elf::File::open_path(path) {
+ let packet1 = &mut [
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
+ 0x00, // ethertype
+ 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.
+ 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 [
+ 0x98, 0x76, 0xc6, 0xcc, // start tcp_hdr
+ vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
+ let res = vm.execute_program(packet1).unwrap();
+ println!("Packet #1, program returned: {res:?} ({res:#x})");
+ assert_eq!(res, 0xffffffff);
+ #[cfg(not(windows))]
+ {
+ 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);
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: (APACHE-2.0 OR MIT)
+// 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)
+ if (iph->protocol != IPPROTO_TCP)
+ if (tcp->source == BLOCKED_TCP_PORT || tcp->dest == BLOCKED_TCP_PORT)
+ return -1;
@@ -0,0 +1,126 @@
+// Copyright Microsoft Corporation
+// 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.
+ 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());
+ // 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");
+ #[cfg(all(not(windows), feature = "std"))]
+ jit = true;
+ "--cranelift" => {
+ cranelift = true;
+ #[cfg(not(feature = "cranelift"))]
+ let _ = cranelift;
+ println!("Cranelift is not enabled");
+ "--program" => {
+ if args.len() < 2 {
+ println!("Missing argument to --program");
+ program_text.clone_from(&args[0]);
+ _ => panic!("Unknown argument {}", args[0]),
+ if program_text.is_empty() {
+ // Read program text from stdin
+ std::io::stdin().read_to_string(&mut program_text).unwrap();
+ 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 {
+ result = vm.execute_program_jit(&mut memory).unwrap();
+ } else if cranelift {
+ #[cfg(feature = "cranelift")]
+ vm.cranelift_compile().unwrap();
+ result = vm.execute_program_cranelift(&mut memory).unwrap();
+ result = vm.execute_program(&mut memory).unwrap();
+ println!("{result:x}");
+#[macro_use]
+extern crate json;
+// 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.
+ // Let's reuse this file from `load_elf/example`.
+ println!("{}", to_json(prog));
+// 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.
+ let prog1 = &[
+ 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>
+ // Create a VM: this one takes no data. Load prog1 in it.
+ let mut vm = rbpf::EbpfVmNoData::new(Some(prog1)).unwrap();
+ // Execute prog1.
+ // 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)
+ let time;
+ time = unsafe { vm.execute_program_jit().unwrap() };
+ 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
@@ -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
+if [%Platform%] NEQ [Win32] exit 1
+set TARGET_ARCH=i686
+set TARGET_PROGRAM_FILES=%ProgramFiles(x86)%
+call %VCVARSALL% amd64_x86
+:download
+REM vcvarsall turns echo off
+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')"
+ echo ...downloading Rust failed.
+start /wait msiexec /i build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi INSTALLDIR="%TARGET_PROGRAM_FILES%\Rust %RUST%" /quiet /qn /norestart
+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%
+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%
+REM Verify that we can build with all features
+cargo build --all-features -vv %CARGO_MODE%
+group_imports="StdExternalCrate"
+reorder_imports=true
+imports_granularity="Crate"
@@ -0,0 +1,642 @@
+// 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>
+ 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>
+ char('r')
+ .with(many1(digit()))
+ .map(|x: String| x.parse::<i64>().unwrap())
+fn operand<I>() -> impl Parser<I, Output = Operand>
+ 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>
+ 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)) {
+#[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(), "*")));
+ 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, "")));
+ integer().parse("0x123456789abcdef0"),
+ Ok((0x123456789abcdef0, ""))
+ assert_eq!(integer().parse("-0x1f"), Ok((-31, "")));
+ fn test_register() {
+ assert_eq!(register().parse("r0"), Ok((0, "")));
+ assert_eq!(register().parse("r15"), Ok((15, "")));
+ 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), "")));
+ operand().parse("[r3+0x1f]"),
+ Ok((Operand::Memory(3, 31), ""))
+ operand().parse("[r3-0x1f]"),
+ Ok((Operand::Memory(3, -31), ""))
+ fn test_instruction() {
+ instruction().parse("exit"),
+ Ok((
+ Instruction {
+ name: "exit".to_string(),
+ operands: vec![],
+ ""
+ ))
+ instruction().parse("call 2"),
+ name: "call".to_string(),
+ operands: vec![Operand::Integer(2)],
+ instruction().parse("addi r1, 2"),
+ name: "addi".to_string(),
+ operands: vec![Operand::Register(1), Operand::Integer(2)],
+ instruction().parse("ldxb r2, [r1+12]"),
+ name: "ldxb".to_string(),
+ operands: vec![Operand::Register(2), Operand::Memory(1, 12)],
+ instruction().parse("lsh r3, 0x8"),
+ name: "lsh".to_string(),
+ operands: vec![Operand::Register(3), Operand::Integer(8)],
+ instruction().parse("jne r3, 0x8, +37"),
+ name: "jne".to_string(),
+ operands: vec![
+ Operand::Register(3),
+ Operand::Integer(8),
+ Operand::Integer(37)
+ ],
+ // Whitespace between operands is optional.
+ instruction().parse("jne r3,0x8,+37"),
+ // Other unit tests: try to parse various set of instructions.
+ fn test_empty() {
+ assert_eq!(parse(""), Ok(vec![]));
+ fn test_exit() {
+ // No operands.
+ parse("exit"),
+ Ok(vec![Instruction {
+ }])
+ fn test_lsh() {
+ // Register and immediate operands.
+ parse("lsh r3, 0x20"),
+ operands: vec![Operand::Register(3), Operand::Integer(0x20)],
+ fn test_ja() {
+ // Jump offset operand.
+ parse("ja +1"),
+ name: "ja".to_string(),
+ operands: vec![Operand::Integer(1)],
+ fn test_ldxh() {
+ // Register and memory operands.
+ parse("ldxh r4, [r1+12]"),
+ name: "ldxh".to_string(),
+ operands: vec![Operand::Register(4), Operand::Memory(1, 12)],
+ 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
+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
+ldxb r3, [r4+1]
+add r3, r6
+lsh r3, 0x20
+arsh r3, 0x20
+jsgt r2, r3, -18
+ja +1
+mov r0, 0x1
+";
+ parse(src),
+ Ok(vec![
+ operands: vec![Operand::Register(3), Operand::Memory(1, 13)],
+ name: "or".to_string(),
+ operands: vec![Operand::Register(3), Operand::Register(2)],
+ name: "mov".to_string(),
+ operands: vec![Operand::Register(0), Operand::Integer(0)],
+ operands: vec![Operand::Register(2), Operand::Memory(1, 23)],
+ Operand::Register(2),
+ Operand::Integer(6),
+ Operand::Integer(35)
+ operands: vec![Operand::Register(2), Operand::Memory(1, 14)],
+ name: "add".to_string(),
+ operands: vec![Operand::Register(1), Operand::Integer(14)],
+ name: "and".to_string(),
+ operands: vec![Operand::Register(2), Operand::Integer(15)],
+ operands: vec![Operand::Register(2), Operand::Integer(2)],
+ operands: vec![Operand::Register(1), Operand::Register(2)],
+ operands: vec![Operand::Register(1), Operand::Integer(20)],
+ name: "rsh".to_string(),
+ operands: vec![Operand::Register(4), Operand::Integer(2)],
+ operands: vec![Operand::Register(4), Operand::Integer(60)],
+ operands: vec![Operand::Register(2), Operand::Register(4)],
+ operands: vec![Operand::Register(2), Operand::Integer(4294967276)],
+ operands: vec![Operand::Register(5), Operand::Integer(21)],
+ operands: vec![Operand::Register(3), Operand::Integer(0)],
+ name: "jgt".to_string(),
+ Operand::Register(5),
+ Operand::Register(4),
+ Operand::Integer(20)
+ operands: vec![Operand::Register(5), Operand::Register(3)],
+ operands: vec![Operand::Register(5), Operand::Integer(32)],
+ name: "arsh".to_string(),
+ operands: vec![Operand::Register(4), Operand::Register(1)],
+ operands: vec![Operand::Register(4), Operand::Register(5)],
+ operands: vec![Operand::Register(5), Operand::Memory(4, 0)],
+ name: "jeq".to_string(),
+ Operand::Integer(1),
+ Operand::Integer(4)
+ Operand::Integer(0),
+ Operand::Integer(12)
+ operands: vec![Operand::Register(6), Operand::Register(3)],
+ Operand::Integer(5),
+ Operand::Integer(9)
+ operands: vec![Operand::Register(3), Operand::Integer(1)],
+ operands: vec![Operand::Register(3), Operand::Memory(4, 1)],
+ operands: vec![Operand::Register(3), Operand::Register(6)],
+ operands: vec![Operand::Register(3), Operand::Integer(32)],
+ name: "jsgt".to_string(),
+ Operand::Integer(-18)
+ operands: vec![Operand::Register(0), Operand::Integer(1)],
+ ])
+ /// 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.
+ fn test_error_eof() {
+ let expected_error;
+ expected_error = Err(
+ "Parse error at line: 1, column: 6\nUnexpected end of input\nExpected digit\n"
+ .to_string(),
+ expected_error = Err("unexpected parse".to_string());
+ // Unexpected end of input in a register name.
+ assert_eq!(parse("lsh r"), expected_error);
+ fn test_error_unexpected_character() {
+ "Parse error at line: 2, column: 1\nUnexpected `^`\nExpected letter or digit, whitespaces, `r`, `-`, `+`, `[` or end of input\n".to_string()
+ // Unexpected character at end of input.
+ assert_eq!(parse("exit\n^"), expected_error);
+ fn test_initial_whitespace() {
+ parse(
+ "
+ exit"
@@ -0,0 +1,277 @@
+//! This module translates eBPF assembly language to binary.
+ collections::BTreeMap,
+ format,
+ 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}"),
+ ebpf::BPF_ABS | ebpf::BPF_LD | size,
+ &format!("ldind{suffix}"),
+ ebpf::BPF_IND | ebpf::BPF_LD | size,
+ &format!("ldx{suffix}"),
+ ebpf::BPF_MEM | ebpf::BPF_LDX | size,
+ &format!("st{suffix}"),
+ ebpf::BPF_MEM | ebpf::BPF_ST | size,
+ &format!("stx{suffix}"),
+ ebpf::BPF_MEM | ebpf::BPF_STX | size,
+ // JumpConditional.
+ for &(name, condition) in &jump_conditions {
+ entry(name, JumpConditional, ebpf::BPF_JMP | condition);
+ &format!("{name}32"),
+ 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![];
+ result.extend_from_slice(&insn.to_array());
@@ -0,0 +1,1230 @@
+use alloc::{collections::BTreeMap, format, vec, vec::Vec};
+use core::{mem, mem::ManuallyDrop};
+use std::io::ErrorKind;
+use cranelift_codegen::{
+ entity::EntityRef,
+ ir::{
+ condcodes::IntCC,
+ types::{I16, I32, I64, I8},
+ AbiParam, Block, Endianness, FuncRef, Function, InstBuilder, MemFlags, Signature,
+ SourceLoc, StackSlotData, StackSlotKind, TrapCode, Type, UserFuncName, Value,
+ isa::OwnedTargetIsa,
+ settings::{self, Configurable},
+use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
+use cranelift_jit::{JITBuilder, JITModule};
+use cranelift_module::{FuncId, Linkage, Module};
+use super::{Error, HashMap, HashSet};
+use crate::ebpf::{
+ self, Insn, BPF_ALU_OP_MASK, BPF_IND, BPF_JEQ, BPF_JGE, BPF_JGT, BPF_JLE, BPF_JLT, BPF_JMP32,
+ BPF_JNE, BPF_JSET, BPF_JSGE, BPF_JSGT, BPF_JSLE, BPF_JSLT, BPF_X, STACK_SIZE,
+pub type JittedFunction = extern "C" fn(
+ *mut u8, // mem_ptr
+ usize, // mem_len
+ *mut u8, // mbuff_ptr
+ usize, // mbuff_len
+) -> u64;
+pub(crate) struct CraneliftCompiler {
+ isa: OwnedTargetIsa,
+ module: JITModule,
+ helpers: HashMap<u32, ebpf::Helper>,
+ helper_func_refs: HashMap<u32, FuncRef>,
+ /// List of blocks corresponding to each instruction.
+ /// We only store the first instruction that observes a new block
+ insn_blocks: BTreeMap<u32, Block>,
+ /// Map of block targets for each jump/branching instruction.
+ insn_targets: BTreeMap<u32, (Block, Block)>,
+ filled_blocks: HashSet<Block>,
+ /// Map of register numbers to Cranelift variables.
+ registers: [Variable; 11],
+ /// Other usefull variables used throughout the program.
+ mem_start: Variable,
+ mem_end: Variable,
+ mbuf_start: Variable,
+ mbuf_end: Variable,
+ stack_start: Variable,
+ stack_end: Variable,
+impl CraneliftCompiler {
+ pub(crate) fn new(helpers: HashMap<u32, ebpf::Helper>) -> Self {
+ let mut flag_builder = settings::builder();
+ flag_builder.set("opt_level", "speed").unwrap();
+ // Enable stack probes
+ flag_builder.enable("enable_probestack").unwrap();
+ flag_builder.set("probestack_strategy", "inline").unwrap();
+ let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
+ panic!("host machine is not supported: {}", msg);
+ let isa = isa_builder
+ .finish(settings::Flags::new(flag_builder))
+ let mut jit_builder =
+ JITBuilder::with_isa(isa.clone(), cranelift_module::default_libcall_names());
+ // Register all the helpers
+ for (k, v) in helpers.iter() {
+ let name = format!("helper_{}", k);
+ jit_builder.symbol(name, (*v) as usize as *const u8);
+ let mut module = JITModule::new(jit_builder);
+ let registers = (0..11)
+ .map(|i| Variable::new(i))
+ .collect::<Vec<_>>()
+ .try_into()
+ Self {
+ isa,
+ module,
+ helpers,
+ helper_func_refs: HashMap::new(),
+ insn_blocks: BTreeMap::new(),
+ insn_targets: BTreeMap::new(),
+ filled_blocks: HashSet::new(),
+ registers,
+ mem_start: Variable::new(11),
+ mem_end: Variable::new(12),
+ mbuf_start: Variable::new(13),
+ mbuf_end: Variable::new(14),
+ stack_start: Variable::new(15),
+ stack_end: Variable::new(16),
+ pub(crate) fn compile_function(mut self, prog: &[u8]) -> Result<CraneliftProgram, Error> {
+ let name = "main";
+ // This is not a standard eBPF function! We use an informal ABI with just 4 parameters.
+ // See [JittedFunction] which is the signature of this function.
+ // Since this function only serves as the entrypoint for the JITed program, it doesen't
+ // really matter.
+ let sig = Signature {
+ params: vec![
+ AbiParam::new(I64),
+ returns: vec![AbiParam::new(I64)],
+ call_conv: self.isa.default_call_conv(),
+ let func_id = self
+ .module
+ .declare_function(name, Linkage::Local, &sig)
+ let mut ctx = self.module.make_context();
+ ctx.func = Function::with_name_signature(UserFuncName::testcase(name.as_bytes()), sig);
+ let mut func_ctx = FunctionBuilderContext::new();
+ let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
+ let entry = builder.create_block();
+ builder.append_block_params_for_function_params(entry);
+ builder.switch_to_block(entry);
+ self.build_cfg(&mut builder, prog)?;
+ self.build_function_prelude(&mut builder, entry)?;
+ self.translate_program(&mut builder, prog)?;
+ builder.seal_all_blocks();
+ builder.finalize();
+ self.module.define_function(func_id, &mut ctx).unwrap();
+ self.module.finalize_definitions().unwrap();
+ self.module.clear_context(&mut ctx);
+ Ok(CraneliftProgram::new(self.module, func_id))
+ fn build_function_prelude(
+ &mut self,
+ bcx: &mut FunctionBuilder,
+ entry: Block,
+ ) -> Result<(), Error> {
+ // Register the VM registers as variables
+ for var in self.registers.iter() {
+ bcx.declare_var(*var, I64);
+ // Register the bounds check variables
+ bcx.declare_var(self.mem_start, I64);
+ bcx.declare_var(self.mem_end, I64);
+ bcx.declare_var(self.mbuf_start, I64);
+ bcx.declare_var(self.mbuf_end, I64);
+ bcx.declare_var(self.stack_start, I64);
+ bcx.declare_var(self.stack_end, I64);
+ // Register the helpers
+ for (k, _) in self.helpers.iter() {
+ .declare_function(&name, Linkage::Import, &sig)
+ let func_ref = self.module.declare_func_in_func(func_id, bcx.func);
+ self.helper_func_refs.insert(*k, func_ref);
+ // Register the stack
+ let ss = bcx.create_sized_stack_slot(StackSlotData {
+ kind: StackSlotKind::ExplicitSlot,
+ size: STACK_SIZE as u32,
+ let addr_ty = self.isa.pointer_type();
+ let stack_addr = bcx.ins().stack_addr(addr_ty, ss, STACK_SIZE as i32);
+ bcx.def_var(self.registers[10], stack_addr);
+ // Initialize the bounds check variables
+ let stack_start = bcx.ins().stack_addr(addr_ty, ss, 0);
+ bcx.def_var(self.stack_start, stack_start);
+ let stack_end = bcx.ins().stack_addr(addr_ty, ss, STACK_SIZE as i32);
+ bcx.def_var(self.stack_end, stack_end);
+ // This is our internal ABI where the first 2 params are the memory
+ let mem_start = bcx.block_params(entry)[0];
+ let mem_len = bcx.block_params(entry)[1];
+ let mem_end = bcx.ins().iadd(mem_start, mem_len);
+ bcx.def_var(self.mem_start, mem_start);
+ bcx.def_var(self.mem_end, mem_end);
+ // And the next 2 are the mbuf
+ let mbuf_start = bcx.block_params(entry)[2];
+ let mbuf_len = bcx.block_params(entry)[3];
+ let mbuf_end = bcx.ins().iadd(mbuf_start, mbuf_len);
+ bcx.def_var(self.mbuf_start, mbuf_start);
+ bcx.def_var(self.mbuf_end, mbuf_end);
+ // The ABI for eBPF specifies that R1 must contain either the memory, or mbuff pointer
+ // If the mbuf length is non-zero, then we use that, otherwise we use the memory pointer
+ let mbuf_exists = bcx.ins().icmp_imm(IntCC::NotEqual, mbuf_len, 0);
+ let mem_or_mbuf = bcx.ins().select(mbuf_exists, mbuf_start, mem_start);
+ bcx.def_var(self.registers[1], mem_or_mbuf);
+ // R2 should contain the length of the memory or mbuf
+ // At least ebpf-conformance tests expect this
+ let mem_or_mbuf_len = bcx.ins().select(mbuf_exists, mbuf_len, mem_len);
+ bcx.def_var(self.registers[2], mem_or_mbuf_len);
+ // Insert the *actual* initial block
+ let program_entry = bcx.create_block();
+ bcx.ins().jump(program_entry, &[]);
+ self.filled_blocks.insert(bcx.current_block().unwrap());
+ self.insn_blocks.insert(0, program_entry);
+ Ok(())
+ fn translate_program(&mut self, bcx: &mut FunctionBuilder, prog: &[u8]) -> Result<(), Error> {
+ let mut insn_ptr: usize = 0;
+ while insn_ptr * ebpf::INSN_SIZE < prog.len() {
+ let insn = ebpf::get_insn(prog, insn_ptr);
+ // If this instruction is on a new block switch to it.
+ if let Some(block) = self.insn_blocks.get(&(insn_ptr as u32)) {
+ // Blocks must have a terminator instruction at the end before we switch away from them
+ let current_block = bcx.current_block().unwrap();
+ if !self.filled_blocks.contains(¤t_block) {
+ bcx.ins().jump(*block, &[]);
+ bcx.switch_to_block(*block);
+ // Set the source location for the instruction
+ bcx.set_srcloc(SourceLoc::new(insn_ptr as u32));
+ 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
+ | 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 => {
+ let ty = match insn.opc {
+ ebpf::LD_ABS_B | ebpf::LD_IND_B => I8,
+ ebpf::LD_ABS_H | ebpf::LD_IND_H => I16,
+ ebpf::LD_ABS_W | ebpf::LD_IND_W => I32,
+ ebpf::LD_ABS_DW | ebpf::LD_IND_DW => I64,
+ _ => unreachable!(),
+ // Both instructions add the imm part of the instruction to the pointer
+ let ptr = bcx.use_var(self.mem_start);
+ let offset = bcx
+ .ins()
+ .iconst(self.isa.pointer_type(), insn.imm as u32 as i64);
+ let addr = bcx.ins().iadd(ptr, offset);
+ // IND instructions additionally add the value of the source register
+ let is_ind = (insn.opc & BPF_IND) != 0;
+ let addr = if is_ind {
+ let src_reg = self.insn_src(bcx, &insn);
+ bcx.ins().iadd(addr, src_reg)
+ addr
+ // The offset here has already been added to the pointer, so we pass 0
+ let loaded = self.reg_load(bcx, ty, addr, 0);
+ let ext = if ty != I64 {
+ bcx.ins().uextend(I64, loaded)
+ loaded
+ self.set_dst(bcx, &insn, ext);
+ ebpf::LD_DW_IMM => {
+ insn_ptr += 1;
+ let next_insn = ebpf::get_insn(prog, insn_ptr);
+ let imm = (((insn.imm as u32) as u64) + ((next_insn.imm as u64) << 32)) as i64;
+ let iconst = bcx.ins().iconst(I64, imm);
+ self.set_dst(bcx, &insn, iconst);
+ // BPF_LDX class
+ ebpf::LD_B_REG | ebpf::LD_H_REG | ebpf::LD_W_REG | ebpf::LD_DW_REG => {
+ ebpf::LD_B_REG => I8,
+ ebpf::LD_H_REG => I16,
+ ebpf::LD_W_REG => I32,
+ ebpf::LD_DW_REG => I64,
+ let base = self.insn_src(bcx, &insn);
+ let loaded = self.reg_load(bcx, ty, base, insn.off);
+ // BPF_ST and BPF_STX class
+ ebpf::ST_B_IMM
+ | ebpf::ST_H_IMM
+ | ebpf::ST_W_IMM
+ | ebpf::ST_DW_IMM
+ | ebpf::ST_B_REG
+ | ebpf::ST_H_REG
+ | ebpf::ST_W_REG
+ | ebpf::ST_DW_REG => {
+ ebpf::ST_B_IMM | ebpf::ST_B_REG => I8,
+ ebpf::ST_H_IMM | ebpf::ST_H_REG => I16,
+ ebpf::ST_W_IMM | ebpf::ST_W_REG => I32,
+ ebpf::ST_DW_IMM | ebpf::ST_DW_REG => I64,
+ let is_imm = match insn.opc {
+ ebpf::ST_B_IMM | ebpf::ST_H_IMM | ebpf::ST_W_IMM | ebpf::ST_DW_IMM => true,
+ ebpf::ST_B_REG | ebpf::ST_H_REG | ebpf::ST_W_REG | ebpf::ST_DW_REG => false,
+ let value = if is_imm {
+ self.insn_imm64(bcx, &insn)
+ self.insn_src(bcx, &insn)
+ let narrow = if ty != I64 {
+ bcx.ins().ireduce(ty, value)
+ value
+ let base = self.insn_dst(bcx, &insn);
+ self.reg_store(bcx, ty, base, insn.off, narrow);
+ 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 => {
+ let src = self.insn_dst32(bcx, &insn);
+ let imm = self.insn_imm32(bcx, &insn);
+ let res = bcx.ins().iadd(src, imm);
+ self.set_dst32(bcx, &insn, res);
+ ebpf::ADD32_REG => {
+ //((reg[_dst] & U32MAX) + (reg[_src] & U32MAX)) & U32MAX,
+ let lhs = self.insn_dst32(bcx, &insn);
+ let rhs = self.insn_src32(bcx, &insn);
+ let res = bcx.ins().iadd(lhs, rhs);
+ ebpf::SUB32_IMM => {
+ // reg[_dst] = (reg[_dst] as i32).wrapping_sub(insn.imm) as u64,
+ let res = bcx.ins().isub(src, imm);
+ ebpf::SUB32_REG => {
+ // reg[_dst] = (reg[_dst] as i32).wrapping_sub(reg[_src] as i32) as u64,
+ let res = bcx.ins().isub(lhs, rhs);
+ ebpf::MUL32_IMM => {
+ // reg[_dst] = (reg[_dst] as i32).wrapping_mul(insn.imm) as u64,
+ let res = bcx.ins().imul(src, imm);
+ ebpf::MUL32_REG => {
+ // reg[_dst] = (reg[_dst] as i32).wrapping_mul(reg[_src] as i32) as u64,
+ let res = bcx.ins().imul(lhs, rhs);
+ ebpf::DIV32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32 / insn.imm as u32) as u64,
+ let res = if insn.imm == 0 {
+ bcx.ins().iconst(I32, 0)
+ bcx.ins().udiv(src, imm)
+ ebpf::DIV32_REG => {
+ // reg[_dst] = (reg[_dst] as u32 / reg[_src] as u32) as u64,
+ let zero = bcx.ins().iconst(I32, 0);
+ let one = bcx.ins().iconst(I32, 1);
+ let rhs_is_zero = bcx.ins().icmp(IntCC::Equal, rhs, zero);
+ let safe_rhs = bcx.ins().select(rhs_is_zero, one, rhs);
+ let div_res = bcx.ins().udiv(lhs, safe_rhs);
+ let res = bcx.ins().select(rhs_is_zero, zero, div_res);
+ ebpf::OR32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32 | insn.imm as u32) as u64,
+ let res = bcx.ins().bor(src, imm);
+ ebpf::OR32_REG => {
+ // reg[_dst] = (reg[_dst] as u32 | reg[_src] as u32) as u64,
+ let res = bcx.ins().bor(lhs, rhs);
+ ebpf::AND32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32 & insn.imm as u32) as u64,
+ let res = bcx.ins().band(src, imm);
+ ebpf::AND32_REG => {
+ // reg[_dst] = (reg[_dst] as u32 & reg[_src] as u32) as u64,
+ let res = bcx.ins().band(lhs, rhs);
+ ebpf::LSH32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32).wrapping_shl(insn.imm as u32) as u64,
+ let res = bcx.ins().ishl(src, imm);
+ ebpf::LSH32_REG => {
+ // reg[_dst] = (reg[_dst] as u32).wrapping_shl(reg[_src] as u32) as u64,
+ let res = bcx.ins().ishl(lhs, rhs);
+ ebpf::RSH32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32).wrapping_shr(insn.imm as u32) as u64,
+ let res = bcx.ins().ushr(src, imm);
+ ebpf::RSH32_REG => {
+ // reg[_dst] = (reg[_dst] as u32).wrapping_shr(reg[_src] as u32) as u64,
+ let res = bcx.ins().ushr(lhs, rhs);
+ ebpf::NEG32 => {
+ // { reg[_dst] = (reg[_dst] as i32).wrapping_neg() as u64; reg[_dst] &= U32MAX; },
+ let res = bcx.ins().ineg(src);
+ // TODO: Do we need to mask the result?
+ ebpf::MOD32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32 % insn.imm as u32) as u64,
+ if insn.imm != 0 {
+ let res = bcx.ins().urem(src, imm);
+ ebpf::MOD32_REG => {
+ // reg[_dst] = (reg[_dst] as u32 % reg[_src] as u32) as u64,
+ let div_res = bcx.ins().urem(lhs, safe_rhs);
+ let res = bcx.ins().select(rhs_is_zero, lhs, div_res);
+ ebpf::XOR32_IMM => {
+ // reg[_dst] = (reg[_dst] as u32 ^ insn.imm as u32) as u64,
+ let res = bcx.ins().bxor(src, imm);
+ ebpf::XOR32_REG => {
+ // reg[_dst] = (reg[_dst] as u32 ^ reg[_src] as u32) as u64,
+ let res = bcx.ins().bxor(lhs, rhs);
+ ebpf::MOV32_IMM => {
+ self.set_dst32(bcx, &insn, imm);
+ ebpf::MOV32_REG => {
+ // reg[_dst] = (reg[_src] as u32) as u64,
+ let src = self.insn_src32(bcx, &insn);
+ self.set_dst32(bcx, &insn, src);
+ ebpf::ARSH32_IMM => {
+ // { reg[_dst] = (reg[_dst] as i32).wrapping_shr(insn.imm as u32) as u64; reg[_dst] &= U32MAX; },
+ let res = bcx.ins().sshr(src, imm);
+ ebpf::ARSH32_REG => {
+ // { reg[_dst] = (reg[_dst] as i32).wrapping_shr(reg[_src] as u32) as u64; reg[_dst] &= U32MAX; },
+ let res = bcx.ins().sshr(lhs, rhs);
+ ebpf::BE | ebpf::LE => {
+ let should_swap = match insn.opc {
+ ebpf::BE => self.isa.endianness() == Endianness::Little,
+ ebpf::LE => self.isa.endianness() == Endianness::Big,
+ let ty: Type = match insn.imm {
+ 16 => I16,
+ 32 => I32,
+ 64 => I64,
+ if should_swap {
+ let src = self.insn_dst(bcx, &insn);
+ let src_narrow = if ty != I64 {
+ bcx.ins().ireduce(ty, src)
+ src
+ let res = bcx.ins().bswap(src_narrow);
+ let res_wide = if ty != I64 {
+ bcx.ins().uextend(I64, res)
+ self.set_dst(bcx, &insn, res_wide);
+ // BPF_ALU64 class
+ ebpf::ADD64_IMM => {
+ // reg[_dst] = reg[_dst].wrapping_add(insn.imm as u64),
+ let imm = self.insn_imm64(bcx, &insn);
+ self.set_dst(bcx, &insn, res);
+ ebpf::ADD64_REG => {
+ // reg[_dst] = reg[_dst].wrapping_add(reg[_src]),
+ let lhs = self.insn_dst(bcx, &insn);
+ let rhs = self.insn_src(bcx, &insn);
+ 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 => {
+ // reg[_dst] /= insn.imm as u64,
+ bcx.ins().iconst(I64, 0)
+ ebpf::DIV64_REG => {
+ // reg[_dst] /= reg[_src], if reg[_src] != 0
+ // reg[_dst] = 0, if reg[_src] == 0
+ let zero = bcx.ins().iconst(I64, 0);
+ let one = bcx.ins().iconst(I64, 1);
+ ebpf::MOD64_IMM => {
+ // reg[_dst] %= insn.imm as u64,
+ ebpf::MOD64_REG => {
+ // reg[_dst] %= reg[_src], if reg[_src] != 0
+ 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,
+ ebpf::LSH64_REG => {
+ // reg[_dst] <<= reg[_src],
+ ebpf::RSH64_IMM => {
+ // reg[_dst] >>= insn.imm as u64,
+ ebpf::RSH64_REG => {
+ // reg[_dst] >>= reg[_src],
+ ebpf::NEG64 => {
+ // reg[_dst] = -(reg[_dst] as i64) as u64,
+ 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,
+ bcx.def_var(self.registers[insn.dst as usize], imm);
+ ebpf::MOV64_REG => {
+ // reg[_dst] = reg[_src],
+ let src = self.insn_src(bcx, &insn);
+ bcx.def_var(self.registers[insn.dst as usize], src);
+ ebpf::ARSH64_IMM => {
+ // reg[_dst] = (reg[_dst] as i64 >> insn.imm) as u64,
+ ebpf::ARSH64_REG => {
+ // reg[_dst] = (reg[_dst] as i64 >> reg[_src]) as u64,
+ // BPF_JMP & BPF_JMP32 class
+ ebpf::JA => {
+ let (_, target_block) = self.insn_targets[&(insn_ptr as u32)];
+ bcx.ins().jump(target_block, &[]);
+ ebpf::JEQ_IMM
+ | ebpf::JEQ_REG
+ | ebpf::JGT_IMM
+ | ebpf::JGT_REG
+ | ebpf::JGE_IMM
+ | ebpf::JGE_REG
+ | ebpf::JLT_IMM
+ | ebpf::JLT_REG
+ | ebpf::JLE_IMM
+ | ebpf::JLE_REG
+ | ebpf::JNE_IMM
+ | ebpf::JNE_REG
+ | ebpf::JSGT_IMM
+ | ebpf::JSGT_REG
+ | ebpf::JSGE_IMM
+ | ebpf::JSGE_REG
+ | ebpf::JSLT_IMM
+ | ebpf::JSLT_REG
+ | ebpf::JSLE_IMM
+ | ebpf::JSLE_REG
+ | ebpf::JSET_IMM
+ | ebpf::JSET_REG
+ | ebpf::JEQ_IMM32
+ | ebpf::JEQ_REG32
+ | ebpf::JGT_IMM32
+ | ebpf::JGT_REG32
+ | ebpf::JGE_IMM32
+ | ebpf::JGE_REG32
+ | ebpf::JLT_IMM32
+ | ebpf::JLT_REG32
+ | ebpf::JLE_IMM32
+ | ebpf::JLE_REG32
+ | ebpf::JNE_IMM32
+ | ebpf::JNE_REG32
+ | ebpf::JSGT_IMM32
+ | ebpf::JSGT_REG32
+ | ebpf::JSGE_IMM32
+ | ebpf::JSGE_REG32
+ | ebpf::JSLT_IMM32
+ | ebpf::JSLT_REG32
+ | ebpf::JSLE_IMM32
+ | ebpf::JSLE_REG32
+ | ebpf::JSET_IMM32
+ | ebpf::JSET_REG32 => {
+ let (fallthrough, target) = self.insn_targets[&(insn_ptr as u32)];
+ let is_reg = (insn.opc & BPF_X) != 0;
+ let is_32 = (insn.opc & BPF_JMP32) != 0;
+ let intcc = match insn.opc {
+ c if (c & BPF_ALU_OP_MASK) == BPF_JEQ => IntCC::Equal,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JNE => IntCC::NotEqual,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JGT => IntCC::UnsignedGreaterThan,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JGE => IntCC::UnsignedGreaterThanOrEqual,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JLT => IntCC::UnsignedLessThan,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JLE => IntCC::UnsignedLessThanOrEqual,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JSGT => IntCC::SignedGreaterThan,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JSGE => IntCC::SignedGreaterThanOrEqual,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JSLT => IntCC::SignedLessThan,
+ c if (c & BPF_ALU_OP_MASK) == BPF_JSLE => IntCC::SignedLessThanOrEqual,
+ // JSET is handled specially below
+ c if (c & BPF_ALU_OP_MASK) == BPF_JSET => IntCC::NotEqual,
+ let lhs = if is_32 {
+ self.insn_dst32(bcx, &insn)
+ self.insn_dst(bcx, &insn)
+ let rhs = match (is_reg, is_32) {
+ (true, false) => self.insn_src(bcx, &insn),
+ (true, true) => self.insn_src32(bcx, &insn),
+ (false, false) => self.insn_imm64(bcx, &insn),
+ (false, true) => self.insn_imm32(bcx, &insn),
+ let cmp_res = if (insn.opc & BPF_ALU_OP_MASK) == BPF_JSET {
+ bcx.ins().band(lhs, rhs)
+ bcx.ins().icmp(intcc, lhs, rhs)
+ bcx.ins().brif(cmp_res, target, &[], fallthrough, &[]);
+ // Do not delegate the check to the verifier, since registered functions can be
+ // changed after the program has been verified.
+ ebpf::CALL => {
+ let func_ref = self
+ .helper_func_refs
+ .get(&(insn.imm as u32))
+ .copied()
+ .ok_or_else(|| {
+ Error::new(
+ ErrorKind::Other,
+ format!(
+ "[CRANELIFT] Error: unknown helper function (id: {:#x})",
+ insn.imm as u32
+ })?;
+ let arg0 = bcx.use_var(self.registers[1]);
+ let arg1 = bcx.use_var(self.registers[2]);
+ let arg2 = bcx.use_var(self.registers[3]);
+ let arg3 = bcx.use_var(self.registers[4]);
+ let arg4 = bcx.use_var(self.registers[5]);
+ let call = bcx.ins().call(func_ref, &[arg0, arg1, arg2, arg3, arg4]);
+ let ret = bcx.inst_results(call)[0];
+ self.set_dst(bcx, &insn, ret);
+ ebpf::TAIL_CALL => unimplemented!(),
+ ebpf::EXIT => {
+ let ret = bcx.use_var(self.registers[0]);
+ bcx.ins().return_(&[ret]);
+ _ => unimplemented!("inst: {:?}", insn),
+ fn insn_imm64(&mut self, bcx: &mut FunctionBuilder, insn: &Insn) -> Value {
+ bcx.ins().iconst(I64, insn.imm as u64 as i64)
+ fn insn_imm32(&mut self, bcx: &mut FunctionBuilder, insn: &Insn) -> Value {
+ bcx.ins().iconst(I32, insn.imm as u32 as u64 as i64)
+ fn insn_dst(&mut self, bcx: &mut FunctionBuilder, insn: &Insn) -> Value {
+ bcx.use_var(self.registers[insn.dst as usize])
+ fn insn_dst32(&mut self, bcx: &mut FunctionBuilder, insn: &Insn) -> Value {
+ let dst = self.insn_dst(bcx, insn);
+ bcx.ins().ireduce(I32, dst)
+ fn insn_src(&mut self, bcx: &mut FunctionBuilder, insn: &Insn) -> Value {
+ bcx.use_var(self.registers[insn.src as usize])
+ fn insn_src32(&mut self, bcx: &mut FunctionBuilder, insn: &Insn) -> Value {
+ let src = self.insn_src(bcx, insn);
+ bcx.ins().ireduce(I32, src)
+ fn set_dst(&mut self, bcx: &mut FunctionBuilder, insn: &Insn, val: Value) {
+ bcx.def_var(self.registers[insn.dst as usize], val);
+ fn set_dst32(&mut self, bcx: &mut FunctionBuilder, insn: &Insn, val: Value) {
+ let val32 = bcx.ins().uextend(I64, val);
+ self.set_dst(bcx, insn, val32);
+ fn reg_load(&mut self, bcx: &mut FunctionBuilder, ty: Type, base: Value, offset: i16) -> Value {
+ self.insert_bounds_check(bcx, ty, base, offset);
+ let mut flags = MemFlags::new();
+ flags.set_endianness(Endianness::Little);
+ bcx.ins().load(ty, flags, base, offset as i32)
+ fn reg_store(
+ ty: Type,
+ base: Value,
+ offset: i16,
+ val: Value,
+ ) {
+ bcx.ins().store(flags, val, base, offset as i32);
+ /// Inserts a bounds check for a memory access
+ /// This emits a conditional trap if the access is out of bounds for any of the known
+ /// valid memory regions. These are the stack, the memory, and the mbuf.
+ fn insert_bounds_check(
+ let access_size = bcx.ins().iconst(I64, ty.bytes() as i64);
+ let offset = bcx.ins().iconst(I64, offset as i64);
+ let start_addr = bcx.ins().iadd(base, offset);
+ let end_addr = bcx.ins().iadd(start_addr, access_size);
+ let does_not_overflow =
+ bcx.ins()
+ .icmp(IntCC::UnsignedGreaterThanOrEqual, end_addr, start_addr);
+ // Check if it's a valid stack access
+ let stack_start = bcx.use_var(self.stack_start);
+ let stack_end = bcx.use_var(self.stack_end);
+ let stack_start_valid =
+ .icmp(IntCC::UnsignedGreaterThanOrEqual, start_addr, stack_start);
+ let stack_end_valid = bcx
+ .icmp(IntCC::UnsignedLessThanOrEqual, end_addr, stack_end);
+ let stack_valid = bcx.ins().band(stack_start_valid, stack_end_valid);
+ // Check if it's a valid memory access
+ let mem_start = bcx.use_var(self.mem_start);
+ let mem_end = bcx.use_var(self.mem_end);
+ let has_mem = bcx.ins().icmp_imm(IntCC::NotEqual, mem_start, 0);
+ let mem_start_valid =
+ .icmp(IntCC::UnsignedGreaterThanOrEqual, start_addr, mem_start);
+ let mem_end_valid = bcx
+ .icmp(IntCC::UnsignedLessThanOrEqual, end_addr, mem_end);
+ let mem_valid = bcx.ins().band(mem_start_valid, mem_end_valid);
+ let mem_valid = bcx.ins().band(mem_valid, has_mem);
+ // Check if it's a valid mbuf access
+ let mbuf_start = bcx.use_var(self.mbuf_start);
+ let mbuf_end = bcx.use_var(self.mbuf_end);
+ let has_mbuf = bcx.ins().icmp_imm(IntCC::NotEqual, mbuf_start, 0);
+ let mbuf_start_valid =
+ .icmp(IntCC::UnsignedGreaterThanOrEqual, start_addr, mbuf_start);
+ let mbuf_end_valid = bcx
+ .icmp(IntCC::UnsignedLessThanOrEqual, end_addr, mbuf_end);
+ let mbuf_valid = bcx.ins().band(mbuf_start_valid, mbuf_end_valid);
+ let mbuf_valid = bcx.ins().band(mbuf_valid, has_mbuf);
+ // Join all of these checks together and trap if any of them fails
+ // We need it to be valid to at least one region of memory
+ let valid_region = bcx.ins().bor(stack_valid, mem_valid);
+ let valid_region = bcx.ins().bor(valid_region, mbuf_valid);
+ // And that it does not overflow
+ let valid = bcx.ins().band(does_not_overflow, valid_region);
+ // TODO: We can potentially throw a custom trap code here to indicate
+ // which check failed.
+ bcx.ins().trapz(valid, TrapCode::HeapOutOfBounds);
+ /// Analyze the program and build the CFG
+ /// We do this because cranelift does not allow us to switch back to a previously
+ /// filled block and add instructions to it. So we can't split the program as we
+ /// translate it.
+ fn build_cfg(&mut self, bcx: &mut FunctionBuilder, prog: &[u8]) -> Result<(), Error> {
+ // This instruction consumes two opcodes
+ ebpf::JA
+ | ebpf::JEQ_IMM
+ | ebpf::JSET_REG32
+ | ebpf::EXIT
+ | ebpf::TAIL_CALL => {
+ self.prepare_jump_blocks(bcx, insn_ptr, &insn);
+ _ => {}
+ fn prepare_jump_blocks(&mut self, bcx: &mut FunctionBuilder, insn_ptr: usize, insn: &Insn) {
+ let insn_ptr = insn_ptr as u32;
+ let next_pc: u32 = insn_ptr + 1;
+ let target_pc: u32 = (insn_ptr as isize + insn.off as isize + 1)
+ // This is the fallthrough block
+ let fallthrough_block = *self
+ .insn_blocks
+ .entry(next_pc)
+ .or_insert_with(|| bcx.create_block());
+ // Jump Target
+ let target_block = *self
+ .entry(target_pc)
+ // Mark the blocks for this instruction
+ self.insn_targets
+ .insert(insn_ptr, (fallthrough_block, target_block));
+/// Contains the backing memory for a previously compiled function.
+/// Currently this will allways just contain code for a single function, but
+/// in the future we might want to support multiple functions per module.
+/// Ensures that the backing memory is freed when dropped.
+pub struct CraneliftProgram {
+ module: ManuallyDrop<JITModule>,
+ main_id: FuncId,
+impl CraneliftProgram {
+ pub(crate) fn new(module: JITModule, main_id: FuncId) -> Self {
+ module: ManuallyDrop::new(module),
+ main_id,
+ /// We shouldn't allow this function pointer to be exposed outside of this
+ /// module, since it's not guaranteed to be valid after the module is dropped.
+ pub(crate) fn get_main_function(&self) -> JittedFunction {
+ let function_ptr = self.module.get_finalized_function(self.main_id);
+ unsafe { mem::transmute(function_ptr) }
+ /// Execute this module by calling the main function
+ pub fn execute(
+ &self,
+ mem_ptr: *mut u8,
+ mem_len: usize,
+ mbuff_ptr: *mut u8,
+ mbuff_len: usize,
+ ) -> u64 {
+ let main = self.get_main_function();
+ main(mem_ptr, mem_len, mbuff_ptr, mbuff_len)
+impl Drop for CraneliftProgram {
+ // We need to have an owned version of `JITModule` to be able to free
+ // it's memory. Use `ManuallyDrop` to get the owned `JITModule`.
+ // We can no longer use `module` after this, but since we are `Drop`
+ // it should be safe.
+ let module = ManuallyDrop::take(&mut self.module);
+ module.free_memory()
@@ -0,0 +1,807 @@
+//! 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 log::warn;
+use crate::ebpf;
+#[inline]
+fn alu_imm_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} r{}, {:#x}", insn.dst, insn.imm)
+fn alu_reg_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} r{}, r{}", insn.dst, insn.src)
+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)
+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)
+ "{name} [r{}-{:#x}], {:#x}",
+ insn.dst,
+ -(insn.off as isize),
+ insn.imm
+fn ld_reg_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} r{}, [r{}+{:#x}]", insn.dst, insn.src, insn.off)
+ "{name} r{}, [r{}-{:#x}]",
+ insn.src,
+ -(insn.off as isize)
+fn st_reg_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} [r{}+{:#x}], r{}", insn.dst, insn.off, insn.src)
+ "{name} [r{}-{:#x}], r{}",
+ insn.src
+fn ldabs_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} {:#x}", insn.imm)
+fn ldind_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} r{}, {:#x}", insn.src, insn.imm)
+fn jmp_imm_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} r{}, {:#x}, +{:#x}", insn.dst, insn.imm, insn.off)
+ "{name} r{}, {:#x}, -{:#x}",
+ insn.imm,
+fn jmp_reg_str(name: &str, insn: &ebpf::Insn) -> String {
+ format!("{name} r{}, r{}, +{:#x}", insn.dst, insn.src, insn.off)
+ "{name} r{}, r{}, -{:#x}",
+/// 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.
+pub struct HLInsn {
+ /// Operation code.
+ pub opc: u8,
+ /// Name (mnemonic). This name is not canon.
+ /// 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.
+/// 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
+/// },
+/// opc: 0x95,
+/// name: "exit".to_string(),
+/// desc: "exit".to_string(),
+/// 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 name;
+ let desc;
+ let mut imm = insn.imm as i64;
+ ebpf::LD_ABS_B => {
+ name = "ldabsb";
+ desc = ldabs_str(name, &insn);
+ ebpf::LD_ABS_H => {
+ name = "ldabsh";
+ ebpf::LD_ABS_W => {
+ name = "ldabsw";
+ ebpf::LD_ABS_DW => {
+ name = "ldabsdw";
+ ebpf::LD_IND_B => {
+ name = "ldindb";
+ desc = ldind_str(name, &insn);
+ ebpf::LD_IND_H => {
+ name = "ldindh";
+ ebpf::LD_IND_W => {
+ name = "ldindw";
+ ebpf::LD_IND_DW => {
+ name = "ldinddw";
+ 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);
+ ebpf::LD_B_REG => {
+ name = "ldxb";
+ desc = ld_reg_str(name, &insn);
+ ebpf::LD_H_REG => {
+ name = "ldxh";
+ ebpf::LD_W_REG => {
+ name = "ldxw";
+ ebpf::LD_DW_REG => {
+ name = "ldxdw";
+ // BPF_ST class
+ ebpf::ST_B_IMM => {
+ name = "stb";
+ desc = ld_st_imm_str(name, &insn);
+ ebpf::ST_H_IMM => {
+ name = "sth";
+ ebpf::ST_W_IMM => {
+ name = "stw";
+ ebpf::ST_DW_IMM => {
+ name = "stdw";
+ // BPF_STX class
+ ebpf::ST_B_REG => {
+ name = "stxb";
+ desc = st_reg_str(name, &insn);
+ ebpf::ST_H_REG => {
+ name = "stxh";
+ ebpf::ST_W_REG => {
+ name = "stxw";
+ ebpf::ST_DW_REG => {
+ name = "stxdw";
+ ebpf::ST_W_XADD => {
+ name = "stxxaddw";
+ ebpf::ST_DW_XADD => {
+ name = "stxxadddw";
+ name = "add32";
+ desc = alu_imm_str(name, &insn);
+ desc = alu_reg_str(name, &insn);
+ name = "sub32";
+ name = "mul32";
+ name = "div32";
+ name = "or32";
+ name = "and32";
+ name = "lsh32";
+ name = "rsh32";
+ name = "neg32";
+ desc = format!("{name} r{:}", insn.dst);
+ name = "mod32";
+ name = "xor32";
+ name = "mov32";
+ name = "arsh32";
+ ebpf::LE => {
+ name = "le";
+ desc = byteswap_str(name, &insn);
+ ebpf::BE => {
+ name = "be";
+ name = "add64";
+ name = "sub64";
+ name = "mul64";
+ name = "div64";
+ name = "or64";
+ name = "and64";
+ name = "lsh64";
+ name = "rsh64";
+ name = "neg64";
+ name = "mod64";
+ name = "xor64";
+ name = "mov64";
+ name = "arsh64";
+ // BPF_JMP class
+ name = "ja";
+ desc = if insn.off >= 0 {
+ format!("{name} +{:#x}", insn.off)
+ format!("{name} -{:#x}", -insn.off)
+ ebpf::JEQ_IMM => {
+ name = "jeq";
+ desc = jmp_imm_str(name, &insn);
+ ebpf::JEQ_REG => {
+ desc = jmp_reg_str(name, &insn);
+ ebpf::JGT_IMM => {
+ name = "jgt";
+ ebpf::JGT_REG => {
+ ebpf::JGE_IMM => {
+ name = "jge";
+ ebpf::JGE_REG => {
+ ebpf::JLT_IMM => {
+ name = "jlt";
+ ebpf::JLT_REG => {
+ ebpf::JLE_IMM => {
+ name = "jle";
+ ebpf::JLE_REG => {
+ ebpf::JSET_IMM => {
+ name = "jset";
+ ebpf::JSET_REG => {
+ ebpf::JNE_IMM => {
+ name = "jne";
+ ebpf::JNE_REG => {
+ ebpf::JSGT_IMM => {
+ name = "jsgt";
+ ebpf::JSGT_REG => {
+ ebpf::JSGE_IMM => {
+ name = "jsge";
+ ebpf::JSGE_REG => {
+ ebpf::JSLT_IMM => {
+ name = "jslt";
+ ebpf::JSLT_REG => {
+ ebpf::JSLE_IMM => {
+ name = "jsle";
+ ebpf::JSLE_REG => {
+ name = "call";
+ desc = format!("{name} {:#x}", insn.imm);
+ ebpf::TAIL_CALL => {
+ name = "tail_call";
+ desc = name.to_string();
+ name = "exit";
+ // BPF_JMP32 class
+ ebpf::JEQ_IMM32 => {
+ name = "jeq32";
+ ebpf::JEQ_REG32 => {
+ ebpf::JGT_IMM32 => {
+ name = "jgt32";
+ ebpf::JGT_REG32 => {
+ ebpf::JGE_IMM32 => {
+ name = "jge32";
+ ebpf::JGE_REG32 => {
+ ebpf::JLT_IMM32 => {
+ name = "jlt32";
+ ebpf::JLT_REG32 => {
+ ebpf::JLE_IMM32 => {
+ name = "jle32";
+ ebpf::JLE_REG32 => {
+ ebpf::JSET_IMM32 => {
+ name = "jset32";
+ ebpf::JSET_REG32 => {
+ ebpf::JNE_IMM32 => {
+ name = "jne32";
+ ebpf::JNE_REG32 => {
+ ebpf::JSGT_IMM32 => {
+ name = "jsgt32";
+ ebpf::JSGT_REG32 => {
+ ebpf::JSGE_IMM32 => {
+ name = "jsge32";
+ ebpf::JSGE_REG32 => {
+ ebpf::JSLT_IMM32 => {
+ name = "jslt32";
+ ebpf::JSLT_REG32 => {
+ ebpf::JSLE_IMM32 => {
+ name = "jsle32";
+ ebpf::JSLE_REG32 => {
+ _ => {
+ "[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);
+/// Disassemble an eBPF program into human-readable instructions and prints it to standard output.
+/// The program is not checked for errors or inconsistencies.
+/// 0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
+/// disassembler::disassemble(prog);
+/// # // "\nadd64 r1, 0x605\nmov64 r2, 0x32\nmov64 r1, r0\nbe16 r0\nneg64 r2\nexit"
+/// add64 r1, 0x605
+/// exit
+pub fn disassemble(prog: &[u8]) {
+ for insn in to_insn_vec(prog) {
+ println!("{}", insn.desc);
+ log::info!("{}", insn.desc);
@@ -0,0 +1,635 @@
+//! 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 |
+// +----------------+----+------------+
+// 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)`.
+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;
+pub const DIV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_DIV;
+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)`.
+pub const ARSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ARSH;
+/// BPF opcode: `arsh64 dst, src` /// `dst >>= src (arithmetic)`.
+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.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Insn {
+ /// 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.
+ /// let prog: Vec<u8> = vec![
+ /// assert_eq!(insn.to_vec(), prog);
+ pub fn to_vec(&self) -> Vec<u8> {
+ vec![
+/// 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).
+/// use rbpf::ebpf;
+/// 0xb7, 0x10, 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
+/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00 // two bytes missing
+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() {
+ "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.
+/// let v = ebpf::to_insn_vec(prog);
+/// ebpf::Insn {
+/// imm: 0x55667788
+/// opc: 0,
+/// imm: 0x11223344
+pub fn to_insn_vec(prog: &[u8]) -> Vec<Insn> {
+ if prog.len() % INSN_SIZE != 0 {
+ "Error: eBPF program length must be a multiple of {:?} octets",
+ INSN_SIZE
+ while insn_ptr * INSN_SIZE < prog.len() {
+ let insn = get_insn(prog, insn_ptr);
+ res.push(insn);
@@ -0,0 +1,488 @@
+// Copyright 2015 Big Switch Networks, Inc
+// (Algorithms for uBPF helpers, originally in C)
+// (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.
+/// 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)]
+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
+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.
+/// 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.
+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
+ (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`.
+/// 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.
+/// 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);
+/// assert_eq!(val, 0x112233);
+pub fn memfrob(ptr: u64, len: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
+ for i in 0..len {
+ 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.
+/// let x = helpers::sqrti(9, 0, 0, 0, 0);
+/// assert_eq!(x, 3);
+#[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.
+/// 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);
+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;
+ 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
+ (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.
+/// 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);
+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.
+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",
@@ -0,0 +1,2199 @@
+// Copyright 2017 Alex Dukhno <alex.dukhno@icloud.com>
+//! Module provides API to create eBPF programs by Rust programming language
+use crate::ebpf::*;
+/// Represents single eBPF instruction
+pub trait Instruction: Sized {
+ /// returns instruction opt code
+ fn opt_code_byte(&self) -> u8;
+ /// returns destination register
+ fn get_dst(&self) -> u8 {
+ self.get_insn().dst
+ /// returns source register
+ fn get_src(&self) -> u8 {
+ self.get_insn().src
+ /// returns offset bytes
+ fn get_off(&self) -> i16 {
+ self.get_insn().off
+ /// returns immediate value
+ fn get_imm(&self) -> i32 {
+ self.get_insn().imm
+ /// sets destination register
+ fn set_dst(mut self, dst: u8) -> Self {
+ self.get_insn_mut().dst = dst;
+ /// sets source register
+ fn set_src(mut self, src: u8) -> Self {
+ self.get_insn_mut().src = src;
+ /// sets offset bytes
+ fn set_off(mut self, offset: i16) -> Self {
+ self.get_insn_mut().off = offset;
+ /// sets immediate value
+ fn set_imm(mut self, imm: i32) -> Self {
+ self.get_insn_mut().imm = imm;
+ /// get `ebpf::Insn` struct
+ fn get_insn(&self) -> &Insn;
+ /// get mutable `ebpf::Insn` struct
+ fn get_insn_mut(&mut self) -> &mut Insn;
+/// General trait for `Instruction`s and `BpfCode`.
+/// Provides functionality to transform `struct` into collection of bytes
+pub trait IntoBytes {
+ /// type of targeted transformation
+ type Bytes;
+ /// consume `Self` with transformation into `Self::Bytes`
+ fn into_bytes(self) -> Self::Bytes;
+/// General implementation of `IntoBytes` for `Instruction`
+impl<I: Instruction> IntoBytes for &'_ I {
+ type Bytes = Vec<u8>;
+ /// transform immutable reference of `Instruction` into `Vec<u8>` with size of 8
+ /// [ 1 byte , 1 byte , 2 bytes, 4 bytes ]
+ /// [ OP_CODE, SRC_REG | DST_REG, OFFSET , IMMEDIATE ]
+ fn into_bytes(self) -> Self::Bytes {
+ let buffer = vec![
+ self.opt_code_byte(),
+ self.get_src() << 4 | self.get_dst(),
+ self.get_off() as u8,
+ (self.get_off() >> 8) as u8,
+ self.get_imm() as u8,
+ (self.get_imm() >> 8) as u8,
+ (self.get_imm() >> 16) as u8,
+ (self.get_imm() >> 24) as u8,
+ buffer
+/// BPF instruction stack in byte representation
+#[derive(Default)]
+pub struct BpfCode {
+ instructions: Vec<u8>,
+impl BpfCode {
+ /// creates new empty BPF instruction stack
+ pub fn new() -> Self {
+ BpfCode {
+ instructions: vec![],
+ /// create ADD instruction
+ pub fn add(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::Add)
+ /// create SUB instruction
+ pub fn sub(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::Sub)
+ /// create MUL instruction
+ pub fn mul(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::Mul)
+ /// create DIV instruction
+ pub fn div(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::Div)
+ /// create OR instruction
+ pub fn bit_or(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::BitOr)
+ /// create AND instruction
+ pub fn bit_and(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::BitAnd)
+ /// create LSHIFT instruction
+ pub fn left_shift(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::LShift)
+ /// create RSHIFT instruction
+ pub fn right_shift(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::RShift)
+ /// create NEGATE instruction
+ pub fn negate(&mut self, arch: Arch) -> Move {
+ self.mov_internal(Source::Imm, arch, OpBits::Negate)
+ /// create MOD instruction
+ pub fn modulo(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::Mod)
+ /// create XOR instruction
+ pub fn bit_xor(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::BitXor)
+ /// create MOV instruction
+ pub fn mov(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::Mov)
+ /// create SIGNED RSHIFT instruction
+ pub fn signed_right_shift(&mut self, source: Source, arch: Arch) -> Move {
+ self.mov_internal(source, arch, OpBits::SignRShift)
+ #[inline]
+ fn mov_internal(&mut self, source: Source, arch_bits: Arch, op_bits: OpBits) -> Move {
+ Move {
+ bpf_code: self,
+ src_bit: source,
+ op_bits,
+ arch_bits,
+ insn: Insn {
+ opc: 0x00,
+ dst: 0x00,
+ src: 0x00,
+ off: 0x00_00,
+ imm: 0x00_00_00_00,
+ /// create byte swap instruction
+ pub fn swap_bytes(&mut self, endian: Endian) -> SwapBytes {
+ SwapBytes {
+ endian,
+ /// create LOAD instruction, IMMEDIATE is the source
+ pub fn load(&mut self, mem_size: MemSize) -> Load {
+ self.load_internal(mem_size, Addressing::Imm, BPF_LD)
+ /// create ABSOLUTE LOAD instruction
+ pub fn load_abs(&mut self, mem_size: MemSize) -> Load {
+ self.load_internal(mem_size, Addressing::Abs, BPF_LD)
+ /// create INDIRECT LOAD instruction
+ pub fn load_ind(&mut self, mem_size: MemSize) -> Load {
+ self.load_internal(mem_size, Addressing::Ind, BPF_LD)
+ /// create LOAD instruction, MEMORY is the source
+ pub fn load_x(&mut self, mem_size: MemSize) -> Load {
+ self.load_internal(mem_size, Addressing::Mem, BPF_LDX)
+ fn load_internal(&mut self, mem_size: MemSize, addressing: Addressing, source: u8) -> Load {
+ Load {
+ addressing,
+ mem_size,
+ source,
+ /// creates STORE instruction, IMMEDIATE is the source
+ pub fn store(&mut self, mem_size: MemSize) -> Store {
+ self.store_internal(mem_size, BPF_IMM)
+ /// creates STORE instruction, MEMORY is the source
+ pub fn store_x(&mut self, mem_size: MemSize) -> Store {
+ self.store_internal(mem_size, BPF_MEM | BPF_STX)
+ fn store_internal(&mut self, mem_size: MemSize, source: u8) -> Store {
+ Store {
+ /// create unconditional JMP instruction
+ pub fn jump_unconditional(&mut self) -> Jump {
+ self.jump_conditional(Cond::Abs, Source::Imm)
+ /// create conditional JMP instruction
+ pub fn jump_conditional(&mut self, cond: Cond, src_bit: Source) -> Jump {
+ Jump {
+ cond,
+ src_bit,
+ /// create CALL instruction
+ pub fn call(&mut self) -> FunctionCall {
+ FunctionCall {
+ /// create EXIT instruction
+ pub fn exit(&mut self) -> Exit {
+ Exit {
+/// Transform `BpfCode` into assemble representation
+impl<'a> IntoBytes for &'a BpfCode {
+ type Bytes = &'a [u8];
+ /// returns `BpfCode` instruction stack as `&[u8]`
+ self.instructions.as_slice()
+/// struct to represent `MOV ALU` instructions
+pub struct Move<'i> {
+ bpf_code: &'i mut BpfCode,
+ src_bit: Source,
+ op_bits: OpBits,
+ arch_bits: Arch,
+ insn: Insn,
+impl<'i> Move<'i> {
+ /// push MOV instruction into BpfCode instruction stack
+ pub fn push(self) -> &'i mut BpfCode {
+ let mut asm = self.into_bytes();
+ self.bpf_code.instructions.append(&mut asm);
+ self.bpf_code
+impl Instruction for Move<'_> {
+ fn opt_code_byte(&self) -> u8 {
+ let op_bits = self.op_bits as u8;
+ let src_bit = self.src_bit as u8;
+ let arch_bits = self.arch_bits as u8;
+ op_bits | src_bit | arch_bits
+ fn get_insn_mut(&mut self) -> &mut Insn {
+ &mut self.insn
+ fn get_insn(&self) -> &Insn {
+ &self.insn
+#[derive(Copy, Clone, PartialEq, Eq)]
+/// The source of ALU and JMP instructions
+pub enum Source {
+ /// immediate field will be used as a source
+ Imm = BPF_IMM as isize,
+ /// src register will be used as a source
+ Reg = BPF_X as isize,
+#[derive(Copy, Clone)]
+enum OpBits {
+ Add = BPF_ADD as isize,
+ Sub = BPF_SUB as isize,
+ Mul = BPF_MUL as isize,
+ Div = BPF_DIV as isize,
+ BitOr = BPF_OR as isize,
+ BitAnd = BPF_AND as isize,
+ LShift = BPF_LSH as isize,
+ RShift = BPF_RSH as isize,
+ Negate = BPF_NEG as isize,
+ Mod = BPF_MOD as isize,
+ BitXor = BPF_XOR as isize,
+ Mov = BPF_MOV as isize,
+ SignRShift = BPF_ARSH as isize,
+/// Architecture of instructions
+pub enum Arch {
+ /// 64-bit instructions
+ X64 = BPF_ALU64 as isize,
+ /// 32-bit instructions
+ X32 = BPF_ALU as isize,
+/// struct representation of byte swap operation
+pub struct SwapBytes<'i> {
+ endian: Endian,
+impl<'i> SwapBytes<'i> {
+ /// push bytes swap instruction into BpfCode instruction stack
+impl Instruction for SwapBytes<'_> {
+ self.endian as u8
+/// Bytes endian
+pub enum Endian {
+ /// Little endian
+ Little = LE as isize,
+ /// Big endian
+ Big = BE as isize,
+/// struct representation of LOAD instructions
+pub struct Load<'i> {
+ addressing: Addressing,
+ mem_size: MemSize,
+ source: u8,
+impl<'i> Load<'i> {
+ /// push LOAD instruction into BpfCode instruction stack
+impl Instruction for Load<'_> {
+ let size = self.mem_size as u8;
+ let addressing = self.addressing as u8;
+ addressing | size | self.source
+/// struct representation of STORE instructions
+pub struct Store<'i> {
+impl<'i> Store<'i> {
+ /// push STORE instruction into BpfCode instruction stack
+impl Instruction for Store<'_> {
+ BPF_MEM | BPF_ST | size | self.source
+/// Memory size for LOAD and STORE instructions
+pub enum MemSize {
+ /// 8-bit size
+ Byte = BPF_B as isize,
+ /// 16-bit size
+ HalfWord = BPF_H as isize,
+ /// 32-bit size
+ Word = BPF_W as isize,
+ /// 64-bit size
+ DoubleWord = BPF_DW as isize,
+enum Addressing {
+ Abs = BPF_ABS as isize,
+ Ind = BPF_IND as isize,
+ Mem = BPF_MEM as isize,
+/// struct representation of JMP instructions
+pub struct Jump<'i> {
+ cond: Cond,
+impl<'i> Jump<'i> {
+ /// push JMP instruction into BpfCode instruction stack
+impl Instruction for Jump<'_> {
+ let cmp: u8 = self.cond as u8;
+ cmp | src_bit | BPF_JMP
+/// Conditions for JMP instructions
+pub enum Cond {
+ /// Absolute or unconditional
+ Abs = BPF_JA as isize,
+ /// Jump if `==`
+ Equals = BPF_JEQ as isize,
+ /// Jump if `>`
+ Greater = BPF_JGT as isize,
+ /// Jump if `>=`
+ GreaterEquals = BPF_JGE as isize,
+ /// Jump if `<`
+ Lower = BPF_JLT as isize,
+ /// Jump if `<=`
+ LowerEquals = BPF_JLE as isize,
+ /// Jump if `src` & `dst`
+ BitAnd = BPF_JSET as isize,
+ /// Jump if `!=`
+ NotEquals = BPF_JNE as isize,
+ /// Jump if `>` (signed)
+ GreaterSigned = BPF_JSGT as isize,
+ /// Jump if `>=` (signed)
+ GreaterEqualsSigned = BPF_JSGE as isize,
+ /// Jump if `<` (signed)
+ LowerSigned = BPF_JSLT as isize,
+ /// Jump if `<=` (signed)
+ LowerEqualsSigned = BPF_JSLE as isize,
+/// struct representation of CALL instruction
+pub struct FunctionCall<'i> {
+impl<'i> FunctionCall<'i> {
+ /// push CALL instruction into BpfCode instruction stack
+impl Instruction for FunctionCall<'_> {
+ BPF_CALL | BPF_JMP
+/// struct representation of EXIT instruction
+pub struct Exit<'i> {
+impl<'i> Exit<'i> {
+ /// push EXIT instruction into BpfCode instruction stack
+impl Instruction for Exit<'_> {
+ BPF_EXIT | BPF_JMP
+ #[cfg(test)]
+ mod special {
+ use super::super::*;
+ fn call_immediate() {
+ let mut program = BpfCode::new();
+ program.call().set_imm(0x11_22_33_44).push();
+ program.into_bytes(),
+ &[0x85, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11]
+ fn exit_operation() {
+ program.exit().push();
+ &[0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod jump_instructions {
+ mod register {
+ use super::super::super::*;
+ fn jump_on_dst_equals_src() {
+ program
+ .jump_conditional(Cond::Equals, Source::Reg)
+ .set_dst(0x01)
+ .set_src(0x02)
+ .push();
+ &[0x1d, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_greater_than_src() {
+ .jump_conditional(Cond::Greater, Source::Reg)
+ .set_dst(0x03)
+ &[0x2d, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_greater_or_equals_to_src() {
+ .jump_conditional(Cond::GreaterEquals, Source::Reg)
+ .set_dst(0x04)
+ .set_src(0x01)
+ &[0x3d, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_lower_than_src() {
+ .jump_conditional(Cond::Lower, Source::Reg)
+ &[0xad, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_lower_or_equals_to_src() {
+ .jump_conditional(Cond::LowerEquals, Source::Reg)
+ &[0xbd, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_bit_and_with_src_not_equal_zero() {
+ .jump_conditional(Cond::BitAnd, Source::Reg)
+ .set_dst(0x05)
+ &[0x4d, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_not_equals_src() {
+ .jump_conditional(Cond::NotEquals, Source::Reg)
+ .set_src(0x05)
+ &[0x5d, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_greater_than_src_signed() {
+ .jump_conditional(Cond::GreaterSigned, Source::Reg)
+ &[0x6d, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_greater_or_equals_src_signed() {
+ .jump_conditional(Cond::GreaterEqualsSigned, Source::Reg)
+ .set_src(0x03)
+ &[0x7d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_lower_than_src_signed() {
+ .jump_conditional(Cond::LowerSigned, Source::Reg)
+ &[0xcd, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_lower_or_equals_src_signed() {
+ .jump_conditional(Cond::LowerEqualsSigned, Source::Reg)
+ &[0xdd, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod immediate {
+ fn jump_to_label() {
+ program.jump_unconditional().set_off(0x00_11).push();
+ &[0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_equals_const() {
+ .jump_conditional(Cond::Equals, Source::Imm)
+ .set_imm(0x00_11_22_33)
+ &[0x15, 0x01, 0x00, 0x00, 0x33, 0x22, 0x11, 0x00]
+ fn jump_on_dst_greater_than_const() {
+ .jump_conditional(Cond::Greater, Source::Imm)
+ .set_dst(0x02)
+ .set_imm(0x00_11_00_11)
+ &[0x25, 0x02, 0x00, 0x00, 0x11, 0x00, 0x11, 0x00]
+ fn jump_on_dst_greater_or_equals_to_const() {
+ .jump_conditional(Cond::GreaterEquals, Source::Imm)
+ .set_imm(0x00_22_11_00)
+ &[0x35, 0x04, 0x00, 0x00, 0x00, 0x11, 0x22, 0x00]
+ fn jump_on_dst_lower_than_const() {
+ .jump_conditional(Cond::Lower, Source::Imm)
+ &[0xa5, 0x02, 0x00, 0x00, 0x11, 0x00, 0x11, 0x00]
+ fn jump_on_dst_lower_or_equals_to_const() {
+ .jump_conditional(Cond::LowerEquals, Source::Imm)
+ &[0xb5, 0x04, 0x00, 0x00, 0x00, 0x11, 0x22, 0x00]
+ fn jump_on_dst_bit_and_with_const_not_equal_zero() {
+ .jump_conditional(Cond::BitAnd, Source::Imm)
+ &[0x45, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_not_equals_const() {
+ .jump_conditional(Cond::NotEquals, Source::Imm)
+ &[0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_greater_than_const_signed() {
+ .jump_conditional(Cond::GreaterSigned, Source::Imm)
+ &[0x65, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .jump_conditional(Cond::GreaterEqualsSigned, Source::Imm)
+ &[0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn jump_on_dst_lower_than_const_signed() {
+ .jump_conditional(Cond::LowerSigned, Source::Imm)
+ &[0xc5, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .jump_conditional(Cond::LowerEqualsSigned, Source::Imm)
+ &[0xd5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod store_instructions {
+ fn store_word_from_dst_into_immediate_address() {
+ .store(MemSize::Word)
+ .set_off(0x00_11)
+ .set_imm(0x11_22_33_44)
+ &[0x62, 0x01, 0x11, 0x00, 0x44, 0x33, 0x22, 0x11]
+ fn store_half_word_from_dst_into_immediate_address() {
+ .store(MemSize::HalfWord)
+ .set_off(0x11_22)
+ &[0x6a, 0x02, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00]
+ fn store_byte_from_dst_into_immediate_address() {
+ program.store(MemSize::Byte).push();
+ &[0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn store_double_word_from_dst_into_immediate_address() {
+ program.store(MemSize::DoubleWord).push();
+ &[0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn store_word_from_dst_into_src_address() {
+ .store_x(MemSize::Word)
+ &[0x63, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn store_half_word_from_dst_into_src_address() {
+ program.store_x(MemSize::HalfWord).push();
+ &[0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn store_byte_from_dst_into_src_address() {
+ program.store_x(MemSize::Byte).push();
+ &[0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn store_double_word_from_dst_into_src_address() {
+ program.store_x(MemSize::DoubleWord).push();
+ &[0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod load_instructions {
+ fn load_word_from_set_src_with_offset() {
+ .load_x(MemSize::Word)
+ .set_off(0x00_02)
+ &[0x61, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_half_word_from_set_src_with_offset() {
+ .load_x(MemSize::HalfWord)
+ &[0x69, 0x12, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00]
+ fn load_byte_from_set_src_with_offset() {
+ .load_x(MemSize::Byte)
+ .set_src(0x04)
+ &[0x71, 0x41, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_double_word_from_set_src_with_offset() {
+ .load_x(MemSize::DoubleWord)
+ .set_off(0x44_55)
+ &[0x79, 0x54, 0x55, 0x44, 0x00, 0x00, 0x00, 0x00]
+ fn load_double_word() {
+ .load(MemSize::DoubleWord)
+ .set_imm(0x00_01_02_03)
+ &[0x18, 0x01, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00]
+ fn load_abs_word() {
+ program.load_abs(MemSize::Word).push();
+ &[0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_abs_half_word() {
+ program.load_abs(MemSize::HalfWord).set_dst(0x05).push();
+ &[0x28, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_abs_byte() {
+ program.load_abs(MemSize::Byte).set_dst(0x01).push();
+ &[0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_abs_double_word() {
+ .load_abs(MemSize::DoubleWord)
+ .set_imm(0x01_02_03_04)
+ &[0x38, 0x01, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01]
+ fn load_indirect_word() {
+ program.load_ind(MemSize::Word).push();
+ &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_indirect_half_word() {
+ program.load_ind(MemSize::HalfWord).push();
+ &[0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_indirect_byte() {
+ program.load_ind(MemSize::Byte).push();
+ &[0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn load_indirect_double_word() {
+ program.load_ind(MemSize::DoubleWord).push();
+ &[0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod byte_swap_instructions {
+ fn convert_host_to_little_endian_16bits() {
+ .swap_bytes(Endian::Little)
+ .set_imm(0x00_00_00_10)
+ &[0xd4, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00]
+ fn convert_host_to_little_endian_32bits() {
+ .set_imm(0x00_00_00_20)
+ &[0xd4, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00]
+ fn convert_host_to_little_endian_64bit() {
+ .set_imm(0x00_00_00_40)
+ &[0xd4, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00]
+ fn convert_host_to_big_endian_16bits() {
+ .swap_bytes(Endian::Big)
+ &[0xdc, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00]
+ fn convert_host_to_big_endian_32bits() {
+ &[0xdc, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00]
+ fn convert_host_to_big_endian_64bit() {
+ &[0xdc, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00]
+ mod moves_instructions {
+ mod arch_x64 {
+ use super::super::super::super::*;
+ fn move_and_add_const_to_register() {
+ .add(Source::Imm, Arch::X64)
+ &[0x07, 0x02, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01]
+ fn move_sub_const_to_register() {
+ .sub(Source::Imm, Arch::X64)
+ &[0x17, 0x04, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00]
+ fn move_mul_const_to_register() {
+ .mul(Source::Imm, Arch::X64)
+ .set_imm(0x04_03_02_01)
+ &[0x27, 0x05, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04]
+ fn move_div_constant_to_register() {
+ .div(Source::Imm, Arch::X64)
+ .set_imm(0x00_ff_00_ff)
+ &[0x37, 0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00]
+ fn move_bit_or_const_to_register() {
+ .bit_or(Source::Imm, Arch::X64)
+ .set_imm(0x00_11_00_22)
+ &[0x47, 0x02, 0x00, 0x00, 0x22, 0x00, 0x11, 0x00]
+ fn move_bit_and_const_to_register() {
+ .bit_and(Source::Imm, Arch::X64)
+ &[0x57, 0x02, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11]
+ fn move_left_shift_const_to_register() {
+ .left_shift(Source::Imm, Arch::X64)
+ &[0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_logical_right_shift_const_to_register() {
+ .right_shift(Source::Imm, Arch::X64)
+ &[0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_negate_register() {
+ program.negate(Arch::X64).set_dst(0x02).push();
+ &[0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_mod_const_to_register() {
+ program.modulo(Source::Imm, Arch::X64).set_dst(0x02).push();
+ &[0x97, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_bit_xor_const_to_register() {
+ program.bit_xor(Source::Imm, Arch::X64).set_dst(0x03).push();
+ &[0xa7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_const_to_register() {
+ .mov(Source::Imm, Arch::X64)
+ .set_imm(0x00_00_00_FF)
+ &[0xb7, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00]
+ fn move_signed_right_shift_const_to_register() {
+ .signed_right_shift(Source::Imm, Arch::X64)
+ &[0xc7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_and_add_from_register() {
+ .add(Source::Reg, Arch::X64)
+ &[0x0f, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_sub_from_register_to_register() {
+ .sub(Source::Reg, Arch::X64)
+ &[0x1f, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_mul_from_register_to_register() {
+ .mul(Source::Reg, Arch::X64)
+ &[0x2f, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_div_from_register_to_register() {
+ .div(Source::Reg, Arch::X64)
+ .set_src(0x00)
+ &[0x3f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_bit_or_from_register_to_register() {
+ .bit_or(Source::Reg, Arch::X64)
+ &[0x4f, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_bit_and_from_register_to_register() {
+ .bit_and(Source::Reg, Arch::X64)
+ &[0x5f, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_left_shift_from_register_to_register() {
+ .left_shift(Source::Reg, Arch::X64)
+ &[0x6f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_logical_right_shift_from_register_to_register() {
+ .right_shift(Source::Reg, Arch::X64)
+ &[0x7f, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_mod_from_register_to_register() {
+ .modulo(Source::Reg, Arch::X64)
+ &[0x9f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_bit_xor_from_register_to_register() {
+ .bit_xor(Source::Reg, Arch::X64)
+ &[0xaf, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_from_register_to_another_register() {
+ program.mov(Source::Reg, Arch::X64).set_src(0x01).push();
+ &[0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ fn move_signed_right_shift_from_register_to_register() {
+ .signed_right_shift(Source::Reg, Arch::X64)
+ &[0xcf, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod arch_x32 {
+ .add(Source::Imm, Arch::X32)
+ &[0x04, 0x02, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01]
+ .sub(Source::Imm, Arch::X32)
+ &[0x14, 0x04, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00]
+ .mul(Source::Imm, Arch::X32)
+ &[0x24, 0x05, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04]
+ .div(Source::Imm, Arch::X32)
+ &[0x34, 0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00]
+ .bit_or(Source::Imm, Arch::X32)
+ &[0x44, 0x02, 0x00, 0x00, 0x22, 0x00, 0x11, 0x00]
+ .bit_and(Source::Imm, Arch::X32)
+ &[0x54, 0x02, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11]
+ .left_shift(Source::Imm, Arch::X32)
+ &[0x64, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .right_shift(Source::Imm, Arch::X32)
+ &[0x74, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ program.negate(Arch::X32).set_dst(0x02).push();
+ &[0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ program.modulo(Source::Imm, Arch::X32).set_dst(0x02).push();
+ &[0x94, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ program.bit_xor(Source::Imm, Arch::X32).set_dst(0x03).push();
+ &[0xa4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .mov(Source::Imm, Arch::X32)
+ &[0xb4, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00]
+ .signed_right_shift(Source::Imm, Arch::X32)
+ &[0xc4, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .add(Source::Reg, Arch::X32)
+ &[0x0c, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .sub(Source::Reg, Arch::X32)
+ &[0x1c, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .mul(Source::Reg, Arch::X32)
+ &[0x2c, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .div(Source::Reg, Arch::X32)
+ &[0x3c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .bit_or(Source::Reg, Arch::X32)
+ &[0x4c, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .bit_and(Source::Reg, Arch::X32)
+ &[0x5c, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .left_shift(Source::Reg, Arch::X32)
+ &[0x6c, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .right_shift(Source::Reg, Arch::X32)
+ &[0x7c, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .modulo(Source::Reg, Arch::X32)
+ &[0x9c, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .bit_xor(Source::Reg, Arch::X32)
+ &[0xac, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .mov(Source::Reg, Arch::X32)
+ .set_dst(0x00)
+ &[0xbc, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ .signed_right_shift(Source::Reg, Arch::X32)
+ &[0xcc, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mod programs {
+ fn example_from_assembler() {
+ .set_dst(1)
+ .set_imm(0x605)
+ .push()
+ .set_dst(2)
+ .set_imm(0x32)
+ .mov(Source::Reg, Arch::X64)
+ .set_src(0)
+ .set_dst(0)
+ .set_imm(0x10)
+ .negate(Arch::X64)
+ .exit()
+ let bytecode = program.into_bytes();
+ let ref_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,
+ // cargo says: "`[{integer}; 48]` cannot be formatted using `{:?}`
+ // because it doesn't implement `std::fmt::Debug`"
+ // So let's check in two steps.
+ assert_eq!(bytecode[..32], ref_prog[..32]);
+ assert_eq!(bytecode[33..], ref_prog[33..]);
@@ -0,0 +1,708 @@
+// Derived from uBPF <https://github.com/iovisor/ubpf>
+// (uBPF: VM architecture, parts of the interpreter, originally in C)
+// (Translation to Rust, MetaBuff/multiple classes addition, hashmaps for helpers)
+ 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> {
+ "check_mem: addr {:#x}, len {}, access_type {}, insn_ptr {}",
+ addr,
+ len,
+ access_type,
+ insn_ptr
+ "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()
+#[cfg(feature = "user")]
+ 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 {
+ if stack.as_ptr() as u64 <= addr && addr_end <= stack.as_ptr() as u64 + stack.len() as u64 {
+ 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()
+ )))
+fn do_jump(insn_ptr: &mut usize, insn: &Insn) {
+ *insn_ptr = (*insn_ptr as i16 + insn.off) as usize;
+#[allow(cyclomatic_complexity)]
+pub fn execute_program(
+ prog_: Option<&[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(
+ "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,
+ 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 =
+ check_mem(addr, len, "store", insn_ptr, mbuff, mem, stack)
+ // Loop on instructions
+ let _dst = insn.dst as usize;
+ let _src = insn.src as usize;
+ 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
+ let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u16;
+ let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u32;
+ log::info!("executing LD_ABS_DW, set reg[{}] to {:#x}", _dst, insn.imm);
+ let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u64;
+ x.read_unaligned()
+ let x =
+ (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u8;
+ (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u16;
+ (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u32;
+ (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u64;
+ // 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);
+ 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)?;
+ 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)?;
+ 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)?;
+ // "executing LD_W_REG, the ptr is REG:{} -> [{:#x}] + {:#x}",
+ // _src,
+ // reg[_src],
+ // insn.off
+ let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u64;
+ 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 {
+ 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 {
+ 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 {
+ 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);
+ ebpf::ST_B_REG => unsafe {
+ x.write_unaligned(reg[_src] as u8);
+ ebpf::ST_H_REG => unsafe {
+ x.write_unaligned(reg[_src] as u16);
+ ebpf::ST_W_REG => unsafe {
+ x.write_unaligned(reg[_src] as u32);
+ ebpf::ST_DW_REG => unsafe {
+ x.write_unaligned(reg[_src]);
+ 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,
+ 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,
+ reg[_dst] = (reg[_dst] as i32).wrapping_shr(insn.imm as u32) as u64;
+ reg[_dst] = (reg[_dst] as i32).wrapping_shr(reg[_src] as u32) as u64;
+ 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(),
+ 16 => (reg[_dst] as u16).to_be() as u64,
+ 32 => (reg[_dst] as u32).to_be() as u64,
+ 64 => reg[_dst].to_be(),
+ 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],
+ reg[_dst] = (reg[_dst] as i64 >> (insn.imm as u64 & SHIFT_MASK_64)) as u64
+ reg[_dst] = (reg[_dst] as i64 >> (reg[_src] as u64 & SHIFT_MASK_64)) as u64
+ // TODO: check this actually works as expected for signed / unsigned ops
+ ebpf::JA => do_jump(&mut insn_ptr, &insn),
+ if reg[_dst] == insn.imm as u64 {
+ do_jump(&mut insn_ptr, &insn);
+ if reg[_dst] == reg[_src] {
+ if reg[_dst] > insn.imm as u64 {
+ if reg[_dst] > reg[_src] {
+ if reg[_dst] >= insn.imm as u64 {
+ if reg[_dst] >= reg[_src] {
+ if reg[_dst] < insn.imm as u64 {
+ if reg[_dst] < reg[_src] {
+ if reg[_dst] <= insn.imm as u64 {
+ if reg[_dst] <= reg[_src] {
+ if reg[_dst] & insn.imm as u64 != 0 {
+ if reg[_dst] & reg[_src] != 0 {
+ if reg[_dst] != insn.imm as u64 {
+ if reg[_dst] != reg[_src] {
+ if reg[_dst] as i64 > insn.imm as i64 {
+ if reg[_dst] as i64 > reg[_src] as i64 {
+ if reg[_dst] as i64 >= insn.imm as i64 {
+ if reg[_dst] as i64 >= reg[_src] as i64 {
+ if (reg[_dst] as i64) < insn.imm as i64 {
+ if (reg[_dst] as i64) < reg[_src] as i64 {
+ if reg[_dst] as i64 <= insn.imm as i64 {
+ if reg[_dst] as i64 <= reg[_src] as i64 {
+ if reg[_dst] as u32 == insn.imm as u32 {
+ if reg[_dst] as u32 == reg[_src] as u32 {
+ if reg[_dst] as u32 > insn.imm as u32 {
+ if reg[_dst] as u32 > reg[_src] as u32 {
+ if reg[_dst] as u32 >= insn.imm as u32 {
+ if reg[_dst] as u32 >= reg[_src] as u32 {
+ if (reg[_dst] as u32) < insn.imm as u32 {
+ if (reg[_dst] as u32) < reg[_src] as u32 {
+ if reg[_dst] as u32 <= insn.imm as u32 {
+ if reg[_dst] as u32 <= reg[_src] as u32 {
+ if reg[_dst] as u32 & insn.imm as u32 != 0 {
+ if reg[_dst] as u32 & reg[_src] as u32 != 0 {
+ if reg[_dst] as u32 != insn.imm as u32 {
+ if reg[_dst] as u32 != reg[_src] as u32 {
+ if reg[_dst] as i32 > insn.imm {
+ if reg[_dst] as i32 > reg[_src] as i32 {
+ if reg[_dst] as i32 >= insn.imm {
+ if reg[_dst] as i32 >= reg[_src] as i32 {
+ if (reg[_dst] as i32) < insn.imm {
+ if (reg[_dst] as i32) < reg[_src] as i32 {
+ if reg[_dst] as i32 <= insn.imm {
+ if reg[_dst] as i32 <= reg[_src] as i32 {
+ // 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]);
+ }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{
+ "Error: bpf to bpf call stack limit reached (instruction #{}) max depth: {}",
+ insn_ptr, ebpf::RBPF_MAX_CALL_DEPTH
+ let mut pre_stack = stacks.last_mut().unwrap();
+ // Save the callee saved registers
+ pre_stack.save_registers(®[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;
+ insn_ptr += insn.imm as usize;
+ _ =>{
+ "Error: the function call type (id: {:#x}) [{}], (instruction #{}) not supported",
+ if let Err(e) = call_func_res {
+ Err(Error::new(ErrorKind::Other, e))?;
+ if stacks.len() == 1 {
+ return Ok(reg[0]);
+ // 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!()
@@ -0,0 +1,1054 @@
+// (uBPF: JIT algorithm, originally in C)
+// (Translation to Rust, MetaBuff addition)
+use std::{
+ fmt::{Error as FormatterError, Formatter},
+ io::{Error, ErrorKind},
+ mem,
+ ops::{Index, IndexMut},
+use crate::{ebpf, HashMap};
+extern crate libc;
+type MachineCode = unsafe fn(*mut u8, usize, *mut u8, usize, usize, usize) -> u64;
+const PAGE_SIZE: usize = 4096;
+// TODO: check how long the page must be to be sure to support an eBPF program of maximum possible
+// length
+const NUM_PAGES: usize = 1;
+// Special values for target_pc in struct Jump
+const TARGET_OFFSET: isize = ebpf::PROG_MAX_INSNS as isize;
+const TARGET_PC_EXIT: isize = TARGET_OFFSET + 1;
+enum OperandSize {
+ S8 = 8,
+ S16 = 16,
+ S32 = 32,
+ S64 = 64,
+// Registers
+const RAX: u8 = 0;
+const RCX: u8 = 1;
+const RDX: u8 = 2;
+const RBX: u8 = 3;
+const RSP: u8 = 4;
+const RBP: u8 = 5;
+const RSI: u8 = 6;
+const RDI: u8 = 7;
+const R8: u8 = 8;
+const R9: u8 = 9;
+const R10: u8 = 10;
+const R11: u8 = 11;
+//const R12: u8 = 12;
+const R13: u8 = 13;
+const R14: u8 = 14;
+const R15: u8 = 15;
+const REGISTER_MAP_SIZE: usize = 11;
+const REGISTER_MAP: [u8; REGISTER_MAP_SIZE] = [
+ RAX, // 0 return value
+ RDI, // 1 arg 1
+ RSI, // 2 arg 2
+ RDX, // 3 arg 3
+ R9, // 4 arg 4
+ R8, // 5 arg 5
+ RBX, // 6 callee-saved
+ R13, // 7 callee-saved
+ R14, // 8 callee-saved
+ R15, // 9 callee-saved
+ RBP, // 10 stack pointer
+ // R10 and R11 are used to compute store a constant pointer to mem and to compute offset for
+ // LD_ABS_* and LD_IND_* operations, so they are not mapped to any eBPF register.
+// Return the x86 register for the given eBPF register
+fn map_register(r: u8) -> u8 {
+ assert!(r < REGISTER_MAP_SIZE as u8);
+ REGISTER_MAP[(r % REGISTER_MAP_SIZE as u8) as usize]
+macro_rules! emit_bytes {
+ ( $mem:ident, $data:tt, $t:ty ) => {{
+ let size = mem::size_of::<$t>() as usize;
+ assert!($mem.offset + size <= $mem.contents.len());
+ let mut ptr = $mem.contents.as_ptr().add($mem.offset) as *mut $t;
+ ptr.write_unaligned($data);
+ $mem.offset += size;
+ }};
+struct Jump {
+ offset_loc: usize,
+ target_pc: isize,
+struct JitCompiler {
+ pc_locs: Vec<usize>,
+ special_targets: HashMap<isize, usize>,
+ jumps: Vec<Jump>,
+impl JitCompiler {
+ fn new() -> JitCompiler {
+ JitCompiler {
+ pc_locs: vec![],
+ jumps: vec![],
+ special_targets: HashMap::new(),
+ fn emit1(&self, mem: &mut JitMemory, data: u8) {
+ emit_bytes!(mem, data, u8);
+ fn emit2(&self, mem: &mut JitMemory, data: u16) {
+ emit_bytes!(mem, data, u16);
+ fn emit4(&self, mem: &mut JitMemory, data: u32) {
+ emit_bytes!(mem, data, u32);
+ fn emit8(&self, mem: &mut JitMemory, data: u64) {
+ emit_bytes!(mem, data, u64);
+ fn emit_modrm(&self, mem: &mut JitMemory, modrm: u8, r: u8, m: u8) {
+ assert_eq!((modrm | 0xc0), 0xc0);
+ self.emit1(mem, (modrm & 0xc0) | ((r & 0b111) << 3) | (m & 0b111));
+ fn emit_modrm_reg2reg(&self, mem: &mut JitMemory, r: u8, m: u8) {
+ self.emit_modrm(mem, 0xc0, r, m);
+ fn emit_modrm_and_displacement(&self, mem: &mut JitMemory, r: u8, m: u8, d: i32) {
+ if d == 0 && (m & 0b111) != RBP {
+ self.emit_modrm(mem, 0x00, r, m);
+ } else if (-128..=127).contains(&d) {
+ self.emit_modrm(mem, 0x40, r, m);
+ self.emit1(mem, d as u8);
+ self.emit_modrm(mem, 0x80, r, m);
+ self.emit4(mem, d as u32);
+ fn basix_rex_would_set_bits(&self, w: u8, src: u8, dst: u8) -> bool {
+ w != 0 || (src & 0b1000) != 0 || (dst & 0b1000) != 0
+ fn emit_rex(&self, mem: &mut JitMemory, w: u8, r: u8, x: u8, b: u8) {
+ assert_eq!((w | 1), 1);
+ assert_eq!((r | 1), 1);
+ assert_eq!((x | 1), 1);
+ assert_eq!((b | 1), 1);
+ self.emit1(mem, 0x40 | (w << 3) | (r << 2) | (x << 1) | b);
+ // Emits a REX prefix with the top bit of src and dst.
+ // Skipped if no bits would be set.
+ fn emit_basic_rex(&self, mem: &mut JitMemory, w: u8, src: u8, dst: u8) {
+ if self.basix_rex_would_set_bits(w, src, dst) {
+ let is_masked = |val, mask| match val & mask {
+ 0 => 0,
+ self.emit_rex(mem, w, is_masked(src, 8), 0, is_masked(dst, 8));
+ fn emit_push(&self, mem: &mut JitMemory, r: u8) {
+ self.emit_basic_rex(mem, 0, 0, r);
+ self.emit1(mem, 0x50 | (r & 0b111));
+ fn emit_pop(&self, mem: &mut JitMemory, r: u8) {
+ self.emit1(mem, 0x58 | (r & 0b111));
+ // REX prefix and ModRM byte
+ // We use the MR encoding when there is a choice
+ // 'src' is often used as an opcode extension
+ fn emit_alu32(&self, mem: &mut JitMemory, op: u8, src: u8, dst: u8) {
+ self.emit_basic_rex(mem, 0, src, dst);
+ self.emit1(mem, op);
+ self.emit_modrm_reg2reg(mem, src, dst);
+ // REX prefix, ModRM byte, and 32-bit immediate
+ fn emit_alu32_imm32(&self, mem: &mut JitMemory, op: u8, src: u8, dst: u8, imm: i32) {
+ self.emit_alu32(mem, op, src, dst);
+ self.emit4(mem, imm as u32);
+ // REX prefix, ModRM byte, and 8-bit immediate
+ fn emit_alu32_imm8(&self, mem: &mut JitMemory, op: u8, src: u8, dst: u8, imm: i8) {
+ self.emit1(mem, imm as u8);
+ // REX.W prefix and ModRM byte
+ fn emit_alu64(&self, mem: &mut JitMemory, op: u8, src: u8, dst: u8) {
+ self.emit_basic_rex(mem, 1, src, dst);
+ // REX.W prefix, ModRM byte, and 32-bit immediate
+ fn emit_alu64_imm32(&self, mem: &mut JitMemory, op: u8, src: u8, dst: u8, imm: i32) {
+ self.emit_alu64(mem, op, src, dst);
+ // REX.W prefix, ModRM byte, and 8-bit immediate
+ fn emit_alu64_imm8(&self, mem: &mut JitMemory, op: u8, src: u8, dst: u8, imm: i8) {
+ // Register to register mov
+ fn emit_mov(&self, mem: &mut JitMemory, src: u8, dst: u8) {
+ self.emit_alu64(mem, 0x89, src, dst);
+ fn emit_cmp_imm32(&self, mem: &mut JitMemory, dst: u8, imm: i32) {
+ self.emit_alu64_imm32(mem, 0x81, 7, dst, imm);
+ fn emit_cmp(&self, mem: &mut JitMemory, src: u8, dst: u8) {
+ self.emit_alu64(mem, 0x39, src, dst);
+ fn emit_cmp32_imm32(&self, mem: &mut JitMemory, dst: u8, imm: i32) {
+ self.emit_alu32_imm32(mem, 0x81, 7, dst, imm);
+ fn emit_cmp32(&self, mem: &mut JitMemory, src: u8, dst: u8) {
+ self.emit_alu32(mem, 0x39, src, dst);
+ // Load [src + offset] into dst
+ fn emit_load(&self, mem: &mut JitMemory, size: OperandSize, src: u8, dst: u8, offset: i32) {
+ let data = match size {
+ OperandSize::S64 => 1,
+ _ => 0,
+ self.emit_basic_rex(mem, data, dst, src);
+ match size {
+ OperandSize::S8 => {
+ // movzx
+ self.emit1(mem, 0x0f);
+ self.emit1(mem, 0xb6);
+ OperandSize::S16 => {
+ self.emit1(mem, 0xb7);
+ OperandSize::S32 | OperandSize::S64 => {
+ // mov
+ self.emit1(mem, 0x8b);
+ self.emit_modrm_and_displacement(mem, dst, src, offset);
+ // Load sign-extended immediate into register
+ fn emit_load_imm(&self, mem: &mut JitMemory, dst: u8, imm: i64) {
+ if imm >= i32::MIN as i64 && imm <= i32::MAX as i64 {
+ self.emit_alu64_imm32(mem, 0xc7, 0, dst, imm as i32);
+ // movabs $imm,dst
+ self.emit_basic_rex(mem, 1, 0, dst);
+ self.emit1(mem, 0xb8 | (dst & 0b111));
+ self.emit8(mem, imm as u64);
+ // Store register src to [dst + offset]
+ fn emit_store(&self, mem: &mut JitMemory, size: OperandSize, src: u8, dst: u8, offset: i32) {
+ OperandSize::S16 => self.emit1(mem, 0x66), // 16-bit override
+ let (is_s8, is_u64, rexw) = match size {
+ OperandSize::S8 => (true, false, 0),
+ OperandSize::S64 => (false, true, 1),
+ _ => (false, false, 0),
+ if is_u64 || (src & 0b1000) != 0 || (dst & 0b1000) != 0 || is_s8 {
+ self.emit_rex(mem, rexw, is_masked(src, 8), 0, is_masked(dst, 8));
+ OperandSize::S8 => self.emit1(mem, 0x88),
+ _ => self.emit1(mem, 0x89),
+ self.emit_modrm_and_displacement(mem, src, dst, offset);
+ // Store immediate to [dst + offset]
+ fn emit_store_imm32(
+ mem: &mut JitMemory,
+ size: OperandSize,
+ dst: u8,
+ offset: i32,
+ imm: i32,
+ OperandSize::S64 => self.emit_basic_rex(mem, 1, 0, dst),
+ _ => self.emit_basic_rex(mem, 0, 0, dst),
+ OperandSize::S8 => self.emit1(mem, 0xc6),
+ _ => self.emit1(mem, 0xc7),
+ self.emit_modrm_and_displacement(mem, 0, dst, offset);
+ OperandSize::S8 => self.emit1(mem, imm as u8),
+ OperandSize::S16 => self.emit2(mem, imm as u16),
+ _ => self.emit4(mem, imm as u32),
+ fn emit_direct_jcc(&self, mem: &mut JitMemory, code: u8, offset: u32) {
+ self.emit1(mem, code);
+ emit_bytes!(mem, offset, u32);
+ fn emit_call(&self, mem: &mut JitMemory, target: usize) {
+ // TODO use direct call when possible
+ self.emit_load_imm(mem, RAX, target as i64);
+ // callq *%rax
+ self.emit1(mem, 0xff);
+ self.emit1(mem, 0xd0);
+ fn emit_jump_offset(&mut self, mem: &mut JitMemory, target_pc: isize) {
+ let jump = Jump {
+ offset_loc: mem.offset,
+ target_pc,
+ self.jumps.push(jump);
+ self.emit4(mem, 0);
+ fn emit_jcc(&mut self, mem: &mut JitMemory, code: u8, target_pc: isize) {
+ self.emit_jump_offset(mem, target_pc);
+ fn emit_jmp(&mut self, mem: &mut JitMemory, target_pc: isize) {
+ self.emit1(mem, 0xe9);
+ fn set_anchor(&mut self, mem: &mut JitMemory, target: isize) {
+ self.special_targets.insert(target, mem.offset);
+ fn emit_muldivmod(
+ pc: u16,
+ opc: u8,
+ src: u8,
+ let mul = (opc & ebpf::BPF_ALU_OP_MASK) == (ebpf::MUL32_IMM & ebpf::BPF_ALU_OP_MASK);
+ let div = (opc & ebpf::BPF_ALU_OP_MASK) == (ebpf::DIV32_IMM & ebpf::BPF_ALU_OP_MASK);
+ let modrm = (opc & ebpf::BPF_ALU_OP_MASK) == (ebpf::MOD32_IMM & ebpf::BPF_ALU_OP_MASK);
+ let is64 = (opc & ebpf::BPF_CLS_MASK) == ebpf::BPF_ALU64;
+ let is_reg = (opc & ebpf::BPF_X) == ebpf::BPF_X;
+ if (div || mul) && !is_reg && imm == 0 {
+ // Division by zero returns 0
+ // Set register to 0: xor with itself
+ self.emit_alu32(mem, 0x31, dst, dst);
+ if modrm && !is_reg && imm == 0 {
+ // Modulo remainder of division by zero keeps destination register unchanged
+ if (div || modrm) && is_reg {
+ self.emit_load_imm(mem, RCX, pc as i64);
+ // test src,src
+ if is64 {
+ self.emit_alu64(mem, 0x85, src, src);
+ self.emit_alu32(mem, 0x85, src, src);
+ if div {
+ // No division by 0: skip next instructions
+ // Jump offset: emit_alu32 adds 2 to 3 bytes, emit_jmp adds 5
+ let offset = match self.basix_rex_would_set_bits(0, dst, dst) {
+ true => 3 + 5,
+ false => 2 + 5,
+ self.emit_direct_jcc(mem, 0x85, offset);
+ // Division by 0: set dst to 0 then go to next instruction
+ self.emit_jmp(mem, (pc + 1) as isize);
+ if modrm {
+ // Modulo by zero: keep destination register unchanged
+ self.emit_jcc(mem, 0x84, (pc + 1) as isize);
+ if dst != RAX {
+ self.emit_push(mem, RAX);
+ if dst != RDX {
+ self.emit_push(mem, RDX);
+ if imm != 0 {
+ self.emit_load_imm(mem, RCX, imm as i64);
+ self.emit_mov(mem, src, RCX);
+ self.emit_mov(mem, dst, RAX);
+ if div || modrm {
+ // Set register to 0: xor %edx,%edx
+ self.emit_alu32(mem, 0x31, RDX, RDX);
+ self.emit_rex(mem, 1, 0, 0, 0);
+ // mul %ecx or div %ecx
+ self.emit_alu32(mem, 0xf7, if mul { 4 } else { 6 }, RCX);
+ self.emit_mov(mem, RDX, dst);
+ self.emit_pop(mem, RDX);
+ if div || mul {
+ self.emit_mov(mem, RAX, dst);
+ self.emit_pop(mem, RAX);
+ fn jit_compile(
+ prog: &[u8],
+ use_mbuff: bool,
+ update_data_ptr: bool,
+ self.emit_push(mem, RBP);
+ self.emit_push(mem, RBX);
+ self.emit_push(mem, R13);
+ self.emit_push(mem, R14);
+ self.emit_push(mem, R15);
+ // RDI: mbuff
+ // RSI: mbuff_len
+ // RDX: mem
+ // RCX: mem_len
+ // R8: mem_offset
+ // R9: mem_end_offset
+ // Save mem pointer for use with LD_ABS_* and LD_IND_* instructions
+ self.emit_mov(mem, RDX, R10);
+ match (use_mbuff, update_data_ptr) {
+ (false, _) => {
+ // We do not use any mbuff. Move mem pointer into register 1.
+ if map_register(1) != RDX {
+ self.emit_mov(mem, RDX, map_register(1));
+ (true, false) => {
+ // We use a mbuff already pointing to mem and mem_end: move it to register 1.
+ if map_register(1) != RDI {
+ self.emit_mov(mem, RDI, map_register(1));
+ (true, true) => {
+ // We have a fixed (simulated) mbuff: update mem and mem_end offset values in it.
+ // Store mem at mbuff + mem_offset. Trash R8.
+ self.emit_alu64(mem, 0x01, RDI, R8); // add mbuff to mem_offset in R8
+ self.emit_store(mem, OperandSize::S64, RDX, R8, 0); // set mem at mbuff + mem_offset
+ // Store mem_end at mbuff + mem_end_offset. Trash R9.
+ self.emit_load(mem, OperandSize::S64, RDX, R8, 0); // load mem into R8
+ self.emit_alu64(mem, 0x01, RCX, R8); // add mem_len to mem (= mem_end)
+ self.emit_alu64(mem, 0x01, RDI, R9); // add mbuff to mem_end_offset
+ self.emit_store(mem, OperandSize::S64, R8, R9, 0); // store mem_end
+ // Move rdi into register 1
+ // Copy stack pointer to R10
+ self.emit_mov(mem, RSP, map_register(10));
+ // Allocate stack space
+ self.emit_alu64_imm32(mem, 0x81, 5, RSP, ebpf::STACK_SIZE as i32);
+ self.pc_locs = vec![0; prog.len() / ebpf::INSN_SIZE + 1];
+ self.pc_locs[insn_ptr] = mem.offset;
+ let dst = map_register(insn.dst);
+ let src = map_register(insn.src);
+ let target_pc = insn_ptr as isize + insn.off as isize + 1;
+ // R10 is a constant pointer to mem.
+ ebpf::LD_ABS_B => self.emit_load(mem, OperandSize::S8, R10, RAX, insn.imm),
+ ebpf::LD_ABS_H => self.emit_load(mem, OperandSize::S16, R10, RAX, insn.imm),
+ ebpf::LD_ABS_W => self.emit_load(mem, OperandSize::S32, R10, RAX, insn.imm),
+ ebpf::LD_ABS_DW => self.emit_load(mem, OperandSize::S64, R10, RAX, insn.imm),
+ self.emit_mov(mem, R10, R11); // load mem into R11
+ self.emit_alu64(mem, 0x01, src, R11); // add src to R11
+ self.emit_load(mem, OperandSize::S8, R11, RAX, insn.imm); // ld R0, mem[src+imm]
+ self.emit_load(mem, OperandSize::S16, R11, RAX, insn.imm); // ld R0, mem[src+imm]
+ self.emit_load(mem, OperandSize::S32, R11, RAX, insn.imm); // ld R0, mem[src+imm]
+ self.emit_load(mem, OperandSize::S64, R11, RAX, insn.imm); // ld R0, mem[src+imm]
+ let second_part = ebpf::get_insn(prog, insn_ptr).imm as u64;
+ let imm = (insn.imm as u32) as u64 | second_part.wrapping_shl(32);
+ self.emit_load_imm(mem, dst, imm as i64);
+ ebpf::LD_B_REG => self.emit_load(mem, OperandSize::S8, src, dst, insn.off as i32),
+ ebpf::LD_H_REG => self.emit_load(mem, OperandSize::S16, src, dst, insn.off as i32),
+ ebpf::LD_W_REG => self.emit_load(mem, OperandSize::S32, src, dst, insn.off as i32),
+ ebpf::LD_DW_REG => self.emit_load(mem, OperandSize::S64, src, dst, insn.off as i32),
+ self.emit_store_imm32(mem, OperandSize::S8, dst, insn.off as i32, insn.imm)
+ self.emit_store_imm32(mem, OperandSize::S16, dst, insn.off as i32, insn.imm)
+ self.emit_store_imm32(mem, OperandSize::S32, dst, insn.off as i32, insn.imm)
+ self.emit_store_imm32(mem, OperandSize::S64, dst, insn.off as i32, insn.imm)
+ ebpf::ST_B_REG => self.emit_store(mem, OperandSize::S8, src, dst, insn.off as i32),
+ ebpf::ST_H_REG => self.emit_store(mem, OperandSize::S16, src, dst, insn.off as i32),
+ ebpf::ST_W_REG => self.emit_store(mem, OperandSize::S32, src, dst, insn.off as i32),
+ self.emit_store(mem, OperandSize::S64, src, dst, insn.off as i32)
+ ebpf::ADD32_IMM => self.emit_alu32_imm32(mem, 0x81, 0, dst, insn.imm),
+ ebpf::ADD32_REG => self.emit_alu32(mem, 0x01, src, dst),
+ ebpf::SUB32_IMM => self.emit_alu32_imm32(mem, 0x81, 5, dst, insn.imm),
+ ebpf::SUB32_REG => self.emit_alu32(mem, 0x29, src, dst),
+ ebpf::MUL32_IMM
+ | ebpf::MUL32_REG
+ | ebpf::DIV32_IMM
+ | ebpf::DIV32_REG
+ | ebpf::MOD32_IMM
+ | ebpf::MOD32_REG => {
+ self.emit_muldivmod(mem, insn_ptr as u16, insn.opc, src, dst, insn.imm)
+ ebpf::OR32_IMM => self.emit_alu32_imm32(mem, 0x81, 1, dst, insn.imm),
+ ebpf::OR32_REG => self.emit_alu32(mem, 0x09, src, dst),
+ ebpf::AND32_IMM => self.emit_alu32_imm32(mem, 0x81, 4, dst, insn.imm),
+ ebpf::AND32_REG => self.emit_alu32(mem, 0x21, src, dst),
+ ebpf::LSH32_IMM => self.emit_alu32_imm8(mem, 0xc1, 4, dst, insn.imm as i8),
+ self.emit_alu32(mem, 0xd3, 4, dst);
+ ebpf::RSH32_IMM => self.emit_alu32_imm8(mem, 0xc1, 5, dst, insn.imm as i8),
+ self.emit_alu32(mem, 0xd3, 5, dst);
+ ebpf::NEG32 => self.emit_alu32(mem, 0xf7, 3, dst),
+ ebpf::XOR32_IMM => self.emit_alu32_imm32(mem, 0x81, 6, dst, insn.imm),
+ ebpf::XOR32_REG => self.emit_alu32(mem, 0x31, src, dst),
+ ebpf::MOV32_IMM => self.emit_alu32_imm32(mem, 0xc7, 0, dst, insn.imm),
+ ebpf::MOV32_REG => self.emit_mov(mem, src, dst),
+ ebpf::ARSH32_IMM => self.emit_alu32_imm8(mem, 0xc1, 7, dst, insn.imm as i8),
+ self.emit_alu32(mem, 0xd3, 7, dst);
+ ebpf::LE => {} // No-op
+ 16 => {
+ // rol
+ self.emit1(mem, 0x66); // 16-bit override
+ self.emit_alu32_imm8(mem, 0xc1, 0, dst, 8);
+ // and
+ self.emit_alu32_imm32(mem, 0x81, 4, dst, 0xffff);
+ 32 | 64 => {
+ // bswap
+ let bit = match insn.imm {
+ 64 => 1,
+ self.emit_basic_rex(mem, bit, 0, dst);
+ self.emit1(mem, 0xc8 | (dst & 0b111));
+ _ => unreachable!(), // Should have been caught by verifier
+ ebpf::ADD64_IMM => self.emit_alu64_imm32(mem, 0x81, 0, dst, insn.imm),
+ ebpf::ADD64_REG => self.emit_alu64(mem, 0x01, src, dst),
+ ebpf::SUB64_IMM => self.emit_alu64_imm32(mem, 0x81, 5, dst, insn.imm),
+ ebpf::SUB64_REG => self.emit_alu64(mem, 0x29, src, dst),
+ ebpf::MUL64_IMM
+ | ebpf::MUL64_REG
+ | ebpf::DIV64_IMM
+ | ebpf::DIV64_REG
+ | ebpf::MOD64_IMM
+ | ebpf::MOD64_REG => {
+ ebpf::OR64_IMM => self.emit_alu64_imm32(mem, 0x81, 1, dst, insn.imm),
+ ebpf::OR64_REG => self.emit_alu64(mem, 0x09, src, dst),
+ ebpf::AND64_IMM => self.emit_alu64_imm32(mem, 0x81, 4, dst, insn.imm),
+ ebpf::AND64_REG => self.emit_alu64(mem, 0x21, src, dst),
+ ebpf::LSH64_IMM => self.emit_alu64_imm8(mem, 0xc1, 4, dst, insn.imm as i8),
+ self.emit_alu64(mem, 0xd3, 4, dst);
+ ebpf::RSH64_IMM => self.emit_alu64_imm8(mem, 0xc1, 5, dst, insn.imm as i8),
+ self.emit_alu64(mem, 0xd3, 5, dst);
+ ebpf::NEG64 => self.emit_alu64(mem, 0xf7, 3, dst),
+ ebpf::XOR64_IMM => self.emit_alu64_imm32(mem, 0x81, 6, dst, insn.imm),
+ ebpf::XOR64_REG => self.emit_alu64(mem, 0x31, src, dst),
+ ebpf::MOV64_IMM => self.emit_load_imm(mem, dst, insn.imm as i64),
+ ebpf::MOV64_REG => self.emit_mov(mem, src, dst),
+ ebpf::ARSH64_IMM => self.emit_alu64_imm8(mem, 0xc1, 7, dst, insn.imm as i8),
+ self.emit_alu64(mem, 0xd3, 7, dst);
+ ebpf::JA => self.emit_jmp(mem, target_pc),
+ self.emit_cmp_imm32(mem, dst, insn.imm);
+ self.emit_jcc(mem, 0x84, target_pc);
+ self.emit_cmp(mem, src, dst);
+ self.emit_jcc(mem, 0x87, target_pc);
+ self.emit_jcc(mem, 0x83, target_pc);
+ self.emit_jcc(mem, 0x82, target_pc);
+ self.emit_jcc(mem, 0x86, target_pc);
+ self.emit_alu64_imm32(mem, 0xf7, 0, dst, insn.imm);
+ self.emit_jcc(mem, 0x85, target_pc);
+ self.emit_alu64(mem, 0x85, src, dst);
+ self.emit_jcc(mem, 0x8f, target_pc);
+ self.emit_jcc(mem, 0x8d, target_pc);
+ self.emit_jcc(mem, 0x8c, target_pc);
+ self.emit_jcc(mem, 0x8e, target_pc);
+ self.emit_cmp32_imm32(mem, dst, insn.imm);
+ self.emit_cmp32(mem, src, dst);
+ self.emit_alu32_imm32(mem, 0xf7, 0, dst, insn.imm);
+ self.emit_alu32(mem, 0x85, src, dst);
+ // For JIT, helpers in use MUST be registered at compile time. They can be
+ // updated later, but not created after compiling (we need the address of the
+ // helper function in the JIT-compiled program).
+ if let Some(helper) = helpers.get(&(insn.imm as u32)) {
+ // We reserve RCX for shifts
+ self.emit_mov(mem, R9, RCX);
+ self.emit_call(mem, *helper as usize);
+ Err(Error::new(
+ "[JIT] Error: unknown helper function (id: {:#x})",
+ ))?;
+ unimplemented!()
+ if insn_ptr != prog.len() / ebpf::INSN_SIZE - 1 {
+ self.emit_jmp(mem, TARGET_PC_EXIT);
+ "[JIT] Error: unknown eBPF opcode {:#2x} (insn #{insn_ptr:?})",
+ insn.opc
+ // Epilogue
+ self.set_anchor(mem, TARGET_PC_EXIT);
+ // Move register 0 into rax
+ if map_register(0) != RAX {
+ self.emit_mov(mem, map_register(0), RAX);
+ // Deallocate stack space
+ self.emit_alu64_imm32(mem, 0x81, 0, RSP, ebpf::STACK_SIZE as i32);
+ self.emit_pop(mem, R15);
+ self.emit_pop(mem, R14);
+ self.emit_pop(mem, R13);
+ self.emit_pop(mem, RBX);
+ self.emit_pop(mem, RBP);
+ self.emit1(mem, 0xc3); // ret
+ fn resolve_jumps(&mut self, mem: &mut JitMemory) -> Result<(), Error> {
+ for jump in &self.jumps {
+ let target_loc = match self.special_targets.get(&jump.target_pc) {
+ Some(target) => *target,
+ None => self.pc_locs[jump.target_pc as usize],
+ // Assumes jump offset is at end of instruction
+ let offset_loc = jump.offset_loc as i32 + std::mem::size_of::<i32>() as i32;
+ let rel = &(target_loc as i32 - offset_loc) as *const i32;
+ let offset_ptr = mem.contents.as_ptr().add(jump.offset_loc);
+ libc::memcpy(
+ offset_ptr as *mut libc::c_void,
+ rel as *const libc::c_void,
+ std::mem::size_of::<i32>(),
+} // impl JitCompiler
+pub struct JitMemory<'a> {
+ contents: &'a mut [u8],
+impl<'a> JitMemory<'a> {
+ ) -> Result<JitMemory<'a>, Error> {
+ let contents: &mut [u8];
+ let mut raw: mem::MaybeUninit<*mut libc::c_void> = mem::MaybeUninit::uninit();
+ let size = NUM_PAGES * PAGE_SIZE;
+ libc::posix_memalign(raw.as_mut_ptr(), PAGE_SIZE, size);
+ libc::mprotect(
+ *raw.as_mut_ptr(),
+ size,
+ libc::PROT_EXEC | libc::PROT_READ | libc::PROT_WRITE,
+ std::ptr::write_bytes(*raw.as_mut_ptr(), 0xc3, size); // for now, prepopulate with 'RET' calls
+ contents =
+ std::slice::from_raw_parts_mut(*raw.as_mut_ptr() as *mut u8, NUM_PAGES * PAGE_SIZE);
+ raw.assume_init();
+ let mut mem = JitMemory {
+ contents,
+ offset: 0,
+ let mut jit = JitCompiler::new();
+ jit.jit_compile(&mut mem, prog, use_mbuff, update_data_ptr, helpers)?;
+ jit.resolve_jumps(&mut mem)?;
+ Ok(mem)
+ pub fn get_prog(&self) -> MachineCode {
+ unsafe { mem::transmute(self.contents.as_ptr()) }
+impl<'a> Index<usize> for JitMemory<'a> {
+ type Output = u8;
+ fn index(&self, _index: usize) -> &u8 {
+ &self.contents[_index]
+impl<'a> IndexMut<usize> for JitMemory<'a> {
+ fn index_mut(&mut self, _index: usize) -> &mut u8 {
+ &mut self.contents[_index]
+impl<'a> Drop for JitMemory<'a> {
+ libc::free(self.contents.as_mut_ptr() as *mut libc::c_void);
+impl<'a> std::fmt::Debug for JitMemory<'a> {
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatterError> {
+ fmt.write_str("JIT contents: [")?;
+ fmt.write_str(" ] | ")?;
+ fmt.debug_struct("JIT memory")
@@ -0,0 +1,1782 @@
+// Copyright 2023 Isovalent, Inc. <quentin@isovalent.com>
+//! Virtual machine and JIT compiler for eBPF programs.
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/qmonnet/rbpf/main/misc/rbpf.png",
+ html_favicon_url = "https://raw.githubusercontent.com/qmonnet/rbpf/main/misc/rbpf.ico"
+)]
+#![warn(missing_docs)]
+// There are unused mut warnings due to unsafe code.
+#![allow(unused_mut)]
+// Allows old-style clippy
+#![allow(renamed_and_removed_lints)]
+#![cfg_attr(
+ clippy,
+ allow(
+ redundant_field_names,
+ single_match,
+ cast_lossless,
+ doc_markdown,
+ match_same_arms,
+ unreadable_literal
+// Configures the crate to be `no_std` when `std` feature is disabled.
+#![cfg_attr(not(feature = "std"), no_std)]
+type HashMap<K, V> = BTreeMap<K, V>;
+#[cfg(feature = "cranelift")]
+type HashSet<T> = alloc::collections::BTreeSet<T>;
+mod asm_parser;
+pub mod assembler;
+mod cranelift;
+pub mod disassembler;
+pub mod ebpf;
+pub mod helpers;
+pub mod insn_builder;
+mod interpreter;
+#[cfg(all(not(windows), feature = "std"))]
+mod jit;
+#[cfg(not(feature = "std"))]
+mod no_std_error;
+mod stack;
+mod verifier;
+pub use std::io::{Error, ErrorKind};
+/// In no_std we use a custom implementation of the error which acts as a
+/// replacement for the io Error.
+pub use crate::no_std_error::{Error, ErrorKind};
+/// eBPF verification function that returns an error if the program does not meet its requirements.
+/// Some examples of things the verifier may reject the program for:
+/// - Program does not terminate.
+/// - Unknown instructions.
+/// - Bad formed instruction.
+/// - Unknown eBPF helper index.
+/// eBPF helper function.
+// A metadata buffer with two offset indications. It can be used in one kind of eBPF VM to simulate
+// the use of a metadata buffer each time the program is executed, without the user having to
+// actually handle it. The offsets are used to tell the VM where in the buffer the pointers to
+// packet data start and end should be stored each time the program is run on a new packet.
+struct MetaBuff {
+ data_end_offset: usize,
+ buffer: Vec<u8>,
+/// A virtual machine to run eBPF program. This kind of VM is used for programs expecting to work
+/// on a metadata buffer containing pointers to packet data.
+/// 0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // Load mem from mbuff at offset 8 into R1.
+/// 0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ldhx r1[2], r0
+/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
+/// 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 = [0u8; 32];
+/// 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;
+/// // Instantiate a VM.
+/// let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
+/// // Provide both a reference to the packet data, and to the metadata buffer.
+/// let res = vm.execute_program(mem, &mut mbuff).unwrap();
+/// assert_eq!(res, 0x2211);
+pub struct EbpfVmMbuff<'a> {
+ prog: Option<&'a [u8]>,
+ verifier: Verifier,
+ jit: Option<jit::JitMemory<'a>>,
+ cranelift_prog: Option<cranelift::CraneliftProgram>,
+impl<'a> EbpfVmMbuff<'a> {
+ /// Create a new virtual machine instance, and load an eBPF program into that instance.
+ /// When attempting to load the program, it passes through a simple verifier.
+ /// let prog = &[
+ /// 0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // Load mem from mbuff into R1.
+ /// 0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ldhx r1[2], r0
+ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
+ /// // Instantiate a VM.
+ /// let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
+ pub fn new(prog: Option<&'a [u8]>) -> Result<EbpfVmMbuff<'a>, Error> {
+ if let Some(prog) = prog {
+ verifier::check(prog)?;
+ Ok(EbpfVmMbuff {
+ prog,
+ verifier: verifier::check,
+ jit: None,
+ cranelift_prog: None,
+ helpers: HashMap::new(),
+ /// Load a new eBPF program into the virtual machine instance.
+ /// let prog1 = &[
+ /// 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
+ /// let prog2 = &[
+ /// let mut vm = rbpf::EbpfVmMbuff::new(Some(prog1)).unwrap();
+ /// vm.set_program(prog2).unwrap();
+ pub fn set_program(&mut self, prog: &'a [u8]) -> Result<(), Error> {
+ (self.verifier)(prog)?;
+ self.prog = Some(prog);
+ /// Set a new verifier function. The function should return an `Error` if the program should be
+ /// rejected by the virtual machine. If a program has been loaded to the VM already, the
+ /// verifier is immediately run.
+ /// use rbpf::{Error, ErrorKind};
+ /// // Define a simple verifier function.
+ /// fn verifier(prog: &[u8]) -> Result<(), Error> {
+ /// let last_insn = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1);
+ /// if last_insn.opc != ebpf::EXIT {
+ /// return Err(Error::new(ErrorKind::Other,
+ /// "[Verifier] Error: program does not end with “EXIT” instruction"));
+ /// }
+ /// Ok(())
+ /// // Change the verifier.
+ /// vm.set_verifier(verifier).unwrap();
+ pub fn set_verifier(&mut self, verifier: Verifier) -> Result<(), Error> {
+ if let Some(prog) = self.prog {
+ verifier(prog)?;
+ self.verifier = verifier;
+ /// Register a built-in or user-defined helper function in order to use it later from within
+ /// the eBPF program. The helper is registered into a hashmap, so the `key` can be any `u32`.
+ /// If using JIT-compiled eBPF programs, be sure to register all helpers before compiling the
+ /// program. You should be able to change registered helpers after compiling, but not to add
+ /// new ones (i.e. with new keys).
+ /// use rbpf::helpers;
+ /// // This program was compiled with clang, from a C program containing the following single
+ /// // instruction: `return bpf_trace_printk("foo %c %c %c\n", 10, 1, 2, 3);`
+ /// 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load 0 as u64 into r1 (That would be
+ /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // replaced by tc by the address of
+ /// // the format string, in the .map
+ /// // section of the ELF file).
+ /// 0xb7, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, // mov r2, 10
+ /// 0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // mov r3, 1
+ /// 0xb7, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov r4, 2
+ /// 0xb7, 0x05, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // mov r5, 3
+ /// 0x85, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // call helper with key 6
+ /// // Register a helper.
+ /// // On running the program this helper will print the content of registers r3, r4 and r5 to
+ /// // standard output.
+ /// # #[cfg(feature = "std")]
+ /// vm.register_helper(6, helpers::bpf_trace_printf).unwrap();
+ pub fn register_helper(&mut self, key: u32, function: Helper) -> Result<(), Error> {
+ self.helpers.insert(key, function);
+ /// Execute the program loaded, with the given packet data and metadata buffer.
+ /// If the program is made to be compatible with Linux kernel, it is expected to load the
+ /// address of the beginning and of the end of the memory area used for packet data from the
+ /// metadata buffer, at some appointed offsets. It is up to the user to ensure that these
+ /// pointers are correctly stored in the buffer.
+ /// 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 = [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;
+ /// // Provide both a reference to the packet data, and to the metadata buffer.
+ /// let res = vm.execute_program(mem, &mut mbuff).unwrap();
+ /// assert_eq!(res, 0x2211);
+ pub fn execute_program(&self, mem: &[u8], mbuff: &[u8]) -> Result<u64, Error> {
+ interpreter::execute_program(self.prog, mem, mbuff, &self.helpers)
+ /// JIT-compile the loaded program. No argument required for this.
+ /// If using helper functions, be sure to register them into the VM before calling this
+ /// function.
+ /// vm.jit_compile();
+ pub fn jit_compile(&mut self) -> Result<(), Error> {
+ let prog = match self.prog {
+ self.jit = Some(jit::JitMemory::new(prog, &self.helpers, true, false)?);
+ /// Execute the previously JIT-compiled program, with the given packet data and metadata
+ /// buffer, in a manner very similar to `execute_program()`.
+ /// # Safety
+ /// **WARNING:** JIT-compiled assembly code is not safe, in particular there is no runtime
+ /// check for memory access; so if the eBPF program attempts erroneous accesses, this may end
+ /// very bad (program may segfault). It may be wise to check that the program works with the
+ /// interpreter before running the JIT-compiled version of it.
+ /// For this reason the function should be called from within an `unsafe` bloc.
+ /// 0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // Load mem from mbuff into r1.
+ /// # #[cfg(all(not(windows), feature = "std"))]
+ /// let res = vm.execute_program_jit(mem, &mut mbuff).unwrap();
+ pub unsafe fn execute_program_jit(
+ mem: &mut [u8],
+ mbuff: &'a mut [u8],
+ ) -> Result<u64, Error> {
+ // If packet data is empty, do not send the address of an empty slice; send a null pointer
+ // as first argument instead, as this is uBPF's behavior (empty packet should not happen
+ // in the kernel; anyway the verifier would prevent the use of uninitialized registers).
+ // See `mul_loop` test.
+ let mem_ptr = match mem.len() {
+ 0 => std::ptr::null_mut(),
+ _ => mem.as_ptr() as *mut u8,
+ // The last two arguments are not used in this function. They would be used if there was a
+ // need to indicate to the JIT at which offset in the mbuff mem_ptr and mem_ptr + mem.len()
+ // should be stored; this is what happens with struct EbpfVmFixedMbuff.
+ match &self.jit {
+ Some(jit) => Ok(jit.get_prog()(
+ mbuff.as_ptr() as *mut u8,
+ mem_ptr,
+ )),
+ "Error: program has not been JIT-compiled",
+ /// Compile the loaded program using the Cranelift JIT.
+ /// vm.cranelift_compile();
+ pub fn cranelift_compile(&mut self) -> Result<(), Error> {
+ use crate::cranelift::CraneliftCompiler;
+ let mut compiler = CraneliftCompiler::new(self.helpers.clone());
+ let program = compiler.compile_function(prog)?;
+ self.cranelift_prog = Some(program);
+ /// Execute the previously compiled program, with the given packet data and metadata
+ /// let res = vm.execute_program_cranelift(mem, &mut mbuff).unwrap();
+ pub fn execute_program_cranelift(
+ 0 => core::ptr::null_mut(),
+ match &self.cranelift_prog {
+ Some(prog) => {
+ Ok(prog.execute(mem_ptr, mem.len(), mbuff.as_ptr() as *mut u8, mbuff.len()))
+ "Error: program has not been compiled with cranelift",
+/// on a metadata buffer containing pointers to packet data, but it internally handles the buffer
+/// so as to save the effort to manually handle the metadata buffer for the user.
+/// This struct implements 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 `execute_program()` or `execute_program_jit()` functions, 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.
+/// This was compiled with clang from the following program, in C:
+/// SEC(".classifier")
+/// int classifier(struct __sk_buff *skb)
+/// void *data = (void *)(long)skb->data;
+/// void *data_end = (void *)(long)skb->data_end;
+/// // Check program is long enough.
+/// if (data + 5 > data_end)
+/// return 0;
+/// return *((char *)data + 5);
+/// Some small modifications have been brought to have it work, see comments.
+/// 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
+/// // Here opcode 0x61 had to be replace by 0x79 so as to load a 8-bytes long address.
+/// // Also, offset 0x4c had to be replace with e.g. 0x40 so as to prevent the two pointers
+/// // from overlapping in the buffer.
+/// 0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load pointer to mem from r1[0x40] to r2
+/// 0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
+/// 0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load ptr to mem_end from r1[0x50] to r1
+/// 0x2d, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
+/// 0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
+/// 0x67, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, // r0 >>= 56
+/// 0xc7, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, // r0 <<= 56 (arsh) extend byte sign to u64
+/// let mem1 = &mut [
+/// let mem2 = &mut [
+/// 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x27
+/// // Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
+/// let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
+/// // Provide only a reference to the packet data. We do not manage the metadata buffer.
+/// let res = vm.execute_program(mem1).unwrap();
+/// assert_eq!(res, 0xffffffffffffffdd);
+/// let res = vm.execute_program(mem2).unwrap();
+/// assert_eq!(res, 0x27);
+pub struct EbpfVmFixedMbuff<'a> {
+ parent: EbpfVmMbuff<'a>,
+ mbuff: MetaBuff,
+impl<'a> EbpfVmFixedMbuff<'a> {
+ /// 0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
+ /// 0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
+ /// 0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
+ /// 0x2d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 3 instructions
+ /// 0x71, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r0
+ /// // Instantiate a VM. Note that we provide the start and end offsets for mem pointers.
+ /// let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
+ ) -> Result<EbpfVmFixedMbuff<'a>, Error> {
+ let parent = EbpfVmMbuff::new(prog)?;
+ let get_buff_len = |x: usize, y: usize| if x >= y { x + 8 } else { y + 8 };
+ let buffer = vec![0u8; get_buff_len(data_offset, data_end_offset)];
+ let mbuff = MetaBuff {
+ data_offset,
+ data_end_offset,
+ buffer,
+ Ok(EbpfVmFixedMbuff { parent, mbuff })
+ /// At the same time, load new offsets for storing pointers to start and end of packet data in
+ /// the internal metadata buffer.
+ /// 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x27,
+ /// let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog1), 0, 0).unwrap();
+ /// vm.set_program(prog2, 0x40, 0x50);
+ /// let res = vm.execute_program(mem).unwrap();
+ /// assert_eq!(res, 0x27);
+ pub fn set_program(
+ prog: &'a [u8],
+ self.mbuff.buffer = buffer;
+ self.mbuff.data_offset = data_offset;
+ self.mbuff.data_end_offset = data_end_offset;
+ self.parent.set_program(prog)?;
+ self.parent.set_verifier(verifier)
+ /// #[cfg(feature = "std")] {
+ /// 0x2d, 0x12, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 6 instructions
+ /// 0x71, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r1
+ /// 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
+ /// 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
+ /// 0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
+ /// 0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
+ /// 0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
+ /// 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x09,
+ /// // Register a helper. This helper will store the result of the square root of r1 into r0.
+ /// vm.register_helper(1, helpers::sqrti);
+ /// assert_eq!(res, 3);
+ pub fn register_helper(
+ function: fn(u64, u64, u64, u64, u64) -> u64,
+ self.parent.register_helper(key, function)
+ /// Execute the program loaded, with the given packet data.
+ /// address of the beginning and of the end of the memory area used for packet data from some
+ /// metadata buffer, which in the case of this VM is handled internally. The offsets at which
+ /// the addresses should be placed should have be set at the creation of the VM.
+ /// // Provide only a reference to the packet data. We do not manage the metadata buffer.
+ /// assert_eq!(res, 0xdd);
+ pub fn execute_program(&mut self, mem: &'a mut [u8]) -> Result<u64, Error> {
+ let l = self.mbuff.buffer.len();
+ // Can this ever happen? Probably not, should be ensured at mbuff creation.
+ if self.mbuff.data_offset + 8 > l || self.mbuff.data_end_offset + 8 > l {
+ Err(Error::new(ErrorKind::Other, format!("Error: buffer too small ({:?}), cannot use data_offset {:?} and data_end_offset {:?}",
+ l, self.mbuff.data_offset, self.mbuff.data_end_offset)))?;
+ LittleEndian::write_u64(
+ &mut self.mbuff.buffer[(self.mbuff.data_offset)..],
+ &mut self.mbuff.buffer[(self.mbuff.data_end_offset)..],
+ mem.as_ptr() as u64 + mem.len() as u64,
+ self.parent.execute_program(mem, &self.mbuff.buffer)
+ let prog = match self.parent.prog {
+ self.parent.jit = Some(jit::JitMemory::new(prog, &self.parent.helpers, true, true)?);
+ /// Execute the previously JIT-compiled program, with the given packet data, in a manner very
+ /// similar to `execute_program()`.
+ /// let res = vm.execute_program_jit(mem).unwrap();
+ // This struct redefines the `execute_program_jit()` function, in order to pass the offsets
+ // associated with the fixed mbuff.
+ pub unsafe fn execute_program_jit(&mut self, mem: &'a mut [u8]) -> Result<u64, Error> {
+ match &self.parent.jit {
+ self.mbuff.buffer.as_ptr() as *mut u8,
+ self.mbuff.buffer.len(),
+ self.mbuff.data_offset,
+ self.mbuff.data_end_offset,
+ let mut compiler = CraneliftCompiler::new(self.parent.helpers.clone());
+ self.parent.cranelift_prog = Some(program);
+ /// let res = vm.execute_program_cranelift(mem).unwrap();
+ pub fn execute_program_cranelift(&mut self, mem: &'a mut [u8]) -> Result<u64, Error> {
+ match &self.parent.cranelift_prog {
+ Some(prog) => Ok(prog.execute(
+/// directly on the memory area representing packet data.
+/// 0x71, 0x11, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxb r1[0x04], r1
+/// 0x07, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, // add r1, 0x22
+/// 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, r1
+/// let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
+/// // Provide only a reference to the packet data.
+/// let res = vm.execute_program(mem).unwrap();
+/// assert_eq!(res, 0x22cc);
+pub struct EbpfVmRaw<'a> {
+impl<'a> EbpfVmRaw<'a> {
+ /// 0x71, 0x11, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxb r1[0x04], r1
+ /// 0x07, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, // add r1, 0x22
+ /// 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, r1
+ /// let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
+ pub fn new(prog: Option<&'a [u8]>) -> Result<EbpfVmRaw<'a>, Error> {
+ Ok(EbpfVmRaw { parent })
+ /// let mut vm = rbpf::EbpfVmRaw::new(Some(prog1)).unwrap();
+ /// vm.set_program(prog2);
+ /// assert_eq!(res, 0x22cc);
+ /// 0x79, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxdw r1, r1[0x00]
+ /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ /// let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
+ /// assert_eq!(res, 0x10000000);
+ /// Register a set of built-in or user-defined helper functions in order to use them later from
+ /// within the eBPF program. The helpers are registered into a hashmap, so the `key` can be any
+ /// `u32`.
+ #[allow(clippy::type_complexity)]
+ pub fn register_helper_set(
+ helpers: &HashMap<u32, fn(u64, u64, u64, u64, u64) -> u64>,
+ for (key, function) in helpers {
+ self.parent.register_helper(*key, *function)?;
+ /// 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x27
+ pub fn execute_program(&self, mem: &'a mut [u8]) -> Result<u64, Error> {
+ self.parent.execute_program(mem, &[])
+ self.parent.jit = Some(jit::JitMemory::new(
+ &self.parent.helpers,
+ false,
+ )?);
+ pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8]) -> Result<u64, Error> {
+ let mut mbuff = vec![];
+ self.parent.execute_program_jit(mem, &mut mbuff)
+ /// Execute the previously compiled program, with the given packet data, in a manner very
+ pub fn execute_program_cranelift(&self, mem: &'a mut [u8]) -> Result<u64, Error> {
+ self.parent.execute_program_cranelift(mem, &mut mbuff)
+/// A virtual machine to run eBPF program. This kind of VM is used for programs that do not work
+/// with any memory area—no metadata buffer, no packet data either.
+/// 0xb7, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // mov r1, 1
+/// 0xb7, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov r2, 2
+/// 0xb7, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // mov r3, 3
+/// 0xb7, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, // mov r4, 4
+/// 0xb7, 0x05, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // mov r5, 5
+/// 0xb7, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // mov r6, 6
+/// 0xb7, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, // mov r7, 7
+/// 0xb7, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // mov r8, 8
+/// 0x4f, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // or r0, r5
+/// 0x47, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, // or r0, 0xa0
+/// 0x57, 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, // and r0, 0xa3
+/// 0xb7, 0x09, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, // mov r9, 0x91
+/// 0x5f, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // and r0, r9
+/// 0x67, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // lsh r0, 32
+/// 0x67, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, // lsh r0, 22
+/// 0x6f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // lsh r0, r8
+/// 0x77, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // rsh r0, 32
+/// 0x77, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, // rsh r0, 19
+/// 0x7f, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rsh r0, r7
+/// 0xa7, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // xor r0, 0x03
+/// 0xaf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xor r0, r2
+/// let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
+/// let res = vm.execute_program().unwrap();
+/// assert_eq!(res, 0x11);
+pub struct EbpfVmNoData<'a> {
+ parent: EbpfVmRaw<'a>,
+impl<'a> EbpfVmNoData<'a> {
+ /// 0xb7, 0x00, 0x00, 0x00, 0x11, 0x22, 0x00, 0x00, // mov r0, 0x2211
+ /// 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // be16 r0
+ /// let vm = rbpf::EbpfVmNoData::new(Some(prog));
+ pub fn new(prog: Option<&'a [u8]>) -> Result<EbpfVmNoData<'a>, Error> {
+ let parent = EbpfVmRaw::new(prog)?;
+ Ok(EbpfVmNoData { parent })
+ /// let mut vm = rbpf::EbpfVmNoData::new(Some(prog1)).unwrap();
+ /// let res = vm.execute_program().unwrap();
+ /// assert_eq!(res, 0x1122);
+ /// 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // mov r1, 0x010000000
+ /// let mut vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
+ /// vm.register_helper(1, helpers::sqrti).unwrap();
+ /// assert_eq!(res, 0x1000);
+ self.parent.jit_compile()
+ /// Execute the program loaded, without providing pointers to any memory area whatsoever.
+ /// let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
+ /// // For this kind of VM, the `execute_program()` function needs no argument.
+ pub fn execute_program(&self) -> Result<u64, Error> {
+ self.parent.execute_program(&mut [])
+ /// Execute the previously JIT-compiled program, without providing pointers to any memory area
+ /// whatsoever, in a manner very similar to `execute_program()`.
+ /// let res = vm.execute_program_jit().unwrap();
+ pub unsafe fn execute_program_jit(&self) -> Result<u64, Error> {
+ self.parent.execute_program_jit(&mut [])
+ self.parent.cranelift_compile()
+ /// let res = vm.execute_program_cranelift().unwrap();
+ pub fn execute_program_cranelift(&self) -> Result<u64, Error> {
+ self.parent.execute_program_cranelift(&mut [])
+/// EbpfVm with Owned data
+pub struct EbpfVmRawOwned {
+ parent: EbpfVmRaw<'static>,
+ data_len: usize,
+ data_cap: usize,
+impl EbpfVmRawOwned {
+ pub fn new(prog: Option<Vec<u8>>) -> Result<EbpfVmRawOwned, Error> {
+ let (prog, data_len, data_cap) = match prog {
+ let data_len = prog.len();
+ let data_cap = prog.capacity();
+ let slice = prog.leak();
+ let slice = unsafe { core::slice::from_raw_parts(slice.as_ptr(), data_len) };
+ (Some(slice), data_len, data_cap)
+ None => (None, 0, 0),
+ Ok(Self {
+ parent,
+ data_len,
+ data_cap,
+ /// Load a new eBPF program into the virtual machine instance
+ pub fn set_program(&mut self, prog: Vec<u8>) -> Result<(), Error> {
+ self.data_len = prog.len();
+ self.data_cap = prog.capacity();
+ self.parent.set_program(slice)?;
+ /// Set a new verifier function. The function should return an Error if the program should be rejected by the virtual machine.
+ /// If a program has been loaded to the VM already, the verifier is immediately run.
+ /// Register a built-in or user-defined helper function in order to use it later from within the eBPF program.
+ /// The helper is registered into a hashmap, so the key can be any u32.
+ /// If using JIT-compiled eBPF programs, be sure to register all helpers before compiling the program.
+ /// You should be able to change registered helpers after compiling, but not to add new ones (i. e. with new keys).
+ /// Execute the previously JIT-compiled program, with the given packet data, in a manner very similar to execute_program().
+ /// Safety
+ /// **WARNING:** JIT-compiled assembly code is not safe, in particular there is no runtime check for memory access;
+ /// so if the eBPF program attempts erroneous accesses, this may end very bad (program may segfault).
+ /// It may be wise to check that the program works with the interpreter before running the JIT-compiled version of it.
+ /// For this reason the function should be called from within an unsafe bloc.
+ pub fn execute_program(&self, mem: &mut [u8]) -> Result<u64, Error> {
+ self.parent.execute_program(mem)
+impl Drop for EbpfVmRawOwned {
+ match self.parent.parent.prog {
+ Some(prog) => unsafe {
+ let ptr = prog.as_ptr();
+ let _prog = Vec::from_raw_parts(ptr as *mut u8, self.data_len, self.data_cap);
+ None => {}
@@ -0,0 +1,41 @@
+//! 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`.
+/// 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.
+pub struct Error {
+ #[allow(dead_code)]
+ kind: ErrorKind,
+ error: String,
+impl Error {
+ /// New function exposing the same signature as `std::io::Error::new`.
+ 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`.
+pub enum ErrorKind {
+ /// The no_std code only uses this variant.
+ Other,
@@ -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
+ 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 {
+ 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
@@ -0,0 +1,386 @@
+// (uBPF: safety checks, originally in C)
+// (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> {
+ reject(format!(
+ "eBPF program length must be a multiple of {:?} octets",
+ if prog.len() > ebpf::PROG_MAX_SIZE {
+ "eBPF program length limited to {:?}, here {:?}",
+ ebpf::PROG_MAX_INSNS,
+ prog.len() / ebpf::INSN_SIZE
+ 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")?;
+fn check_imm_endian(insn: &ebpf::Insn, insn_ptr: usize) -> Result<(), Error> {
+ 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:?})"))?;
+fn check_jmp_offset(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
+ 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) {
+ "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 {
+ "jump to middle of LD_DW at #{dst_insn_ptr:?} (insn #{insn_ptr:?})"
+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 store = false;
+ 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 => {}
+ store = true;
+ check_load_dw(prog, insn_ptr)?;
+ ebpf::LD_B_REG => {}
+ ebpf::LD_H_REG => {}
+ ebpf::LD_W_REG => {}
+ ebpf::LD_DW_REG => {}
+ ebpf::ST_B_IMM => store = true,
+ ebpf::ST_H_IMM => store = true,
+ ebpf::ST_W_IMM => store = true,
+ ebpf::ST_DW_IMM => store = true,
+ ebpf::ST_B_REG => store = true,
+ ebpf::ST_H_REG => store = true,
+ ebpf::ST_W_REG => store = true,
+ ebpf::ST_DW_REG => store = true,
+ unimplemented!();
+ 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 => {}
+ check_imm_endian(&insn, insn_ptr)?;
+ 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 => {}
+ check_jmp_offset(prog, insn_ptr)?;
+ ebpf::CALL => {}
+ ebpf::EXIT => {}
+ "unknown eBPF opcode {:#2x} (insn #{insn_ptr:?})",
+ check_registers(&insn, store, insn_ptr)?;
+ // 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:?}"))?;
@@ -0,0 +1,655 @@
+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 {
+ dst,
+ src,
+ off,
+#[test]
+fn test_empty() {
+ assert_eq!(asm(""), Ok(vec![]));
+// Example for InstructionType::NoOperand.
+fn test_exit() {
+ assert_eq!(asm("exit"), Ok(vec![insn(ebpf::EXIT, 0, 0, 0, 0)]));
+// Example for InstructionType::AluBinary.
+fn test_add64() {
+ asm("add64 r1, r3"),
+ Ok(vec![insn(ebpf::ADD64_REG, 1, 3, 0, 0)])
+ asm("add64 r1, 5"),
+ Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 5)])
+// Example for InstructionType::AluUnary.
+fn test_neg64() {
+ assert_eq!(asm("neg64 r1"), Ok(vec![insn(ebpf::NEG64, 1, 0, 0, 0)]));
+// Example for InstructionType::LoadReg.
+fn test_ldxw() {
+ asm("ldxw r1, [r2+5]"),
+ Ok(vec![insn(ebpf::LD_W_REG, 1, 2, 5, 0)])
+// Example for InstructionType::StoreImm.
+fn test_stw() {
+ asm("stw [r2+5], 7"),
+ Ok(vec![insn(ebpf::ST_W_IMM, 2, 0, 5, 7)])
+// Example for InstructionType::StoreReg.
+fn test_stxw() {
+ asm("stxw [r2+5], r8"),
+ Ok(vec![insn(ebpf::ST_W_REG, 2, 8, 5, 0)])
+// Example for InstructionType::JumpUnconditional.
+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.
+fn test_jeq() {
+ asm("jeq r1, 4, +8"),
+ Ok(vec![insn(ebpf::JEQ_IMM, 1, 0, 8, 4)])
+ asm("jeq r1, r3, +8"),
+ Ok(vec![insn(ebpf::JEQ_REG, 1, 3, 8, 0)])
+// Example for InstructionType::Call.
+fn test_call() {
+ assert_eq!(asm("call 300"), Ok(vec![insn(ebpf::CALL, 0, 0, 0, 300)]));
+// Example for InstructionType::Endian.
+fn test_be32() {
+ assert_eq!(asm("be32 r1"), Ok(vec![insn(ebpf::BE, 1, 0, 0, 32)]));
+// Example for InstructionType::LoadImm.
+fn test_lddw() {
+ asm("lddw r1, 0x1234abcd5678eeff"),
+ insn(ebpf::LD_DW_IMM, 1, 0, 0, 0x5678eeff),
+ insn(0, 0, 0, 0, 0x1234abcd)
+ asm("lddw r1, 0xff11ee22dd33cc44"),
+ insn(ebpf::LD_DW_IMM, 1, 0, 0, 0xdd33cc44u32 as i32),
+ insn(0, 0, 0, 0, 0xff11ee22u32 as i32)
+// Example for InstructionType::LoadAbs.
+fn test_ldabsw() {
+ assert_eq!(asm("ldabsw 1"), Ok(vec![insn(ebpf::LD_ABS_W, 0, 0, 0, 1)]));
+// Example for InstructionType::LoadInd.
+fn test_ldindw() {
+ asm("ldindw r1, 2"),
+ Ok(vec![insn(ebpf::LD_IND_W, 0, 1, 0, 2)])
+fn test_ldxdw() {
+ asm("ldxdw r1, [r2+3]"),
+ Ok(vec![insn(ebpf::LD_DW_REG, 1, 2, 3, 0)])
+fn test_sth() {
+ asm("sth [r1+2], 3"),
+ Ok(vec![insn(ebpf::ST_H_IMM, 1, 0, 2, 3)])
+fn test_stxh() {
+ asm("stxh [r1+2], r3"),
+ Ok(vec![insn(ebpf::ST_H_REG, 1, 3, 2, 0)])
+// Test all supported AluBinary mnemonics.
+fn test_alu_binary() {
+ 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"),
+ 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)
+ 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"),
+ 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)
+ 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"),
+ 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"),
+ 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"),
+ 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)
+ 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"),
+ 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.
+fn test_alu_unary() {
+ asm("neg r1
+ neg64 r1
+ neg32 r1"),
+ insn(ebpf::NEG64, 1, 0, 0, 0),
+ insn(ebpf::NEG32, 1, 0, 0, 0)
+// Test all supported LoadAbs mnemonics.
+fn test_load_abs() {
+ asm("ldabsw 1
+ ldabsh 1
+ ldabsb 1
+ ldabsdw 1"),
+ 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.
+fn test_load_ind() {
+ asm("ldindw r1, 2
+ ldindh r1, 2
+ ldindb r1, 2
+ ldinddw r1, 2"),
+ 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.
+fn test_load_reg() {
+ asm("ldxw r1, [r2+3]
+ ldxh r1, [r2+3]
+ ldxb r1, [r2+3]
+ ldxdw r1, [r2+3]"),
+ 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.
+fn test_store_imm() {
+ asm("stw [r1+2], 3
+ sth [r1+2], 3
+ stb [r1+2], 3
+ stdw [r1+2], 3"),
+ 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.
+fn test_store_reg() {
+ asm("stxw [r1+2], r3
+ stxh [r1+2], r3
+ stxb [r1+2], r3
+ stxdw [r1+2], r3"),
+ 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.
+fn test_jump_conditional() {
+ 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"),
+ 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)
+ 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"),
+ 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)
+ 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"),
+ 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)
+ 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"),
+ 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.
+fn test_endian() {
+ asm("be16 r1
+ be32 r1
+ be64 r1
+ le16 r1
+ le32 r1
+ le64 r1"),
+ 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)
+fn test_large_immediate() {
+ asm("add64 r1, 2147483647"),
+ Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 2147483647)])
+ asm("add64 r1, -2147483648"),
+ Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, -2147483648)])
+fn test_tcp_sack() {
+ assert_eq!(assemble(TCP_SACK_ASM), Ok(TCP_SACK_BIN.to_vec()));
+fn test_error_invalid_instruction() {
+ assert_eq!(asm("abcd"), Err("Invalid instruction \"abcd\"".to_string()));
+fn test_error_unexpected_operands() {
+ asm("add 1, 2"),
+ Err("Failed to encode add: Unexpected operands: [Integer(1), Integer(2)]".to_string())
+fn test_error_too_many_operands() {
+ asm("add 1, 2, 3, 4"),
+ Err("Failed to encode add: Too many operands".to_string())
+fn test_error_operands_out_of_range() {
+ asm("add r16, r2"),
+ Err("Failed to encode add: Invalid destination register 16".to_string())
+ asm("add r1, r16"),
+ Err("Failed to encode add: Invalid source register 16".to_string())
+ asm("ja -32769"),
+ Err("Failed to encode ja: Invalid offset -32769".to_string())
+ asm("ja 32768"),
+ Err("Failed to encode ja: Invalid offset 32768".to_string())
+ asm("add r1, 4294967296"),
+ Err("Failed to encode add: Invalid immediate 4294967296".to_string())
+ asm("add r1, 2147483648"),
+ Err("Failed to encode add: Invalid immediate 2147483648".to_string())
+ asm("add r1, -2147483649"),
+ Err("Failed to encode add: Invalid immediate -2147483649".to_string())
@@ -0,0 +1,97 @@
+// Converted from the tests for uBPF <https://github.com/iovisor/ubpf>
+// Assembly code and data for tcp_sack testcases.
+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
+ 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
+ ldxb r3, [r4+1]
+ add r3, r6
+ lsh r3, 0x20
+ arsh r3, 0x20
+ jsgt r2, r3, -18
+ ja +1
+ mov r0, 0x1
+ exit";
+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,
+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,
+pub const TCP_SACK_NOMATCH: [u8; 66] = [
+ 0xf3, 0x61, 0xe5, 0xc0, 0x00, 0x50, 0xe5, 0x94, 0x3f, 0x77, 0xa3, 0xc4, 0xc4, 0x80, 0x80, 0x10,
+ 0x9e, 0x27,
@@ -0,0 +1,2257 @@
+#![cfg(feature = "cranelift")]
+use rbpf::{assembler::assemble, helpers};
+use crate::common::{TCP_SACK_ASM, TCP_SACK_MATCH, TCP_SACK_NOMATCH};
+macro_rules! test_cranelift {
+ ($name:ident, $prog:expr, $expected:expr) => {
+ fn $name() {
+ let prog = assemble($prog).unwrap();
+ let mut vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
+ assert_eq!(vm.execute_program_cranelift().unwrap(), $expected);
+ ($name:ident, $prog:expr, $mem:expr, $expected:expr) => {
+ let mem = &mut $mem;
+ let mut vm = rbpf::EbpfVmRaw::new(Some(&prog)).unwrap();
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), $expected);
+test_cranelift!(
+ test_cranelift_add,
+ mov32 r0, 0
+ add32 r0, 1
+ add32 r0, r1
+ exit
+ ",
+ 0x3
+);
+ test_cranelift_alu64_arith,
+ mov r0, 0
+ mov r1, 1
+ mov r2, 2
+ mov r3, 3
+ mov r4, 4
+ mov r5, 5
+ mov r6, 6
+ mov r7, 7
+ mov r8, 8
+ mov r9, 9
+ add r0, 23
+ add r0, r7
+ sub r0, 13
+ sub r0, r1
+ mul r0, 7
+ mul r0, r3
+ div r0, 2
+ div r0, r4
+ 0x2a
+ test_cranelift_alu64_bit,
+ or r0, r5
+ or r0, 0xa0
+ and r0, 0xa3
+ mov r9, 0x91
+ and r0, r9
+ lsh r0, 32
+ lsh r0, 22
+ lsh r0, r8
+ rsh r0, 32
+ rsh r0, 19
+ rsh r0, r7
+ xor r0, 0x03
+ xor r0, r2
+ 0x11
+ test_cranelift_alu_arith,
+ mov32 r1, 1
+ mov32 r2, 2
+ mov32 r3, 3
+ mov32 r4, 4
+ mov32 r5, 5
+ mov32 r6, 6
+ mov32 r7, 7
+ mov32 r8, 8
+ mov32 r9, 9
+ add32 r0, 23
+ add32 r0, r7
+ sub32 r0, 13
+ sub32 r0, r1
+ mul32 r0, 7
+ mul32 r0, r3
+ div32 r0, 2
+ div32 r0, r4
+ test_cranelift_alu_bit,
+ or32 r0, r5
+ or32 r0, 0xa0
+ and32 r0, 0xa3
+ mov32 r9, 0x91
+ and32 r0, r9
+ lsh32 r0, 22
+ lsh32 r0, r8
+ rsh32 r0, 19
+ rsh32 r0, r7
+ xor32 r0, 0x03
+ xor32 r0, r2
+ test_cranelift_arsh32_high_shift,
+ mov r0, 8
+ lddw r1, 0x100000001
+ arsh32 r0, r1
+ 0x4
+ test_cranelift_arsh,
+ mov32 r0, 0xf8
+ lsh32 r0, 28
+ arsh32 r0, 16
+ 0xffff8000
+ test_cranelift_arsh64,
+ mov32 r0, 1
+ lsh r0, 63
+ arsh r0, 55
+ mov32 r1, 5
+ arsh r0, r1
+ 0xfffffffffffffff8
+ test_cranelift_arsh_reg,
+ mov32 r1, 16
+ test_cranelift_be16,
+ ldxh r0, [r1]
+ [0x11, 0x22],
+ 0x1122
+ test_cranelift_be16_high,
+ ldxdw r0, [r1]
+ [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
+ test_cranelift_be32,
+ ldxw r0, [r1]
+ be32 r0
+ [0x11, 0x22, 0x33, 0x44],
+ 0x11223344
+ test_cranelift_be32_high,
+ test_cranelift_be64,
+ be64 r0
+ 0x1122334455667788
+fn test_cranelift_call() {
+ let prog = assemble(
+ call 0
+ exit",
+ vm.register_helper(0, helpers::gather_bytes).unwrap();
+ assert_eq!(vm.execute_program_cranelift().unwrap(), 0x0102030405);
+#[should_panic(expected = "[CRANELIFT] Error: unknown helper function (id: 0x3f)")]
+fn test_cranelift_err_call_unreg() {
+ call 63
+fn test_cranelift_call_memfrob() {
+ mov r6, r1
+ add r1, 2
+ mov r2, 4
+ call 1
+ ldxdw r0, [r6]
+ vm.register_helper(1, helpers::memfrob).unwrap();
+ let mem = &mut [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
+ vm.execute_program_cranelift(mem).unwrap(),
+ 0x102292e2f2c0708
+ test_cranelift_div32_high_divisor,
+ mov r0, 12
+ lddw r1, 0x100000004
+ div32 r0, r1
+ test_cranelift_div32_imm,
+ lddw r0, 0x10000000c
+ div32 r0, 4
+ test_cranelift_div32_reg,
+ mov r1, 4
+ test_cranelift_div64_imm,
+ mov r0, 0xc
+ div r0, 4
+ 0x300000000
+ test_cranelift_div64_reg,
+ div r0, r1
+ test_cranelift_early_exit,
+ mov r0, 3
+ mov r0, 4
+ test_cranelift_div64_by_zero_imm,
+ div r0, 0
+ 0x0
+ test_cranelift_div_by_zero_imm,
+ div32 r0, 0
+ test_cranelift_mod64_by_zero_imm,
+ mod r0, 0
+ 0x1
+ test_cranelift_mod_by_zero_imm,
+ mod32 r0, 0
+ test_cranelift_div64_by_zero_reg,
+ mov32 r1, 0
+ test_cranelift_div_by_zero_reg,
+ test_cranelift_mod64_by_zero_reg,
+ mod r0, r1
+ test_cranelift_mod_by_zero_reg,
+ mod32 r0, r1
+// #[should_panic(expected = "Error: out of bounds memory store (insn #1)")]
+#[ignore = "We have stack OOB checks, but we don't yet catch the trap code and convert it into a panic"]
+fn test_cranelift_err_stack_out_of_bound() {
+ let prog = [
+ 0x72, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ vm.execute_program_cranelift().unwrap();
+ test_cranelift_exit,
+ test_cranelift_ja,
+ mov r0, 1
+ mov r0, 2
+ test_cranelift_jeq_imm,
+ mov32 r1, 0xa
+ jeq r1, 0xb, +4
+ mov32 r1, 0xb
+ jeq r1, 0xb, +1
+ mov32 r0, 2
+ test_cranelift_jeq_reg,
+ mov32 r2, 0xb
+ jeq r1, r2, +4
+ jeq r1, r2, +1
+ test_cranelift_jge_imm,
+ jge r1, 0xb, +4
+ mov32 r1, 0xc
+ jge r1, 0xb, +1
+ test_cranelift_jle_imm,
+ jle r1, 4, +1
+ jle r1, 6, +1
+ jle r1, 5, +1
+ test_cranelift_jle_reg,
+ mov r1, 5
+ mov r3, 6
+ jle r1, r2, +2
+ jle r1, r1, +1
+ jle r1, r3, +1
+ test_cranelift_jgt_imm,
+ jgt r1, 6, +2
+ jgt r1, 5, +1
+ jgt r1, 4, +1
+ test_cranelift_jgt_reg,
+ mov r2, 6
+ mov r3, 4
+ jgt r1, r2, +2
+ jgt r1, r1, +1
+ jgt r1, r3, +1
+ test_cranelift_jlt_imm,
+ jlt r1, 4, +2
+ jlt r1, 5, +1
+ jlt r1, 6, +1
+ test_cranelift_jlt_reg,
+ jlt r1, r2, +2
+ jlt r1, r1, +1
+ jlt r1, r3, +1
+ test_cranelift_jit_bounce,
+ mov r6, r0
+ mov r7, r6
+ mov r8, r7
+ mov r9, r8
+ mov r0, r9
+ test_cranelift_jne_reg,
+ jne r1, r2, +4
+ jne r1, r2, +1
+ test_cranelift_jset_imm,
+ mov32 r1, 0x7
+ jset r1, 0x8, +4
+ mov32 r1, 0x9
+ jset r1, 0x8, +1
+ test_cranelift_jset_reg,
+ mov32 r2, 0x8
+ jset r1, r2, +4
+ jset r1, r2, +1
+ test_cranelift_jsge_imm,
+ mov r1, -2
+ jsge r1, -1, +5
+ jsge r1, 0, +4
+ mov r1, -1
+ jsge r1, -1, +1
+ test_cranelift_jsge_reg,
+ mov r2, -1
+ mov32 r3, 0
+ jsge r1, r2, +5
+ jsge r1, r3, +4
+ jsge r1, r2, +1
+ test_cranelift_jsle_imm,
+ jsle r1, -3, +1
+ jsle r1, -1, +1
+ jsle r1, -2, +1
+ test_cranelift_jsle_reg,
+ mov r2, -2
+ jsle r1, r2, +1
+ jsle r1, r3, +1
+ test_cranelift_jsgt_imm,
+ jsgt r1, -1, +4
+ jsgt r1, -1, +1
+ test_cranelift_jsgt_reg,
+ jsgt r1, r2, +4
+ jsgt r1, r2, +1
+ test_cranelift_jslt_imm,
+ jslt r1, -3, +2
+ jslt r1, -2, +1
+ jslt r1, -1, +1
+ test_cranelift_jslt_reg,
+ mov r2, -3
+ mov r3, -1
+ jslt r1, r1, +2
+ jslt r1, r2, +1
+ jslt r1, r3, +1
+ test_cranelift_jeq32_imm,
+ mov r9, 1
+ lsh r9, 32
+ mov32 r0, 0x0
+ jeq32 r1, 0xb, +5
+ mov r1, 0xb
+ or r1, r9
+ jeq32 r1, 0xb, +1
+ test_cranelift_jeq32_reg,
+ jeq32 r1, r2, +5
+ jeq32 r1, r2, +1
+ test_cranelift_jge32_imm,
+ jge32 r1, 0xb, +5
+ jge32 r1, 0xb, +1
+ test_cranelift_jge32_reg,
+ jge32 r1, r2, +5
+ jge32 r1, r2, +1
+ test_cranelift_jgt32_imm,
+ jgt32 r1, 6, +4
+ jgt32 r1, 5, +3
+ jgt32 r1, 4, +1
+ test_cranelift_jgt32_reg,
+ jgt32 r1, r2, +4
+ jgt32 r1, r1, +3
+ jgt32 r1, r3, +1
+ test_cranelift_jle32_imm,
+ jle32 r1, 4, +5
+ jle32 r1, 6, +1
+ jle32 r1, 5, +1
+ test_cranelift_jle32_reg,
+ jle32 r1, r2, +5
+ jle32 r1, r1, +1
+ jle32 r1, r3, +1
+ test_cranelift_jlt32_imm,
+ jlt32 r1, 4, +4
+ jlt32 r1, 5, +3
+ jlt32 r1, 6, +1
+ test_cranelift_jlt32_reg,
+ jlt32 r1, r2, +4
+ jlt32 r1, r1, +3
+ jlt32 r1, r3, +1
+ test_cranelift_jne32_imm,
+ jne32 r1, 0xb, +4
+ jne32 r1, 0xb, +1
+ test_cranelift_jne32_reg,
+ jne32 r1, r2, +4
+ jne32 r1, r2, +1
+ test_cranelift_jset32_imm,
+ jset32 r1, 0x8, +4
+ jset32 r1, 0x8, +1
+ test_cranelift_jset32_reg,
+ jset32 r1, r2, +4
+ jset32 r1, r2, +1
+ test_cranelift_jsge32_imm,
+ mov32 r1, -2
+ jsge32 r1, -1, +5
+ jsge32 r1, 0, +4
+ jsge32 r1, -1, +1
+ test_cranelift_jsge32_reg,
+ jsge32 r1, r2, +5
+ jsge32 r1, r3, +4
+ jsge32 r1, r2, +1
+ test_cranelift_jsgt32_imm,
+ jsgt32 r1, -1, +4
+ jsgt32 r1, -1, +1
+ test_cranelift_jsgt32_reg,
+ jsgt32 r1, r2, +4
+ jsgt32 r1, r2, +1
+ test_cranelift_jsle32_imm,
+ jsle32 r1, -3, +5
+ jsle32 r1, -1, +1
+ jsle32 r1, -2, +1
+ test_cranelift_jsle32_reg,
+ jsle32 r1, r2, +6
+ jsle32 r1, r3, +1
+ jsle32 r1, r2, +1
+ test_cranelift_jslt32_imm,
+ jslt32 r1, -3, +4
+ jslt32 r1, -2, +3
+ jslt32 r1, -1, +1
+ test_cranelift_jslt32_reg,
+ jslt32 r1, r1, +4
+ jslt32 r1, r3, +1
+ test_cranelift_lddw,
+ lddw r0, 0x1122334455667788
+ test_cranelift_lddw2,
+ lddw r0, 0x0000000080000000
+ 0x80000000
+ test_cranelift_ldxb_all,
+ mov r0, r1
+ ldxb r9, [r0+0]
+ lsh r9, 0
+ ldxb r8, [r0+1]
+ lsh r8, 4
+ ldxb r7, [r0+2]
+ lsh r7, 8
+ ldxb r6, [r0+3]
+ lsh r6, 12
+ ldxb r5, [r0+4]
+ lsh r5, 16
+ ldxb r4, [r0+5]
+ lsh r4, 20
+ ldxb r3, [r0+6]
+ lsh r3, 24
+ ldxb r2, [r0+7]
+ lsh r2, 28
+ ldxb r1, [r0+8]
+ lsh r1, 32
+ ldxb r0, [r0+9]
+ lsh r0, 36
+ or r0, r1
+ or r0, r2
+ or r0, r3
+ or r0, r4
+ or r0, r6
+ or r0, r7
+ or r0, r8
+ or r0, r9
+ [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09],
+ 0x9876543210
+ test_cranelift_ldxb,
+ ldxb r0, [r1+2]
+ [0xaa, 0xbb, 0x11, 0xcc, 0xdd],
+ test_cranelift_ldxdw,
+ ldxdw r0, [r1+2]
+ [0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xcc, 0xdd],
+ 0x8877665544332211
+ test_cranelift_ldxh_all,
+ ldxh r9, [r0+0]
+ be16 r9
+ ldxh r8, [r0+2]
+ be16 r8
+ ldxh r7, [r0+4]
+ be16 r7
+ ldxh r6, [r0+6]
+ be16 r6
+ ldxh r5, [r0+8]
+ be16 r5
+ ldxh r4, [r0+10]
+ be16 r4
+ ldxh r3, [r0+12]
+ be16 r3
+ ldxh r2, [r0+14]
+ be16 r2
+ ldxh r1, [r0+16]
+ be16 r1
+ ldxh r0, [r0+18]
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00,
+ 0x07, 0x00, 0x08, 0x00, 0x09
+ test_cranelift_ldxh_all2,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00,
+ 0x80, 0x01, 0x00, 0x02, 0x00
+ 0x3ff
+ test_cranelift_ldxh,
+ ldxh r0, [r1+2]
+ [0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd],
+ 0x2211
+ test_cranelift_ldxh_same_reg,
+ sth [r0], 0x1234
+ ldxh r0, [r0]
+ [0xff, 0xff],
+ 0x1234
+ test_cranelift_ldxw_all,
+ ldxw r9, [r0+0]
+ be32 r9
+ ldxw r8, [r0+4]
+ be32 r8
+ ldxw r7, [r0+8]
+ be32 r7
+ ldxw r6, [r0+12]
+ be32 r6
+ ldxw r5, [r0+16]
+ be32 r5
+ ldxw r4, [r0+20]
+ be32 r4
+ ldxw r3, [r0+24]
+ be32 r3
+ ldxw r2, [r0+28]
+ be32 r2
+ ldxw r1, [r0+32]
+ ldxw r0, [r0+36]
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00
+ 0x030f0f
+ test_cranelift_ldxw,
+ ldxw r0, [r1+2]
+ [0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0xcc, 0xdd],
+ 0x44332211
+ test_cranelift_le16,
+ le16 r0
+ [0x22, 0x11],
+ test_cranelift_le32,
+ le32 r0
+ [0x44, 0x33, 0x22, 0x11],
+ test_cranelift_le64,
+ le64 r0
+ [0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11],
+ test_cranelift_lsh_reg,
+ mov r7, 4
+ lsh r0, r7
+ 0x10
+ test_cranelift_mod,
+ mov32 r0, 5748
+ mod32 r0, 92
+ mov32 r1, 13
+ 0x5
+ test_cranelift_mod32,
+ lddw r0, 0x100000003
+ mod32 r0, 3
+ test_cranelift_mod64,
+ mov32 r0, -1316649930
+ or r0, 0x100dc5c8
+ mov32 r1, 0xdde263e
+ or r1, 0x3cbef7f3
+ mod r0, 0x658f1778
+ 0x30ba5a04
+ test_cranelift_mov,
+ mov32 r0, r1
+ test_cranelift_mul32_imm,
+ mul32 r0, 4
+ 0xc
+ test_cranelift_mul32_reg,
+ mul32 r0, r1
+ test_cranelift_mul32_reg_overflow,
+ mov r0, 0x40000001
+ test_cranelift_mul64_imm,
+ mul r0, 4
+ 0x100000004
+ test_cranelift_mul64_reg,
+ mul r0, r1
+ test_cranelift_mul_loop,
+ mov r0, 0x7
+ add r1, 0xa
+ lsh r1, 0x20
+ rsh r1, 0x20
+ jeq r1, 0x0, +4
+ mul r0, 0x7
+ add r1, -1
+ jne r1, 0x0, -3
+ 0x75db9c97
+ test_cranelift_neg64,
+ neg r0
+ 0xfffffffffffffffe
+ test_cranelift_neg,
+ neg32 r0
+ 0xfffffffe
+ test_cranelift_prime,
+ mov r1, 67
+ mov r2, 0x2
+ jgt r1, 0x2, +4
+ ja +10
+ add r2, 0x1
+ jge r2, r1, +7
+ mov r3, r1
+ div r3, r2
+ mul r3, r2
+ sub r4, r3
+ jne r4, 0x0, -10
+ test_cranelift_rhs32,
+ xor r0, r0
+ sub r0, 1
+ rsh32 r0, 8
+ 0x00ffffff
+ test_cranelift_rsh_reg,
+ mov r0, 0x10
+ test_cranelift_stack,
+ mov r1, 51
+ stdw [r10-16], 0xab
+ stdw [r10-8], 0xcd
+ and r1, 1
+ lsh r1, 3
+ mov r2, r10
+ add r2, r1
+ ldxdw r0, [r2-16]
+ 0xcd
+fn test_cranelift_stack2() {
+ stb [r10-4], 0x01
+ stb [r10-3], 0x02
+ stb [r10-2], 0x03
+ stb [r10-1], 0x04
+ mov r1, r10
+ mov r2, 0x4
+ mov r1, 0
+ ldxb r2, [r10-4]
+ ldxb r3, [r10-3]
+ ldxb r4, [r10-2]
+ ldxb r5, [r10-1]
+ xor r0, 0x2a2a2a2a
+ assert_eq!(vm.execute_program_cranelift().unwrap(), 0x01020304);
+ test_cranelift_stb,
+ stb [r1+2], 0x11
+ [0xaa, 0xbb, 0xff, 0xcc, 0xdd],
+ test_cranelift_stdw,
+ stdw [r1+2], 0x44332211
+ [0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd],
+ test_cranelift_sth,
+ sth [r1+2], 0x2211
+ [0xaa, 0xbb, 0xff, 0xff, 0xcc, 0xdd],
+#[ignore]
+fn test_cranelift_string_stack() {
+ mov r1, 0x78636261
+ stxw [r10-8], r1
+ mov r6, 0x0
+ stxb [r10-4], r6
+ stxb [r10-12], r6
+ mov r1, 0x79636261
+ stxw [r10-16], r1
+ add r1, -8
+ mov r2, r1
+ call 0x4
+ mov r1, r0
+ jne r1, 0x0, +11
+ add r2, -16
+ jeq r1, r6, +1
+ vm.register_helper(4, helpers::strcmp).unwrap();
+ assert_eq!(vm.execute_program_cranelift().unwrap(), 0x0);
+ test_cranelift_stw,
+ stw [r1+2], 0x44332211
+ [0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd],
+ test_cranelift_stxb,
+ mov32 r2, 0x11
+ stxb [r1+2], r2
+ test_cranelift_stxb_all,
+ mov r0, 0xf0
+ mov r2, 0xf2
+ mov r3, 0xf3
+ mov r4, 0xf4
+ mov r5, 0xf5
+ mov r6, 0xf6
+ mov r7, 0xf7
+ mov r8, 0xf8
+ stxb [r1], r0
+ stxb [r1+1], r2
+ stxb [r1+3], r4
+ stxb [r1+4], r5
+ stxb [r1+5], r6
+ stxb [r1+6], r7
+ stxb [r1+7], r8
+ [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
+ 0xf0f2f3f4f5f6f7f8
+ test_cranelift_stxb_all2,
+ mov r1, 0xf1
+ mov r9, 0xf9
+ stxb [r0], r1
+ stxb [r0+1], r9
+ 0xf1f9
+ test_cranelift_stxb_chain,
+ stxb [r0+2], r8
+ stxb [r0+3], r7
+ stxb [r0+4], r6
+ stxb [r0+5], r5
+ stxb [r0+6], r4
+ stxb [r0+7], r3
+ stxb [r0+8], r2
+ stxb [r0+9], r1
+ [0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+ test_cranelift_stxdw,
+ mov r2, -2005440939
+ lsh r2, 32
+ or r2, 0x44332211
+ stxdw [r1+2], r2
+ test_cranelift_stxh,
+ mov32 r2, 0x2211
+ stxh [r1+2], r2
+ test_cranelift_stxw,
+ mov32 r2, 0x44332211
+ stxw [r1+2], r2
+ test_cranelift_subnet,
+ mov r2, 0xe
+ ldxh r3, [r1+12]
+ jne r3, 0x81, +2
+ mov r2, 0x12
+ ldxh r3, [r1+16]
+ and r3, 0xffff
+ jne r3, 0x8, +5
+ ldxw r1, [r1+16]
+ and r1, 0xffffff
+ jeq r1, 0x1a8c0, +1
+ 0x00, 0x00, 0xc0, 0x9f, 0xa0, 0x97, 0x00, 0xa0, 0xcc, 0x3b, 0xbf, 0xfa, 0x08, 0x00, 0x45,
+ 0x10, 0x00, 0x3c, 0x46, 0x3c, 0x40, 0x00, 0x40, 0x06, 0x73, 0x1c, 0xc0, 0xa8, 0x01, 0x02,
+ 0xc0, 0xa8, 0x01, 0x01, 0x06, 0x0e, 0x00, 0x17, 0x99, 0xc5, 0xa0, 0xec, 0x00, 0x00, 0x00,
+ 0x00, 0xa0, 0x02, 0x7d, 0x78, 0xe0, 0xa3, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02,
+ 0x08, 0x0a, 0x00, 0x9c, 0x27, 0x24, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x00,
+const PROG_TCP_PORT_80: [u8; 152] = [
+ 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x71, 0x12, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x0a, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x15, 0x02, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x69, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x55, 0x01, 0x01, 0x00, 0x00, 0x50, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+fn test_cranelift_tcp_port80_match() {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x08, 0x00, 0x45,
+ 0x00, 0x00, 0x56, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06, 0xf9, 0x4d, 0xc0, 0xa8, 0x00, 0x01,
+ 0xc0, 0xa8, 0x00, 0x02, 0x27, 0x10, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x50, 0x02, 0x20, 0x00, 0xc5, 0x18, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ let prog = &PROG_TCP_PORT_80;
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x1);
+fn test_cranelift_tcp_port80_nomatch() {
+ 0xc0, 0xa8, 0x00, 0x02, 0x00, 0x16, 0x27, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x51, 0x02, 0x20, 0x00, 0xc5, 0x18, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x0);
+fn test_cranelift_tcp_port80_nomatch_ethertype() {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x08, 0x01, 0x45,
+fn test_cranelift_tcp_port80_nomatch_proto() {
+ 0x00, 0x00, 0x56, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11, 0xf9, 0x4d, 0xc0, 0xa8, 0x00, 0x01,
+fn test_cranelift_tcp_sack_match() {
+ let mut mem = TCP_SACK_MATCH.to_vec();
+ let prog = assemble(TCP_SACK_ASM).unwrap();
+ vm.execute_program_cranelift(mem.as_mut_slice()).unwrap(),
+fn test_cranelift_tcp_sack_nomatch() {
+ let mut mem = TCP_SACK_NOMATCH.to_vec();
+fn test_cranelift_ldabsb() {
+ 0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
+ 0xff,
+ let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x00, 0x08).unwrap();
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x33);
+fn test_cranelift_ldabsh() {
+ 0x28, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x4433);
+fn test_cranelift_ldabsw() {
+ 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x66554433);
+fn test_cranelift_ldabsdw() {
+ 0x38, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xaa99887766554433
+fn test_cranelift_ldindb() {
+ 0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x88);
+fn test_cranelift_ldindh() {
+ 0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x48, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x9988);
+fn test_cranelift_ldindw() {
+ 0xb7, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00,
+ assert_eq!(vm.execute_program_cranelift(mem).unwrap(), 0x88776655);
+fn test_cranelift_ldinddw() {
+ 0xb7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x58, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0xccbbaa9988776655
@@ -0,0 +1,377 @@
+// Copyright 2017 Jan-Erik Rediger <badboy@archlinux.us>
+// Adopted from tests in `tests/assembler.rs`
+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)
+ .join("\n");
+ assert_eq!(src, reasm);
+ disasm!("");
+ disasm!("exit");
+ disasm!("add64 r1, r3");
+ disasm!("add64 r1, 0x5");
+ disasm!("neg64 r1");
+ disasm!("ldxw r1, [r2+0x5]");
+ disasm!("stw [r2+0x5], 0x7");
+ disasm!("stxw [r2+0x5], r8");
+ disasm!("ja +0x8");
+ disasm!("jeq r1, 0x4, +0x8");
+ disasm!("jeq r1, r3, +0x8");
+ disasm!("call 0x3");
+ disasm!("be32 r1");
+ disasm!("lddw r1, 0x1234abcd5678eeff");
+ disasm!("lddw r1, 0xff11ee22dd33cc44");
+ disasm!("ldabsw 0x1");
+ disasm!("ldindw r1, 0x2");
+ disasm!("ldxdw r1, [r2+0x3]");
+ disasm!("sth [r1+0x2], 0x3");
+ disasm!("stxh [r1+0x2], r3");
+ 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"
+ "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"
+ "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"
+ "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"
+ "neg64 r1
+neg32 r1"
+ "ldabsw 0x1
+ldabsh 0x1
+ldabsb 0x1
+ldabsdw 0x1"
+ "ldindw r1, 0x2
+ldindh r1, 0x2
+ldindb r1, 0x2
+ldinddw r1, 0x2"
+ r"ldxw r1, [r2+0x3]
+ldxh r1, [r2+0x3]
+ldxb r1, [r2+0x3]
+ldxdw r1, [r2+0x3]"
+ "stw [r1+0x2], 0x3
+sth [r1+0x2], 0x3
+stb [r1+0x2], 0x3
+stdw [r1+0x2], 0x3"
+ "stxw [r1+0x2], r3
+stxh [r1+0x2], r3
+stxb [r1+0x2], r3
+stxdw [r1+0x2], r3"
+ "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"
+ "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"
+ "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"
+ "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"
+ "be16 r1
+be32 r1
+be64 r1
+le16 r1
+le32 r1
+le64 r1"
+ disasm!("add64 r1, 0x7fffffff");
+// Non-regression tests for overflow when trying to negate offset 0x8000i16.
+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
+ assert_eq!(asm, expected_output);
@@ -0,0 +1,571 @@
+// 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;
+use rbpf::{assembler::assemble, Error, ErrorKind};
+// The following two examples have been compiled from C with the following command:
+// 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)
+// if (iph->protocol != IPPROTO_TCP)
+// if (tcp->source == BLOCKED_TCP_PORT || tcp->dest == BLOCKED_TCP_PORT)
+// return -1;
+// char _license[] SEC(".license") = "GPL";
+// This program, once compiled, can be injected into Linux kernel, with tc for instance. Sadly, we
+// 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.
+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;
+ 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,
+ println!("Program returned: {res:?} ({res:#x})");
+fn test_jit_block_port() {
+ let res = vm.execute_program_jit(packet).unwrap();
+// Program and memory come from uBPF test ldxh.
+fn test_vm_mbuff() {
+ // 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,
+ let mem = &[0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
+ let mbuff = [0u8; 32];
+ 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);
+fn test_vm_mbuff_with_rust_api() {
+ use rbpf::insn_builder::*;
+ .set_off(0x00_08)
+ let vm = rbpf::EbpfVmMbuff::new(Some(program.into_bytes())).unwrap();
+fn test_jit_mbuff() {
+ let mem = &mut [0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
+ let mut mbuff = [0u8; 32];
+ assert_eq!(vm.execute_program_jit(mem, &mut mbuff).unwrap(), 0x2211);
+fn test_vm_jit_ldabsb() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x33);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x33);
+fn test_vm_jit_ldabsh() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x4433);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x4433);
+fn test_vm_jit_ldabsw() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x66554433);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x66554433);
+fn test_vm_jit_ldabsdw() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0xaa99887766554433);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xaa99887766554433);
+#[should_panic(expected = "Error: out of bounds memory load (insn #1),")]
+fn test_vm_err_ldabsb_oob() {
+ 0x38, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
+ vm.execute_program(mem).unwrap();
+ // Memory check not implemented for JIT yet.
+fn test_vm_err_ldabsb_nomem() {
+ vm.execute_program().unwrap();
+fn test_vm_jit_ldindb() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x88);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x88);
+fn test_vm_jit_ldindh() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x9988);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x9988);
+fn test_vm_jit_ldindw() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x88776655);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x88776655);
+fn test_vm_jit_ldinddw() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0xccbbaa9988776655);
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xccbbaa9988776655);
+#[should_panic(expected = "Error: out of bounds memory load (insn #2),")]
+fn test_vm_err_ldindb_oob() {
+ 0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x38, 0x10, 0x00, 0x00, 0x33, 0x00, 0x00,
+fn test_vm_err_ldindb_nomem() {
+ 0xb7, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
+#[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> {
+fn verifier_fail(_prog: &[u8]) -> Result<(), Error> {
+ Err(Error::new(ErrorKind::Other, "Gaggablaghblagh!"))
+fn test_verifier_success() {
+ "mov32 r0, 0xBEE
+ let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();
+ vm.set_verifier(verifier_success).unwrap();
+ vm.set_program(&prog).unwrap();
+#[should_panic(expected = "Gaggablaghblagh!")]
+fn test_verifier_fail() {
+ vm.set_verifier(verifier_fail).unwrap();
@@ -0,0 +1,2891 @@
+// 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_jit_<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 JIT compiler.
+#![cfg(all(not(windows), feature = "std"))]
+use common::{TCP_SACK_ASM, TCP_SACK_MATCH, TCP_SACK_NOMATCH};
+fn test_jit_add() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x3);
+fn test_jit_alu64_arith() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x2a);
+fn test_jit_alu64_bit() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x11);
+fn test_jit_alu_arith() {
+fn test_jit_alu_bit() {
+fn test_jit_arsh32_high_shift() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x4);
+fn test_jit_arsh() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0xffff8000);
+fn test_jit_arsh64() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0xfffffffffffffff8);
+fn test_jit_arsh_reg() {
+fn test_jit_be16() {
+ let mem = &mut [0x11, 0x22];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x1122);
+fn test_jit_be16_high() {
+ let mem = &mut [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
+fn test_jit_be32() {
+ let mem = &mut [0x11, 0x22, 0x33, 0x44];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x11223344);
+fn test_jit_be32_high() {
+fn test_jit_be64() {
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x1122334455667788);
+fn test_jit_call() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x0102030405);
+fn test_jit_call_memfrob() {
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x102292e2f2c0708);
+// TODO: helpers::trash_registers needs asm!().
+// Try this again once asm!() is available in stable.
+//#[test]
+//fn test_jit_call_save() {
+//let prog = &[
+//0xb7, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+//0xb7, 0x07, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+//0xb7, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+//0xb7, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
+//0x85, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+//0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+//0x4f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+//0x4f, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+//0x4f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+//0x4f, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+//0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+//];
+//let mut vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
+//vm.register_helper(2, helpers::trash_registers);
+//vm.jit_compile().unwrap();
+//unsafe { assert_eq!(vm.execute_program_jit().unwrap(), 0x4321); }
+//}
+fn test_jit_div32_high_divisor() {
+fn test_jit_div32_imm() {
+fn test_jit_div32_reg() {
+fn test_jit_div64_imm() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x300000000);
+fn test_jit_div64_reg() {
+// For some register numbers, we don't emit the same instructions for handling divisions by zero,
+// which means we don't use the same offset to skip these instructions when the divisor is not
+// zero. We've had a regression because of this before, make sure we test it.
+fn test_jit_div32_highreg() {
+ div32 r7, r0
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x2);
+fn test_jit_div64_highreg() {
+ div r7, r0
+fn test_jit_early_exit() {
+// uBPF limits the number of user functions at 64. We don't.
+//fn test_jit_err_call_bad_imm() {
+#[should_panic(expected = "[JIT] Error: unknown helper function (id: 0x3f)")]
+fn test_jit_err_call_unreg() {
+ vm.execute_program_jit().unwrap();
+fn test_jit_div64_by_zero_imm() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x0);
+fn test_jit_div_by_zero_imm() {
+fn test_jit_mod64_by_zero_imm() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x1);
+fn test_jit_mod_by_zero_imm() {
+fn test_jit_div64_by_zero_reg() {
+fn test_jit_div_by_zero_reg() {
+fn test_jit_mod64_by_zero_reg() {
+fn test_jit_mod_by_zero_reg() {
+// TODO SKIP: JIT disabled for this testcase (stack oob check not implemented)
+// #[test]
+// fn test_jit_err_stack_out_of_bound() {
+// let prog = &[
+// 0x72, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+// ];
+// let mut vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
+// vm.jit_compile().unwrap();
+// unsafe { vm.execute_program_jit().unwrap(); }
+fn test_jit_exit() {
+fn test_jit_ja() {
+fn test_jit_jeq_imm() {
+fn test_jit_jeq_reg() {
+fn test_jit_jge_imm() {
+fn test_jit_jle_imm() {
+fn test_jit_jle_reg() {
+fn test_jit_jgt_imm() {
+fn test_jit_jgt_reg() {
+fn test_jit_jlt_imm() {
+fn test_jit_jlt_reg() {
+fn test_jit_jit_bounce() {
+fn test_jit_jne_reg() {
+fn test_jit_jset_imm() {
+fn test_jit_jset_reg() {
+fn test_jit_jsge_imm() {
+fn test_jit_jsge_reg() {
+fn test_jit_jsle_imm() {
+fn test_jit_jsle_reg() {
+fn test_jit_jsgt_imm() {
+fn test_jit_jsgt_reg() {
+fn test_jit_jslt_imm() {
+fn test_jit_jslt_reg() {
+fn test_jit_jeq32_imm() {
+fn test_jit_jeq32_reg() {
+fn test_jit_jge32_imm() {
+fn test_jit_jge32_reg() {
+fn test_jit_jgt32_imm() {
+fn test_jit_jgt32_reg() {
+fn test_jit_jle32_imm() {
+fn test_jit_jle32_reg() {
+fn test_jit_jlt32_imm() {
+fn test_jit_jlt32_reg() {
+fn test_jit_jne32_imm() {
+fn test_jit_jne32_reg() {
+fn test_jit_jset32_imm() {
+fn test_jit_jset32_reg() {
+fn test_jit_jsge32_imm() {
+fn test_jit_jsge32_reg() {
+fn test_jit_jsgt32_imm() {
+fn test_jit_jsgt32_reg() {
+fn test_jit_jsle32_imm() {
+fn test_jit_jsle32_reg() {
+fn test_jit_jslt32_imm() {
+fn test_jit_jslt32_reg() {
+fn test_jit_lddw() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x1122334455667788);
+fn test_jit_lddw2() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x80000000);
+fn test_jit_ldxb_all() {
+ let mem = &mut [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x9876543210);
+fn test_jit_ldxb() {
+ let mem = &mut [0xaa, 0xbb, 0x11, 0xcc, 0xdd];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x11);
+fn test_jit_ldxdw() {
+ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xcc, 0xdd,
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x8877665544332211);
+fn test_jit_ldxh_all() {
+ 0x07, 0x00, 0x08, 0x00, 0x09,
+fn test_jit_ldxh_all2() {
+ 0x80, 0x01, 0x00, 0x02, 0x00,
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x3ff);
+fn test_jit_ldxh() {
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x2211);
+fn test_jit_ldxh_same_reg() {
+ let mem = &mut [0xff, 0xff];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x1234);
+fn test_jit_ldxw_all() {
+ 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x030f0f);
+fn test_jit_ldxw() {
+ let mem = &mut [0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0xcc, 0xdd];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x44332211);
+fn test_jit_le16() {
+ let mem = &mut [0x22, 0x11];
+fn test_jit_le32() {
+ let mem = &mut [0x44, 0x33, 0x22, 0x11];
+fn test_jit_le64() {
+ let mem = &mut [0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11];
+fn test_jit_lsh_reg() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x10);
+fn test_jit_mod() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x5);
+fn test_jit_mod32() {
+fn test_jit_mod64() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x30ba5a04);
+fn test_jit_mov() {
+fn test_jit_mul32_imm() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0xc);
+fn test_jit_mul32_reg() {
+fn test_jit_mul32_reg_overflow() {
+fn test_jit_mul64_imm() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x100000004);
+fn test_jit_mul64_reg() {
+fn test_jit_mul_loop() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x75db9c97);
+fn test_jit_neg64() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0xfffffffffffffffe);
+fn test_jit_neg() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0xfffffffe);
+fn test_jit_prime() {
+fn test_jit_rhs32() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x00ffffff);
+fn test_jit_rsh_reg() {
+fn test_jit_stack() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0xcd);
+fn test_jit_stack2() {
+ assert_eq!(vm.execute_program_jit().unwrap(), 0x01020304);
+fn test_jit_stb() {
+ let mem = &mut [0xaa, 0xbb, 0xff, 0xcc, 0xdd];
+fn test_jit_stdw() {
+ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd,
+fn test_jit_sth() {
+ let mem = &mut [0xaa, 0xbb, 0xff, 0xff, 0xcc, 0xdd];
+fn test_jit_string_stack() {
+fn test_jit_stw() {
+ let mem = &mut [0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd];
+fn test_jit_stxb() {
+fn test_jit_stxb_all() {
+ let mem = &mut [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xf0f2f3f4f5f6f7f8);
+fn test_jit_stxb_all2() {
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xf1f9);
+fn test_jit_stxb_chain() {
+ let mem = &mut [0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x2a);
+fn test_jit_stxdw() {
+fn test_jit_stxh() {
+fn test_jit_stxw() {
+fn test_jit_subnet() {
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x1);
+fn test_jit_tcp_port80_match() {
+fn test_jit_tcp_port80_nomatch() {
+ assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x0);
+fn test_jit_tcp_port80_nomatch_ethertype() {
+fn test_jit_tcp_port80_nomatch_proto() {
+fn test_jit_tcp_sack_match() {
+ assert_eq!(vm.execute_program_jit(mem.as_mut_slice()).unwrap(), 0x1);
+fn test_jit_tcp_sack_nomatch() {
+ assert_eq!(vm.execute_program_jit(mem.as_mut_slice()).unwrap(), 0x0);
@@ -0,0 +1,177 @@
+// 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
+// These are unit tests for the eBPF “verifier”.
+#[should_panic(expected = "[Verifier] Error: unsupported argument for LE/BE (insn #0)")]
+fn test_verifier_err_endian_size() {
+ 0xdc, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+#[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
+ 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+#[should_panic(expected = "[Verifier] Error: infinite loop")]
+fn test_verifier_err_infinite_loop() {
+ ja -1
+ let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
+#[should_panic(expected = "[Verifier] Error: invalid destination register (insn #0)")]
+fn test_verifier_err_invalid_reg_dst() {
+ mov r11, 1
+#[should_panic(expected = "[Verifier] Error: invalid source register (insn #0)")]
+fn test_verifier_err_invalid_reg_src() {
+ mov r0, r11
+#[should_panic(expected = "[Verifier] Error: jump to middle of LD_DW at #2 (insn #0)")]
+fn test_verifier_err_jmp_lddw() {
+#[should_panic(expected = "[Verifier] Error: jump out of code to #3 (insn #0)")]
+fn test_verifier_err_jmp_out() {
+#[should_panic(expected = "[Verifier] Error: program does not end with “EXIT” instruction")]
+fn test_verifier_err_no_exit() {
+ mov32 r0, 0",
+fn test_verifier_err_no_exit_backward_jump() {
+ ja -2",
+#[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,
+ .collect::<Vec<u8>>();
+ prog.append(&mut vec![0x95, 0, 0, 0, 0, 0, 0, 0]);
+#[should_panic(expected = "[Verifier] Error: unknown eBPF opcode 0x6 (insn #0)")]
+fn test_verifier_err_unknown_opcode() {
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+#[should_panic(expected = "[Verifier] Error: cannot write into register r10 (insn #0)")]
+fn test_verifier_err_write_r10() {
+ mov r10, 1
@@ -0,0 +1,2674 @@
+// in this file has a name in the form `test_vm_<name>`, and corresponds to the (human-readable)
+// These are unit tests for the eBPF interpreter.
+fn test_vm_add() {
+fn test_vm_alu64_arith() {
+ assert_eq!(vm.execute_program().unwrap(), 0x2a);
+fn test_vm_alu64_bit() {
+ assert_eq!(vm.execute_program().unwrap(), 0x11);
+fn test_vm_alu_arith() {
+fn test_vm_alu_bit() {
+fn test_vm_arsh32_high_shift() {
+ assert_eq!(vm.execute_program().unwrap(), 0x4);
+fn test_vm_arsh() {
+ assert_eq!(vm.execute_program().unwrap(), 0xffff8000);
+fn test_vm_arsh64() {
+ assert_eq!(vm.execute_program().unwrap(), 0xfffffffffffffff8);
+fn test_vm_arsh_reg() {
+fn test_vm_arsh_imm_overflow() {
+ arsh r0, 0xff20
+ assert_eq!(vm.execute_program().unwrap(), 0xffffffff80000000);
+fn test_vm_arsh_reg_overflow() {
+ mov r1, 0xff04
+ assert_eq!(vm.execute_program().unwrap(), 0xf800000000000000);
+fn test_vm_arsh32_imm_overflow() {
+ lsh32 r0, 31
+ arsh32 r0, 0xff10
+fn test_vm_arsh32_reg_overflow() {
+ mov32 r1, 32
+ assert_eq!(vm.execute_program().unwrap(), 0x80000000);
+fn test_vm_be16() {
+ let vm = rbpf::EbpfVmRaw::new(Some(&prog)).unwrap();
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x1122);
+fn test_vm_be16_high() {
+fn test_vm_be32() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x11223344);
+fn test_vm_be32_high() {
+fn test_vm_be64() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x1122334455667788);
+fn test_vm_call() {
+ assert_eq!(vm.execute_program().unwrap(), 0x0102030405);
+fn test_vm_call_memfrob() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x102292e2f2c0708);
+//fn test_vm_call_save() {
+//assert_eq!(vm.execute_program().unwrap(), 0x4321);
+fn test_vm_div32_high_divisor() {
+fn test_vm_div32_imm() {
+fn test_vm_div32_reg() {
+fn test_vm_div64_imm() {
+ assert_eq!(vm.execute_program().unwrap(), 0x300000000);
+fn test_vm_div64_reg() {
+fn test_vm_early_exit() {
+//fn test_vm_err_call_bad_imm() {
+#[should_panic(expected = "Error: unknown helper function (id: 0x3f)")]
+fn test_vm_err_call_unreg() {
+fn test_vm_div64_by_zero_imm() {
+ assert_eq!(vm.execute_program().unwrap(), 0x0);
+fn test_vm_div_by_zero_imm() {
+fn test_vm_mod64_by_zero_imm() {
+ assert_eq!(vm.execute_program().unwrap(), 0x1);
+fn test_vm_mod_by_zero_imm() {
+// Make sure we only consider the last 32 bits of the divisor.
+fn test_vm_mod_by_zero_reg_long() {
+ lddw r1, 0x100000000
+fn test_vm_div64_by_zero_reg() {
+fn test_vm_div_by_zero_reg() {
+fn test_vm_div_by_zero_reg_long() {
+fn test_vm_mod64_by_zero_reg() {
+fn test_vm_mod_by_zero_reg() {
+#[should_panic(expected = "Error: out of bounds memory store (insn #1)")]
+fn test_vm_err_stack_out_of_bound() {
+ stb [r10], 0
+fn test_vm_exit() {
+fn test_vm_ja() {
+fn test_vm_jeq_imm() {
+fn test_vm_jeq_reg() {
+fn test_vm_jge_imm() {
+fn test_vm_jle_imm() {
+fn test_vm_jle_reg() {
+fn test_vm_jgt_imm() {
+fn test_vm_jgt_reg() {
+fn test_vm_jlt_imm() {
+fn test_vm_jlt_reg() {
+fn test_vm_jit_bounce() {
+fn test_vm_jne_reg() {
+fn test_vm_jset_imm() {
+fn test_vm_jset_reg() {
+fn test_vm_jsge_imm() {
+fn test_vm_jsge_reg() {
+fn test_vm_jsle_imm() {
+fn test_vm_jsle_reg() {
+fn test_vm_jsgt_imm() {
+fn test_vm_jsgt_reg() {
+fn test_vm_jslt_imm() {
+fn test_vm_jslt_reg() {
+fn test_vm_jeq32_imm() {
+fn test_vm_jeq32_reg() {
+fn test_vm_jge32_imm() {
+fn test_vm_jge32_reg() {
+fn test_vm_jgt32_imm() {
+fn test_vm_jgt32_reg() {
+fn test_vm_jle32_imm() {
+fn test_vm_jle32_reg() {
+fn test_vm_jlt32_imm() {
+fn test_vm_jlt32_reg() {
+fn test_vm_jne32_imm() {
+fn test_vm_jne32_reg() {
+fn test_vm_jset32_imm() {
+fn test_vm_jset32_reg() {
+fn test_vm_jsge32_imm() {
+fn test_vm_jsge32_reg() {
+fn test_vm_jsgt32_imm() {
+fn test_vm_jsgt32_reg() {
+fn test_vm_jsle32_imm() {
+fn test_vm_jsle32_reg() {
+fn test_vm_jslt32_imm() {
+fn test_vm_jslt32_reg() {
+fn test_vm_lddw() {
+ "lddw r0, 0x1122334455667788
+ assert_eq!(vm.execute_program().unwrap(), 0x1122334455667788);
+fn test_vm_lddw2() {
+fn test_vm_ldxb_all() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x9876543210);
+fn test_vm_ldxb() {
+fn test_vm_ldxdw() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x8877665544332211);
+fn test_vm_ldxh_all() {
+fn test_vm_ldxh_all2() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x3ff);
+fn test_vm_ldxh() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x2211);
+fn test_vm_ldxh_same_reg() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x1234);
+fn test_vm_ldxw_all() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x030f0f);
+fn test_vm_ldxw() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x44332211);
+fn test_vm_le16() {
+fn test_vm_le32() {
+fn test_vm_le64() {
+fn test_vm_lsh_imm() {
+ lsh r0, 4
+ assert_eq!(vm.execute_program().unwrap(), 0x10);
+fn test_vm_lsh_reg() {
+fn test_vm_lsh32_imm() {
+ lsh32 r0, 4
+fn test_vm_lsh32_reg() {
+ mov32 r7, 4
+ lsh32 r0, r7
+fn test_vm_lsh_imm_overflow() {
+ lsh r0, 64
+fn test_vm_lsh_reg_overflow() {
+ mov r7, 64
+fn test_vm_lsh32_imm_overflow() {
+ lsh32 r0, 32
+fn test_vm_lsh32_reg_overflow() {
+ mov32 r7, 32
+fn test_vm_mod() {
+ assert_eq!(vm.execute_program().unwrap(), 0x5);
+fn test_vm_mod32() {
+fn test_vm_mod64() {
+ assert_eq!(vm.execute_program().unwrap(), 0x30ba5a04);
+fn test_vm_mov() {
+fn test_vm_mul32_imm() {
+ assert_eq!(vm.execute_program().unwrap(), 0xc);
+fn test_vm_mul32_reg() {
+fn test_vm_mul32_reg_overflow() {
+fn test_vm_mul64_imm() {
+ assert_eq!(vm.execute_program().unwrap(), 0x100000004);
+fn test_vm_mul64_reg() {
+fn test_vm_mul_loop() {
+ assert_eq!(vm.execute_program().unwrap(), 0x75db9c97);
+fn test_vm_neg64() {
+ assert_eq!(vm.execute_program().unwrap(), 0xfffffffffffffffe);
+fn test_vm_neg() {
+ assert_eq!(vm.execute_program().unwrap(), 0xfffffffe);
+fn test_vm_prime() {
+fn test_vm_rhs32() {
+ assert_eq!(vm.execute_program().unwrap(), 0x00ffffff);
+fn test_vm_rsh_reg() {
+fn test_vm_stack() {
+ assert_eq!(vm.execute_program().unwrap(), 0xcd);
+fn test_vm_stack2() {
+ assert_eq!(vm.execute_program().unwrap(), 0x01020304);
+fn test_vm_stb() {
+fn test_vm_stdw() {
+// If this case is not handled properly in check_mem(), then we may overflow when adding the
+// context address and the offset, and make the thread panic with "attempt to add with overflow".
+// Check that we panic with the expected out-of-bounds error.
+// The new toolchain introduced `assert_unsafe_precondition` which panics with a different message and can't be
+// caught by `#[should_panic]`. This is why we use `#[ignore]` here.
+fn test_vm_stdw_add_overflow() {
+ stdw [r2-0x1], 0x44332211
+ let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(&prog), 0x00, 0x10).unwrap();
+ _ = vm.execute_program(mem).unwrap();
+fn test_vm_sth() {
+fn test_vm_string_stack() {
+fn test_vm_stw() {
+fn test_vm_stxb() {
+fn test_vm_stxb_all() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0xf0f2f3f4f5f6f7f8);
+fn test_vm_stxb_all2() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0xf1f9);
+fn test_vm_stxb_chain() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x2a);
+fn test_vm_stxdw() {
+fn test_vm_stxh() {
+fn test_vm_stxw() {
+fn test_vm_subnet() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x1);
+fn test_vm_tcp_port80_match() {
+fn test_vm_tcp_port80_nomatch() {
+ assert_eq!(vm.execute_program(mem).unwrap(), 0x0);
+fn test_vm_tcp_port80_nomatch_ethertype() {
+fn test_vm_tcp_port80_nomatch_proto() {
+fn test_vm_tcp_sack_match() {
+ assert_eq!(vm.execute_program(mem.as_mut_slice()).unwrap(), 0x1);
+fn test_vm_tcp_sack_nomatch() {
+ assert_eq!(vm.execute_program(mem.as_mut_slice()).unwrap(), 0x0);
@@ -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))]
#![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>;
+ /// 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 {
+ /// The caller must ensure that the memory is valid and that the size is correct.
unsafe fn free_slab_page(&self, _: *mut u8, _: usize) {}
@@ -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")
@@ -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.
+ /// The caller must ensure that the `layout` is valid.
pub unsafe fn deallocate(
@@ -6,7 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-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"
@@ -1,7 +1,7 @@
#![allow(clippy::upper_case_acronyms)]
+#![allow(non_local_definitions)]
use num_derive::{FromPrimitive, ToPrimitive};
#[repr(i32)]
@@ -11,4 +11,4 @@ path = "src/main.rs"
unified-init-macros = { path = "macros" }
-system_error = { path = "../system_error" }
+system_error = { path = "../system_error" }
@@ -2,6 +2,8 @@
//! 然后在当前目录执行 `cargo expand --bin unified-init-expand`
//! 就可以看到把proc macro展开后的代码了
+#![allow(internal_features)]
+#![feature(lang_items)]
fn main() {
todo!()
@@ -14,6 +16,10 @@ pub fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
+#[cfg(target_os = "none")]
+#[lang = "eh_personality"]
+unsafe extern "C" fn eh_personality() {}
#[cfg(test)]
mod tests {
use system_error::SystemError;
@@ -1,3 +1,3 @@
[toolchain]
-channel = "nightly-2024-07-23"
+channel = "nightly-2024-11-05"
components = ["rust-src", "clippy"]
@@ -14,11 +14,9 @@ CFLAGS_UNWIND =
LDFLAGS_UNWIND =
RUSTFLAGS_UNWIND =
ifeq ($(UNWIND_ENABLE), yes)
- CFLAGS_UNWIND = -funwind-tables
- LDFLAGS_UNWIND = --eh-frame-hdr
- RUSTFLAGS_UNWIND = -Cforce-unwind-tables -Clink-arg=-Wl,eh_frame.ld
+ CFLAGS_UNWIND = -funwind-tables
+ LDFLAGS_UNWIND = --eh-frame-hdr
+ RUSTFLAGS_UNWIND = -Cforce-unwind-tables -Clink-arg=-Wl,eh_frame.ld -Cpanic=unwind
RUSTFLAGS += $(RUSTFLAGS_UNWIND)
@@ -40,7 +38,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
@@ -58,7 +56,6 @@ ECHO:
$(kernel_subdirs): ECHO
$(MAKE) -C $@ all CFLAGS="$(CFLAGS)" ASFLAGS="$(ASFLAGS)" kernel_root_path="$(shell pwd)"
kernel: $(kernel_subdirs) kernel_rust
@@ -66,7 +63,26 @@ kernel: $(kernel_subdirs) kernel_rust
__link_riscv64_kernel:
@echo "Linking kernel..."
$(LD) -b elf64-littleriscv -z muldefs $(LDFLAGS_UNWIND) -o kernel $(shell find . -name "*.o") ../target/riscv64gc-unknown-none-elf/release/libdragonos_kernel.a -T arch/riscv64/link.ld --no-relax
+ # 生成kallsyms
+ current_dir=$(pwd)
+ @dbg='debug';for x in $$dbg; do \
+ cd $$x;\
+ $(MAKE) generate_kallsyms kernel_root_path="$(shell pwd)"||exit 1;\
+ cd ..;\
+ done
+# 重新链接
+ @echo "Re-Linking kernel..."
+ @echo $(shell find . -name "*.o")
+ $(LD) -b elf64-littleriscv -z muldefs $(LDFLAGS_UNWIND) -o kernel $(shell find . -name "*.o") ../target/riscv64gc-unknown-none-elf/release/libdragonos_kernel.a ./debug/kallsyms.o -T arch/riscv64/link.ld --no-relax
+ @echo "Generating kernel ELF file..."
+ifeq ($(UNWIND_ENABLE), yes)
+ $(OBJCOPY) -I elf64-littleriscv -O elf64-littleriscv kernel ../../bin/kernel/kernel.elf
+else
$(OBJCOPY) -I elf64-littleriscv -O elf64-littleriscv -R ".eh_frame" kernel ../../bin/kernel/kernel.elf
+endif
@rm kernel
$(MAKE) __dragon_stub PAYLOAD_ELF="$(shell pwd)/../../bin/kernel/kernel.elf"
@@ -111,3 +127,7 @@ clean:
cd $$subdir && $(MAKE) clean;\
cd .. ;\
done
+.PHONY: check
+check:
+ RUSTFLAGS="$(RUSTFLAGS)" cargo +nightly-2024-11-05 $(CARGO_ZBUILD) check --workspace --message-format=json --target $(TARGET_JSON)
@@ -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]
unsafe extern "C" fn _restore_kernel_tpsp() -> ! {
// 这次是从内核态进入中断
// 从sscratch寄存器加载当前cpu的上下文
@@ -48,16 +46,14 @@ unsafe extern "C" fn _restore_kernel_tpsp() -> ! {
lc_off_kernel_sp = const offset_of!(LocalContext, kernel_sp),
- _save_context = sym _save_context,
+ _save_context = sym _save_context
unsafe extern "C" fn _save_context() -> ! {
@@ -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,
+ csr_scratch = const CSR_SSCRATCH
pub unsafe extern "C" fn ret_from_exception() -> ! {
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),
+ off_epc = const offset_of!(TrapFrame, epc)
@@ -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 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
@@ -1,3 +1,5 @@
+use core::any::Any;
+use kprobe::ProbeArgs;
use riscv::register::{scause::Scause, sstatus::Sstatus};
@@ -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.epc
@@ -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 {
+ 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,
@@ -28,6 +28,7 @@ SECTIONS
. = ALIGN(4096);
text_start_pa = .;
+ __executable_start = .;
.text (text_start_pa): AT(text_start_pa - KERNEL_VMA)
_text = .;
@@ -39,6 +40,7 @@ SECTIONS
*(.text.*)
_etext = .;
+ __etext = .;
. = ALIGN(32768);
data_start_pa = .;
@@ -60,6 +62,7 @@ SECTIONS
_rodata = .;
*(.rodata)
*(.rodata.*)
+ *(.gcc_except_table .gcc_except_table.*)
_erodata = .;
@@ -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;