mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2025-12-31 21:06:25 +00:00
Compare commits
80 Commits
v0.1.2
...
5c8b132cb9
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c8b132cb9 | |||
| 58c372bcb3 | |||
| 83e4094ca9 | |||
| 5654ee91da | |||
| c27da22bcb | |||
| b095828ae2 | |||
| b31d6ff144 | |||
| 8d5067996f | |||
| 1ba2055450 | |||
| 8658143b16 | |||
| 475e07c4e2 | |||
| 1847d96219 | |||
| 430617de7e | |||
| 7d91f88c4d | |||
| 44b95b69ca | |||
| 0e1cbb30b7 | |||
| f489129949 | |||
| 0d0a318e46 | |||
| 18811933bf | |||
| 912d0c8eac | |||
| b88888fc68 | |||
| e80486975f | |||
| 63944c5f93 | |||
| ce9be8750d | |||
| 6afcffb4ed | |||
| af004cb864 | |||
| 980f2af414 | |||
| 87908e8713 | |||
| 0f3cc434a3 | |||
| d943b78a25 | |||
| 744aa3fbdf | |||
| 9fa362ec6a | |||
| ca51b7ce01 | |||
| 2e005f6eb5 | |||
| cbc6b93cd8 | |||
| 9ae1b6ce15 | |||
| 1a1f2cf634 | |||
| 0d691865bc | |||
| 0fb1cafd20 | |||
| 1adf7d7fcc | |||
| 3ded17bf8b | |||
| 715442d7bf | |||
| e464a3fdd5 | |||
| fed4c179e6 | |||
| 32c22c3148 | |||
| 4557b094e1 | |||
| 84500305db | |||
| 0d21f84529 | |||
| 5bcc02a931 | |||
| fe91a176e2 | |||
| 083ee21e38 | |||
| ea5a1ab2de | |||
| de5cc438ab | |||
| 8c2196c05c | |||
| a2f86d680d | |||
| 0f365be65e | |||
| 4ebf0480dd | |||
| b9ddecd6b1 | |||
| 737c4d3039 | |||
| da8a495da7 | |||
| ee03ac04d0 | |||
| 51595f9ec2 | |||
| 4cf284a81f | |||
| 1517f6e052 | |||
| 95f360059b | |||
| dad57bd340 | |||
| 529b0bde19 | |||
| 943697ac9f | |||
| ba90af9ff2 | |||
| 35969c4ff7 | |||
| 9e87ee52f2 | |||
| d0be8893eb | |||
| dda05bd044 | |||
| 28e6f97708 | |||
| a1bc813ec5 | |||
| fefd6840c8 | |||
| 79f0949abc | |||
| a1371697cc | |||
| 3c976b88d3 | |||
| 69a86c2433 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
tests/c-form/vmlinux.h linguist-vendored
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
22
.github/workflows/format.yml
vendored
Normal file
22
.github/workflows/format.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# This is a format job. Pre-commit has a first-party GitHub action, so we use
|
||||
# that: https://github.com/pre-commit/action
|
||||
|
||||
name: Format
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- uses: pre-commit/action@v3.0.1
|
||||
6
.github/workflows/python-publish.yml
vendored
6
.github/workflows/python-publish.yml
vendored
@ -20,9 +20,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Retrieve release distributions
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: release-dists
|
||||
path: dist/
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,4 +5,5 @@
|
||||
.vscode/
|
||||
__pycache__/
|
||||
*.ll
|
||||
*.o
|
||||
*.o
|
||||
.ipynb_checkpoints/
|
||||
|
||||
60
.pre-commit-config.yaml
Normal file
60
.pre-commit-config.yaml
Normal file
@ -0,0 +1,60 @@
|
||||
# To use:
|
||||
#
|
||||
# pre-commit run -a
|
||||
#
|
||||
# Or:
|
||||
#
|
||||
# pre-commit install # (runs every time you commit in git)
|
||||
#
|
||||
# To update this file:
|
||||
#
|
||||
# pre-commit autoupdate
|
||||
#
|
||||
# See https://github.com/pre-commit/pre-commit
|
||||
|
||||
exclude: 'vmlinux.*\.py$'
|
||||
|
||||
ci:
|
||||
autoupdate_commit_msg: "chore: update pre-commit hooks"
|
||||
autofix_commit_msg: "style: pre-commit fixes"
|
||||
|
||||
repos:
|
||||
# Standard hooks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-merge-conflict
|
||||
- id: check-symlinks
|
||||
- id: check-yaml
|
||||
exclude: ^conda\.recipe/meta\.yaml$
|
||||
- id: debug-statements
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: requirements-txt-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.4.2"
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--fix", "--show-fixes"]
|
||||
- id: ruff-format
|
||||
exclude: ^(docs)
|
||||
|
||||
## Checking static types
|
||||
#- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
# rev: "v1.10.0"
|
||||
# hooks:
|
||||
# - id: mypy
|
||||
# files: "setup.py"
|
||||
# args: []
|
||||
# additional_dependencies: [types-setuptools]
|
||||
|
||||
# Changes tabs to spaces
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||
rev: v1.5.5
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: '^(docs)|.*/Makefile$|Makefile$'
|
||||
1
LICENSE
1
LICENSE
@ -200,4 +200,3 @@
|
||||
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.
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@ -1,14 +1,10 @@
|
||||
compile:
|
||||
chmod +x ./tools/compile.py
|
||||
./tools/compile.py ./examples/execve3.py
|
||||
|
||||
install:
|
||||
install:
|
||||
pip install -e .
|
||||
|
||||
clean:
|
||||
rm -rf build dist *.egg-info
|
||||
rm -rf examples/*.ll examples/*.o
|
||||
|
||||
all: install compile
|
||||
all: clean install
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
218
README.md
218
README.md
@ -1,77 +1,201 @@
|
||||
# Python-BPF
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcset="https://github.com/user-attachments/assets/f3738131-d7cb-4b5c-8699-c7010295a159"
|
||||
width="450"
|
||||
alt="Light‐mode image">
|
||||
<img
|
||||
src="https://github.com/user-attachments/assets/b175bf39-23cb-475d-a6e1-7b5c99a1ed72"
|
||||
width="450"
|
||||
alt="Dark‐mode image">
|
||||
</picture>
|
||||
<!-- Badges -->
|
||||
<p align="center">
|
||||
<a href="https://www.python.org/downloads/release/python-3080/"><img src="https://img.shields.io/badge/python-3.8-blue.svg"></a>
|
||||
<a href="https://pypi.org/project/pythonbpf"><img src="https://badge.fury.io/py/pythonbpf.svg"></a>
|
||||
<!-- PyPI -->
|
||||
<a href="https://pypi.org/project/pythonbpf/"><img src="https://img.shields.io/pypi/v/pythonbpf?color=blue" alt="PyPI version"></a>
|
||||
<!-- <a href="https://pypi.org/project/pythonbpf/"><img src="https://img.shields.io/pypi/pyversions/pythonbpf" alt="Python versions"></a> -->
|
||||
<!-- <a href="https://pypi.org/project/pythonbpf/"><img src="https://img.shields.io/pypi/dm/pythonbpf" alt="PyPI downloads"></a> -->
|
||||
<!-- <a href="https://pypi.org/project/pythonbpf/"><img src="https://img.shields.io/pypi/status/pythonbpf" alt="PyPI Status"></a> -->
|
||||
<a href="https://pepy.tech/project/pythonbpf"><img src="https://pepy.tech/badge/pythonbpf" alt="Downloads"></a>
|
||||
<!-- Build & CI -->
|
||||
<a href="https://github.com/pythonbpf/python-bpf/actions"><img src="https://github.com/pythonbpf/python-bpf/actions/workflows/python-publish.yml/badge.svg" alt="Build Status"></a>
|
||||
<!-- Meta -->
|
||||
<a href="https://github.com/pythonbpf/python-bpf/blob/main/LICENSE"><img src="https://img.shields.io/github/license/pythonbpf/python-bpf" alt="License"></a>
|
||||
</p>
|
||||
|
||||
This is an LLVM IR generator for eBPF programs in Python. We use llvmlite to generate LLVM IR from pure Python. This is then compiled to LLVM object files, which can be loaded into the kernel for execution. We do not rely on BCC to do our compilation.
|
||||
|
||||
# DO NOT USE IN PRODUCTION. IN DEVELOPMENT.
|
||||
Python-BPF is an LLVM IR generator for eBPF programs written in Python. It uses [llvmlite](https://github.com/numba/llvmlite) to generate LLVM IR and then compiles to LLVM object files. These object files can be loaded into the kernel for execution. Python-BPF performs compilation without relying on BCC.
|
||||
|
||||
## Video Demo
|
||||
[Video demo for code under demo/](https://youtu.be/eMyLW8iWbks)
|
||||
> **Note**: This project is under active development and not ready for production use.
|
||||
|
||||
## Slide Deck
|
||||
[Slide deck explaining the project](https://docs.google.com/presentation/d/1DsWDIVrpJhM4RgOETO9VWqUtEHo3-c7XIWmNpi6sTSo/edit?usp=sharing)
|
||||
---
|
||||
|
||||
## Installation
|
||||
- Have `clang` installed.
|
||||
- `pip install pythonbpf`
|
||||
## Overview
|
||||
|
||||
* Generate eBPF programs directly from Python.
|
||||
* Compile to LLVM object files for kernel execution.
|
||||
* Built with `llvmlite` for IR generation.
|
||||
* Supports maps, helpers, and global definitions for BPF.
|
||||
* Companion project: [pylibbpf](https://github.com/pythonbpf/pylibbpf), which provides the bindings required for object loading and execution.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Dependencies:
|
||||
|
||||
* `clang`
|
||||
* Python ≥ 3.8
|
||||
|
||||
Install via pip:
|
||||
|
||||
```bash
|
||||
pip install pythonbpf pylibbpf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Usage
|
||||
|
||||
## Usage
|
||||
```python
|
||||
# pythonbpf_example.py
|
||||
from pythonbpf import bpf, map, bpfglobal, section, compile
|
||||
from pythonbpf.helpers import bpf_ktime_get_ns
|
||||
import time
|
||||
from pythonbpf import bpf, map, section, bpfglobal, BPF
|
||||
from pythonbpf.helpers import pid
|
||||
from pythonbpf.maps import HashMap
|
||||
from pylibbpf import *
|
||||
from ctypes import c_void_p, c_int64, c_uint64, c_int32
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||
# This program attaches an eBPF tracepoint to sys_enter_clone,
|
||||
# counts per-PID clone syscalls, stores them in a hash map,
|
||||
# and then plots the distribution as a histogram using matplotlib.
|
||||
# It provides a quick view of process creation activity over 10 seconds.
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def last() -> HashMap:
|
||||
return HashMap(key=c_uint64, value=c_uint64, max_entries=1)
|
||||
def hist() -> HashMap:
|
||||
return HashMap(key=c_int32, value=c_uint64, max_entries=4096)
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_enter_execve")
|
||||
def hello(ctx: c_void_p) -> c_int32:
|
||||
print("entered")
|
||||
return c_int32(0)
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_exit_execve")
|
||||
def hello_again(ctx: c_void_p) -> c_int64:
|
||||
print("exited")
|
||||
key = 0
|
||||
tsp = last().lookup(key)
|
||||
print(tsp)
|
||||
ts = bpf_ktime_get_ns()
|
||||
@section("tracepoint/syscalls/sys_enter_clone")
|
||||
def hello(ctx: c_void_p) -> c_int64:
|
||||
process_id = pid()
|
||||
one = 1
|
||||
prev = hist().lookup(process_id)
|
||||
if prev:
|
||||
previous_value = prev + 1
|
||||
print(f"count: {previous_value} with {process_id}")
|
||||
hist().update(process_id, previous_value)
|
||||
return c_int64(0)
|
||||
else:
|
||||
hist().update(process_id, one)
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
def some_normal_function():
|
||||
print("normal function")
|
||||
|
||||
# compiles and dumps object file in the same directory
|
||||
compile()
|
||||
b = BPF()
|
||||
b.load_and_attach()
|
||||
hist = BpfMap(b, hist)
|
||||
print("Recording")
|
||||
time.sleep(10)
|
||||
|
||||
counts = list(hist.values())
|
||||
|
||||
plt.hist(counts, bins=20)
|
||||
plt.xlabel("Clone calls per PID")
|
||||
plt.ylabel("Frequency")
|
||||
plt.title("Syscall clone counts")
|
||||
plt.show()
|
||||
```
|
||||
- Run `python pythonbpf_example.py` to get the compiled object file that can be then loaded into the kernel.
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
Python-BPF provides a complete pipeline to write, compile, and load eBPF programs in Python:
|
||||
|
||||
1. **Python Source Code**
|
||||
|
||||
* Users write BPF programs in Python using decorators like `@bpf`, `@map`, `@section`, and `@bpfglobal`.
|
||||
* Maps (hash maps), helpers (e.g., `ktime`, `deref`), and tracepoints are defined using Python constructs, preserving a syntax close to standard Python.
|
||||
|
||||
2. **AST Generation**
|
||||
|
||||
* The Python `ast` module parses the source code into an Abstract Syntax Tree (AST).
|
||||
* Decorators and type annotations are captured to determine BPF maps, tracepoints, and global variables.
|
||||
|
||||
3. **LLVM IR Emission**
|
||||
|
||||
* The AST is transformed into LLVM Intermediate Representation (IR) using `llvmlite`.
|
||||
* IR captures BPF maps, control flow, assignments, and calls to helper functions.
|
||||
* Debug information is emitted for easier inspection.
|
||||
|
||||
4. **LLVM Object File Compilation**
|
||||
|
||||
* The LLVM IR (`.ll`) is compiled into a BPF target object file (`.o`) using `llc -march=bpf -O2`.
|
||||
* This produces a kernel-loadable ELF object file containing the BPF bytecode.
|
||||
|
||||
5. **libbpf Integration (via pylibbpf)**
|
||||
|
||||
* The compiled object file can be loaded into the kernel using `pylibbpf`.
|
||||
* Maps, tracepoints, and program sections are initialized, and helper functions are resolved.
|
||||
* Programs are attached to kernel hooks (e.g., syscalls) for execution.
|
||||
|
||||
6. **Execution in Kernel**
|
||||
|
||||
* The kernel executes the loaded eBPF program.
|
||||
* Hash maps, helpers, and global variables behave as defined in the Python source.
|
||||
* Output can be read via BPF maps, helper functions, or trace printing.
|
||||
|
||||
This architecture eliminates the need for embedding C code in Python, allowing full Python tooling support while generating true BPF object files ready for kernel execution.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
- Make a virtual environment and activate it using `python3 -m venv .venv && source .venv/bin/activate`.
|
||||
- Run `make install` to install the required dependencies.
|
||||
- Run `make` to see the compilation output of the example.
|
||||
- Run `check.sh` to check if generated object file passes through the verifier inside the examples directory.
|
||||
- Run `make` in the `examples/c-form` directory to modify the example C BPF program to check the actual LLVM IR generated by clang.
|
||||
|
||||
### Development Notes
|
||||
- Run ` ./check.sh check execve2.o;` in examples folder to check if the object code passes the verifier.
|
||||
- Run ` ./check.sh run execve2.o;` in examples folder to run the object code using `bpftool`.
|
||||
1. Create a virtual environment and activate it:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
Then, run any example in `examples`
|
||||
3. Verify an object file with the kernel verifier:
|
||||
|
||||
```bash
|
||||
./tools/check.sh check execve2.o
|
||||
```
|
||||
|
||||
5. Run an object file using `bpftool`:
|
||||
|
||||
```bash
|
||||
./tools/check.sh run execve2.o
|
||||
```
|
||||
|
||||
6. Explore LLVM IR output from clang in `examples/c-form` by running `make`.
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
* [Video demonstration](https://youtu.be/eMyLW8iWbks)
|
||||
* [Slide deck](https://docs.google.com/presentation/d/1DsWDIVrpJhM4RgOETO9VWqUtEHo3-c7XIWmNpi6sTSo/edit?usp=sharing)
|
||||
|
||||
---
|
||||
|
||||
## Authors
|
||||
- [@r41k0u](https://github.com/r41k0u)
|
||||
- [@varun-r-mallya](https://github.com/varun-r-mallya)
|
||||
|
||||
* [@r41k0u](https://github.com/r41k0u)
|
||||
* [@varun-r-mallya](https://github.com/varun-r-mallya)
|
||||
|
||||
---
|
||||
|
||||
6
TODO.md
6
TODO.md
@ -1,7 +1,11 @@
|
||||
## Short term
|
||||
|
||||
- Implement enough functionality to port the BCC tutorial examples in PythonBPF
|
||||
|
||||
- Static Typing
|
||||
- Add all maps
|
||||
- XDP support in pylibbpf
|
||||
- ringbuf support
|
||||
- recursive expression resolution
|
||||
|
||||
## Long term
|
||||
|
||||
|
||||
46
demo/bcc.py
46
demo/bcc.py
@ -1,46 +0,0 @@
|
||||
from __future__ import print_function
|
||||
from bcc import BPF
|
||||
from bcc.utils import printb
|
||||
|
||||
# load BPF program
|
||||
b = BPF(text="""
|
||||
#include <uapi/linux/ptrace.h>
|
||||
|
||||
BPF_HASH(last);
|
||||
|
||||
int do_trace(struct pt_regs *ctx) {
|
||||
u64 ts, *tsp, delta, key = 0;
|
||||
|
||||
// attempt to read stored timestamp
|
||||
tsp = last.lookup(&key);
|
||||
if (tsp != NULL) {
|
||||
delta = bpf_ktime_get_ns() - *tsp;
|
||||
if (delta < 1000000000) {
|
||||
// output if time is less than 1 second
|
||||
bpf_trace_printk("%d\\n", delta / 1000000);
|
||||
}
|
||||
last.delete(&key);
|
||||
}
|
||||
|
||||
// update stored timestamp
|
||||
ts = bpf_ktime_get_ns();
|
||||
last.update(&key, &ts);
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_trace")
|
||||
print("Tracing for quick sync's... Ctrl-C to end")
|
||||
|
||||
# TODO
|
||||
# format output
|
||||
start = 0
|
||||
while 1:
|
||||
try:
|
||||
(task, pid, cpu, flags, ts, ms) = b.trace_fields()
|
||||
if start == 0:
|
||||
start = ts
|
||||
ts = ts - start
|
||||
printb(b"At time %.2f s: multiple syncs detected, last %s ms ago" % (ts, ms))
|
||||
except KeyboardInterrupt:
|
||||
exit()
|
||||
@ -1,23 +0,0 @@
|
||||
from pythonbpf import bpf, section, bpfglobal, compile
|
||||
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
# Instructions to how to run this program
|
||||
# 1. Install PythonBPF: pip install pythonbpf
|
||||
# 2. Run the program: python demo/pybpf0.py
|
||||
# 3. Run the program with sudo: sudo examples/check.sh run demo/pybpf0.o
|
||||
# 4. Start up any program and watch the output
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_enter_execve")
|
||||
def hello_world(ctx: c_void_p) -> c_int64:
|
||||
print("Hello, World!")
|
||||
return c_int64(0)
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
compile()
|
||||
@ -6,10 +6,11 @@ from ctypes import c_void_p, c_int64, c_uint64
|
||||
|
||||
# Instructions to how to run this program
|
||||
# 1. Install PythonBPF: pip install pythonbpf
|
||||
# 2. Run the program: python demo/pybpf3.py
|
||||
# 3. Run the program with sudo: sudo examples/check.sh run demo/pybpf3.o
|
||||
# 2. Run the program: python examples/binops_demo.py
|
||||
# 3. Run the program with sudo: sudo tools/check.sh run examples/binops_demo.py
|
||||
# 4. Start up any program and watch the output
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def last() -> HashMap:
|
||||
@ -23,9 +24,9 @@ def do_trace(ctx: c_void_p) -> c_int64:
|
||||
tsp = last().lookup(key)
|
||||
if tsp:
|
||||
kt = ktime()
|
||||
delta = (kt - tsp)
|
||||
delta = kt - tsp
|
||||
if delta < 1000000000:
|
||||
time_ms = (delta // 1000000)
|
||||
time_ms = delta // 1000000
|
||||
print(f"Execve syscall entered within last second, last {time_ms} ms ago")
|
||||
last().delete(key)
|
||||
else:
|
||||
@ -33,16 +34,18 @@ def do_trace(ctx: c_void_p) -> c_int64:
|
||||
last().update(key, kt)
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_exit_execve")
|
||||
def do_exit(ctx: c_void_p) -> c_int64:
|
||||
va = 8
|
||||
nm = 5 ^ va
|
||||
al = 6 & 3
|
||||
ru = (nm + al)
|
||||
ru = nm + al
|
||||
print(f"this is a variable {ru}")
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
@ -1,8 +1,8 @@
|
||||
from pythonbpf import bpf, map, section, bpfglobal, compile
|
||||
from pythonbpf.helpers import ktime, deref
|
||||
from pythonbpf.helpers import ktime
|
||||
from pythonbpf.maps import HashMap
|
||||
|
||||
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||
from ctypes import c_void_p, c_int32, c_uint64
|
||||
|
||||
|
||||
@bpf
|
||||
@ -15,7 +15,7 @@ def last() -> HashMap:
|
||||
@section("blk_start_request")
|
||||
def trace_start(ctx: c_void_p) -> c_int32:
|
||||
ts = ktime()
|
||||
print("req started")
|
||||
print(f"req started {ts}")
|
||||
return c_int32(0)
|
||||
|
||||
|
||||
121617
examples/c-form/vmlinux.h
121617
examples/c-form/vmlinux.h
File diff suppressed because it is too large
Load Diff
397
examples/clone-matplotlib.ipynb
Normal file
397
examples/clone-matplotlib.ipynb
Normal file
File diff suppressed because one or more lines are too long
58
examples/clone_plot.py
Normal file
58
examples/clone_plot.py
Normal file
@ -0,0 +1,58 @@
|
||||
import time
|
||||
|
||||
from pythonbpf import bpf, map, section, bpfglobal, BPF
|
||||
from pythonbpf.helpers import pid
|
||||
from pythonbpf.maps import HashMap
|
||||
from pylibbpf import BpfMap
|
||||
from ctypes import c_void_p, c_int64, c_uint64, c_int32
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# This program attaches an eBPF tracepoint to sys_enter_clone,
|
||||
# counts per-PID clone syscalls, stores them in a hash map,
|
||||
# and then plots the distribution as a histogram using matplotlib.
|
||||
# It provides a quick view of process creation activity over 10 seconds.
|
||||
# Everything is done with Python only code and with the new pylibbpf library.
|
||||
# Run `sudo /path/to/python/binary/ clone_plot.py`
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def hist() -> HashMap:
|
||||
return HashMap(key=c_int32, value=c_uint64, max_entries=4096)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_enter_clone")
|
||||
def hello(ctx: c_void_p) -> c_int64:
|
||||
process_id = pid()
|
||||
one = 1
|
||||
prev = hist().lookup(process_id)
|
||||
if prev:
|
||||
previous_value = prev + 1
|
||||
print(f"count: {previous_value} with {process_id}")
|
||||
hist().update(process_id, previous_value)
|
||||
return c_int64(0)
|
||||
else:
|
||||
hist().update(process_id, one)
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
b = BPF()
|
||||
b.load_and_attach()
|
||||
hist = BpfMap(b, hist)
|
||||
print("Recording")
|
||||
time.sleep(10)
|
||||
|
||||
counts = list(hist.values())
|
||||
|
||||
plt.hist(counts, bins=20)
|
||||
plt.xlabel("Clone calls per PID")
|
||||
plt.ylabel("Frequency")
|
||||
plt.title("Syscall clone counts")
|
||||
plt.show()
|
||||
@ -1,35 +0,0 @@
|
||||
from pythonbpf.decorators import bpf, map, section, bpfglobal
|
||||
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||
from pythonbpf.helpers import ktime
|
||||
from pythonbpf.maps import HashMap
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def last() -> HashMap:
|
||||
return HashMap(key=c_uint64, value=c_uint64, max_entries=1)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_enter_execve")
|
||||
def hello(ctx: c_void_p) -> c_int32:
|
||||
print("entered")
|
||||
print("multi constant support")
|
||||
return c_int32(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_exit_execve")
|
||||
def hello_again(ctx: c_void_p) -> c_int64:
|
||||
print("exited")
|
||||
key = 0
|
||||
tsp = last().lookup(key)
|
||||
print(tsp)
|
||||
ts = ktime()
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
@ -1,55 +0,0 @@
|
||||
from pythonbpf import bpf, map, section, bpfglobal, compile
|
||||
from pythonbpf.helpers import ktime, deref
|
||||
from pythonbpf.maps import HashMap
|
||||
|
||||
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def last() -> HashMap:
|
||||
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_enter_execve")
|
||||
def hello(ctx: c_void_p) -> c_int32:
|
||||
print("entered")
|
||||
print("multi constant support")
|
||||
return c_int32(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_exit_execve")
|
||||
def hello_again(ctx: c_void_p) -> c_int64:
|
||||
print("exited")
|
||||
key = 0
|
||||
delta = 0
|
||||
dddelta = 0
|
||||
tsp = last().lookup(key)
|
||||
if True:
|
||||
delta = ktime()
|
||||
ddelta = deref(delta)
|
||||
ttsp = deref(deref(tsp))
|
||||
dddelta = ddelta - ttsp
|
||||
if dddelta < 1000000000:
|
||||
print("execve called within last second")
|
||||
last().delete(key)
|
||||
ts = ktime()
|
||||
last().update(key, ts)
|
||||
|
||||
va = 8
|
||||
nm = 5 + va
|
||||
al = 6 & 3
|
||||
print(f"this is a variable {nm}")
|
||||
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
@ -1,15 +1,30 @@
|
||||
# This is what it is going to look like
|
||||
# pylint: disable-all# type: ignore
|
||||
from pythonbpf.decorators import tracepoint, syscalls, bpfglobal, bpf
|
||||
from ctypes import c_void_p, c_int32
|
||||
from pythonbpf import bpf, section, bpfglobal, BPF
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
# Instructions to how to run this program
|
||||
# 1. Install PythonBPF: pip install pythonbpf
|
||||
# 2. Run the program: sudo python examples/hello_world.py
|
||||
# 4. Start up any program and watch the output
|
||||
|
||||
|
||||
@bpf
|
||||
@tracepoint(syscalls.sys_clone)
|
||||
def trace_clone(ctx: c_void_p) -> c_int32:
|
||||
@section("tracepoint/syscalls/sys_enter_execve")
|
||||
def hello_world(ctx: c_void_p) -> c_int64:
|
||||
print("Hello, World!")
|
||||
return c_int32(0)
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
b = BPF()
|
||||
b.load_and_attach()
|
||||
if b.is_loaded() and b.is_attached():
|
||||
print("Successfully loaded and attached")
|
||||
else:
|
||||
print("Could not load successfully")
|
||||
|
||||
# Now cat /sys/kernel/debug/tracing/trace_pipe to see results of the execve syscall.
|
||||
|
||||
@ -2,7 +2,7 @@ from pythonbpf import bpf, map, struct, section, bpfglobal, compile
|
||||
from pythonbpf.helpers import ktime, pid
|
||||
from pythonbpf.maps import PerfEventArray
|
||||
|
||||
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||
from ctypes import c_void_p, c_int32, c_uint64
|
||||
|
||||
|
||||
@bpf
|
||||
@ -10,6 +10,7 @@ from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||
class data_t:
|
||||
pid: c_uint64
|
||||
ts: c_uint64
|
||||
comm: str(16)
|
||||
|
||||
|
||||
@bpf
|
||||
@ -23,10 +24,14 @@ def events() -> PerfEventArray:
|
||||
def hello(ctx: c_void_p) -> c_int32:
|
||||
dataobj = data_t()
|
||||
ts = ktime()
|
||||
process_id = pid()
|
||||
dataobj.pid = process_id
|
||||
dataobj.ts = ts
|
||||
print(f"clone called at {ts} by pid {process_id}")
|
||||
strobj = "hellohellohello"
|
||||
dataobj.pid = pid()
|
||||
dataobj.ts = ktime()
|
||||
# dataobj.comm = strobj
|
||||
print(
|
||||
f"clone called at {dataobj.ts} by pid {dataobj.pid}, comm {strobj} at time {ts}"
|
||||
)
|
||||
events.output(dataobj)
|
||||
return c_int32(0)
|
||||
|
||||
|
||||
@ -6,10 +6,11 @@ from ctypes import c_void_p, c_int64, c_uint64
|
||||
|
||||
# Instructions to how to run this program
|
||||
# 1. Install PythonBPF: pip install pythonbpf
|
||||
# 2. Run the program: python demo/pybpf2.py
|
||||
# 3. Run the program with sudo: sudo examples/check.sh run demo/pybpf2.o
|
||||
# 2. Run the program: python examples/sys_sync.py
|
||||
# 3. Run the program with sudo: sudo tools/check.sh run examples/sys_sync.o
|
||||
# 4. Start a Python repl and `import os` and then keep entering `os.sync()` to see reponses.
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def last() -> HashMap:
|
||||
@ -23,9 +24,9 @@ def do_trace(ctx: c_void_p) -> c_int64:
|
||||
tsp = last().lookup(key)
|
||||
if tsp:
|
||||
kt = ktime()
|
||||
delta = (kt - tsp)
|
||||
delta = kt - tsp
|
||||
if delta < 1000000000:
|
||||
time_ms = (delta // 1000000)
|
||||
time_ms = delta // 1000000
|
||||
print(f"sync called within last second, last {time_ms} ms ago")
|
||||
last().delete(key)
|
||||
else:
|
||||
@ -149,7 +149,7 @@ class FunctionFactoryStub:
|
||||
|
||||
# libraries['FIXME_STUB'] explanation
|
||||
# As you did not list (-l libraryname.so) a library that exports this function
|
||||
# This is a non-working stub instead.
|
||||
# This is a non-working stub instead.
|
||||
# You can either re-run clan2py with -l /path/to/library.so
|
||||
# Or manually fix this by comment the ctypes.CDLL loading
|
||||
_libraries = {}
|
||||
|
||||
@ -6,11 +6,12 @@ from ctypes import c_void_p, c_int64
|
||||
|
||||
# Instructions to how to run this program
|
||||
# 1. Install PythonBPF: pip install pythonbpf
|
||||
# 2. Run the program: python demo/pybpf1.py
|
||||
# 3. Run the program with sudo: sudo examples/check.sh run demo/pybpf1.o
|
||||
# 4. Attach object file to any network device with something like ./check.sh xdp ../demo/pybpf1.o tailscale0
|
||||
# 2. Run the program: python examples/xdp_pass.py
|
||||
# 3. Run the program with sudo: sudo tools/check.sh run examples/xdp_pass.o
|
||||
# 4. Attach object file to any network device with something like ./check.sh xdp examples/xdp_pass.o tailscale0
|
||||
# 5. send traffic through the device and observe effects
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def count() -> HashMap:
|
||||
@ -33,9 +34,11 @@ def hello_world(ctx: c_void_p) -> c_int64:
|
||||
|
||||
return XDP_PASS
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pythonbpf"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = "Reduced Python frontend for eBPF"
|
||||
authors = [
|
||||
{ name = "r41k0u", email="pragyanshchaturvedi18@gmail.com" },
|
||||
@ -16,7 +16,8 @@ requires-python = ">=3.8"
|
||||
|
||||
dependencies = [
|
||||
"llvmlite",
|
||||
"astpretty"
|
||||
"astpretty",
|
||||
"pylibbpf"
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@ -1,2 +1,13 @@
|
||||
from .decorators import bpf, map, section, bpfglobal, struct
|
||||
from .codegen import compile_to_ir, compile
|
||||
from .codegen import compile_to_ir, compile, BPF
|
||||
|
||||
__all__ = [
|
||||
"bpf",
|
||||
"map",
|
||||
"section",
|
||||
"bpfglobal",
|
||||
"struct",
|
||||
"compile_to_ir",
|
||||
"compile",
|
||||
"BPF",
|
||||
]
|
||||
|
||||
@ -3,7 +3,7 @@ from llvmlite import ir
|
||||
|
||||
|
||||
def recursive_dereferencer(var, builder):
|
||||
""" dereference until primitive type comes out"""
|
||||
"""dereference until primitive type comes out"""
|
||||
if var.type == ir.PointerType(ir.PointerType(ir.IntType(64))):
|
||||
a = builder.load(var)
|
||||
return recursive_dereferencer(a, builder)
|
||||
@ -15,6 +15,7 @@ def recursive_dereferencer(var, builder):
|
||||
else:
|
||||
raise TypeError(f"Unsupported type for dereferencing: {var.type}")
|
||||
|
||||
|
||||
def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab, func):
|
||||
print(module)
|
||||
left = rval.left
|
||||
@ -24,7 +25,7 @@ def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab
|
||||
# Handle left operand
|
||||
if isinstance(left, ast.Name):
|
||||
if left.id in local_sym_tab:
|
||||
left = recursive_dereferencer(local_sym_tab[left.id], builder)
|
||||
left = recursive_dereferencer(local_sym_tab[left.id][0], builder)
|
||||
else:
|
||||
raise SyntaxError(f"Undefined variable: {left.id}")
|
||||
elif isinstance(left, ast.Constant):
|
||||
@ -34,7 +35,7 @@ def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab
|
||||
|
||||
if isinstance(right, ast.Name):
|
||||
if right.id in local_sym_tab:
|
||||
right = recursive_dereferencer(local_sym_tab[right.id], builder)
|
||||
right = recursive_dereferencer(local_sym_tab[right.id][0], builder)
|
||||
else:
|
||||
raise SyntaxError(f"Undefined variable: {right.id}")
|
||||
elif isinstance(right, ast.Constant):
|
||||
@ -45,37 +46,26 @@ def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab
|
||||
print(f"left is {left}, right is {right}, op is {op}")
|
||||
|
||||
if isinstance(op, ast.Add):
|
||||
builder.store(builder.add(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.add(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.Sub):
|
||||
builder.store(builder.sub(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.sub(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.Mult):
|
||||
builder.store(builder.mul(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.mul(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.Div):
|
||||
builder.store(builder.sdiv(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.sdiv(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.Mod):
|
||||
builder.store(builder.srem(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.srem(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.LShift):
|
||||
builder.store(builder.shl(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.shl(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.RShift):
|
||||
builder.store(builder.lshr(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.lshr(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.BitOr):
|
||||
builder.store(builder.or_(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.or_(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.BitXor):
|
||||
builder.store(builder.xor(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.xor(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.BitAnd):
|
||||
builder.store(builder.and_(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.and_(left, right), local_sym_tab[var_name][0])
|
||||
elif isinstance(op, ast.FloorDiv):
|
||||
builder.store(builder.udiv(left, right),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(builder.udiv(left, right), local_sym_tab[var_name][0])
|
||||
else:
|
||||
raise SyntaxError("Unsupported binary operation")
|
||||
|
||||
@ -3,7 +3,16 @@ from llvmlite import ir
|
||||
from .expr_pass import eval_expr
|
||||
|
||||
|
||||
def bpf_ktime_get_ns_emitter(call, map_ptr, module, builder, func, local_sym_tab=None):
|
||||
def bpf_ktime_get_ns_emitter(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
"""
|
||||
Emit LLVM IR for bpf_ktime_get_ns helper function call.
|
||||
"""
|
||||
@ -13,24 +22,35 @@ def bpf_ktime_get_ns_emitter(call, map_ptr, module, builder, func, local_sym_tab
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
|
||||
result = builder.call(fn_ptr, [], tail=False)
|
||||
return result
|
||||
return result, ir.IntType(64)
|
||||
|
||||
|
||||
def bpf_map_lookup_elem_emitter(call, map_ptr, module, builder, local_sym_tab=None):
|
||||
def bpf_map_lookup_elem_emitter(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
"""
|
||||
Emit LLVM IR for bpf_map_lookup_elem helper function call.
|
||||
"""
|
||||
if call.args and len(call.args) != 1:
|
||||
raise ValueError("Map lookup expects exactly one argument, got "
|
||||
f"{len(call.args)}")
|
||||
raise ValueError(
|
||||
"Map lookup expects exactly one argument, got " f"{len(call.args)}"
|
||||
)
|
||||
key_arg = call.args[0]
|
||||
if isinstance(key_arg, ast.Name):
|
||||
key_name = key_arg.id
|
||||
if local_sym_tab and key_name in local_sym_tab:
|
||||
key_ptr = local_sym_tab[key_name]
|
||||
key_ptr = local_sym_tab[key_name][0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Key variable {key_name} not found in local symbol table.")
|
||||
f"Key variable {key_name} not found in local symbol table."
|
||||
)
|
||||
elif isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, int):
|
||||
# handle constant integer keys
|
||||
key_val = key_arg.value
|
||||
@ -40,7 +60,8 @@ def bpf_map_lookup_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
builder.store(ir.Constant(key_type, key_val), key_ptr)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only simple variable names are supported as keys in map lookup.")
|
||||
"Only simple variable names are supported as keys in map lookup."
|
||||
)
|
||||
|
||||
if key_ptr is None:
|
||||
raise ValueError("Key pointer is None.")
|
||||
@ -50,7 +71,7 @@ def bpf_map_lookup_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
fn_type = ir.FunctionType(
|
||||
ir.PointerType(), # Return type: void*
|
||||
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
|
||||
var_arg=False
|
||||
var_arg=False,
|
||||
)
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
|
||||
@ -60,10 +81,19 @@ def bpf_map_lookup_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
|
||||
result = builder.call(fn_ptr, [map_void_ptr, key_ptr], tail=False)
|
||||
|
||||
return result
|
||||
return result, ir.PointerType()
|
||||
|
||||
|
||||
def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None):
|
||||
def bpf_printk_emitter(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
if not hasattr(func, "_fmt_counter"):
|
||||
func._fmt_counter = 0
|
||||
|
||||
@ -75,6 +105,7 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None)
|
||||
exprs = []
|
||||
|
||||
for value in call.args[0].values:
|
||||
print("Value in f-string:", ast.dump(value))
|
||||
if isinstance(value, ast.Constant):
|
||||
if isinstance(value.value, str):
|
||||
fmt_parts.append(value.value)
|
||||
@ -83,32 +114,90 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None)
|
||||
exprs.append(ir.Constant(ir.IntType(64), value.value))
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only string and integer constants are supported in f-string.")
|
||||
"Only string and integer constants are supported in f-string."
|
||||
)
|
||||
elif isinstance(value, ast.FormattedValue):
|
||||
# Assume int for now
|
||||
fmt_parts.append("%lld")
|
||||
print("Formatted value:", ast.dump(value))
|
||||
# TODO: Dirty handling here, only checks for int or str
|
||||
if isinstance(value.value, ast.Name):
|
||||
exprs.append(value.value)
|
||||
if local_sym_tab and value.value.id in local_sym_tab:
|
||||
var_ptr, var_type = local_sym_tab[value.value.id]
|
||||
if isinstance(var_type, ir.IntType):
|
||||
fmt_parts.append("%lld")
|
||||
exprs.append(value.value)
|
||||
elif var_type == ir.PointerType(ir.IntType(8)):
|
||||
# Case with string
|
||||
fmt_parts.append("%s")
|
||||
exprs.append(value.value)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only integer and pointer types are supported in formatted values."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Variable {value.value.id} not found in local symbol table."
|
||||
)
|
||||
elif isinstance(value.value, ast.Attribute):
|
||||
# object field access from struct
|
||||
if (
|
||||
isinstance(value.value.value, ast.Name)
|
||||
and local_sym_tab
|
||||
and value.value.value.id in local_sym_tab
|
||||
):
|
||||
var_name = value.value.value.id
|
||||
field_name = value.value.attr
|
||||
if local_var_metadata and var_name in local_var_metadata:
|
||||
var_type = local_var_metadata[var_name]
|
||||
if var_type in struct_sym_tab:
|
||||
struct_info = struct_sym_tab[var_type]
|
||||
if field_name in struct_info.fields:
|
||||
field_type = struct_info.field_type(field_name)
|
||||
if isinstance(field_type, ir.IntType):
|
||||
fmt_parts.append("%lld")
|
||||
exprs.append(value.value)
|
||||
elif field_type == ir.PointerType(ir.IntType(8)):
|
||||
fmt_parts.append("%s")
|
||||
exprs.append(value.value)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only integer and pointer types are supported in formatted values."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Field {field_name} not found in struct {var_type}."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Struct type {var_type} for variable {var_name} not found in struct symbol table."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Metadata for variable {var_name} not found in local variable metadata."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Variable {value.value.value.id} not found in local symbol table."
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only simple variable names are supported in formatted values.")
|
||||
"Only simple variable names are supported in formatted values."
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Unsupported value type in f-string.")
|
||||
raise NotImplementedError("Unsupported value type in f-string.")
|
||||
|
||||
fmt_str = "".join(fmt_parts) + "\n" + "\0"
|
||||
fmt_name = f"{func.name}____fmt{func._fmt_counter}"
|
||||
func._fmt_counter += 1
|
||||
|
||||
fmt_gvar = ir.GlobalVariable(
|
||||
module, ir.ArrayType(ir.IntType(8), len(fmt_str)), name=fmt_name)
|
||||
module, ir.ArrayType(ir.IntType(8), len(fmt_str)), name=fmt_name
|
||||
)
|
||||
fmt_gvar.global_constant = True
|
||||
fmt_gvar.initializer = ir.Constant(
|
||||
ir.ArrayType(ir.IntType(8), len(fmt_str)),
|
||||
bytearray(fmt_str.encode("utf8"))
|
||||
fmt_gvar.initializer = ir.Constant( # type: ignore
|
||||
ir.ArrayType(ir.IntType(8), len(fmt_str)), bytearray(fmt_str.encode("utf8"))
|
||||
)
|
||||
fmt_gvar.linkage = "internal"
|
||||
fmt_gvar.align = 1
|
||||
fmt_gvar.align = 1 # type: ignore
|
||||
|
||||
fmt_ptr = builder.bitcast(fmt_gvar, ir.PointerType())
|
||||
|
||||
@ -117,10 +206,21 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None)
|
||||
# Only 3 args supported in bpf_printk
|
||||
if len(exprs) > 3:
|
||||
print(
|
||||
"Warning: bpf_printk supports up to 3 arguments, extra arguments will be ignored.")
|
||||
"Warning: bpf_printk supports up to 3 arguments, extra arguments will be ignored."
|
||||
)
|
||||
|
||||
for expr in exprs[:3]:
|
||||
val = eval_expr(func, module, builder, expr, local_sym_tab, None)
|
||||
print(f"{ast.dump(expr)}")
|
||||
val, _ = eval_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
expr,
|
||||
local_sym_tab,
|
||||
None,
|
||||
struct_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
if val:
|
||||
if isinstance(val.type, ir.PointerType):
|
||||
val = builder.ptrtoint(val, ir.IntType(64))
|
||||
@ -129,16 +229,18 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None)
|
||||
val = builder.sext(val, ir.IntType(64))
|
||||
else:
|
||||
print(
|
||||
"Warning: Only integer and pointer types are supported in bpf_printk arguments. Others will be converted to 0.")
|
||||
"Warning: Only integer and pointer types are supported in bpf_printk arguments. Others will be converted to 0."
|
||||
)
|
||||
val = ir.Constant(ir.IntType(64), 0)
|
||||
args.append(val)
|
||||
else:
|
||||
print(
|
||||
"Warning: Failed to evaluate expression for bpf_printk argument. It will be converted to 0.")
|
||||
"Warning: Failed to evaluate expression for bpf_printk argument. It will be converted to 0."
|
||||
)
|
||||
args.append(ir.Constant(ir.IntType(64), 0))
|
||||
|
||||
fn_type = ir.FunctionType(ir.IntType(
|
||||
64), [ir.PointerType(), ir.IntType(32)], var_arg=True)
|
||||
fn_type = ir.FunctionType(
|
||||
ir.IntType(64), [ir.PointerType(), ir.IntType(32)], var_arg=True
|
||||
)
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
fn_addr = ir.Constant(ir.IntType(64), 6)
|
||||
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
|
||||
@ -151,35 +253,50 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None)
|
||||
func._fmt_counter += 1
|
||||
|
||||
fmt_gvar = ir.GlobalVariable(
|
||||
module, ir.ArrayType(ir.IntType(8), len(fmt_str)), name=fmt_name)
|
||||
module, ir.ArrayType(ir.IntType(8), len(fmt_str)), name=fmt_name
|
||||
)
|
||||
fmt_gvar.global_constant = True
|
||||
fmt_gvar.initializer = ir.Constant( # type: ignore
|
||||
fmt_gvar.initializer = ir.Constant( # type: ignore
|
||||
ir.ArrayType(ir.IntType(8), len(fmt_str)),
|
||||
bytearray(fmt_str.encode("utf8"))
|
||||
bytearray(fmt_str.encode("utf8")),
|
||||
)
|
||||
fmt_gvar.linkage = "internal"
|
||||
fmt_gvar.align = 1 # type: ignore
|
||||
fmt_gvar.align = 1 # type: ignore
|
||||
|
||||
fmt_ptr = builder.bitcast(fmt_gvar, ir.PointerType())
|
||||
|
||||
fn_type = ir.FunctionType(ir.IntType(
|
||||
64), [ir.PointerType(), ir.IntType(32)], var_arg=True)
|
||||
fn_type = ir.FunctionType(
|
||||
ir.IntType(64), [ir.PointerType(), ir.IntType(32)], var_arg=True
|
||||
)
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
fn_addr = ir.Constant(ir.IntType(64), 6)
|
||||
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
|
||||
|
||||
builder.call(fn_ptr, [fmt_ptr, ir.Constant(
|
||||
ir.IntType(32), len(fmt_str))], tail=True)
|
||||
builder.call(
|
||||
fn_ptr, [fmt_ptr, ir.Constant(ir.IntType(32), len(fmt_str))], tail=True
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=None):
|
||||
def bpf_map_update_elem_emitter(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
"""
|
||||
Emit LLVM IR for bpf_map_update_elem helper function call.
|
||||
Expected call signature: map.update(key, value, flags=0)
|
||||
"""
|
||||
if not call.args or len(call.args) < 2 or len(call.args) > 3:
|
||||
raise ValueError("Map update expects 2 or 3 arguments (key, value, flags), got "
|
||||
f"{len(call.args)}")
|
||||
raise ValueError(
|
||||
"Map update expects 2 or 3 arguments (key, value, flags), got "
|
||||
f"{len(call.args)}"
|
||||
)
|
||||
|
||||
key_arg = call.args[0]
|
||||
value_arg = call.args[1]
|
||||
@ -189,10 +306,11 @@ def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
if isinstance(key_arg, ast.Name):
|
||||
key_name = key_arg.id
|
||||
if local_sym_tab and key_name in local_sym_tab:
|
||||
key_ptr = local_sym_tab[key_name]
|
||||
key_ptr = local_sym_tab[key_name][0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Key variable {key_name} not found in local symbol table.")
|
||||
f"Key variable {key_name} not found in local symbol table."
|
||||
)
|
||||
elif isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, int):
|
||||
# Handle constant integer keys
|
||||
key_val = key_arg.value
|
||||
@ -202,16 +320,18 @@ def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
builder.store(ir.Constant(key_type, key_val), key_ptr)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only simple variable names and integer constants are supported as keys in map update.")
|
||||
"Only simple variable names and integer constants are supported as keys in map update."
|
||||
)
|
||||
|
||||
# Handle value
|
||||
if isinstance(value_arg, ast.Name):
|
||||
value_name = value_arg.id
|
||||
if local_sym_tab and value_name in local_sym_tab:
|
||||
value_ptr = local_sym_tab[value_name]
|
||||
value_ptr = local_sym_tab[value_name][0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Value variable {value_name} not found in local symbol table.")
|
||||
f"Value variable {value_name} not found in local symbol table."
|
||||
)
|
||||
elif isinstance(value_arg, ast.Constant) and isinstance(value_arg.value, int):
|
||||
# Handle constant integers
|
||||
value_val = value_arg.value
|
||||
@ -221,7 +341,8 @@ def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
builder.store(ir.Constant(value_type, value_val), value_ptr)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only simple variable names and integer constants are supported as values in map update.")
|
||||
"Only simple variable names and integer constants are supported as values in map update."
|
||||
)
|
||||
|
||||
# Handle flags argument (defaults to 0)
|
||||
if flags_arg is not None:
|
||||
@ -231,14 +352,16 @@ def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
flags_name = flags_arg.id
|
||||
if local_sym_tab and flags_name in local_sym_tab:
|
||||
# Assume it's a stored integer value, load it
|
||||
flags_ptr = local_sym_tab[flags_name]
|
||||
flags_ptr = local_sym_tab[flags_name][0]
|
||||
flags_val = builder.load(flags_ptr)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Flags variable {flags_name} not found in local symbol table.")
|
||||
f"Flags variable {flags_name} not found in local symbol table."
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only integer constants and simple variable names are supported as flags in map update.")
|
||||
"Only integer constants and simple variable names are supported as flags in map update."
|
||||
)
|
||||
else:
|
||||
flags_val = 0
|
||||
|
||||
@ -249,7 +372,7 @@ def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
fn_type = ir.FunctionType(
|
||||
ir.IntType(64),
|
||||
[ir.PointerType(), ir.PointerType(), ir.PointerType(), ir.IntType(64)],
|
||||
var_arg=False
|
||||
var_arg=False,
|
||||
)
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
|
||||
@ -263,20 +386,31 @@ def bpf_map_update_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
flags_const = flags_val
|
||||
|
||||
result = builder.call(
|
||||
fn_ptr, [map_void_ptr, key_ptr, value_ptr, flags_const], tail=False)
|
||||
fn_ptr, [map_void_ptr, key_ptr, value_ptr, flags_const], tail=False
|
||||
)
|
||||
|
||||
return result
|
||||
return result, None
|
||||
|
||||
|
||||
def bpf_map_delete_elem_emitter(call, map_ptr, module, builder, local_sym_tab=None):
|
||||
def bpf_map_delete_elem_emitter(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
"""
|
||||
Emit LLVM IR for bpf_map_delete_elem helper function call.
|
||||
Expected call signature: map.delete(key)
|
||||
"""
|
||||
# Check for correct number of arguments
|
||||
if not call.args or len(call.args) != 1:
|
||||
raise ValueError("Map delete expects exactly 1 argument (key), got "
|
||||
f"{len(call.args)}")
|
||||
raise ValueError(
|
||||
"Map delete expects exactly 1 argument (key), got " f"{len(call.args)}"
|
||||
)
|
||||
|
||||
key_arg = call.args[0]
|
||||
|
||||
@ -284,10 +418,11 @@ def bpf_map_delete_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
if isinstance(key_arg, ast.Name):
|
||||
key_name = key_arg.id
|
||||
if local_sym_tab and key_name in local_sym_tab:
|
||||
key_ptr = local_sym_tab[key_name]
|
||||
key_ptr = local_sym_tab[key_name][0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Key variable {key_name} not found in local symbol table.")
|
||||
f"Key variable {key_name} not found in local symbol table."
|
||||
)
|
||||
elif isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, int):
|
||||
# Handle constant integer keys
|
||||
key_val = key_arg.value
|
||||
@ -297,7 +432,8 @@ def bpf_map_delete_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
builder.store(ir.Constant(key_type, key_val), key_ptr)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only simple variable names and integer constants are supported as keys in map delete.")
|
||||
"Only simple variable names and integer constants are supported as keys in map delete."
|
||||
)
|
||||
|
||||
if key_ptr is None:
|
||||
raise ValueError("Key pointer is None.")
|
||||
@ -309,7 +445,7 @@ def bpf_map_delete_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
fn_type = ir.FunctionType(
|
||||
ir.IntType(64), # Return type: int64 (status code)
|
||||
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
|
||||
var_arg=False
|
||||
var_arg=False,
|
||||
)
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
|
||||
@ -320,10 +456,19 @@ def bpf_map_delete_elem_emitter(call, map_ptr, module, builder, local_sym_tab=No
|
||||
# Call the helper function
|
||||
result = builder.call(fn_ptr, [map_void_ptr, key_ptr], tail=False)
|
||||
|
||||
return result
|
||||
return result, None
|
||||
|
||||
|
||||
def bpf_get_current_pid_tgid_emitter(call, map_ptr, module, builder, func, local_sym_tab=None):
|
||||
def bpf_get_current_pid_tgid_emitter(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
"""
|
||||
Emit LLVM IR for bpf_get_current_pid_tgid helper function call.
|
||||
"""
|
||||
@ -337,7 +482,82 @@ def bpf_get_current_pid_tgid_emitter(call, map_ptr, module, builder, func, local
|
||||
# Extract the lower 32 bits (PID) using bitwise AND with 0xFFFFFFFF
|
||||
mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
|
||||
pid = builder.and_(result, mask)
|
||||
return pid
|
||||
return pid, ir.IntType(64)
|
||||
|
||||
|
||||
def bpf_perf_event_output_handler(
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
if len(call.args) != 1:
|
||||
raise ValueError(
|
||||
"Perf event output expects exactly one argument (data), got "
|
||||
f"{len(call.args)}"
|
||||
)
|
||||
data_arg = call.args[0]
|
||||
ctx_ptr = func.args[0] # First argument to the function is ctx
|
||||
|
||||
if isinstance(data_arg, ast.Name):
|
||||
data_name = data_arg.id
|
||||
if local_sym_tab and data_name in local_sym_tab:
|
||||
data_ptr = local_sym_tab[data_name][0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Data variable {data_name} not found in local symbol table."
|
||||
)
|
||||
# Check is data_name is a struct
|
||||
if local_var_metadata and data_name in local_var_metadata:
|
||||
data_type = local_var_metadata[data_name]
|
||||
if data_type in struct_sym_tab:
|
||||
struct_info = struct_sym_tab[data_type]
|
||||
size_val = ir.Constant(ir.IntType(64), struct_info.size)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Struct type {data_type} for variable {data_name} not found in struct symbol table."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Metadata for variable {data_name} not found in local variable metadata."
|
||||
)
|
||||
|
||||
# BPF_F_CURRENT_CPU is -1 in 32 bit
|
||||
flags_val = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
|
||||
|
||||
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
|
||||
data_void_ptr = builder.bitcast(data_ptr, ir.PointerType())
|
||||
fn_type = ir.FunctionType(
|
||||
ir.IntType(64),
|
||||
[
|
||||
ir.PointerType(ir.IntType(8)),
|
||||
ir.PointerType(),
|
||||
ir.IntType(64),
|
||||
ir.PointerType(),
|
||||
ir.IntType(64),
|
||||
],
|
||||
var_arg=False,
|
||||
)
|
||||
fn_ptr_type = ir.PointerType(fn_type)
|
||||
|
||||
# helper id
|
||||
fn_addr = ir.Constant(ir.IntType(64), 25)
|
||||
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
|
||||
|
||||
result = builder.call(
|
||||
fn_ptr,
|
||||
[ctx_ptr, map_void_ptr, flags_val, data_void_ptr, size_val],
|
||||
tail=False,
|
||||
)
|
||||
return result, None
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Only simple object names are supported as data in perf event output."
|
||||
)
|
||||
|
||||
|
||||
helper_func_list = {
|
||||
@ -347,34 +567,88 @@ helper_func_list = {
|
||||
"update": bpf_map_update_elem_emitter,
|
||||
"delete": bpf_map_delete_elem_emitter,
|
||||
"pid": bpf_get_current_pid_tgid_emitter,
|
||||
"output": bpf_perf_event_output_handler,
|
||||
}
|
||||
|
||||
|
||||
def handle_helper_call(call, module, builder, func, local_sym_tab=None, map_sym_tab=None):
|
||||
def handle_helper_call(
|
||||
call,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab=None,
|
||||
map_sym_tab=None,
|
||||
struct_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
print(local_var_metadata)
|
||||
if isinstance(call.func, ast.Name):
|
||||
func_name = call.func.id
|
||||
if func_name in helper_func_list:
|
||||
# it is not a map method call
|
||||
return helper_func_list[func_name](call, None, module, builder, func, local_sym_tab)
|
||||
return helper_func_list[func_name](
|
||||
call,
|
||||
None,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
struct_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Function {func_name} is not implemented as a helper function.")
|
||||
f"Function {func_name} is not implemented as a helper function."
|
||||
)
|
||||
elif isinstance(call.func, ast.Attribute):
|
||||
# likely a map method call
|
||||
if isinstance(call.func.value, ast.Call) and isinstance(call.func.value.func, ast.Name):
|
||||
if isinstance(call.func.value, ast.Call) and isinstance(
|
||||
call.func.value.func, ast.Name
|
||||
):
|
||||
map_name = call.func.value.func.id
|
||||
method_name = call.func.attr
|
||||
if map_sym_tab and map_name in map_sym_tab:
|
||||
map_ptr = map_sym_tab[map_name]
|
||||
if method_name in helper_func_list:
|
||||
print(local_var_metadata)
|
||||
return helper_func_list[method_name](
|
||||
call, map_ptr, module, builder, local_sym_tab)
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
struct_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Map method {method_name} is not implemented as a helper function.")
|
||||
f"Map method {method_name} is not implemented as a helper function."
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Map variable {map_name} not found in symbol tables.")
|
||||
raise ValueError(f"Map variable {map_name} not found in symbol tables.")
|
||||
elif isinstance(call.func.value, ast.Name):
|
||||
obj_name = call.func.value.id
|
||||
method_name = call.func.attr
|
||||
if map_sym_tab and obj_name in map_sym_tab:
|
||||
map_ptr = map_sym_tab[obj_name]
|
||||
if method_name in helper_func_list:
|
||||
return helper_func_list[method_name](
|
||||
call,
|
||||
map_ptr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
struct_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Map method {method_name} is not implemented as a helper function."
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Map variable {obj_name} not found in symbol tables.")
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Attribute not supported for map method calls.")
|
||||
raise NotImplementedError("Attribute not supported for map method calls.")
|
||||
return None
|
||||
|
||||
@ -2,13 +2,18 @@ import ast
|
||||
from llvmlite import ir
|
||||
from .license_pass import license_processing
|
||||
from .functions_pass import func_proc
|
||||
from .maps_pass import maps_proc
|
||||
from .structs_pass import structs_proc
|
||||
from .maps import maps_proc
|
||||
from .structs import structs_proc
|
||||
from .globals_pass import globals_processing
|
||||
from .debuginfo import DW_LANG_C11, DwarfBehaviorEnum
|
||||
import os
|
||||
import subprocess
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
from pylibbpf import BpfProgram
|
||||
import tempfile
|
||||
|
||||
VERSION = "v0.1.3"
|
||||
|
||||
|
||||
def find_bpf_chunks(tree):
|
||||
@ -47,60 +52,85 @@ def compile_to_ir(filename: str, output: str):
|
||||
module.data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
|
||||
module.triple = "bpf"
|
||||
|
||||
if not hasattr(module, '_debug_compile_unit'):
|
||||
module._file_metadata = module.add_debug_info("DIFile", { # type: ignore
|
||||
"filename": filename,
|
||||
"directory": os.path.dirname(filename)
|
||||
})
|
||||
if not hasattr(module, "_debug_compile_unit"):
|
||||
module._file_metadata = module.add_debug_info(
|
||||
"DIFile",
|
||||
{ # type: ignore
|
||||
"filename": filename,
|
||||
"directory": os.path.dirname(filename),
|
||||
},
|
||||
)
|
||||
|
||||
module._debug_compile_unit = module.add_debug_info("DICompileUnit", { # type: ignore
|
||||
"language": 29, # DW_LANG_C11
|
||||
"file": module._file_metadata, # type: ignore
|
||||
"producer": "PythonBPF DSL Compiler",
|
||||
"isOptimized": True,
|
||||
"runtimeVersion": 0,
|
||||
"emissionKind": 1,
|
||||
"splitDebugInlining": False,
|
||||
"nameTableKind": 0
|
||||
}, is_distinct=True)
|
||||
module._debug_compile_unit = module.add_debug_info(
|
||||
"DICompileUnit",
|
||||
{ # type: ignore
|
||||
"language": DW_LANG_C11,
|
||||
"file": module._file_metadata, # type: ignore
|
||||
"producer": f"PythonBPF {VERSION}",
|
||||
"isOptimized": True, # TODO: This is probably not true
|
||||
# TODO: add a global field here that keeps track of all the globals. Works without it, but I think it might
|
||||
# be required for kprobes.
|
||||
"runtimeVersion": 0,
|
||||
"emissionKind": 1,
|
||||
"splitDebugInlining": False,
|
||||
"nameTableKind": 0,
|
||||
},
|
||||
is_distinct=True,
|
||||
)
|
||||
|
||||
module.add_named_metadata(
|
||||
"llvm.dbg.cu", module._debug_compile_unit) # type: ignore
|
||||
module.add_named_metadata("llvm.dbg.cu", module._debug_compile_unit) # type: ignore
|
||||
|
||||
processor(source, filename, module)
|
||||
|
||||
wchar_size = module.add_metadata([ir.Constant(ir.IntType(32), 1),
|
||||
"wchar_size",
|
||||
ir.Constant(ir.IntType(32), 4)])
|
||||
frame_pointer = module.add_metadata([ir.Constant(ir.IntType(32), 7),
|
||||
"frame-pointer",
|
||||
ir.Constant(ir.IntType(32), 2)])
|
||||
wchar_size = module.add_metadata(
|
||||
[
|
||||
DwarfBehaviorEnum.ERROR_IF_MISMATCH,
|
||||
"wchar_size",
|
||||
ir.Constant(ir.IntType(32), 4),
|
||||
]
|
||||
)
|
||||
frame_pointer = module.add_metadata(
|
||||
[
|
||||
DwarfBehaviorEnum.OVERRIDE_USE_LARGEST,
|
||||
"frame-pointer",
|
||||
ir.Constant(ir.IntType(32), 2),
|
||||
]
|
||||
)
|
||||
# Add Debug Info Version (3 = DWARF v3, which LLVM expects)
|
||||
debug_info_version = module.add_metadata([ir.Constant(ir.IntType(32), 2),
|
||||
"Debug Info Version",
|
||||
ir.Constant(ir.IntType(32), 3)])
|
||||
debug_info_version = module.add_metadata(
|
||||
[
|
||||
DwarfBehaviorEnum.WARNING_IF_MISMATCH,
|
||||
"Debug Info Version",
|
||||
ir.Constant(ir.IntType(32), 3),
|
||||
]
|
||||
)
|
||||
|
||||
# Add explicit DWARF version (4 is common, works with LLVM BPF backend)
|
||||
dwarf_version = module.add_metadata([ir.Constant(ir.IntType(32), 2),
|
||||
"Dwarf Version",
|
||||
ir.Constant(ir.IntType(32), 4)])
|
||||
# Add explicit DWARF version 5
|
||||
dwarf_version = module.add_metadata(
|
||||
[
|
||||
DwarfBehaviorEnum.OVERRIDE_USE_LARGEST,
|
||||
"Dwarf Version",
|
||||
ir.Constant(ir.IntType(32), 5),
|
||||
]
|
||||
)
|
||||
|
||||
module.add_named_metadata("llvm.module.flags", wchar_size)
|
||||
module.add_named_metadata("llvm.module.flags", frame_pointer)
|
||||
module.add_named_metadata("llvm.module.flags", debug_info_version)
|
||||
module.add_named_metadata("llvm.module.flags", dwarf_version)
|
||||
|
||||
module.add_named_metadata("llvm.ident", ["llvmlite PythonBPF v0.0.1"])
|
||||
module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"])
|
||||
|
||||
print(f"IR written to {output}")
|
||||
with open(output, "w") as f:
|
||||
f.write(f"source_filename = \"{filename}\"\n")
|
||||
f.write(f'source_filename = "{filename}"\n')
|
||||
f.write(str(module))
|
||||
f.write("\n")
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def compile():
|
||||
def compile() -> bool:
|
||||
# Look one level up the stack to the caller of this function
|
||||
caller_frame = inspect.stack()[1]
|
||||
caller_file = Path(caller_frame.filename).resolve()
|
||||
@ -108,11 +138,54 @@ def compile():
|
||||
ll_file = Path("/tmp") / caller_file.with_suffix(".ll").name
|
||||
o_file = caller_file.with_suffix(".o")
|
||||
|
||||
compile_to_ir(str(caller_file), str(ll_file))
|
||||
success = True
|
||||
success = compile_to_ir(str(caller_file), str(ll_file)) and success
|
||||
|
||||
subprocess.run([
|
||||
"llc", "-march=bpf", "-filetype=obj", "-O2",
|
||||
str(ll_file), "-o", str(o_file)
|
||||
], check=True)
|
||||
success = (
|
||||
subprocess.run(
|
||||
[
|
||||
"llc",
|
||||
"-march=bpf",
|
||||
"-filetype=obj",
|
||||
"-O2",
|
||||
str(ll_file),
|
||||
"-o",
|
||||
str(o_file),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
and success
|
||||
)
|
||||
|
||||
print(f"Object written to {o_file}, {ll_file} can be removed")
|
||||
print(f"Object written to {o_file}")
|
||||
return success
|
||||
|
||||
|
||||
def BPF() -> BpfProgram:
|
||||
caller_frame = inspect.stack()[1]
|
||||
src = inspect.getsource(caller_frame.frame)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w+", delete=True, suffix=".py"
|
||||
) as f, tempfile.NamedTemporaryFile(
|
||||
mode="w+", delete=True, suffix=".ll"
|
||||
) as inter, tempfile.NamedTemporaryFile(
|
||||
mode="w+", delete=False, suffix=".o"
|
||||
) as obj_file:
|
||||
f.write(src)
|
||||
f.flush()
|
||||
source = f.name
|
||||
compile_to_ir(source, str(inter.name))
|
||||
subprocess.run(
|
||||
[
|
||||
"llc",
|
||||
"-march=bpf",
|
||||
"-filetype=obj",
|
||||
"-O2",
|
||||
str(inter.name),
|
||||
"-o",
|
||||
str(obj_file.name),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
return BpfProgram(str(obj_file.name))
|
||||
|
||||
5
pythonbpf/debuginfo/__init__.py
Normal file
5
pythonbpf/debuginfo/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .dwarf_constants import * # noqa: F403
|
||||
from .dtypes import * # noqa: F403
|
||||
from .debug_info_generator import DebugInfoGenerator
|
||||
|
||||
__all__ = ["DebugInfoGenerator"]
|
||||
107
pythonbpf/debuginfo/debug_info_generator.py
Normal file
107
pythonbpf/debuginfo/debug_info_generator.py
Normal file
@ -0,0 +1,107 @@
|
||||
"""
|
||||
Debug information generation module for Python-BPF
|
||||
Provides utilities for generating DWARF/BTF debug information
|
||||
"""
|
||||
|
||||
from . import dwarf_constants as dc
|
||||
from typing import Any, List
|
||||
|
||||
|
||||
class DebugInfoGenerator:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self._type_cache = {} # Cache for common debug types
|
||||
|
||||
def get_basic_type(self, name: str, size: int, encoding: int) -> Any:
|
||||
"""Get or create a basic type with caching"""
|
||||
key = (name, size, encoding)
|
||||
if key not in self._type_cache:
|
||||
self._type_cache[key] = self.module.add_debug_info(
|
||||
"DIBasicType", {"name": name, "size": size, "encoding": encoding}
|
||||
)
|
||||
return self._type_cache[key]
|
||||
|
||||
def get_uint32_type(self) -> Any:
|
||||
"""Get debug info for unsigned 32-bit integer"""
|
||||
return self.get_basic_type("unsigned int", 32, dc.DW_ATE_unsigned)
|
||||
|
||||
def get_uint64_type(self) -> Any:
|
||||
"""Get debug info for unsigned 64-bit integer"""
|
||||
return self.get_basic_type("unsigned long long", 64, dc.DW_ATE_unsigned)
|
||||
|
||||
def create_pointer_type(self, base_type: Any, size: int = 64) -> Any:
|
||||
"""Create a pointer type to the given base type"""
|
||||
return self.module.add_debug_info(
|
||||
"DIDerivedType",
|
||||
{"tag": dc.DW_TAG_pointer_type, "baseType": base_type, "size": size},
|
||||
)
|
||||
|
||||
def create_array_type(self, base_type: Any, count: int) -> Any:
|
||||
"""Create an array type of the given base type with specified count"""
|
||||
subrange = self.module.add_debug_info("DISubrange", {"count": count})
|
||||
return self.module.add_debug_info(
|
||||
"DICompositeType",
|
||||
{
|
||||
"tag": dc.DW_TAG_array_type,
|
||||
"baseType": base_type,
|
||||
"size": self._compute_array_size(base_type, count),
|
||||
"elements": [subrange],
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _compute_array_size(base_type: Any, count: int) -> int:
|
||||
# Extract size from base_type if possible
|
||||
# For simplicity, assuming base_type has a size attribute
|
||||
return getattr(base_type, "size", 32) * count
|
||||
|
||||
def create_struct_member(self, name: str, base_type: Any, offset: int) -> Any:
|
||||
"""Create a struct member with the given name, type, and offset"""
|
||||
return self.module.add_debug_info(
|
||||
"DIDerivedType",
|
||||
{
|
||||
"tag": dc.DW_TAG_member,
|
||||
"name": name,
|
||||
"file": self.module._file_metadata,
|
||||
"baseType": base_type,
|
||||
"size": getattr(base_type, "size", 64),
|
||||
"offset": offset,
|
||||
},
|
||||
)
|
||||
|
||||
def create_struct_type(
|
||||
self, members: List[Any], size: int, is_distinct: bool
|
||||
) -> Any:
|
||||
"""Create a struct type with the given members and size"""
|
||||
return self.module.add_debug_info(
|
||||
"DICompositeType",
|
||||
{
|
||||
"tag": dc.DW_TAG_structure_type,
|
||||
"file": self.module._file_metadata,
|
||||
"size": size,
|
||||
"elements": members,
|
||||
},
|
||||
is_distinct=is_distinct,
|
||||
)
|
||||
|
||||
def create_global_var_debug_info(
|
||||
self, name: str, var_type: Any, is_local: bool = False
|
||||
) -> Any:
|
||||
"""Create debug info for a global variable"""
|
||||
global_var = self.module.add_debug_info(
|
||||
"DIGlobalVariable",
|
||||
{
|
||||
"name": name,
|
||||
"scope": self.module._debug_compile_unit,
|
||||
"file": self.module._file_metadata,
|
||||
"type": var_type,
|
||||
"isLocal": is_local,
|
||||
"isDefinition": True,
|
||||
},
|
||||
is_distinct=True,
|
||||
)
|
||||
|
||||
return self.module.add_debug_info(
|
||||
"DIGlobalVariableExpression",
|
||||
{"var": global_var, "expr": self.module.add_debug_info("DIExpression", {})},
|
||||
)
|
||||
7
pythonbpf/debuginfo/dtypes.py
Normal file
7
pythonbpf/debuginfo/dtypes.py
Normal file
@ -0,0 +1,7 @@
|
||||
import llvmlite.ir as ir
|
||||
|
||||
|
||||
class DwarfBehaviorEnum:
|
||||
ERROR_IF_MISMATCH = ir.Constant(ir.IntType(32), 1)
|
||||
WARNING_IF_MISMATCH = ir.Constant(ir.IntType(32), 2)
|
||||
OVERRIDE_USE_LARGEST = ir.Constant(ir.IntType(32), 7)
|
||||
@ -7,7 +7,7 @@ DW_UT_skeleton = 0x04
|
||||
DW_UT_split_compile = 0x05
|
||||
DW_UT_split_type = 0x06
|
||||
DW_UT_lo_user = 0x80
|
||||
DW_UT_hi_user = 0xff
|
||||
DW_UT_hi_user = 0xFF
|
||||
|
||||
DW_TAG_array_type = 0x01
|
||||
DW_TAG_class_type = 0x02
|
||||
@ -15,10 +15,10 @@ DW_TAG_entry_point = 0x03
|
||||
DW_TAG_enumeration_type = 0x04
|
||||
DW_TAG_formal_parameter = 0x05
|
||||
DW_TAG_imported_declaration = 0x08
|
||||
DW_TAG_label = 0x0a
|
||||
DW_TAG_lexical_block = 0x0b
|
||||
DW_TAG_member = 0x0d
|
||||
DW_TAG_pointer_type = 0x0f
|
||||
DW_TAG_label = 0x0A
|
||||
DW_TAG_lexical_block = 0x0B
|
||||
DW_TAG_member = 0x0D
|
||||
DW_TAG_pointer_type = 0x0F
|
||||
DW_TAG_reference_type = 0x10
|
||||
DW_TAG_compile_unit = 0x11
|
||||
DW_TAG_string_type = 0x12
|
||||
@ -28,12 +28,12 @@ DW_TAG_typedef = 0x16
|
||||
DW_TAG_union_type = 0x17
|
||||
DW_TAG_unspecified_parameters = 0x18
|
||||
DW_TAG_variant = 0x19
|
||||
DW_TAG_common_block = 0x1a
|
||||
DW_TAG_common_inclusion = 0x1b
|
||||
DW_TAG_inheritance = 0x1c
|
||||
DW_TAG_inlined_subroutine = 0x1d
|
||||
DW_TAG_module = 0x1e
|
||||
DW_TAG_ptr_to_member_type = 0x1f
|
||||
DW_TAG_common_block = 0x1A
|
||||
DW_TAG_common_inclusion = 0x1B
|
||||
DW_TAG_inheritance = 0x1C
|
||||
DW_TAG_inlined_subroutine = 0x1D
|
||||
DW_TAG_module = 0x1E
|
||||
DW_TAG_ptr_to_member_type = 0x1F
|
||||
DW_TAG_set_type = 0x20
|
||||
DW_TAG_subrange_type = 0x21
|
||||
DW_TAG_with_stmt = 0x22
|
||||
@ -44,12 +44,12 @@ DW_TAG_const_type = 0x26
|
||||
DW_TAG_constant = 0x27
|
||||
DW_TAG_enumerator = 0x28
|
||||
DW_TAG_file_type = 0x29
|
||||
DW_TAG_friend = 0x2a
|
||||
DW_TAG_namelist = 0x2b
|
||||
DW_TAG_namelist_item = 0x2c
|
||||
DW_TAG_packed_type = 0x2d
|
||||
DW_TAG_subprogram = 0x2e
|
||||
DW_TAG_template_type_parameter = 0x2f
|
||||
DW_TAG_friend = 0x2A
|
||||
DW_TAG_namelist = 0x2B
|
||||
DW_TAG_namelist_item = 0x2C
|
||||
DW_TAG_packed_type = 0x2D
|
||||
DW_TAG_subprogram = 0x2E
|
||||
DW_TAG_template_type_parameter = 0x2F
|
||||
DW_TAG_template_value_parameter = 0x30
|
||||
DW_TAG_thrown_type = 0x31
|
||||
DW_TAG_try_block = 0x32
|
||||
@ -60,11 +60,11 @@ DW_TAG_dwarf_procedure = 0x36
|
||||
DW_TAG_restrict_type = 0x37
|
||||
DW_TAG_interface_type = 0x38
|
||||
DW_TAG_namespace = 0x39
|
||||
DW_TAG_imported_module = 0x3a
|
||||
DW_TAG_unspecified_type = 0x3b
|
||||
DW_TAG_partial_unit = 0x3c
|
||||
DW_TAG_imported_unit = 0x3d
|
||||
DW_TAG_condition = 0x3f
|
||||
DW_TAG_imported_module = 0x3A
|
||||
DW_TAG_unspecified_type = 0x3B
|
||||
DW_TAG_partial_unit = 0x3C
|
||||
DW_TAG_imported_unit = 0x3D
|
||||
DW_TAG_condition = 0x3F
|
||||
DW_TAG_shared_type = 0x40
|
||||
DW_TAG_type_unit = 0x41
|
||||
DW_TAG_rvalue_reference_type = 0x42
|
||||
@ -75,8 +75,8 @@ DW_TAG_dynamic_type = 0x46
|
||||
DW_TAG_atomic_type = 0x47
|
||||
DW_TAG_call_site = 0x48
|
||||
DW_TAG_call_site_parameter = 0x49
|
||||
DW_TAG_skeleton_unit = 0x4a
|
||||
DW_TAG_immutable_type = 0x4b
|
||||
DW_TAG_skeleton_unit = 0x4A
|
||||
DW_TAG_immutable_type = 0x4B
|
||||
DW_TAG_lo_user = 0x4080
|
||||
DW_TAG_MIPS_loop = 0x4081
|
||||
DW_TAG_format_label = 0x4101
|
||||
@ -88,8 +88,8 @@ DW_TAG_GNU_template_template_param = 0x4106
|
||||
DW_TAG_GNU_template_parameter_pack = 0x4107
|
||||
DW_TAG_GNU_formal_parameter_pack = 0x4108
|
||||
DW_TAG_GNU_call_site = 0x4109
|
||||
DW_TAG_GNU_call_site_parameter = 0x410a
|
||||
DW_TAG_hi_user = 0xffff
|
||||
DW_TAG_GNU_call_site_parameter = 0x410A
|
||||
DW_TAG_hi_user = 0xFFFF
|
||||
|
||||
DW_CHILDREN_no = 0
|
||||
DW_CHILDREN_yes = 1
|
||||
@ -98,9 +98,9 @@ DW_AT_sibling = 0x01
|
||||
DW_AT_location = 0x02
|
||||
DW_AT_name = 0x03
|
||||
DW_AT_ordering = 0x09
|
||||
DW_AT_byte_size = 0x0b
|
||||
DW_AT_bit_offset = 0x0c
|
||||
DW_AT_bit_size = 0x0d
|
||||
DW_AT_byte_size = 0x0B
|
||||
DW_AT_bit_offset = 0x0C
|
||||
DW_AT_bit_size = 0x0D
|
||||
DW_AT_stmt_list = 0x10
|
||||
DW_AT_low_pc = 0x11
|
||||
DW_AT_high_pc = 0x12
|
||||
@ -110,20 +110,20 @@ DW_AT_discr_value = 0x16
|
||||
DW_AT_visibility = 0x17
|
||||
DW_AT_import = 0x18
|
||||
DW_AT_string_length = 0x19
|
||||
DW_AT_common_reference = 0x1a
|
||||
DW_AT_comp_dir = 0x1b
|
||||
DW_AT_const_value = 0x1c
|
||||
DW_AT_containing_type = 0x1d
|
||||
DW_AT_default_value = 0x1e
|
||||
DW_AT_common_reference = 0x1A
|
||||
DW_AT_comp_dir = 0x1B
|
||||
DW_AT_const_value = 0x1C
|
||||
DW_AT_containing_type = 0x1D
|
||||
DW_AT_default_value = 0x1E
|
||||
DW_AT_inline = 0x20
|
||||
DW_AT_is_optional = 0x21
|
||||
DW_AT_lower_bound = 0x22
|
||||
DW_AT_producer = 0x25
|
||||
DW_AT_prototyped = 0x27
|
||||
DW_AT_return_addr = 0x2a
|
||||
DW_AT_start_scope = 0x2c
|
||||
DW_AT_bit_stride = 0x2e
|
||||
DW_AT_upper_bound = 0x2f
|
||||
DW_AT_return_addr = 0x2A
|
||||
DW_AT_start_scope = 0x2C
|
||||
DW_AT_bit_stride = 0x2E
|
||||
DW_AT_upper_bound = 0x2F
|
||||
DW_AT_abstract_origin = 0x31
|
||||
DW_AT_accessibility = 0x32
|
||||
DW_AT_address_class = 0x33
|
||||
@ -133,12 +133,12 @@ DW_AT_calling_convention = 0x36
|
||||
DW_AT_count = 0x37
|
||||
DW_AT_data_member_location = 0x38
|
||||
DW_AT_decl_column = 0x39
|
||||
DW_AT_decl_file = 0x3a
|
||||
DW_AT_decl_line = 0x3b
|
||||
DW_AT_declaration = 0x3c
|
||||
DW_AT_discr_list = 0x3d
|
||||
DW_AT_encoding = 0x3e
|
||||
DW_AT_external = 0x3f
|
||||
DW_AT_decl_file = 0x3A
|
||||
DW_AT_decl_line = 0x3B
|
||||
DW_AT_declaration = 0x3C
|
||||
DW_AT_discr_list = 0x3D
|
||||
DW_AT_encoding = 0x3E
|
||||
DW_AT_external = 0x3F
|
||||
DW_AT_frame_base = 0x40
|
||||
DW_AT_friend = 0x41
|
||||
DW_AT_identifier_case = 0x42
|
||||
@ -149,12 +149,12 @@ DW_AT_segment = 0x46
|
||||
DW_AT_specification = 0x47
|
||||
DW_AT_static_link = 0x48
|
||||
DW_AT_type = 0x49
|
||||
DW_AT_use_location = 0x4a
|
||||
DW_AT_variable_parameter = 0x4b
|
||||
DW_AT_virtuality = 0x4c
|
||||
DW_AT_vtable_elem_location = 0x4d
|
||||
DW_AT_allocated = 0x4e
|
||||
DW_AT_associated = 0x4f
|
||||
DW_AT_use_location = 0x4A
|
||||
DW_AT_variable_parameter = 0x4B
|
||||
DW_AT_virtuality = 0x4C
|
||||
DW_AT_vtable_elem_location = 0x4D
|
||||
DW_AT_allocated = 0x4E
|
||||
DW_AT_associated = 0x4F
|
||||
DW_AT_data_location = 0x50
|
||||
DW_AT_byte_stride = 0x51
|
||||
DW_AT_entry_pc = 0x52
|
||||
@ -165,12 +165,12 @@ DW_AT_trampoline = 0x56
|
||||
DW_AT_call_column = 0x57
|
||||
DW_AT_call_file = 0x58
|
||||
DW_AT_call_line = 0x59
|
||||
DW_AT_description = 0x5a
|
||||
DW_AT_binary_scale = 0x5b
|
||||
DW_AT_decimal_scale = 0x5c
|
||||
DW_AT_small = 0x5d
|
||||
DW_AT_decimal_sign = 0x5e
|
||||
DW_AT_digit_count = 0x5f
|
||||
DW_AT_description = 0x5A
|
||||
DW_AT_binary_scale = 0x5B
|
||||
DW_AT_decimal_scale = 0x5C
|
||||
DW_AT_small = 0x5D
|
||||
DW_AT_decimal_sign = 0x5E
|
||||
DW_AT_digit_count = 0x5F
|
||||
DW_AT_picture_string = 0x60
|
||||
DW_AT_mutable = 0x61
|
||||
DW_AT_threads_scaled = 0x62
|
||||
@ -181,12 +181,12 @@ DW_AT_elemental = 0x66
|
||||
DW_AT_pure = 0x67
|
||||
DW_AT_recursive = 0x68
|
||||
DW_AT_signature = 0x69
|
||||
DW_AT_main_subprogram = 0x6a
|
||||
DW_AT_data_bit_offset = 0x6b
|
||||
DW_AT_const_expr = 0x6c
|
||||
DW_AT_enum_class = 0x6d
|
||||
DW_AT_linkage_name = 0x6e
|
||||
DW_AT_string_length_bit_size = 0x6f
|
||||
DW_AT_main_subprogram = 0x6A
|
||||
DW_AT_data_bit_offset = 0x6B
|
||||
DW_AT_const_expr = 0x6C
|
||||
DW_AT_enum_class = 0x6D
|
||||
DW_AT_linkage_name = 0x6E
|
||||
DW_AT_string_length_bit_size = 0x6F
|
||||
DW_AT_string_length_byte_size = 0x70
|
||||
DW_AT_rank = 0x71
|
||||
DW_AT_str_offsets_base = 0x72
|
||||
@ -196,12 +196,12 @@ DW_AT_dwo_name = 0x76
|
||||
DW_AT_reference = 0x77
|
||||
DW_AT_rvalue_reference = 0x78
|
||||
DW_AT_macros = 0x79
|
||||
DW_AT_call_all_calls = 0x7a
|
||||
DW_AT_call_all_source_calls = 0x7b
|
||||
DW_AT_call_all_tail_calls = 0x7c
|
||||
DW_AT_call_return_pc = 0x7d
|
||||
DW_AT_call_value = 0x7e
|
||||
DW_AT_call_origin = 0x7f
|
||||
DW_AT_call_all_calls = 0x7A
|
||||
DW_AT_call_all_source_calls = 0x7B
|
||||
DW_AT_call_all_tail_calls = 0x7C
|
||||
DW_AT_call_return_pc = 0x7D
|
||||
DW_AT_call_value = 0x7E
|
||||
DW_AT_call_origin = 0x7F
|
||||
DW_AT_call_parameter = 0x80
|
||||
DW_AT_call_pc = 0x81
|
||||
DW_AT_call_tail_call = 0x82
|
||||
@ -212,9 +212,9 @@ DW_AT_call_data_value = 0x86
|
||||
DW_AT_noreturn = 0x87
|
||||
DW_AT_alignment = 0x88
|
||||
DW_AT_export_symbols = 0x89
|
||||
DW_AT_deleted = 0x8a
|
||||
DW_AT_defaulted = 0x8b
|
||||
DW_AT_loclists_base = 0x8c
|
||||
DW_AT_deleted = 0x8A
|
||||
DW_AT_defaulted = 0x8B
|
||||
DW_AT_loclists_base = 0x8C
|
||||
DW_AT_lo_user = 0x2000
|
||||
DW_AT_MIPS_fde = 0x2001
|
||||
DW_AT_MIPS_loop_begin = 0x2002
|
||||
@ -225,12 +225,12 @@ DW_AT_MIPS_software_pipeline_depth = 0x2006
|
||||
DW_AT_MIPS_linkage_name = 0x2007
|
||||
DW_AT_MIPS_stride = 0x2008
|
||||
DW_AT_MIPS_abstract_name = 0x2009
|
||||
DW_AT_MIPS_clone_origin = 0x200a
|
||||
DW_AT_MIPS_has_inlines = 0x200b
|
||||
DW_AT_MIPS_stride_byte = 0x200c
|
||||
DW_AT_MIPS_stride_elem = 0x200d
|
||||
DW_AT_MIPS_ptr_dopetype = 0x200e
|
||||
DW_AT_MIPS_allocatable_dopetype = 0x200f
|
||||
DW_AT_MIPS_clone_origin = 0x200A
|
||||
DW_AT_MIPS_has_inlines = 0x200B
|
||||
DW_AT_MIPS_stride_byte = 0x200C
|
||||
DW_AT_MIPS_stride_elem = 0x200D
|
||||
DW_AT_MIPS_ptr_dopetype = 0x200E
|
||||
DW_AT_MIPS_allocatable_dopetype = 0x200F
|
||||
DW_AT_MIPS_assumed_shape_dopetype = 0x2010
|
||||
DW_AT_MIPS_assumed_size = 0x2011
|
||||
DW_AT_sf_names = 0x2101
|
||||
@ -242,12 +242,12 @@ DW_AT_body_end = 0x2106
|
||||
DW_AT_GNU_vector = 0x2107
|
||||
DW_AT_GNU_guarded_by = 0x2108
|
||||
DW_AT_GNU_pt_guarded_by = 0x2109
|
||||
DW_AT_GNU_guarded = 0x210a
|
||||
DW_AT_GNU_pt_guarded = 0x210b
|
||||
DW_AT_GNU_locks_excluded = 0x210c
|
||||
DW_AT_GNU_exclusive_locks_required = 0x210d
|
||||
DW_AT_GNU_shared_locks_required = 0x210e
|
||||
DW_AT_GNU_odr_signature = 0x210f
|
||||
DW_AT_GNU_guarded = 0x210A
|
||||
DW_AT_GNU_pt_guarded = 0x210B
|
||||
DW_AT_GNU_locks_excluded = 0x210C
|
||||
DW_AT_GNU_exclusive_locks_required = 0x210D
|
||||
DW_AT_GNU_shared_locks_required = 0x210E
|
||||
DW_AT_GNU_odr_signature = 0x210F
|
||||
DW_AT_GNU_template_name = 0x2110
|
||||
DW_AT_GNU_call_site_value = 0x2111
|
||||
DW_AT_GNU_call_site_data_value = 0x2112
|
||||
@ -260,7 +260,7 @@ DW_AT_GNU_all_source_call_sites = 0x2118
|
||||
DW_AT_GNU_locviews = 0x2137
|
||||
DW_AT_GNU_entry_view = 0x2138
|
||||
DW_AT_GNU_macros = 0x2119
|
||||
DW_AT_GNU_deleted = 0x211a
|
||||
DW_AT_GNU_deleted = 0x211A
|
||||
DW_AT_GNU_dwo_name = 0x2130
|
||||
DW_AT_GNU_dwo_id = 0x2131
|
||||
DW_AT_GNU_ranges_base = 0x2132
|
||||
@ -270,7 +270,7 @@ DW_AT_GNU_pubtypes = 0x2135
|
||||
DW_AT_GNU_numerator = 0x2303
|
||||
DW_AT_GNU_denominator = 0x2304
|
||||
DW_AT_GNU_bias = 0x2305
|
||||
DW_AT_hi_user = 0x3fff
|
||||
DW_AT_hi_user = 0x3FFF
|
||||
|
||||
DW_FORM_addr = 0x01
|
||||
DW_FORM_block2 = 0x03
|
||||
@ -280,12 +280,12 @@ DW_FORM_data4 = 0x06
|
||||
DW_FORM_data8 = 0x07
|
||||
DW_FORM_string = 0x08
|
||||
DW_FORM_block = 0x09
|
||||
DW_FORM_block1 = 0x0a
|
||||
DW_FORM_data1 = 0x0b
|
||||
DW_FORM_flag = 0x0c
|
||||
DW_FORM_sdata = 0x0d
|
||||
DW_FORM_strp = 0x0e
|
||||
DW_FORM_udata = 0x0f
|
||||
DW_FORM_block1 = 0x0A
|
||||
DW_FORM_data1 = 0x0B
|
||||
DW_FORM_flag = 0x0C
|
||||
DW_FORM_sdata = 0x0D
|
||||
DW_FORM_strp = 0x0E
|
||||
DW_FORM_udata = 0x0F
|
||||
DW_FORM_ref_addr = 0x10
|
||||
DW_FORM_ref1 = 0x11
|
||||
DW_FORM_ref2 = 0x12
|
||||
@ -296,12 +296,12 @@ DW_FORM_indirect = 0x16
|
||||
DW_FORM_sec_offset = 0x17
|
||||
DW_FORM_exprloc = 0x18
|
||||
DW_FORM_flag_present = 0x19
|
||||
DW_FORM_strx = 0x1a
|
||||
DW_FORM_addrx = 0x1b
|
||||
DW_FORM_ref_sup4 = 0x1c
|
||||
DW_FORM_strp_sup = 0x1d
|
||||
DW_FORM_data16 = 0x1e
|
||||
DW_FORM_line_strp = 0x1f
|
||||
DW_FORM_strx = 0x1A
|
||||
DW_FORM_addrx = 0x1B
|
||||
DW_FORM_ref_sup4 = 0x1C
|
||||
DW_FORM_strp_sup = 0x1D
|
||||
DW_FORM_data16 = 0x1E
|
||||
DW_FORM_line_strp = 0x1F
|
||||
DW_FORM_ref_sig8 = 0x20
|
||||
DW_FORM_implicit_const = 0x21
|
||||
DW_FORM_loclistx = 0x22
|
||||
@ -312,24 +312,24 @@ DW_FORM_strx2 = 0x26
|
||||
DW_FORM_strx3 = 0x27
|
||||
DW_FORM_strx4 = 0x28
|
||||
DW_FORM_addrx1 = 0x29
|
||||
DW_FORM_addrx2 = 0x2a
|
||||
DW_FORM_addrx3 = 0x2b
|
||||
DW_FORM_addrx4 = 0x2c
|
||||
DW_FORM_GNU_addr_index = 0x1f01
|
||||
DW_FORM_GNU_str_index = 0x1f02
|
||||
DW_FORM_GNU_ref_alt = 0x1f20
|
||||
DW_FORM_GNU_strp_alt = 0x1f21
|
||||
DW_FORM_addrx2 = 0x2A
|
||||
DW_FORM_addrx3 = 0x2B
|
||||
DW_FORM_addrx4 = 0x2C
|
||||
DW_FORM_GNU_addr_index = 0x1F01
|
||||
DW_FORM_GNU_str_index = 0x1F02
|
||||
DW_FORM_GNU_ref_alt = 0x1F20
|
||||
DW_FORM_GNU_strp_alt = 0x1F21
|
||||
|
||||
DW_OP_addr = 0x03
|
||||
DW_OP_deref = 0x06
|
||||
DW_OP_const1u = 0x08
|
||||
DW_OP_const1s = 0x09
|
||||
DW_OP_const2u = 0x0a
|
||||
DW_OP_const2s = 0x0b
|
||||
DW_OP_const4u = 0x0c
|
||||
DW_OP_const4s = 0x0d
|
||||
DW_OP_const8u = 0x0e
|
||||
DW_OP_const8s = 0x0f
|
||||
DW_OP_const2u = 0x0A
|
||||
DW_OP_const2s = 0x0B
|
||||
DW_OP_const4u = 0x0C
|
||||
DW_OP_const4s = 0x0D
|
||||
DW_OP_const8u = 0x0E
|
||||
DW_OP_const8s = 0x0F
|
||||
DW_OP_constu = 0x10
|
||||
DW_OP_consts = 0x11
|
||||
DW_OP_dup = 0x12
|
||||
@ -340,12 +340,12 @@ DW_OP_swap = 0x16
|
||||
DW_OP_rot = 0x17
|
||||
DW_OP_xderef = 0x18
|
||||
DW_OP_abs = 0x19
|
||||
DW_OP_and = 0x1a
|
||||
DW_OP_div = 0x1b
|
||||
DW_OP_minus = 0x1c
|
||||
DW_OP_mod = 0x1d
|
||||
DW_OP_mul = 0x1e
|
||||
DW_OP_neg = 0x1f
|
||||
DW_OP_and = 0x1A
|
||||
DW_OP_div = 0x1B
|
||||
DW_OP_minus = 0x1C
|
||||
DW_OP_mod = 0x1D
|
||||
DW_OP_mul = 0x1E
|
||||
DW_OP_neg = 0x1F
|
||||
DW_OP_not = 0x20
|
||||
DW_OP_or = 0x21
|
||||
DW_OP_plus = 0x22
|
||||
@ -356,12 +356,12 @@ DW_OP_shra = 0x26
|
||||
DW_OP_xor = 0x27
|
||||
DW_OP_bra = 0x28
|
||||
DW_OP_eq = 0x29
|
||||
DW_OP_ge = 0x2a
|
||||
DW_OP_gt = 0x2b
|
||||
DW_OP_le = 0x2c
|
||||
DW_OP_lt = 0x2d
|
||||
DW_OP_ne = 0x2e
|
||||
DW_OP_skip = 0x2f
|
||||
DW_OP_ge = 0x2A
|
||||
DW_OP_gt = 0x2B
|
||||
DW_OP_le = 0x2C
|
||||
DW_OP_lt = 0x2D
|
||||
DW_OP_ne = 0x2E
|
||||
DW_OP_skip = 0x2F
|
||||
DW_OP_lit0 = 0x30
|
||||
DW_OP_lit1 = 0x31
|
||||
DW_OP_lit2 = 0x32
|
||||
@ -372,12 +372,12 @@ DW_OP_lit6 = 0x36
|
||||
DW_OP_lit7 = 0x37
|
||||
DW_OP_lit8 = 0x38
|
||||
DW_OP_lit9 = 0x39
|
||||
DW_OP_lit10 = 0x3a
|
||||
DW_OP_lit11 = 0x3b
|
||||
DW_OP_lit12 = 0x3c
|
||||
DW_OP_lit13 = 0x3d
|
||||
DW_OP_lit14 = 0x3e
|
||||
DW_OP_lit15 = 0x3f
|
||||
DW_OP_lit10 = 0x3A
|
||||
DW_OP_lit11 = 0x3B
|
||||
DW_OP_lit12 = 0x3C
|
||||
DW_OP_lit13 = 0x3D
|
||||
DW_OP_lit14 = 0x3E
|
||||
DW_OP_lit15 = 0x3F
|
||||
DW_OP_lit16 = 0x40
|
||||
DW_OP_lit17 = 0x41
|
||||
DW_OP_lit18 = 0x42
|
||||
@ -388,12 +388,12 @@ DW_OP_lit22 = 0x46
|
||||
DW_OP_lit23 = 0x47
|
||||
DW_OP_lit24 = 0x48
|
||||
DW_OP_lit25 = 0x49
|
||||
DW_OP_lit26 = 0x4a
|
||||
DW_OP_lit27 = 0x4b
|
||||
DW_OP_lit28 = 0x4c
|
||||
DW_OP_lit29 = 0x4d
|
||||
DW_OP_lit30 = 0x4e
|
||||
DW_OP_lit31 = 0x4f
|
||||
DW_OP_lit26 = 0x4A
|
||||
DW_OP_lit27 = 0x4B
|
||||
DW_OP_lit28 = 0x4C
|
||||
DW_OP_lit29 = 0x4D
|
||||
DW_OP_lit30 = 0x4E
|
||||
DW_OP_lit31 = 0x4F
|
||||
DW_OP_reg0 = 0x50
|
||||
DW_OP_reg1 = 0x51
|
||||
DW_OP_reg2 = 0x52
|
||||
@ -404,12 +404,12 @@ DW_OP_reg6 = 0x56
|
||||
DW_OP_reg7 = 0x57
|
||||
DW_OP_reg8 = 0x58
|
||||
DW_OP_reg9 = 0x59
|
||||
DW_OP_reg10 = 0x5a
|
||||
DW_OP_reg11 = 0x5b
|
||||
DW_OP_reg12 = 0x5c
|
||||
DW_OP_reg13 = 0x5d
|
||||
DW_OP_reg14 = 0x5e
|
||||
DW_OP_reg15 = 0x5f
|
||||
DW_OP_reg10 = 0x5A
|
||||
DW_OP_reg11 = 0x5B
|
||||
DW_OP_reg12 = 0x5C
|
||||
DW_OP_reg13 = 0x5D
|
||||
DW_OP_reg14 = 0x5E
|
||||
DW_OP_reg15 = 0x5F
|
||||
DW_OP_reg16 = 0x60
|
||||
DW_OP_reg17 = 0x61
|
||||
DW_OP_reg18 = 0x62
|
||||
@ -420,12 +420,12 @@ DW_OP_reg22 = 0x66
|
||||
DW_OP_reg23 = 0x67
|
||||
DW_OP_reg24 = 0x68
|
||||
DW_OP_reg25 = 0x69
|
||||
DW_OP_reg26 = 0x6a
|
||||
DW_OP_reg27 = 0x6b
|
||||
DW_OP_reg28 = 0x6c
|
||||
DW_OP_reg29 = 0x6d
|
||||
DW_OP_reg30 = 0x6e
|
||||
DW_OP_reg31 = 0x6f
|
||||
DW_OP_reg26 = 0x6A
|
||||
DW_OP_reg27 = 0x6B
|
||||
DW_OP_reg28 = 0x6C
|
||||
DW_OP_reg29 = 0x6D
|
||||
DW_OP_reg30 = 0x6E
|
||||
DW_OP_reg31 = 0x6F
|
||||
DW_OP_breg0 = 0x70
|
||||
DW_OP_breg1 = 0x71
|
||||
DW_OP_breg2 = 0x72
|
||||
@ -436,12 +436,12 @@ DW_OP_breg6 = 0x76
|
||||
DW_OP_breg7 = 0x77
|
||||
DW_OP_breg8 = 0x78
|
||||
DW_OP_breg9 = 0x79
|
||||
DW_OP_breg10 = 0x7a
|
||||
DW_OP_breg11 = 0x7b
|
||||
DW_OP_breg12 = 0x7c
|
||||
DW_OP_breg13 = 0x7d
|
||||
DW_OP_breg14 = 0x7e
|
||||
DW_OP_breg15 = 0x7f
|
||||
DW_OP_breg10 = 0x7A
|
||||
DW_OP_breg11 = 0x7B
|
||||
DW_OP_breg12 = 0x7C
|
||||
DW_OP_breg13 = 0x7D
|
||||
DW_OP_breg14 = 0x7E
|
||||
DW_OP_breg15 = 0x7F
|
||||
DW_OP_breg16 = 0x80
|
||||
DW_OP_breg17 = 0x81
|
||||
DW_OP_breg18 = 0x82
|
||||
@ -452,12 +452,12 @@ DW_OP_breg22 = 0x86
|
||||
DW_OP_breg23 = 0x87
|
||||
DW_OP_breg24 = 0x88
|
||||
DW_OP_breg25 = 0x89
|
||||
DW_OP_breg26 = 0x8a
|
||||
DW_OP_breg27 = 0x8b
|
||||
DW_OP_breg28 = 0x8c
|
||||
DW_OP_breg29 = 0x8d
|
||||
DW_OP_breg30 = 0x8e
|
||||
DW_OP_breg31 = 0x8f
|
||||
DW_OP_breg26 = 0x8A
|
||||
DW_OP_breg27 = 0x8B
|
||||
DW_OP_breg28 = 0x8C
|
||||
DW_OP_breg29 = 0x8D
|
||||
DW_OP_breg30 = 0x8E
|
||||
DW_OP_breg31 = 0x8F
|
||||
DW_OP_regx = 0x90
|
||||
DW_OP_fbreg = 0x91
|
||||
DW_OP_bregx = 0x92
|
||||
@ -468,38 +468,38 @@ DW_OP_nop = 0x96
|
||||
DW_OP_push_object_address = 0x97
|
||||
DW_OP_call2 = 0x98
|
||||
DW_OP_call4 = 0x99
|
||||
DW_OP_call_ref = 0x9a
|
||||
DW_OP_form_tls_address = 0x9b
|
||||
DW_OP_call_frame_cfa = 0x9c
|
||||
DW_OP_bit_piece = 0x9d
|
||||
DW_OP_implicit_value = 0x9e
|
||||
DW_OP_stack_value = 0x9f
|
||||
DW_OP_implicit_pointer = 0xa0
|
||||
DW_OP_addrx = 0xa1
|
||||
DW_OP_constx = 0xa2
|
||||
DW_OP_entry_value = 0xa3
|
||||
DW_OP_const_type = 0xa4
|
||||
DW_OP_regval_type = 0xa5
|
||||
DW_OP_deref_type = 0xa6
|
||||
DW_OP_xderef_type = 0xa7
|
||||
DW_OP_convert = 0xa8
|
||||
DW_OP_reinterpret = 0xa9
|
||||
DW_OP_GNU_push_tls_address = 0xe0
|
||||
DW_OP_GNU_uninit = 0xf0
|
||||
DW_OP_GNU_encoded_addr = 0xf1
|
||||
DW_OP_GNU_implicit_pointer = 0xf2
|
||||
DW_OP_GNU_entry_value = 0xf3
|
||||
DW_OP_GNU_const_type = 0xf4
|
||||
DW_OP_GNU_regval_type = 0xf5
|
||||
DW_OP_GNU_deref_type = 0xf6
|
||||
DW_OP_GNU_convert = 0xf7
|
||||
DW_OP_GNU_reinterpret = 0xf9
|
||||
DW_OP_GNU_parameter_ref = 0xfa
|
||||
DW_OP_GNU_addr_index = 0xfb
|
||||
DW_OP_GNU_const_index = 0xfc
|
||||
DW_OP_GNU_variable_value = 0xfd
|
||||
DW_OP_lo_user = 0xe0
|
||||
DW_OP_hi_user = 0xff
|
||||
DW_OP_call_ref = 0x9A
|
||||
DW_OP_form_tls_address = 0x9B
|
||||
DW_OP_call_frame_cfa = 0x9C
|
||||
DW_OP_bit_piece = 0x9D
|
||||
DW_OP_implicit_value = 0x9E
|
||||
DW_OP_stack_value = 0x9F
|
||||
DW_OP_implicit_pointer = 0xA0
|
||||
DW_OP_addrx = 0xA1
|
||||
DW_OP_constx = 0xA2
|
||||
DW_OP_entry_value = 0xA3
|
||||
DW_OP_const_type = 0xA4
|
||||
DW_OP_regval_type = 0xA5
|
||||
DW_OP_deref_type = 0xA6
|
||||
DW_OP_xderef_type = 0xA7
|
||||
DW_OP_convert = 0xA8
|
||||
DW_OP_reinterpret = 0xA9
|
||||
DW_OP_GNU_push_tls_address = 0xE0
|
||||
DW_OP_GNU_uninit = 0xF0
|
||||
DW_OP_GNU_encoded_addr = 0xF1
|
||||
DW_OP_GNU_implicit_pointer = 0xF2
|
||||
DW_OP_GNU_entry_value = 0xF3
|
||||
DW_OP_GNU_const_type = 0xF4
|
||||
DW_OP_GNU_regval_type = 0xF5
|
||||
DW_OP_GNU_deref_type = 0xF6
|
||||
DW_OP_GNU_convert = 0xF7
|
||||
DW_OP_GNU_reinterpret = 0xF9
|
||||
DW_OP_GNU_parameter_ref = 0xFA
|
||||
DW_OP_GNU_addr_index = 0xFB
|
||||
DW_OP_GNU_const_index = 0xFC
|
||||
DW_OP_GNU_variable_value = 0xFD
|
||||
DW_OP_lo_user = 0xE0
|
||||
DW_OP_hi_user = 0xFF
|
||||
|
||||
DW_ATE_void = 0x0
|
||||
DW_ATE_address = 0x1
|
||||
@ -511,17 +511,17 @@ DW_ATE_signed_char = 0x6
|
||||
DW_ATE_unsigned = 0x7
|
||||
DW_ATE_unsigned_char = 0x8
|
||||
DW_ATE_imaginary_float = 0x9
|
||||
DW_ATE_packed_decimal = 0xa
|
||||
DW_ATE_numeric_string = 0xb
|
||||
DW_ATE_edited = 0xc
|
||||
DW_ATE_signed_fixed = 0xd
|
||||
DW_ATE_unsigned_fixed = 0xe
|
||||
DW_ATE_decimal_float = 0xf
|
||||
DW_ATE_packed_decimal = 0xA
|
||||
DW_ATE_numeric_string = 0xB
|
||||
DW_ATE_edited = 0xC
|
||||
DW_ATE_signed_fixed = 0xD
|
||||
DW_ATE_unsigned_fixed = 0xE
|
||||
DW_ATE_decimal_float = 0xF
|
||||
DW_ATE_UTF = 0x10
|
||||
DW_ATE_UCS = 0x11
|
||||
DW_ATE_ASCII = 0x12
|
||||
DW_ATE_lo_user = 0x80
|
||||
DW_ATE_hi_user = 0xff
|
||||
DW_ATE_hi_user = 0xFF
|
||||
|
||||
DW_DS_unsigned = 1
|
||||
DW_DS_leading_overpunch = 2
|
||||
@ -533,7 +533,7 @@ DW_END_default = 0
|
||||
DW_END_big = 1
|
||||
DW_END_little = 2
|
||||
DW_END_lo_user = 0x40
|
||||
DW_END_hi_user = 0xff
|
||||
DW_END_hi_user = 0xFF
|
||||
|
||||
DW_ACCESS_public = 1
|
||||
DW_ACCESS_protected = 2
|
||||
@ -556,12 +556,12 @@ DW_LANG_Cobol85 = 0x0006
|
||||
DW_LANG_Fortran77 = 0x0007
|
||||
DW_LANG_Fortran90 = 0x0008
|
||||
DW_LANG_Pascal83 = 0x0009
|
||||
DW_LANG_Modula2 = 0x000a
|
||||
DW_LANG_Java = 0x000b
|
||||
DW_LANG_C99 = 0x000c
|
||||
DW_LANG_Ada95 = 0x000d
|
||||
DW_LANG_Fortran95 = 0x000e
|
||||
DW_LANG_PLI = 0x000f
|
||||
DW_LANG_Modula2 = 0x000A
|
||||
DW_LANG_Java = 0x000B
|
||||
DW_LANG_C99 = 0x000C
|
||||
DW_LANG_Ada95 = 0x000D
|
||||
DW_LANG_Fortran95 = 0x000E
|
||||
DW_LANG_PLI = 0x000F
|
||||
DW_LANG_ObjC = 0x0010
|
||||
DW_LANG_ObjC_plus_plus = 0x0011
|
||||
DW_LANG_UPC = 0x0012
|
||||
@ -572,12 +572,12 @@ DW_LANG_Go = 0x0016
|
||||
DW_LANG_Modula3 = 0x0017
|
||||
DW_LANG_Haskell = 0x0018
|
||||
DW_LANG_C_plus_plus_03 = 0x0019
|
||||
DW_LANG_C_plus_plus_11 = 0x001a
|
||||
DW_LANG_OCaml = 0x001b
|
||||
DW_LANG_Rust = 0x001c
|
||||
DW_LANG_C11 = 0x001d
|
||||
DW_LANG_Swift = 0x001e
|
||||
DW_LANG_Julia = 0x001f
|
||||
DW_LANG_C_plus_plus_11 = 0x001A
|
||||
DW_LANG_OCaml = 0x001B
|
||||
DW_LANG_Rust = 0x001C
|
||||
DW_LANG_C11 = 0x001D
|
||||
DW_LANG_Swift = 0x001E
|
||||
DW_LANG_Julia = 0x001F
|
||||
DW_LANG_Dylan = 0x0020
|
||||
DW_LANG_C_plus_plus_14 = 0x0021
|
||||
DW_LANG_Fortran03 = 0x0022
|
||||
@ -586,7 +586,7 @@ DW_LANG_RenderScript = 0x0024
|
||||
DW_LANG_BLISS = 0x0025
|
||||
DW_LANG_lo_user = 0x8000
|
||||
DW_LANG_Mips_Assembler = 0x8001
|
||||
DW_LANG_hi_user = 0xffff
|
||||
DW_LANG_hi_user = 0xFFFF
|
||||
|
||||
DW_ID_case_sensitive = 0
|
||||
DW_ID_up_case = 1
|
||||
@ -599,7 +599,7 @@ DW_CC_nocall = 0x3
|
||||
DW_CC_pass_by_reference = 0x4
|
||||
DW_CC_pass_by_value = 0x5
|
||||
DW_CC_lo_user = 0x40
|
||||
DW_CC_hi_user = 0xff
|
||||
DW_CC_hi_user = 0xFF
|
||||
|
||||
DW_INL_not_inlined = 0
|
||||
DW_INL_inlined = 1
|
||||
@ -622,7 +622,7 @@ DW_LNCT_timestamp = 0x3
|
||||
DW_LNCT_size = 0x4
|
||||
DW_LNCT_MD5 = 0x5
|
||||
DW_LNCT_lo_user = 0x2000
|
||||
DW_LNCT_hi_user = 0x3fff
|
||||
DW_LNCT_hi_user = 0x3FFF
|
||||
|
||||
DW_LNS_copy = 1
|
||||
DW_LNS_advance_pc = 2
|
||||
@ -659,11 +659,11 @@ DW_MACRO_undef_strp = 0x06
|
||||
DW_MACRO_import = 0x07
|
||||
DW_MACRO_define_sup = 0x08
|
||||
DW_MACRO_undef_sup = 0x09
|
||||
DW_MACRO_import_sup = 0x0a
|
||||
DW_MACRO_define_strx = 0x0b
|
||||
DW_MACRO_undef_strx = 0x0c
|
||||
DW_MACRO_lo_user = 0xe0
|
||||
DW_MACRO_hi_user = 0xff
|
||||
DW_MACRO_import_sup = 0x0A
|
||||
DW_MACRO_define_strx = 0x0B
|
||||
DW_MACRO_undef_strx = 0x0C
|
||||
DW_MACRO_lo_user = 0xE0
|
||||
DW_MACRO_hi_user = 0xFF
|
||||
|
||||
DW_RLE_end_of_list = 0x0
|
||||
DW_RLE_base_addressx = 0x1
|
||||
@ -691,7 +691,7 @@ DW_LLE_GNU_start_length_entry = 0x3
|
||||
|
||||
DW_CFA_advance_loc = 0x40
|
||||
DW_CFA_offset = 0x80
|
||||
DW_CFA_restore = 0xc0
|
||||
DW_CFA_restore = 0xC0
|
||||
DW_CFA_extended = 0
|
||||
DW_CFA_nop = 0x00
|
||||
DW_CFA_set_loc = 0x01
|
||||
@ -703,12 +703,12 @@ DW_CFA_restore_extended = 0x06
|
||||
DW_CFA_undefined = 0x07
|
||||
DW_CFA_same_value = 0x08
|
||||
DW_CFA_register = 0x09
|
||||
DW_CFA_remember_state = 0x0a
|
||||
DW_CFA_restore_state = 0x0b
|
||||
DW_CFA_def_cfa = 0x0c
|
||||
DW_CFA_def_cfa_register = 0x0d
|
||||
DW_CFA_def_cfa_offset = 0x0e
|
||||
DW_CFA_def_cfa_expression = 0x0f
|
||||
DW_CFA_remember_state = 0x0A
|
||||
DW_CFA_restore_state = 0x0B
|
||||
DW_CFA_def_cfa = 0x0C
|
||||
DW_CFA_def_cfa_register = 0x0D
|
||||
DW_CFA_def_cfa_offset = 0x0E
|
||||
DW_CFA_def_cfa_expression = 0x0F
|
||||
DW_CFA_expression = 0x10
|
||||
DW_CFA_offset_extended_sf = 0x11
|
||||
DW_CFA_def_cfa_sf = 0x12
|
||||
@ -716,26 +716,26 @@ DW_CFA_def_cfa_offset_sf = 0x13
|
||||
DW_CFA_val_offset = 0x14
|
||||
DW_CFA_val_offset_sf = 0x15
|
||||
DW_CFA_val_expression = 0x16
|
||||
DW_CFA_low_user = 0x1c
|
||||
DW_CFA_MIPS_advance_loc8 = 0x1d
|
||||
DW_CFA_GNU_window_save = 0x2d
|
||||
DW_CFA_GNU_args_size = 0x2e
|
||||
DW_CFA_GNU_negative_offset_extended = 0x2f
|
||||
DW_CFA_high_user = 0x3f
|
||||
DW_CFA_low_user = 0x1C
|
||||
DW_CFA_MIPS_advance_loc8 = 0x1D
|
||||
DW_CFA_GNU_window_save = 0x2D
|
||||
DW_CFA_GNU_args_size = 0x2E
|
||||
DW_CFA_GNU_negative_offset_extended = 0x2F
|
||||
DW_CFA_high_user = 0x3F
|
||||
|
||||
DW_CIE_ID_32 = 0xffffffff
|
||||
DW_CIE_ID_64 = 0xffffffffffffffff
|
||||
DW_CIE_ID_32 = 0xFFFFFFFF
|
||||
DW_CIE_ID_64 = 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
DW_EH_PE_absptr = 0x00
|
||||
DW_EH_PE_omit = 0xff
|
||||
DW_EH_PE_omit = 0xFF
|
||||
DW_EH_PE_uleb128 = 0x01
|
||||
DW_EH_PE_udata2 = 0x02
|
||||
DW_EH_PE_udata4 = 0x03
|
||||
DW_EH_PE_udata8 = 0x04
|
||||
DW_EH_PE_sleb128 = 0x09
|
||||
DW_EH_PE_sdata2 = 0x0a
|
||||
DW_EH_PE_sdata4 = 0x0b
|
||||
DW_EH_PE_sdata8 = 0x0c
|
||||
DW_EH_PE_sdata2 = 0x0A
|
||||
DW_EH_PE_sdata4 = 0x0B
|
||||
DW_EH_PE_sdata8 = 0x0C
|
||||
DW_EH_PE_signed = 0x08
|
||||
DW_EH_PE_pcrel = 0x10
|
||||
DW_EH_PE_textrel = 0x20
|
||||
@ -26,8 +26,10 @@ def section(name: str):
|
||||
def wrapper(fn):
|
||||
fn._section = name
|
||||
return fn
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# from types import SimpleNamespace
|
||||
|
||||
# syscalls = SimpleNamespace(
|
||||
|
||||
@ -2,21 +2,31 @@ import ast
|
||||
from llvmlite import ir
|
||||
|
||||
|
||||
def eval_expr(func, module, builder, expr, local_sym_tab, map_sym_tab):
|
||||
print(f"Evaluating expression: {expr}")
|
||||
def eval_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
expr,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab=None,
|
||||
local_var_metadata=None,
|
||||
):
|
||||
print(f"Evaluating expression: {ast.dump(expr)}")
|
||||
print(local_var_metadata)
|
||||
if isinstance(expr, ast.Name):
|
||||
if expr.id in local_sym_tab:
|
||||
var = local_sym_tab[expr.id]
|
||||
var = local_sym_tab[expr.id][0]
|
||||
val = builder.load(var)
|
||||
return val
|
||||
return val, local_sym_tab[expr.id][1] # return value and type
|
||||
else:
|
||||
print(f"Undefined variable {expr.id}")
|
||||
return None
|
||||
elif isinstance(expr, ast.Constant):
|
||||
if isinstance(expr.value, int):
|
||||
return ir.Constant(ir.IntType(64), expr.value)
|
||||
return ir.Constant(ir.IntType(64), expr.value), ir.IntType(64)
|
||||
elif isinstance(expr.value, bool):
|
||||
return ir.Constant(ir.IntType(1), int(expr.value))
|
||||
return ir.Constant(ir.IntType(1), int(expr.value)), ir.IntType(1)
|
||||
else:
|
||||
print("Unsupported constant type")
|
||||
return None
|
||||
@ -32,40 +42,114 @@ def eval_expr(func, module, builder, expr, local_sym_tab, map_sym_tab):
|
||||
print("deref takes exactly one argument")
|
||||
return None
|
||||
arg = expr.args[0]
|
||||
if isinstance(arg, ast.Call) and isinstance(arg.func, ast.Name) and arg.func.id == "deref":
|
||||
if (
|
||||
isinstance(arg, ast.Call)
|
||||
and isinstance(arg.func, ast.Name)
|
||||
and arg.func.id == "deref"
|
||||
):
|
||||
print("Multiple deref not supported")
|
||||
return None
|
||||
if isinstance(arg, ast.Name):
|
||||
if arg.id in local_sym_tab:
|
||||
arg = local_sym_tab[arg.id]
|
||||
arg = local_sym_tab[arg.id][0]
|
||||
else:
|
||||
print(f"Undefined variable {arg.id}")
|
||||
return None
|
||||
if arg is None:
|
||||
print("Failed to evaluate deref argument")
|
||||
return None
|
||||
# Since we are handling only name case, directly take type from sym tab
|
||||
val = builder.load(arg)
|
||||
return val
|
||||
return val, local_sym_tab[expr.args[0].id][1]
|
||||
|
||||
# check for helpers
|
||||
if expr.func.id in helper_func_list:
|
||||
return handle_helper_call(
|
||||
expr, module, builder, func, local_sym_tab, map_sym_tab)
|
||||
expr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
elif isinstance(expr.func, ast.Attribute):
|
||||
if isinstance(expr.func.value, ast.Call) and isinstance(expr.func.value.func, ast.Name):
|
||||
print(f"Handling method call: {ast.dump(expr.func)}")
|
||||
if isinstance(expr.func.value, ast.Call) and isinstance(
|
||||
expr.func.value.func, ast.Name
|
||||
):
|
||||
method_name = expr.func.attr
|
||||
if method_name in helper_func_list:
|
||||
return handle_helper_call(
|
||||
expr, module, builder, func, local_sym_tab, map_sym_tab)
|
||||
expr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
elif isinstance(expr.func.value, ast.Name):
|
||||
obj_name = expr.func.value.id
|
||||
method_name = expr.func.attr
|
||||
if obj_name in map_sym_tab:
|
||||
if method_name in helper_func_list:
|
||||
return handle_helper_call(
|
||||
expr,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
elif isinstance(expr, ast.Attribute):
|
||||
if isinstance(expr.value, ast.Name):
|
||||
var_name = expr.value.id
|
||||
attr_name = expr.attr
|
||||
if var_name in local_sym_tab:
|
||||
var_ptr, var_type = local_sym_tab[var_name]
|
||||
print(f"Loading attribute " f"{attr_name} from variable {var_name}")
|
||||
print(f"Variable type: {var_type}, Variable ptr: {var_ptr}")
|
||||
print(local_var_metadata)
|
||||
if local_var_metadata and var_name in local_var_metadata:
|
||||
metadata = structs_sym_tab[local_var_metadata[var_name]]
|
||||
if attr_name in metadata.fields:
|
||||
gep = metadata.gep(builder, var_ptr, attr_name)
|
||||
val = builder.load(gep)
|
||||
field_type = metadata.field_type(attr_name)
|
||||
return val, field_type
|
||||
print("Unsupported expression evaluation")
|
||||
return None
|
||||
|
||||
|
||||
def handle_expr(func, module, builder, expr, local_sym_tab, map_sym_tab):
|
||||
def handle_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
expr,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
):
|
||||
"""Handle expression statements in the function body."""
|
||||
print(f"Handling expression: {ast.dump(expr)}")
|
||||
print(local_var_metadata)
|
||||
call = expr.value
|
||||
if isinstance(call, ast.Call):
|
||||
eval_expr(func, module, builder, call, local_sym_tab, map_sym_tab)
|
||||
eval_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
call,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
else:
|
||||
print("Unsupported expression type")
|
||||
|
||||
@ -27,7 +27,9 @@ def get_probe_string(func_node):
|
||||
return "helper"
|
||||
|
||||
|
||||
def handle_assign(func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab):
|
||||
def handle_assign(
|
||||
func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab
|
||||
):
|
||||
"""Handle assignment statements in the function body."""
|
||||
if len(stmt.targets) != 1:
|
||||
print("Unsupported multiassignment")
|
||||
@ -49,79 +51,128 @@ def handle_assign(func, module, builder, stmt, map_sym_tab, local_sym_tab, struc
|
||||
struct_type = local_var_metadata[var_name]
|
||||
struct_info = structs_sym_tab[struct_type]
|
||||
|
||||
if field_name in struct_info["fields"]:
|
||||
field_idx = struct_info["fields"][field_name]
|
||||
struct_ptr = local_sym_tab[var_name]
|
||||
field_ptr = builder.gep(
|
||||
struct_ptr, [ir.Constant(ir.IntType(32), 0),
|
||||
ir.Constant(ir.IntType(32), field_idx)],
|
||||
inbounds=True)
|
||||
val = eval_expr(func, module, builder, rval,
|
||||
local_sym_tab, map_sym_tab)
|
||||
if field_name in struct_info.fields:
|
||||
field_ptr = struct_info.gep(
|
||||
builder, local_sym_tab[var_name][0], field_name
|
||||
)
|
||||
val = eval_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
rval,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
)
|
||||
if isinstance(struct_info.field_type(field_name), ir.ArrayType) and val[
|
||||
1
|
||||
] == ir.PointerType(ir.IntType(8)):
|
||||
# TODO: Figure it out, not a priority rn
|
||||
# Special case for string assignment to char array
|
||||
# str_len = struct_info["field_types"][field_idx].count
|
||||
# assign_string_to_array(builder, field_ptr, val[0], str_len)
|
||||
# print(f"Assigned to struct field {var_name}.{field_name}")
|
||||
pass
|
||||
if val is None:
|
||||
print("Failed to evaluate struct field assignment")
|
||||
return
|
||||
builder.store(val, field_ptr)
|
||||
print(field_ptr)
|
||||
builder.store(val[0], field_ptr)
|
||||
print(f"Assigned to struct field {var_name}.{field_name}")
|
||||
return
|
||||
elif isinstance(rval, ast.Constant):
|
||||
if isinstance(rval.value, bool):
|
||||
if rval.value:
|
||||
builder.store(ir.Constant(ir.IntType(1), 1),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(ir.Constant(ir.IntType(1), 1), local_sym_tab[var_name][0])
|
||||
else:
|
||||
builder.store(ir.Constant(ir.IntType(1), 0),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(ir.Constant(ir.IntType(1), 0), local_sym_tab[var_name][0])
|
||||
print(f"Assigned constant {rval.value} to {var_name}")
|
||||
elif isinstance(rval.value, int):
|
||||
# Assume c_int64 for now
|
||||
# var = builder.alloca(ir.IntType(64), name=var_name)
|
||||
# var.align = 8
|
||||
builder.store(ir.Constant(ir.IntType(64), rval.value),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(
|
||||
ir.Constant(ir.IntType(64), rval.value), local_sym_tab[var_name][0]
|
||||
)
|
||||
# local_sym_tab[var_name] = var
|
||||
print(f"Assigned constant {rval.value} to {var_name}")
|
||||
elif isinstance(rval.value, str):
|
||||
str_val = rval.value.encode("utf-8") + b"\x00"
|
||||
str_const = ir.Constant(
|
||||
ir.ArrayType(ir.IntType(8), len(str_val)), bytearray(str_val)
|
||||
)
|
||||
global_str = ir.GlobalVariable(
|
||||
module, str_const.type, name=f"{var_name}_str"
|
||||
)
|
||||
global_str.linkage = "internal"
|
||||
global_str.global_constant = True
|
||||
global_str.initializer = str_const
|
||||
str_ptr = builder.bitcast(global_str, ir.PointerType(ir.IntType(8)))
|
||||
builder.store(str_ptr, local_sym_tab[var_name][0])
|
||||
print(f"Assigned string constant '{rval.value}' to {var_name}")
|
||||
else:
|
||||
print("Unsupported constant type")
|
||||
elif isinstance(rval, ast.Call):
|
||||
if isinstance(rval.func, ast.Name):
|
||||
call_type = rval.func.id
|
||||
print(f"Assignment call type: {call_type}")
|
||||
if call_type in num_types and len(rval.args) == 1 and isinstance(rval.args[0], ast.Constant) and isinstance(rval.args[0].value, int):
|
||||
if (
|
||||
call_type in num_types
|
||||
and len(rval.args) == 1
|
||||
and isinstance(rval.args[0], ast.Constant)
|
||||
and isinstance(rval.args[0].value, int)
|
||||
):
|
||||
ir_type = ctypes_to_ir(call_type)
|
||||
# var = builder.alloca(ir_type, name=var_name)
|
||||
# var.align = ir_type.width // 8
|
||||
builder.store(ir.Constant(
|
||||
ir_type, rval.args[0].value), local_sym_tab[var_name])
|
||||
print(f"Assigned {call_type} constant "
|
||||
f"{rval.args[0].value} to {var_name}")
|
||||
builder.store(
|
||||
ir.Constant(ir_type, rval.args[0].value), local_sym_tab[var_name][0]
|
||||
)
|
||||
print(
|
||||
f"Assigned {call_type} constant "
|
||||
f"{rval.args[0].value} to {var_name}"
|
||||
)
|
||||
# local_sym_tab[var_name] = var
|
||||
elif call_type in helper_func_list:
|
||||
# var = builder.alloca(ir.IntType(64), name=var_name)
|
||||
# var.align = 8
|
||||
val = handle_helper_call(
|
||||
rval, module, builder, None, local_sym_tab, map_sym_tab)
|
||||
builder.store(val, local_sym_tab[var_name])
|
||||
rval,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
builder.store(val[0], local_sym_tab[var_name][0])
|
||||
# local_sym_tab[var_name] = var
|
||||
print(f"Assigned constant {rval.func.id} to {var_name}")
|
||||
elif call_type == "deref" and len(rval.args) == 1:
|
||||
print(f"Handling deref assignment {ast.dump(rval)}")
|
||||
val = eval_expr(func, module, builder, rval,
|
||||
local_sym_tab, map_sym_tab)
|
||||
val = eval_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
rval,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
)
|
||||
if val is None:
|
||||
print("Failed to evaluate deref argument")
|
||||
return
|
||||
print(f"Dereferenced value: {val}, storing in {var_name}")
|
||||
builder.store(val, local_sym_tab[var_name])
|
||||
builder.store(val[0], local_sym_tab[var_name][0])
|
||||
# local_sym_tab[var_name] = var
|
||||
print(f"Dereferenced and assigned to {var_name}")
|
||||
elif call_type in structs_sym_tab and len(rval.args) == 0:
|
||||
struct_info = structs_sym_tab[call_type]
|
||||
ir_type = struct_info["type"]
|
||||
ir_type = struct_info.ir_type
|
||||
# var = builder.alloca(ir_type, name=var_name)
|
||||
# Null init
|
||||
builder.store(ir.Constant(ir_type, None),
|
||||
local_sym_tab[var_name])
|
||||
builder.store(ir.Constant(ir_type, None), local_sym_tab[var_name][0])
|
||||
local_var_metadata[var_name] = call_type
|
||||
print(f"Assigned struct {call_type} to {var_name}")
|
||||
# local_sym_tab[var_name] = var
|
||||
@ -132,25 +183,36 @@ def handle_assign(func, module, builder, stmt, map_sym_tab, local_sym_tab, struc
|
||||
if isinstance(rval.func.value, ast.Name):
|
||||
# TODO: probably a struct access
|
||||
print(f"TODO STRUCT ACCESS {ast.dump(rval)}")
|
||||
elif isinstance(rval.func.value, ast.Call) and isinstance(rval.func.value.func, ast.Name):
|
||||
elif isinstance(rval.func.value, ast.Call) and isinstance(
|
||||
rval.func.value.func, ast.Name
|
||||
):
|
||||
map_name = rval.func.value.func.id
|
||||
method_name = rval.func.attr
|
||||
if map_name in map_sym_tab:
|
||||
map_ptr = map_sym_tab[map_name]
|
||||
# map_ptr = map_sym_tab[map_name]
|
||||
if method_name in helper_func_list:
|
||||
val = handle_helper_call(
|
||||
rval, module, builder, func, local_sym_tab, map_sym_tab)
|
||||
rval,
|
||||
module,
|
||||
builder,
|
||||
func,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
# var = builder.alloca(ir.IntType(64), name=var_name)
|
||||
# var.align = 8
|
||||
builder.store(val, local_sym_tab[var_name])
|
||||
builder.store(val[0], local_sym_tab[var_name][0])
|
||||
# local_sym_tab[var_name] = var
|
||||
else:
|
||||
print("Unsupported assignment call structure")
|
||||
else:
|
||||
print("Unsupported assignment call function type")
|
||||
elif isinstance(rval, ast.BinOp):
|
||||
handle_binary_op(rval, module, builder, var_name,
|
||||
local_sym_tab, map_sym_tab, func)
|
||||
handle_binary_op(
|
||||
rval, module, builder, var_name, local_sym_tab, map_sym_tab, func
|
||||
)
|
||||
else:
|
||||
print("Unsupported assignment value type")
|
||||
|
||||
@ -166,7 +228,7 @@ def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab):
|
||||
return None
|
||||
elif isinstance(cond, ast.Name):
|
||||
if cond.id in local_sym_tab:
|
||||
var = local_sym_tab[cond.id]
|
||||
var = local_sym_tab[cond.id][0]
|
||||
val = builder.load(var)
|
||||
if val.type != ir.IntType(1):
|
||||
# Convert nonzero values to true, zero to false
|
||||
@ -182,13 +244,13 @@ def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab):
|
||||
print(f"Undefined variable {cond.id} in condition")
|
||||
return None
|
||||
elif isinstance(cond, ast.Compare):
|
||||
lhs = eval_expr(func, module, builder, cond.left,
|
||||
local_sym_tab, map_sym_tab)
|
||||
lhs = eval_expr(func, module, builder, cond.left, local_sym_tab, map_sym_tab)[0]
|
||||
if len(cond.ops) != 1 or len(cond.comparators) != 1:
|
||||
print("Unsupported complex comparison")
|
||||
return None
|
||||
rhs = eval_expr(func, module, builder,
|
||||
cond.comparators[0], local_sym_tab, map_sym_tab)
|
||||
rhs = eval_expr(
|
||||
func, module, builder, cond.comparators[0], local_sym_tab, map_sym_tab
|
||||
)[0]
|
||||
op = cond.ops[0]
|
||||
|
||||
if lhs.type != rhs.type:
|
||||
@ -222,10 +284,12 @@ def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab):
|
||||
return None
|
||||
|
||||
|
||||
def handle_if(func, module, builder, stmt, map_sym_tab, local_sym_tab):
|
||||
def handle_if(
|
||||
func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab=None
|
||||
):
|
||||
"""Handle if statements in the function body."""
|
||||
print("Handling if statement")
|
||||
start = builder.block.parent
|
||||
# start = builder.block.parent
|
||||
then_block = func.append_basic_block(name="if.then")
|
||||
merge_block = func.append_basic_block(name="if.end")
|
||||
if stmt.orelse:
|
||||
@ -233,8 +297,7 @@ def handle_if(func, module, builder, stmt, map_sym_tab, local_sym_tab):
|
||||
else:
|
||||
else_block = None
|
||||
|
||||
cond = handle_cond(func, module, builder, stmt.test,
|
||||
local_sym_tab, map_sym_tab)
|
||||
cond = handle_cond(func, module, builder, stmt.test, local_sym_tab, map_sym_tab)
|
||||
if else_block:
|
||||
builder.cbranch(cond, then_block, else_block)
|
||||
else:
|
||||
@ -242,45 +305,84 @@ def handle_if(func, module, builder, stmt, map_sym_tab, local_sym_tab):
|
||||
|
||||
builder.position_at_end(then_block)
|
||||
for s in stmt.body:
|
||||
process_stmt(func, module, builder, s,
|
||||
local_sym_tab, map_sym_tab, False)
|
||||
process_stmt(
|
||||
func, module, builder, s, local_sym_tab, map_sym_tab, structs_sym_tab, False
|
||||
)
|
||||
if not builder.block.is_terminated:
|
||||
builder.branch(merge_block)
|
||||
|
||||
if else_block:
|
||||
builder.position_at_end(else_block)
|
||||
for s in stmt.orelse:
|
||||
process_stmt(func, module, builder, s,
|
||||
local_sym_tab, map_sym_tab, False)
|
||||
process_stmt(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
s,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
False,
|
||||
)
|
||||
if not builder.block.is_terminated:
|
||||
builder.branch(merge_block)
|
||||
|
||||
builder.position_at_end(merge_block)
|
||||
|
||||
|
||||
def process_stmt(func, module, builder, stmt, local_sym_tab, map_sym_tab, structs_sym_tab, did_return, ret_type=ir.IntType(64)):
|
||||
def process_stmt(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
stmt,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
did_return,
|
||||
ret_type=ir.IntType(64),
|
||||
):
|
||||
print(f"Processing statement: {ast.dump(stmt)}")
|
||||
if isinstance(stmt, ast.Expr):
|
||||
handle_expr(func, module, builder, stmt, local_sym_tab, map_sym_tab)
|
||||
print(local_var_metadata)
|
||||
handle_expr(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
stmt,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
local_var_metadata,
|
||||
)
|
||||
elif isinstance(stmt, ast.Assign):
|
||||
handle_assign(func, module, builder, stmt, map_sym_tab,
|
||||
local_sym_tab, structs_sym_tab)
|
||||
handle_assign(
|
||||
func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab
|
||||
)
|
||||
elif isinstance(stmt, ast.AugAssign):
|
||||
raise SyntaxError("Augmented assignment not supported")
|
||||
elif isinstance(stmt, ast.If):
|
||||
handle_if(func, module, builder, stmt, map_sym_tab, local_sym_tab)
|
||||
handle_if(
|
||||
func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab
|
||||
)
|
||||
elif isinstance(stmt, ast.Return):
|
||||
if stmt.value is None:
|
||||
builder.ret(ir.Constant(ir.IntType(32), 0))
|
||||
did_return = True
|
||||
elif isinstance(stmt.value, ast.Call) and isinstance(stmt.value.func, ast.Name) and len(stmt.value.args) == 1 and isinstance(stmt.value.args[0], ast.Constant) and isinstance(stmt.value.args[0].value, int):
|
||||
elif (
|
||||
isinstance(stmt.value, ast.Call)
|
||||
and isinstance(stmt.value.func, ast.Name)
|
||||
and len(stmt.value.args) == 1
|
||||
and isinstance(stmt.value.args[0], ast.Constant)
|
||||
and isinstance(stmt.value.args[0].value, int)
|
||||
):
|
||||
call_type = stmt.value.func.id
|
||||
if ctypes_to_ir(call_type) != ret_type:
|
||||
raise ValueError("Return type mismatch: expected"
|
||||
f"{ctypes_to_ir(call_type)}, got {call_type}")
|
||||
raise ValueError(
|
||||
"Return type mismatch: expected"
|
||||
f"{ctypes_to_ir(call_type)}, got {call_type}"
|
||||
)
|
||||
else:
|
||||
builder.ret(ir.Constant(
|
||||
ret_type, stmt.value.args[0].value))
|
||||
builder.ret(ir.Constant(ret_type, stmt.value.args[0].value))
|
||||
did_return = True
|
||||
elif isinstance(stmt.value, ast.Name):
|
||||
if stmt.value.id == "XDP_PASS":
|
||||
@ -296,15 +398,33 @@ def process_stmt(func, module, builder, stmt, local_sym_tab, map_sym_tab, struct
|
||||
return did_return
|
||||
|
||||
|
||||
def allocate_mem(module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab):
|
||||
def allocate_mem(
|
||||
module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab
|
||||
):
|
||||
for stmt in body:
|
||||
if isinstance(stmt, ast.If):
|
||||
if stmt.body:
|
||||
local_sym_tab = allocate_mem(
|
||||
module, builder, stmt.body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab)
|
||||
module,
|
||||
builder,
|
||||
stmt.body,
|
||||
func,
|
||||
ret_type,
|
||||
map_sym_tab,
|
||||
local_sym_tab,
|
||||
structs_sym_tab,
|
||||
)
|
||||
if stmt.orelse:
|
||||
local_sym_tab = allocate_mem(
|
||||
module, builder, stmt.orelse, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab)
|
||||
module,
|
||||
builder,
|
||||
stmt.orelse,
|
||||
func,
|
||||
ret_type,
|
||||
map_sym_tab,
|
||||
local_sym_tab,
|
||||
structs_sym_tab,
|
||||
)
|
||||
elif isinstance(stmt, ast.Assign):
|
||||
if len(stmt.targets) != 1:
|
||||
print("Unsupported multiassignment")
|
||||
@ -322,35 +442,32 @@ def allocate_mem(module, builder, body, func, ret_type, map_sym_tab, local_sym_t
|
||||
ir_type = ctypes_to_ir(call_type)
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = ir_type.width // 8
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} of type {call_type}")
|
||||
print(f"Pre-allocated variable {var_name} of type {call_type}")
|
||||
elif call_type in helper_func_list:
|
||||
# Assume return type is int64 for now
|
||||
ir_type = ir.IntType(64)
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = ir_type.width // 8
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} for helper")
|
||||
print(f"Pre-allocated variable {var_name} for helper")
|
||||
elif call_type == "deref" and len(rval.args) == 1:
|
||||
# Assume return type is int64 for now
|
||||
ir_type = ir.IntType(64)
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = ir_type.width // 8
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} for deref")
|
||||
print(f"Pre-allocated variable {var_name} for deref")
|
||||
elif call_type in structs_sym_tab:
|
||||
struct_info = structs_sym_tab[call_type]
|
||||
ir_type = struct_info["type"]
|
||||
ir_type = struct_info.ir_type
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
local_var_metadata[var_name] = call_type
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} for struct {call_type}")
|
||||
f"Pre-allocated variable {var_name} for struct {call_type}"
|
||||
)
|
||||
elif isinstance(rval.func, ast.Attribute):
|
||||
ir_type = ir.PointerType(ir.IntType(64))
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
# var.align = ir_type.width // 8
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} for map")
|
||||
print(f"Pre-allocated variable {var_name} for map")
|
||||
else:
|
||||
print("Unsupported assignment call function type")
|
||||
continue
|
||||
@ -359,15 +476,18 @@ def allocate_mem(module, builder, body, func, ret_type, map_sym_tab, local_sym_t
|
||||
ir_type = ir.IntType(1)
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = 1
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} of type c_bool")
|
||||
print(f"Pre-allocated variable {var_name} of type c_bool")
|
||||
elif isinstance(rval.value, int):
|
||||
# Assume c_int64 for now
|
||||
ir_type = ir.IntType(64)
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = ir_type.width // 8
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} of type c_int64")
|
||||
print(f"Pre-allocated variable {var_name} of type c_int64")
|
||||
elif isinstance(rval.value, str):
|
||||
ir_type = ir.PointerType(ir.IntType(8))
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = 8
|
||||
print(f"Pre-allocated variable {var_name} of type string")
|
||||
else:
|
||||
print("Unsupported constant type")
|
||||
continue
|
||||
@ -376,16 +496,17 @@ def allocate_mem(module, builder, body, func, ret_type, map_sym_tab, local_sym_t
|
||||
ir_type = ir.IntType(64)
|
||||
var = builder.alloca(ir_type, name=var_name)
|
||||
var.align = ir_type.width // 8
|
||||
print(
|
||||
f"Pre-allocated variable {var_name} of type c_int64")
|
||||
print(f"Pre-allocated variable {var_name} of type c_int64")
|
||||
else:
|
||||
print("Unsupported assignment value type")
|
||||
continue
|
||||
local_sym_tab[var_name] = var
|
||||
local_sym_tab[var_name] = (var, ir_type)
|
||||
return local_sym_tab
|
||||
|
||||
|
||||
def process_func_body(module, builder, func_node, func, ret_type, map_sym_tab, structs_sym_tab):
|
||||
def process_func_body(
|
||||
module, builder, func_node, func, ret_type, map_sym_tab, structs_sym_tab
|
||||
):
|
||||
"""Process the body of a bpf function"""
|
||||
# TODO: A lot. We just have print -> bpf_trace_printk for now
|
||||
did_return = False
|
||||
@ -394,13 +515,30 @@ def process_func_body(module, builder, func_node, func, ret_type, map_sym_tab, s
|
||||
|
||||
# pre-allocate dynamic variables
|
||||
local_sym_tab = allocate_mem(
|
||||
module, builder, func_node.body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab)
|
||||
module,
|
||||
builder,
|
||||
func_node.body,
|
||||
func,
|
||||
ret_type,
|
||||
map_sym_tab,
|
||||
local_sym_tab,
|
||||
structs_sym_tab,
|
||||
)
|
||||
|
||||
print(f"Local symbol table: {local_sym_tab.keys()}")
|
||||
|
||||
for stmt in func_node.body:
|
||||
did_return = process_stmt(func, module, builder, stmt, local_sym_tab,
|
||||
map_sym_tab, structs_sym_tab, did_return, ret_type)
|
||||
did_return = process_stmt(
|
||||
func,
|
||||
module,
|
||||
builder,
|
||||
stmt,
|
||||
local_sym_tab,
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
did_return,
|
||||
ret_type,
|
||||
)
|
||||
|
||||
if not did_return:
|
||||
builder.ret(ir.Constant(ir.IntType(32), 0))
|
||||
@ -439,9 +577,9 @@ def process_bpf_chunk(func_node, module, return_type, map_sym_tab, structs_sym_t
|
||||
block = func.append_basic_block(name="entry")
|
||||
builder = ir.IRBuilder(block)
|
||||
|
||||
process_func_body(module, builder, func_node, func,
|
||||
ret_type, map_sym_tab, structs_sym_tab)
|
||||
|
||||
process_func_body(
|
||||
module, builder, func_node, func, ret_type, map_sym_tab, structs_sym_tab
|
||||
)
|
||||
return func
|
||||
|
||||
|
||||
@ -449,7 +587,11 @@ def func_proc(tree, module, chunks, map_sym_tab, structs_sym_tab):
|
||||
for func_node in chunks:
|
||||
is_global = False
|
||||
for decorator in func_node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id in ("map", "bpfglobal", "struct"):
|
||||
if isinstance(decorator, ast.Name) and decorator.id in (
|
||||
"map",
|
||||
"bpfglobal",
|
||||
"struct",
|
||||
):
|
||||
is_global = True
|
||||
break
|
||||
if is_global:
|
||||
@ -457,8 +599,13 @@ def func_proc(tree, module, chunks, map_sym_tab, structs_sym_tab):
|
||||
func_type = get_probe_string(func_node)
|
||||
print(f"Found probe_string of {func_node.name}: {func_type}")
|
||||
|
||||
process_bpf_chunk(func_node, module, ctypes_to_ir(
|
||||
infer_return_type(func_node)), map_sym_tab, structs_sym_tab)
|
||||
process_bpf_chunk(
|
||||
func_node,
|
||||
module,
|
||||
ctypes_to_ir(infer_return_type(func_node)),
|
||||
map_sym_tab,
|
||||
structs_sym_tab,
|
||||
)
|
||||
|
||||
|
||||
def infer_return_type(func_node: ast.FunctionDef):
|
||||
@ -508,12 +655,60 @@ def infer_return_type(func_node: ast.FunctionDef):
|
||||
return ast.unparse(e)
|
||||
except Exception:
|
||||
return type(e).__name__
|
||||
|
||||
for node in ast.walk(func_node):
|
||||
if isinstance(node, ast.Return):
|
||||
t = _expr_type(node.value)
|
||||
if found_type is None:
|
||||
found_type = t
|
||||
elif found_type != t:
|
||||
raise ValueError("Conflicting return types:"
|
||||
f"{found_type} vs {t}")
|
||||
raise ValueError("Conflicting return types:" f"{found_type} vs {t}")
|
||||
return found_type or "None"
|
||||
|
||||
|
||||
# For string assignment to fixed-size arrays
|
||||
|
||||
|
||||
def assign_string_to_array(builder, target_array_ptr, source_string_ptr, array_length):
|
||||
"""
|
||||
Copy a string (i8*) to a fixed-size array ([N x i8]*)
|
||||
"""
|
||||
# Create a loop to copy characters one by one
|
||||
# entry_block = builder.block
|
||||
copy_block = builder.append_basic_block("copy_char")
|
||||
end_block = builder.append_basic_block("copy_end")
|
||||
|
||||
# Create loop counter
|
||||
i = builder.alloca(ir.IntType(32))
|
||||
builder.store(ir.Constant(ir.IntType(32), 0), i)
|
||||
|
||||
# Start the loop
|
||||
builder.branch(copy_block)
|
||||
|
||||
# Copy loop
|
||||
builder.position_at_end(copy_block)
|
||||
idx = builder.load(i)
|
||||
in_bounds = builder.icmp_unsigned(
|
||||
"<", idx, ir.Constant(ir.IntType(32), array_length)
|
||||
)
|
||||
builder.cbranch(in_bounds, copy_block, end_block)
|
||||
|
||||
with builder.if_then(in_bounds):
|
||||
# Load character from source
|
||||
src_ptr = builder.gep(source_string_ptr, [idx])
|
||||
char = builder.load(src_ptr)
|
||||
|
||||
# Store character in target
|
||||
dst_ptr = builder.gep(target_array_ptr, [ir.Constant(ir.IntType(32), 0), idx])
|
||||
builder.store(char, dst_ptr)
|
||||
|
||||
# Increment counter
|
||||
next_idx = builder.add(idx, ir.Constant(ir.IntType(32), 1))
|
||||
builder.store(next_idx, i)
|
||||
|
||||
builder.position_at_end(end_block)
|
||||
|
||||
# Ensure null termination
|
||||
last_idx = ir.Constant(ir.IntType(32), array_length - 1)
|
||||
null_ptr = builder.gep(target_array_ptr, [ir.Constant(ir.IntType(32), 0), last_idx])
|
||||
builder.store(ir.Constant(ir.IntType(8), 0), null_ptr)
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import ctypes
|
||||
|
||||
|
||||
def ktime():
|
||||
return ctypes.c_int64(0)
|
||||
|
||||
|
||||
def pid():
|
||||
return ctypes.c_int32(0)
|
||||
|
||||
|
||||
def deref(ptr):
|
||||
"dereference a pointer"
|
||||
result = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_void_p)).contents.value
|
||||
return result if result is not None else 0
|
||||
|
||||
|
||||
XDP_DROP = ctypes.c_int64(1)
|
||||
XDP_PASS = ctypes.c_int64(2)
|
||||
|
||||
@ -11,10 +11,10 @@ def emit_license(module: ir.Module, license_str: str):
|
||||
|
||||
gvar.initializer = ir.Constant(ty, elems) # type: ignore
|
||||
|
||||
gvar.align = 1 # type: ignore
|
||||
gvar.linkage = "dso_local" # type: ignore
|
||||
gvar.align = 1 # type: ignore
|
||||
gvar.linkage = "dso_local" # type: ignore
|
||||
gvar.global_constant = False
|
||||
gvar.section = "license" # type: ignore
|
||||
gvar.section = "license" # type: ignore
|
||||
|
||||
return gvar
|
||||
|
||||
@ -26,7 +26,8 @@ def license_processing(tree, module):
|
||||
if isinstance(node, ast.FunctionDef) and node.name == "LICENSE":
|
||||
# check decorators
|
||||
decorators = [
|
||||
dec.id for dec in node.decorator_list if isinstance(dec, ast.Name)]
|
||||
dec.id for dec in node.decorator_list if isinstance(dec, ast.Name)
|
||||
]
|
||||
if "bpf" in decorators and "bpfglobal" in decorators:
|
||||
if count == 0:
|
||||
count += 1
|
||||
|
||||
4
pythonbpf/maps/__init__.py
Normal file
4
pythonbpf/maps/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .maps import HashMap, PerfEventArray
|
||||
from .maps_pass import maps_proc
|
||||
|
||||
__all__ = ["HashMap", "PerfEventArray", "maps_proc"]
|
||||
@ -30,3 +30,6 @@ class PerfEventArray:
|
||||
self.key_type = key_size
|
||||
self.value_type = value_size
|
||||
self.entries = {}
|
||||
|
||||
def output(self, data):
|
||||
pass # Placeholder for output method
|
||||
198
pythonbpf/maps/maps_pass.py
Normal file
198
pythonbpf/maps/maps_pass.py
Normal file
@ -0,0 +1,198 @@
|
||||
import ast
|
||||
from llvmlite import ir
|
||||
from enum import Enum
|
||||
from .maps_utils import MapProcessorRegistry
|
||||
from ..debuginfo import DebugInfoGenerator
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def maps_proc(tree, module, chunks):
|
||||
"""Process all functions decorated with @map to find BPF maps"""
|
||||
map_sym_tab = {}
|
||||
for func_node in chunks:
|
||||
if is_map(func_node):
|
||||
print(f"Found BPF map: {func_node.name}")
|
||||
map_sym_tab[func_node.name] = process_bpf_map(func_node, module)
|
||||
return map_sym_tab
|
||||
|
||||
|
||||
def is_map(func_node):
|
||||
return any(
|
||||
isinstance(decorator, ast.Name) and decorator.id == "map"
|
||||
for decorator in func_node.decorator_list
|
||||
)
|
||||
|
||||
|
||||
class BPFMapType(Enum):
|
||||
HASH = 1
|
||||
PERF_EVENT_ARRAY = 4
|
||||
|
||||
|
||||
def create_bpf_map(module, map_name, map_params):
|
||||
"""Create a BPF map in the module with given parameters and debug info"""
|
||||
|
||||
# Create the anonymous struct type for BPF map
|
||||
map_struct_type = ir.LiteralStructType(
|
||||
[ir.PointerType() for _ in range(len(map_params))]
|
||||
)
|
||||
|
||||
# Create the global variable
|
||||
map_global = ir.GlobalVariable(module, map_struct_type, name=map_name)
|
||||
map_global.linkage = "dso_local"
|
||||
map_global.global_constant = False
|
||||
map_global.initializer = ir.Constant(map_struct_type, None)
|
||||
map_global.section = ".maps"
|
||||
map_global.align = 8
|
||||
|
||||
logger.info(f"Created BPF map: {map_name} with params {map_params}")
|
||||
return map_global
|
||||
|
||||
|
||||
def create_map_debug_info(module, map_global, map_name, map_params):
|
||||
"""Generate debug information metadata for BPF map"""
|
||||
generator = DebugInfoGenerator(module)
|
||||
|
||||
uint_type = generator.get_uint32_type()
|
||||
ulong_type = generator.get_uint64_type()
|
||||
array_type = generator.create_array_type(
|
||||
uint_type, map_params.get("type", BPFMapType.HASH).value
|
||||
)
|
||||
type_ptr = generator.create_pointer_type(array_type, 64)
|
||||
key_ptr = generator.create_pointer_type(
|
||||
array_type if "key_size" in map_params else ulong_type, 64
|
||||
)
|
||||
value_ptr = generator.create_pointer_type(
|
||||
array_type if "value_size" in map_params else ulong_type, 64
|
||||
)
|
||||
|
||||
elements_arr = []
|
||||
|
||||
# Create struct members
|
||||
# scope field does not appear for some reason
|
||||
cnt = 0
|
||||
for elem in map_params:
|
||||
if elem == "max_entries":
|
||||
continue
|
||||
if elem == "type":
|
||||
ptr = type_ptr
|
||||
elif "key" in elem:
|
||||
ptr = key_ptr
|
||||
else:
|
||||
ptr = value_ptr
|
||||
# TODO: the best way to do this is not 64, but get the size each time. this will not work for structs.
|
||||
member = generator.create_struct_member(elem, ptr, cnt * 64)
|
||||
elements_arr.append(member)
|
||||
cnt += 1
|
||||
|
||||
if "max_entries" in map_params:
|
||||
max_entries_array = generator.create_array_type(
|
||||
uint_type, map_params["max_entries"]
|
||||
)
|
||||
max_entries_ptr = generator.create_pointer_type(max_entries_array, 64)
|
||||
max_entries_member = generator.create_struct_member(
|
||||
"max_entries", max_entries_ptr, cnt * 64
|
||||
)
|
||||
elements_arr.append(max_entries_member)
|
||||
|
||||
# Create the struct type
|
||||
struct_type = generator.create_struct_type(
|
||||
elements_arr, 64 * len(elements_arr), is_distinct=True
|
||||
)
|
||||
|
||||
# Create global variable debug info
|
||||
global_var = generator.create_global_var_debug_info(
|
||||
map_name, struct_type, is_local=False
|
||||
)
|
||||
|
||||
# Attach debug info to the global variable
|
||||
map_global.set_metadata("dbg", global_var)
|
||||
|
||||
return global_var
|
||||
|
||||
|
||||
@MapProcessorRegistry.register("HashMap")
|
||||
def process_hash_map(map_name, rval, module):
|
||||
"""Process a BPF_HASH map declaration"""
|
||||
logger.info(f"Processing HashMap: {map_name}")
|
||||
map_params = {"type": BPFMapType.HASH}
|
||||
|
||||
# Assuming order: key_type, value_type, max_entries
|
||||
if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Name):
|
||||
map_params["key"] = rval.args[0].id
|
||||
if len(rval.args) >= 2 and isinstance(rval.args[1], ast.Name):
|
||||
map_params["value"] = rval.args[1].id
|
||||
if len(rval.args) >= 3 and isinstance(rval.args[2], ast.Constant):
|
||||
const_val = rval.args[2].value
|
||||
if isinstance(const_val, (int, str)): # safe check
|
||||
map_params["max_entries"] = const_val
|
||||
|
||||
for keyword in rval.keywords:
|
||||
if keyword.arg == "key" and isinstance(keyword.value, ast.Name):
|
||||
map_params["key"] = keyword.value.id
|
||||
elif keyword.arg == "value" and isinstance(keyword.value, ast.Name):
|
||||
map_params["value"] = keyword.value.id
|
||||
elif keyword.arg == "max_entries" and isinstance(keyword.value, ast.Constant):
|
||||
const_val = keyword.value.value
|
||||
if isinstance(const_val, (int, str)):
|
||||
map_params["max_entries"] = const_val
|
||||
|
||||
logger.info(f"Map parameters: {map_params}")
|
||||
map_global = create_bpf_map(module, map_name, map_params)
|
||||
# Generate debug info for BTF
|
||||
create_map_debug_info(module, map_global, map_name, map_params)
|
||||
return map_global
|
||||
|
||||
|
||||
@MapProcessorRegistry.register("PerfEventArray")
|
||||
def process_perf_event_map(map_name, rval, module):
|
||||
"""Process a BPF_PERF_EVENT_ARRAY map declaration"""
|
||||
logger.info(f"Processing PerfEventArray: {map_name}")
|
||||
map_params = {"type": BPFMapType.PERF_EVENT_ARRAY}
|
||||
|
||||
if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Name):
|
||||
map_params["key_size"] = rval.args[0].id
|
||||
if len(rval.args) >= 2 and isinstance(rval.args[1], ast.Name):
|
||||
map_params["value_size"] = rval.args[1].id
|
||||
|
||||
for keyword in rval.keywords:
|
||||
if keyword.arg == "key_size" and isinstance(keyword.value, ast.Name):
|
||||
map_params["key_size"] = keyword.value.id
|
||||
elif keyword.arg == "value_size" and isinstance(keyword.value, ast.Name):
|
||||
map_params["value_size"] = keyword.value.id
|
||||
|
||||
logger.info(f"Map parameters: {map_params}")
|
||||
map_global = create_bpf_map(module, map_name, map_params)
|
||||
# Generate debug info for BTF
|
||||
create_map_debug_info(module, map_global, map_name, map_params)
|
||||
return map_global
|
||||
|
||||
|
||||
def process_bpf_map(func_node, module):
|
||||
"""Process a BPF map (a function decorated with @map)"""
|
||||
map_name = func_node.name
|
||||
logger.info(f"Processing BPF map: {map_name}")
|
||||
|
||||
# For now, assume single return statement
|
||||
return_stmt = None
|
||||
for stmt in func_node.body:
|
||||
if isinstance(stmt, ast.Return):
|
||||
return_stmt = stmt
|
||||
break
|
||||
if return_stmt is None:
|
||||
raise ValueError("BPF map must have a return statement")
|
||||
|
||||
rval = return_stmt.value
|
||||
|
||||
if isinstance(rval, ast.Call) and isinstance(rval.func, ast.Name):
|
||||
handler = MapProcessorRegistry.get_processor(rval.func.id)
|
||||
if handler:
|
||||
return handler(map_name, rval, module)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Unknown map type " f"{rval.func.id}, defaulting to HashMap"
|
||||
)
|
||||
return process_hash_map(map_name, rval, module)
|
||||
else:
|
||||
raise ValueError("Function under @map must return a map")
|
||||
19
pythonbpf/maps/maps_utils.py
Normal file
19
pythonbpf/maps/maps_utils.py
Normal file
@ -0,0 +1,19 @@
|
||||
class MapProcessorRegistry:
|
||||
"""Registry for map processor functions"""
|
||||
|
||||
_processors = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, map_type_name):
|
||||
"""Decorator to register a processor function for a map type"""
|
||||
|
||||
def decorator(func):
|
||||
cls._processors[map_type_name] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def get_processor(cls, map_type_name):
|
||||
"""Get the processor function for a map type"""
|
||||
return cls._processors.get(map_type_name)
|
||||
@ -1,262 +0,0 @@
|
||||
import ast
|
||||
from llvmlite import ir
|
||||
from .type_deducer import ctypes_to_ir
|
||||
from . import dwarf_constants as dc
|
||||
|
||||
map_sym_tab = {}
|
||||
|
||||
|
||||
def maps_proc(tree, module, chunks):
|
||||
for func_node in chunks:
|
||||
# Check if this function is a map
|
||||
is_map = False
|
||||
for decorator in func_node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id == "map":
|
||||
is_map = True
|
||||
break
|
||||
if is_map:
|
||||
print(f"Found BPF map: {func_node.name}")
|
||||
process_bpf_map(func_node, module)
|
||||
continue
|
||||
return map_sym_tab
|
||||
|
||||
|
||||
BPF_MAP_MAPPINGS = {
|
||||
"HASH": 1, # BPF_MAP_TYPE_HASH
|
||||
"PERF_EVENT_ARRAY": 4, # BPF_MAP_TYPE_PERF_EVENT_ARRAY
|
||||
}
|
||||
|
||||
|
||||
def create_bpf_map(module, map_name, map_params):
|
||||
"""Create a BPF map in the module with the given parameters and debug info"""
|
||||
|
||||
map_type_str = map_params.get("type", "HASH")
|
||||
map_type = BPF_MAP_MAPPINGS.get(map_type_str)
|
||||
|
||||
# Create the anonymous struct type for BPF map
|
||||
map_struct_type = ir.LiteralStructType(
|
||||
[ir.PointerType() for _ in range(len(map_params))])
|
||||
|
||||
# Create the global variable
|
||||
map_global = ir.GlobalVariable(module, map_struct_type, name=map_name)
|
||||
map_global.linkage = 'dso_local'
|
||||
map_global.global_constant = False
|
||||
map_global.initializer = ir.Constant(
|
||||
map_struct_type, None) # type: ignore
|
||||
map_global.section = ".maps"
|
||||
map_global.align = 8 # type: ignore
|
||||
|
||||
# Generate debug info for BTF
|
||||
create_map_debug_info(module, map_global, map_name, map_params)
|
||||
|
||||
print(f"Created BPF map: {map_name}")
|
||||
map_sym_tab[map_name] = map_global
|
||||
return map_global
|
||||
|
||||
|
||||
def create_map_debug_info(module, map_global, map_name, map_params):
|
||||
"""Generate debug information metadata for BPF map"""
|
||||
file_metadata = module._file_metadata
|
||||
compile_unit = module._debug_compile_unit
|
||||
|
||||
# Create basic type for unsigned int (32-bit)
|
||||
uint_type = module.add_debug_info("DIBasicType", {
|
||||
"name": "unsigned int",
|
||||
"size": 32,
|
||||
"encoding": dc.DW_ATE_unsigned
|
||||
})
|
||||
|
||||
# Create basic type for unsigned long long (64-bit)
|
||||
ulong_type = module.add_debug_info("DIBasicType", {
|
||||
"name": "unsigned long long",
|
||||
"size": 64,
|
||||
"encoding": dc.DW_ATE_unsigned
|
||||
})
|
||||
|
||||
# Create array type for map type field (array of 1 unsigned int)
|
||||
array_subrange = module.add_debug_info(
|
||||
"DISubrange", {"count": BPF_MAP_MAPPINGS[map_params.get("type", "HASH")]})
|
||||
array_type = module.add_debug_info("DICompositeType", {
|
||||
"tag": dc.DW_TAG_array_type,
|
||||
"baseType": uint_type,
|
||||
"size": 32,
|
||||
"elements": [array_subrange]
|
||||
})
|
||||
|
||||
# Create pointer types
|
||||
type_ptr = module.add_debug_info("DIDerivedType", {
|
||||
"tag": dc.DW_TAG_pointer_type,
|
||||
"baseType": array_type,
|
||||
"size": 64
|
||||
})
|
||||
|
||||
key_ptr = module.add_debug_info("DIDerivedType", {
|
||||
"tag": dc.DW_TAG_pointer_type,
|
||||
# Adjust based on actual key type
|
||||
"baseType": array_type if "key_size" in map_params else uint_type,
|
||||
"size": 64
|
||||
})
|
||||
|
||||
value_ptr = module.add_debug_info("DIDerivedType", {
|
||||
"tag": dc.DW_TAG_pointer_type,
|
||||
# Adjust based on actual value type
|
||||
"baseType": array_type if "value_size" in map_params else ulong_type,
|
||||
"size": 64
|
||||
})
|
||||
|
||||
elements_arr = []
|
||||
|
||||
# Create struct members
|
||||
# scope field does not appear for some reason
|
||||
cnt = 0
|
||||
for elem in map_params:
|
||||
if elem == "max_entries":
|
||||
continue
|
||||
if elem == "type":
|
||||
ptr = type_ptr
|
||||
elif "key" in elem:
|
||||
ptr = key_ptr
|
||||
else:
|
||||
ptr = value_ptr
|
||||
member = module.add_debug_info("DIDerivedType", {
|
||||
"tag": dc.DW_TAG_member,
|
||||
"name": elem,
|
||||
"file": file_metadata,
|
||||
"baseType": ptr,
|
||||
"size": 64,
|
||||
"offset": cnt * 64
|
||||
})
|
||||
elements_arr.append(member)
|
||||
cnt += 1
|
||||
|
||||
if "max_entries" in map_params:
|
||||
array_subrange_max_entries = module.add_debug_info(
|
||||
"DISubrange", {"count": map_params["max_entries"]})
|
||||
array_type_max_entries = module.add_debug_info("DICompositeType", {
|
||||
"tag": dc.DW_TAG_array_type,
|
||||
"baseType": uint_type,
|
||||
"size": 32,
|
||||
"elements": [array_subrange_max_entries]
|
||||
})
|
||||
max_entries_ptr = module.add_debug_info("DIDerivedType", {
|
||||
"tag": dc.DW_TAG_pointer_type,
|
||||
"baseType": array_type_max_entries,
|
||||
"size": 64
|
||||
})
|
||||
max_entries_member = module.add_debug_info("DIDerivedType", {
|
||||
"tag": dc.DW_TAG_member,
|
||||
"name": "max_entries",
|
||||
"file": file_metadata,
|
||||
"baseType": max_entries_ptr,
|
||||
"size": 64,
|
||||
"offset": cnt * 64
|
||||
})
|
||||
elements_arr.append(max_entries_member)
|
||||
|
||||
# Create the struct type
|
||||
struct_type = module.add_debug_info("DICompositeType", {
|
||||
"tag": dc.DW_TAG_structure_type,
|
||||
"file": file_metadata,
|
||||
"size": 64 * len(elements_arr), # 4 * 64-bit pointers
|
||||
"elements": elements_arr,
|
||||
}, is_distinct=True)
|
||||
|
||||
# Create global variable debug info
|
||||
global_var = module.add_debug_info("DIGlobalVariable", {
|
||||
"name": map_name,
|
||||
"scope": compile_unit,
|
||||
"file": file_metadata,
|
||||
"type": struct_type,
|
||||
"isLocal": False,
|
||||
"isDefinition": True
|
||||
}, is_distinct=True)
|
||||
|
||||
# Create global variable expression
|
||||
global_var_expr = module.add_debug_info("DIGlobalVariableExpression", {
|
||||
"var": global_var,
|
||||
"expr": module.add_debug_info("DIExpression", {})
|
||||
})
|
||||
|
||||
# Attach debug info to the global variable
|
||||
map_global.set_metadata("dbg", global_var_expr)
|
||||
|
||||
return global_var_expr
|
||||
|
||||
|
||||
def process_hash_map(map_name, rval, module):
|
||||
print(f"Creating HashMap map: {map_name}")
|
||||
map_params: dict[str, object] = {"type": "HASH"}
|
||||
|
||||
# Assuming order: key_type, value_type, max_entries
|
||||
if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Name):
|
||||
map_params["key"] = rval.args[0].id
|
||||
if len(rval.args) >= 2 and isinstance(rval.args[1], ast.Name):
|
||||
map_params["value"] = rval.args[1].id
|
||||
if len(rval.args) >= 3 and isinstance(rval.args[2], ast.Constant):
|
||||
const_val = rval.args[2].value
|
||||
if isinstance(const_val, (int, str)): # safe check
|
||||
map_params["max_entries"] = const_val
|
||||
|
||||
for keyword in rval.keywords:
|
||||
if keyword.arg == "key" and isinstance(keyword.value, ast.Name):
|
||||
map_params["key"] = keyword.value.id
|
||||
elif keyword.arg == "value" and isinstance(keyword.value, ast.Name):
|
||||
map_params["value"] = keyword.value.id
|
||||
elif keyword.arg == "max_entries" and isinstance(keyword.value, ast.Constant):
|
||||
const_val = keyword.value.value
|
||||
if isinstance(const_val, (int, str)):
|
||||
map_params["max_entries"] = const_val
|
||||
|
||||
print(f"Map parameters: {map_params}")
|
||||
return create_bpf_map(module, map_name, map_params)
|
||||
|
||||
|
||||
def process_perf_event_map(map_name, rval, module):
|
||||
print(f"Creating PerfEventArray map: {map_name}")
|
||||
map_params = {"type": "PERF_EVENT_ARRAY"}
|
||||
|
||||
if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Name):
|
||||
map_params["key_size"] = rval.args[0].id
|
||||
if len(rval.args) >= 2 and isinstance(rval.args[1], ast.Name):
|
||||
map_params["value_size"] = rval.args[1].id
|
||||
|
||||
for keyword in rval.keywords:
|
||||
if keyword.arg == "key_size" and isinstance(keyword.value, ast.Name):
|
||||
map_params["key_size"] = keyword.value.id
|
||||
elif keyword.arg == "value_size" and isinstance(keyword.value, ast.Name):
|
||||
map_params["value_size"] = keyword.value.id
|
||||
|
||||
print(f"Map parameters: {map_params}")
|
||||
return create_bpf_map(module, map_name, map_params)
|
||||
|
||||
|
||||
def process_bpf_map(func_node, module):
|
||||
"""Process a BPF map (a function decorated with @map)"""
|
||||
map_name = func_node.name
|
||||
print(f"Processing BPF map: {map_name}")
|
||||
|
||||
BPF_MAP_TYPES = {"HashMap": process_hash_map, # BPF_MAP_TYPE_HASH
|
||||
"PerfEventArray": process_perf_event_map, # BPF_MAP_TYPE_PERF_EVENT_ARRAY
|
||||
}
|
||||
|
||||
# For now, assume single return statement
|
||||
return_stmt = None
|
||||
for stmt in func_node.body:
|
||||
if isinstance(stmt, ast.Return):
|
||||
return_stmt = stmt
|
||||
break
|
||||
if return_stmt is None:
|
||||
raise ValueError("BPF map must have a return statement")
|
||||
|
||||
rval = return_stmt.value
|
||||
|
||||
# Handle only HashMap maps
|
||||
if isinstance(rval, ast.Call) and isinstance(rval.func, ast.Name):
|
||||
if rval.func.id in BPF_MAP_TYPES:
|
||||
handler = BPF_MAP_TYPES[rval.func.id]
|
||||
handler(map_name, rval, module)
|
||||
else:
|
||||
print(f"Unknown map type {rval.func.id}, defaulting to HashMap")
|
||||
process_hash_map(map_name, rval, module)
|
||||
else:
|
||||
raise ValueError("Function under @map must return a map")
|
||||
3
pythonbpf/structs/__init__.py
Normal file
3
pythonbpf/structs/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .structs_pass import structs_proc
|
||||
|
||||
__all__ = ["structs_proc"]
|
||||
33
pythonbpf/structs/struct_type.py
Normal file
33
pythonbpf/structs/struct_type.py
Normal file
@ -0,0 +1,33 @@
|
||||
from llvmlite import ir
|
||||
|
||||
|
||||
class StructType:
|
||||
def __init__(self, ir_type, fields, size):
|
||||
self.ir_type = ir_type
|
||||
self.fields = fields
|
||||
self.size = size
|
||||
|
||||
def field_idx(self, field_name):
|
||||
return list(self.fields.keys()).index(field_name)
|
||||
|
||||
def field_type(self, field_name):
|
||||
return self.fields[field_name]
|
||||
|
||||
def gep(self, builder, ptr, field_name):
|
||||
idx = self.field_idx(field_name)
|
||||
return builder.gep(
|
||||
ptr,
|
||||
[ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), idx)],
|
||||
inbounds=True,
|
||||
)
|
||||
|
||||
def field_size(self, field_name):
|
||||
fld = self.fields[field_name]
|
||||
if isinstance(fld, ir.ArrayType):
|
||||
return fld.count * (fld.element.width // 8)
|
||||
elif isinstance(fld, ir.IntType):
|
||||
return fld.width // 8
|
||||
elif isinstance(fld, ir.PointerType):
|
||||
return 8
|
||||
|
||||
raise TypeError(f"Unsupported field type: {fld}")
|
||||
95
pythonbpf/structs/structs_pass.py
Normal file
95
pythonbpf/structs/structs_pass.py
Normal file
@ -0,0 +1,95 @@
|
||||
import ast
|
||||
import logging
|
||||
from llvmlite import ir
|
||||
from pythonbpf.type_deducer import ctypes_to_ir
|
||||
from .struct_type import StructType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# TODO: Shall we allow the following syntax:
|
||||
# struct MyStruct:
|
||||
# field1: int
|
||||
# field2: str(32)
|
||||
# Where int is mapped to c_uint64?
|
||||
# Shall we just int64, int32 and uint32 similarly?
|
||||
|
||||
|
||||
def structs_proc(tree, module, chunks):
|
||||
"""Process all class definitions to find BPF structs"""
|
||||
structs_sym_tab = {}
|
||||
for cls_node in chunks:
|
||||
if is_bpf_struct(cls_node):
|
||||
print(f"Found BPF struct: {cls_node.name}")
|
||||
struct_info = process_bpf_struct(cls_node, module)
|
||||
structs_sym_tab[cls_node.name] = struct_info
|
||||
return structs_sym_tab
|
||||
|
||||
|
||||
def is_bpf_struct(cls_node):
|
||||
return any(
|
||||
isinstance(decorator, ast.Name) and decorator.id == "struct"
|
||||
for decorator in cls_node.decorator_list
|
||||
)
|
||||
|
||||
|
||||
def process_bpf_struct(cls_node, module):
|
||||
"""Process a single BPF struct definition"""
|
||||
|
||||
fields = parse_struct_fields(cls_node)
|
||||
field_types = list(fields.values())
|
||||
total_size = calc_struct_size(field_types)
|
||||
struct_type = ir.LiteralStructType(field_types)
|
||||
logger.info(f"Created struct {cls_node.name} with fields {fields.keys()}")
|
||||
return StructType(struct_type, fields, total_size)
|
||||
|
||||
|
||||
def parse_struct_fields(cls_node):
|
||||
"""Parse fields of a struct class node"""
|
||||
fields = {}
|
||||
|
||||
for item in cls_node.body:
|
||||
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
|
||||
fields[item.target.id] = get_type_from_ann(item.annotation)
|
||||
else:
|
||||
logger.error(f"Unsupported struct field: {ast.dump(item)}")
|
||||
raise TypeError(f"Unsupported field in {ast.dump(cls_node)}")
|
||||
return fields
|
||||
|
||||
|
||||
def get_type_from_ann(annotation):
|
||||
"""Convert an AST annotation node to an LLVM IR type for struct fields"""
|
||||
if isinstance(annotation, ast.Call) and isinstance(annotation.func, ast.Name):
|
||||
if annotation.func.id == "str":
|
||||
# Char array
|
||||
# Assumes constant integer argument
|
||||
length = annotation.args[0].value
|
||||
return ir.ArrayType(ir.IntType(8), length)
|
||||
elif isinstance(annotation, ast.Name):
|
||||
# Int type, written as c_int64, c_uint32, etc.
|
||||
return ctypes_to_ir(annotation.id)
|
||||
|
||||
raise TypeError(f"Unsupported annotation type: {ast.dump(annotation)}")
|
||||
|
||||
|
||||
def calc_struct_size(field_types):
|
||||
"""Calculate total size of the struct with alignment and padding"""
|
||||
curr_offset = 0
|
||||
for ftype in field_types:
|
||||
if isinstance(ftype, ir.IntType):
|
||||
fsize = ftype.width // 8
|
||||
alignment = fsize
|
||||
elif isinstance(ftype, ir.ArrayType):
|
||||
fsize = ftype.count * (ftype.element.width // 8)
|
||||
alignment = ftype.element.width // 8
|
||||
elif isinstance(ftype, ir.PointerType):
|
||||
# We won't encounter this rn, but for the future
|
||||
fsize = 8
|
||||
alignment = 8
|
||||
else:
|
||||
raise TypeError(f"Unsupported field type: {ftype}")
|
||||
|
||||
padding = (alignment - (curr_offset % alignment)) % alignment
|
||||
curr_offset += padding + fsize
|
||||
|
||||
final_padding = (8 - (curr_offset % 8)) % 8
|
||||
return curr_offset + final_padding
|
||||
@ -1,39 +0,0 @@
|
||||
import ast
|
||||
from llvmlite import ir
|
||||
from .type_deducer import ctypes_to_ir
|
||||
from . import dwarf_constants as dc
|
||||
|
||||
structs_sym_tab = {}
|
||||
|
||||
|
||||
def structs_proc(tree, module, chunks):
|
||||
for cls_node in chunks:
|
||||
# Check if this class is a struct
|
||||
is_struct = False
|
||||
for decorator in cls_node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id == "struct":
|
||||
is_struct = True
|
||||
break
|
||||
if is_struct:
|
||||
print(f"Found BPF struct: {cls_node.name}")
|
||||
process_bpf_struct(cls_node, module)
|
||||
continue
|
||||
return structs_sym_tab
|
||||
|
||||
|
||||
def process_bpf_struct(cls_node, module):
|
||||
struct_name = cls_node.name
|
||||
field_names = []
|
||||
field_types = []
|
||||
|
||||
for item in cls_node.body:
|
||||
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
|
||||
field_names.append(item.target.id)
|
||||
field_types.append(ctypes_to_ir(item.annotation.id))
|
||||
|
||||
struct_type = ir.LiteralStructType(field_types)
|
||||
structs_sym_tab[struct_name] = {
|
||||
"type": struct_type,
|
||||
"fields": {name: idx for idx, name in enumerate(field_names)}
|
||||
}
|
||||
print(f"Created struct {struct_name} with fields {field_names}")
|
||||
@ -1,54 +0,0 @@
|
||||
class TraceEvent:
|
||||
def __init__(self, timestamp, comm, pid, cpu, flags, message):
|
||||
"""Represents a parsed trace pipe event"""
|
||||
self.timestamp = timestamp # float: timestamp in seconds
|
||||
self.comm = comm # str: command name
|
||||
self.pid = pid # int: process ID
|
||||
self.cpu = cpu # int: CPU number
|
||||
self.flags = flags # str: trace flags
|
||||
self.message = message # str: the actual message
|
||||
|
||||
def __iter__(self):
|
||||
"""Allow unpacking like the original BCC tuple"""
|
||||
yield self.comm
|
||||
yield self.pid
|
||||
yield self.cpu
|
||||
yield self.flags
|
||||
yield self.timestamp
|
||||
yield self.message
|
||||
|
||||
|
||||
class TraceReader:
|
||||
def __init__(self, trace_pipe_path="/sys/kernel/debug/tracing/trace_pipe"):
|
||||
self.trace_pipe_path = trace_pipe_path
|
||||
self.file = None
|
||||
|
||||
def __enter__(self):
|
||||
self.file = open(self.trace_pipe_path, "r")
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.file:
|
||||
self.file.close()
|
||||
|
||||
def __iter__(self):
|
||||
while True:
|
||||
event = self.trace_fields()
|
||||
if event:
|
||||
yield event
|
||||
|
||||
def trace_fields(self):
|
||||
"""Read and parse one line from the trace pipe"""
|
||||
if not self.file:
|
||||
self.file = open(self.trace_pipe_path, "r")
|
||||
line = self.file.readline()
|
||||
if not line:
|
||||
return None
|
||||
# Parse the line into components (simplified)
|
||||
# Real implementation would need more robust parsing
|
||||
parts = self._parse_trace_line(line)
|
||||
return TraceEvent(*parts)
|
||||
|
||||
def _parse_trace_line(self, line):
|
||||
# TODO: Implement
|
||||
pass
|
||||
@ -17,7 +17,7 @@ def ctypes_to_ir(ctype: str):
|
||||
"c_double": ir.DoubleType(),
|
||||
"c_void_p": ir.IntType(64),
|
||||
# Not so sure about this one
|
||||
"str": ir.PointerType(ir.IntType(8))
|
||||
"str": ir.PointerType(ir.IntType(8)),
|
||||
}
|
||||
if ctype in mapping:
|
||||
return mapping[ctype]
|
||||
|
||||
@ -23,20 +23,20 @@ SEC("tracepoint/syscalls/sys_enter_clone")
|
||||
int hello(struct pt_regs *ctx)
|
||||
{
|
||||
struct data_t data = {};
|
||||
|
||||
|
||||
// Get PID (lower 32 bits of the 64-bit value returned)
|
||||
data.pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF;
|
||||
|
||||
|
||||
// Get timestamp
|
||||
data.ts = bpf_ktime_get_ns();
|
||||
|
||||
|
||||
// Get current process name
|
||||
// bpf_get_current_comm(&data.comm, sizeof(data.comm));
|
||||
|
||||
|
||||
// Submit data to userspace via perf event
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
|
||||
&data, sizeof(data));
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
47
tests/c-form/ex7.bpf.c
Normal file
47
tests/c-form/ex7.bpf.c
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
struct trace_entry {
|
||||
short unsigned int type;
|
||||
unsigned char flags;
|
||||
unsigned char preempt_count;
|
||||
int pid;
|
||||
};
|
||||
|
||||
struct trace_event_raw_sys_enter {
|
||||
struct trace_entry ent;
|
||||
long int id;
|
||||
long unsigned int args[6];
|
||||
char __data[0];
|
||||
};
|
||||
|
||||
struct event {
|
||||
__u32 pid;
|
||||
__u32 uid;
|
||||
__u64 ts;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(int));
|
||||
__uint(value_size, sizeof(int));
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("tp/syscalls/sys_enter_setuid")
|
||||
int handle_setuid_entry(struct trace_event_raw_sys_enter *ctx) {
|
||||
struct event data = {};
|
||||
|
||||
// Extract UID from the syscall arguments
|
||||
data.uid = (unsigned int)ctx->args[0];
|
||||
data.ts = bpf_ktime_get_ns();
|
||||
data.pid = bpf_get_current_pid_tgid() >> 32;
|
||||
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
47
tests/c-form/ex8.bpf.c
Normal file
47
tests/c-form/ex8.bpf.c
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <linux/blkdev.h>
|
||||
#define __TARGET_ARCH_aarch64
|
||||
#define u64 unsigned long long
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 10240);
|
||||
__type(key, struct request *);
|
||||
__type(value, u64);
|
||||
} start SEC(".maps");
|
||||
|
||||
SEC("kprobe/blk_start_request")
|
||||
int BPF_KPROBE(trace_start_req, struct request *req)
|
||||
{
|
||||
u64 ts = bpf_ktime_get_ns();
|
||||
bpf_map_update_elem(&start, &req, &ts, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kprobe/blk_mq_start_request")
|
||||
int BPF_KPROBE(trace_start_mq, struct request *req)
|
||||
{
|
||||
u64 ts = bpf_ktime_get_ns();
|
||||
bpf_map_update_elem(&start, &req, &ts, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kprobe/blk_account_io_completion")
|
||||
int BPF_KPROBE(trace_completion, struct request *req)
|
||||
{
|
||||
u64 *tsp, delta;
|
||||
|
||||
tsp = bpf_map_lookup_elem(&start, &req);
|
||||
if (tsp) {
|
||||
delta = bpf_ktime_get_ns() - *tsp;
|
||||
bpf_printk("%d %x %d\n", req->__data_len,
|
||||
req->cmd_flags, delta / 1000);
|
||||
bpf_map_delete_elem(&start, &req);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
121617
tests/c-form/vmlinux.h
vendored
Normal file
121617
tests/c-form/vmlinux.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
tests/failing_tests/binops.py
Normal file
19
tests/failing_tests/binops.py
Normal file
@ -0,0 +1,19 @@
|
||||
from pythonbpf import compile, bpf, section, bpfglobal
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
|
||||
@bpf
|
||||
@section("sometag1")
|
||||
def sometag(ctx: c_void_p) -> c_int64:
|
||||
a = 1 + 2 + 1
|
||||
print(f"{a}")
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
19
tests/failing_tests/binops1.py
Normal file
19
tests/failing_tests/binops1.py
Normal file
@ -0,0 +1,19 @@
|
||||
from pythonbpf import compile, bpf, section, bpfglobal
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
|
||||
@bpf
|
||||
@section("sometag1")
|
||||
def sometag(ctx: c_void_p) -> c_int64:
|
||||
b = 1 + 2
|
||||
a = 1 + b
|
||||
return c_int64(a)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
33
tests/failing_tests/direct_assign.py
Normal file
33
tests/failing_tests/direct_assign.py
Normal file
@ -0,0 +1,33 @@
|
||||
from pythonbpf import bpf, map, section, bpfglobal, compile
|
||||
from pythonbpf.helpers import XDP_PASS
|
||||
from pythonbpf.maps import HashMap
|
||||
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
|
||||
@bpf
|
||||
@map
|
||||
def count() -> HashMap:
|
||||
return HashMap(key=c_int64, value=c_int64, max_entries=1)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("xdp")
|
||||
def hello_world(ctx: c_void_p) -> c_int64:
|
||||
prev = count().lookup(0)
|
||||
if prev:
|
||||
count().update(0, prev + 1)
|
||||
return XDP_PASS
|
||||
else:
|
||||
count().update(0, 1)
|
||||
|
||||
return XDP_PASS
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
19
tests/failing_tests/if.py
Normal file
19
tests/failing_tests/if.py
Normal file
@ -0,0 +1,19 @@
|
||||
from pythonbpf import compile, bpf, section, bpfglobal
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
|
||||
@bpf
|
||||
@section("sometag1")
|
||||
def sometag(ctx: c_void_p) -> c_int64:
|
||||
if 3 + 2 == 5:
|
||||
return c_int64(5)
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
14
tests/failing_tests/license.py
Normal file
14
tests/failing_tests/license.py
Normal file
@ -0,0 +1,14 @@
|
||||
from pythonbpf import compile, bpf, section
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
|
||||
# FAILS WHEN THERE IS NO LICENSE. which is wrong.
|
||||
@bpf
|
||||
@section("sometag1")
|
||||
def sometag(ctx: c_void_p) -> c_int64:
|
||||
a = 1 + 2
|
||||
print(f"{a}")
|
||||
return c_int64(0)
|
||||
|
||||
|
||||
compile()
|
||||
17
tests/failing_tests/return.py
Normal file
17
tests/failing_tests/return.py
Normal file
@ -0,0 +1,17 @@
|
||||
from pythonbpf import compile, bpf, section, bpfglobal
|
||||
from ctypes import c_void_p, c_int64
|
||||
|
||||
|
||||
@bpf
|
||||
@section("sometag1")
|
||||
def sometag(ctx: c_void_p) -> c_int64:
|
||||
return c_int64(1 - 1)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
compile()
|
||||
47
tests/passing_tests/hash_map.py
Normal file
47
tests/passing_tests/hash_map.py
Normal file
@ -0,0 +1,47 @@
|
||||
from pythonbpf import bpf, map, bpfglobal, BPF, section
|
||||
from pythonbpf.maps import HashMap
|
||||
from pylibbpf import BpfMap
|
||||
from ctypes import c_int32, c_uint64, c_void_p
|
||||
|
||||
|
||||
# Define a map
|
||||
@bpf
|
||||
@map
|
||||
def mymap() -> HashMap:
|
||||
return HashMap(key=c_int32, value=c_uint64, max_entries=16)
|
||||
|
||||
|
||||
@bpf
|
||||
@section("tracepoint/syscalls/sys_enter_clone")
|
||||
def testing(ctx: c_void_p) -> c_int32:
|
||||
return c_int32(0)
|
||||
|
||||
|
||||
@bpf
|
||||
@bpfglobal
|
||||
def LICENSE() -> str:
|
||||
return "GPL"
|
||||
|
||||
|
||||
# Load program (no sections -> nothing attached, just map exists)
|
||||
b = BPF()
|
||||
b.load_and_attach()
|
||||
|
||||
# Access the map
|
||||
bpymap = BpfMap(b, mymap)
|
||||
|
||||
# Insert values
|
||||
bpymap.update(1, 100)
|
||||
bpymap.update(2, 200)
|
||||
|
||||
# Read values
|
||||
print("Key 1 =", bpymap.lookup(1))
|
||||
print("Key 2 =", bpymap.lookup(2))
|
||||
|
||||
# Update again
|
||||
bpymap.update(1, bpymap.lookup(1) + 50)
|
||||
print("Key 1 updated =", bpymap.lookup(1))
|
||||
|
||||
# Iterate through keys
|
||||
for k in bpymap.keys():
|
||||
print("Key:", k, "Value:", bpymap[k])
|
||||
0
tests/passing_tests/perf_buffer_map.py
Normal file
0
tests/passing_tests/perf_buffer_map.py
Normal file
@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse, subprocess, os
|
||||
from pythonbpf import codegen
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source", help="Python BPF program")
|
||||
args = parser.parse_args()
|
||||
|
||||
ll_file = os.path.splitext(args.source)[0] + ".ll"
|
||||
o_file = os.path.splitext(args.source)[0] + ".o"
|
||||
|
||||
print(f"[+] Compiling {args.source} → {ll_file}")
|
||||
codegen.compile_to_ir(args.source, ll_file)
|
||||
|
||||
print("[+] Running llc -march=bpf")
|
||||
subprocess.run(["llc", "-march=bpf", "-filetype=obj", "-O2", ll_file, "-o", o_file], check=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user