Skip to content

Commit 10649f4

Browse files
committed
WARP format 1.0
- Update to flatbuffers `25.2.10` - Add fuzzing targets for type and function `from_bytes` - Update examples - Simplify type spec - Make constraints generic and remove specialized constraint lists - Space optimizations for type and functions specs - More tests with greater coverage - Introduce the concept of a WARP `File` and `Chunk`s - Make chunk compression configurable - Make `Type` objects class field unboxed (decreases memory pressure) - Use standard directory structure for Rust API - Move tests to `tests` directory for more easy discovery - Remove almost all uses of `unwrap` (needed for server-side parsing) - Refactor `TypeMetadata` - Add `mock` module for easy mocking in tests and examples - Make `Symbol` space optimized - Switch to using `.warp` extension to represent general analysis data instead of just signatures - Add format version to `File` and `Chunk` (allow for breaking changes later) - Make analysis data (signatures and types) copy on write (See `ChunkHandler` impl's) This work is being done to allow for networked WARP information and generally to make the WARP format more usable in a wider set of scenarios. After this commit any breaking changes to the format will be held off for 2.0, if that ever becomes a thing.
1 parent aa84f00 commit 10649f4

129 files changed

Lines changed: 4713 additions & 4126 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,10 @@ generated/
5656
**/out
5757

5858
*.vsix
59-
*.deb
59+
*.deb
60+
61+
# cargo-mutants
62+
mutants*
63+
64+
*.warp
65+
*.sbin

Cargo.toml

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,14 @@
1-
[package]
2-
name = "warp"
3-
version = "0.1.0"
4-
edition = "2021"
5-
license = "Apache-2.0"
6-
7-
[lib]
8-
path = "rust/lib.rs"
9-
10-
[dependencies]
11-
flatbuffers = "24.3.25"
12-
bon = "2.3.0"
13-
uuid = { version = "1.11.0", features = ["v5"]}
14-
rand = "0.8.5"
15-
flate2 = "1.0.34"
16-
17-
[features]
18-
default = []
19-
gen_flatbuffers = ["dep:flatbuffers-build"]
20-
21-
[dev-dependencies]
22-
criterion = "0.5.1"
23-
24-
[build-dependencies]
25-
flatbuffers-build = { git = "https://github.com/emesare/flatbuffers-build", features = ["vendored"], optional = true }
1+
[workspace]
2+
resolver = "2"
3+
members = [
4+
"rust",
5+
"rust/fuzz"
6+
]
267

278
[profile.release]
289
panic = "abort"
2910
lto = true
3011
debug = "full"
3112

3213
[profile.bench]
33-
lto = true
34-
35-
[[example]]
36-
name = "simple"
37-
path = "rust/examples/simple.rs"
38-
39-
[[example]]
40-
name = "random"
41-
path = "rust/examples/random.rs"
42-
43-
[[bench]]
44-
name = "void"
45-
path = "rust/benches/void.rs"
46-
harness = false
14+
lto = true

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2020-2024 Vector 35 Inc.
1+
Copyright 2020-2025 Vector 35 Inc.
22

33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ The basic block GUID is the UUIDv5 of the byte sequence of the instructions (sor
6868

6969
#### When are instructions that set a register to itself removed?
7070

71-
To support hot-patching we must remove them as they can be injected by the compiler at the start of a function (see: [1] and [2]).
71+
To support hot-patching, we must remove them as they can be injected by the compiler at the start of a function (see: [1] and [2]).
7272
This does not affect the accuracy of the function GUID as they are only removed when the instruction is a NOP:
7373

7474
- Register groups with no implicit extension will be removed (see: [3] (under 3.4.1.1))
@@ -85,15 +85,33 @@ For the `x86` architecture the instruction `e8b55b0100` (or `call 0x15bba`) woul
8585

8686
The namespace for Basic Block GUID's is `0192a178-7a5f-7936-8653-3cbaa7d6afe7`.
8787

88-
### Function Constraints
88+
### Constraints
8989

90-
Function constraints allow us to further disambiguate between functions with the same GUID, when creating the functions we store information about the following:
90+
Constraints allow us to further disambiguate between functions with the same GUID; when creating the functions, we retrieve extra information
91+
that is consistent between versions of the same function, some examples are:
9192

9293
- Called functions
9394
- Caller functions
9495
- Adjacent functions
9596

96-
Each entry in the lists above is referred to as a "constraint" that can be used to further reduce the number of matches for a given function GUID.
97+
Each extra piece of information is referred to as a "constraint" that can be used to further reduce the number of matches for a given function GUID.
98+
99+
#### Creating a Constraint
100+
101+
Constraints are made up of a GUID and optionally, a matching offset. Adding a matching offset is preferred to give locality to the constraints,
102+
for example, if you have a function `A` which calls into function `B` that is one constraint, but if the function `B` is also adjacent to function `A`
103+
without a matching offset the two constraints may be merged into a single one, reducing the number of matching constraints.
104+
105+
- The adjacent function `B` as a constraint: `(9F188A12-3EA1-477D-B368-361936EEA213, -30)`
106+
- The call to function `B` as a constraint: `(9F188A12-3EA1-477D-B368-361936EEA213, 48)`
107+
108+
#### Creating a Constraint GUID
109+
110+
The constraint GUID is the UUIDv5 of the relevant bytes that would be computable at creation time and lookup time.
111+
112+
##### What is the UUIDv5 namespace?
113+
114+
The namespace for Constraint GUID's is `019701f3-e89c-7afa-9181-371a5e98a576`.
97115

98116
##### Why don't we require matching on constraints for trivial functions?
99117

rust/Cargo.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "warp"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
7+
[dependencies]
8+
flatbuffers = "25.2.10"
9+
bon = "3.6.3"
10+
uuid = { version = "1.11.0", features = ["v5"]}
11+
flate2 = "1.0.34"
12+
13+
[features]
14+
default = []
15+
gen_flatbuffers = ["dep:flatbuffers-build"]
16+
17+
[dev-dependencies]
18+
criterion = "0.6.0"
19+
20+
[build-dependencies]
21+
flatbuffers-build = { git = "https://github.com/emesare/flatbuffers-build", rev = "f962ccf", features = ["vendored"], optional = true }
22+
23+
[[example]]
24+
name = "type_builder"
25+
26+
[[example]]
27+
name = "dumper"
28+
29+
[[bench]]
30+
name = "type"
31+
harness = false
32+
33+
[[bench]]
34+
name = "chunk"
35+
harness = false

rust/benches/chunk.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use warp::chunk::{Chunk, ChunkKind, CompressionType};
3+
use warp::mock::{mock_function, mock_function_type_class, mock_type};
4+
use warp::r#type::chunk::TypeChunk;
5+
use warp::signature::chunk::SignatureChunk;
6+
use warp::{File, FileHeader};
7+
8+
pub fn chunk_benchmark(c: &mut Criterion) {
9+
let count = 10000;
10+
// Fill out a signature chunk with functions.
11+
let mut functions = Vec::new();
12+
for i in 0..count {
13+
functions.push(mock_function(&format!("function_{}", i)));
14+
}
15+
let _signature_chunk = SignatureChunk::new(&functions).expect("Failed to create chunk");
16+
let signature_chunk = Chunk::new(
17+
ChunkKind::Signature(_signature_chunk),
18+
CompressionType::None,
19+
);
20+
21+
// Fill out a type chunk with types.
22+
let mut types = Vec::new();
23+
for i in 0..count {
24+
types.push(mock_type(
25+
&format!("type_{}", i),
26+
mock_function_type_class(),
27+
));
28+
}
29+
let _type_chunk = TypeChunk::new(&types).expect("Failed to create chunk");
30+
let type_chunk = Chunk::new(ChunkKind::Type(_type_chunk), CompressionType::Zstd);
31+
let file = File::new(FileHeader::new(), vec![signature_chunk, type_chunk]);
32+
c.bench_function("file to bytes", |b| {
33+
b.iter(|| {
34+
file.to_bytes();
35+
})
36+
});
37+
}
38+
39+
criterion_group!(benches, chunk_benchmark);
40+
criterion_main!(benches);

rust/benches/type.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use warp::mock::{mock_int_type_class, mock_type};
3+
use warp::r#type::class::{StructureClass, StructureMember, TypeClass};
4+
use warp::r#type::guid::TypeGUID;
5+
use warp::r#type::Type;
6+
7+
pub fn void_benchmark(c: &mut Criterion) {
8+
let void_type = Type::builder()
9+
.name("my_void".to_owned())
10+
.class(TypeClass::Void)
11+
.build();
12+
13+
c.bench_function("uuid void", |b| {
14+
b.iter(|| {
15+
let _ = TypeGUID::from(&void_type);
16+
})
17+
});
18+
19+
c.bench_function("computed void", |b| b.iter(|| void_type.to_bytes()));
20+
}
21+
22+
pub fn struct_benchmark(c: &mut Criterion) {
23+
let int_type = mock_type("my_int", mock_int_type_class(None, false));
24+
let structure_member = StructureMember::builder()
25+
.name("member")
26+
.ty(int_type)
27+
.offset(0)
28+
.build();
29+
let struct_class = StructureClass::new(vec![structure_member]);
30+
let struct_type = Type::builder()
31+
.name("my_struct".to_owned())
32+
.class(TypeClass::Structure(struct_class))
33+
.build();
34+
35+
c.bench_function("uuid struct", |b| {
36+
b.iter(|| {
37+
let _ = TypeGUID::from(&struct_type);
38+
})
39+
});
40+
41+
c.bench_function("computed struct", |b| b.iter(|| struct_type.to_bytes()));
42+
}
43+
44+
criterion_group!(benches, void_benchmark, struct_benchmark);
45+
criterion_main!(benches);

rust/benches/void.rs

Lines changed: 0 additions & 22 deletions
This file was deleted.

build.rs renamed to rust/build.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ pub fn main() {
1010
let _ = std::fs::remove_dir_all("rust/gen_flatbuffers");
1111
let workspace_dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap().into();
1212
BuilderOptions::new_with_files([
13-
workspace_dir.join("type.fbs"),
14-
workspace_dir.join("symbol.fbs"),
15-
workspace_dir.join("signature.fbs"),
13+
workspace_dir.join("../type.fbs"),
14+
workspace_dir.join("../symbol.fbs"),
15+
workspace_dir.join("../signature.fbs"),
16+
workspace_dir.join("../warp.fbs"),
1617
])
17-
.set_output_path("rust/gen_flatbuffers")
18+
.set_output_path("src/gen_flatbuffers")
1819
.compile()
1920
.expect("flatbuffer compilation failed");
2021
}

rust/examples/dumper.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use std::env;
2+
use std::fs::File;
3+
use std::io::Read;
4+
use std::path::Path;
5+
use warp::chunk::ChunkKind;
6+
7+
fn main() -> Result<(), Box<dyn std::error::Error>> {
8+
let args: Vec<String> = env::args().collect();
9+
if args.len() < 2 {
10+
eprintln!("Usage: {} <output file>", args[0]);
11+
std::process::exit(1);
12+
}
13+
14+
let path = Path::new(&args[1]);
15+
let mut file = File::open(path)?;
16+
let mut buffer = Vec::new();
17+
file.read_to_end(&mut buffer)?;
18+
19+
let file = warp::File::from_bytes(&buffer).expect("Failed to parse file");
20+
21+
for chunk in file.chunks {
22+
match chunk.kind {
23+
ChunkKind::Signature(sc) => {
24+
println!("=== Signature chunk ===");
25+
for func in sc.functions() {
26+
println!("{} | {}", func.symbol.name, func.guid);
27+
}
28+
}
29+
ChunkKind::Type(tc) => {
30+
println!("=== Type chunk ===");
31+
for ty in tc.types() {
32+
println!(
33+
"{} | {}",
34+
ty.ty.name.unwrap_or("ANONYMOUS".to_string()),
35+
ty.guid
36+
);
37+
}
38+
}
39+
}
40+
}
41+
42+
Ok(())
43+
}

0 commit comments

Comments
 (0)