Skip to content

Commit 3778fda

Browse files
committed
docs: add basic project documentation
1 parent 919e79e commit 3778fda

14 files changed

Lines changed: 1321 additions & 0 deletions

File tree

docs/advanced/c_parser.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# C Struct Parser
2+
3+
libdestruct can parse C struct definitions directly and convert them into usable Python types. This is powered by [pycparser](https://github.com/eliben/pycparser).
4+
5+
## Basic Usage
6+
7+
```python
8+
from libdestruct.c.struct_parser import definition_to_type
9+
10+
player_t = definition_to_type("""
11+
struct player_t {
12+
int health;
13+
unsigned int score;
14+
long experience;
15+
};
16+
""")
17+
18+
memory = b"\x64\x00\x00\x00\xe8\x03\x00\x00\x39\x05\x00\x00\x00\x00\x00\x00"
19+
player = player_t.from_bytes(memory)
20+
21+
print(player.health.value) # 100
22+
print(player.score.value) # 1000
23+
print(player.experience.value) # 1337
24+
```
25+
26+
## Supported C Types
27+
28+
The parser recognizes these C type specifiers:
29+
30+
| C Type | Maps to |
31+
|---|---|
32+
| `int` | `c_int` |
33+
| `unsigned int` | `c_uint` |
34+
| `long` | `c_long` |
35+
| `unsigned long` | `c_ulong` |
36+
| `char` | `c_char` |
37+
38+
Type names are normalized — `unsigned int`, `uint`, and `unsigned` all map to `c_uint`.
39+
40+
## Pointers
41+
42+
Single, double, and triple pointers are supported:
43+
44+
```python
45+
t = definition_to_type("""
46+
struct test {
47+
int *p;
48+
int **pp;
49+
int ***ppp;
50+
};
51+
""")
52+
```
53+
54+
Self-referential pointers are automatically detected:
55+
56+
```python
57+
node_t = definition_to_type("""
58+
struct node {
59+
int value;
60+
struct node *next;
61+
};
62+
""")
63+
```
64+
65+
## Arrays
66+
67+
Fixed-size arrays are converted to `array_of()`:
68+
69+
```python
70+
t = definition_to_type("""
71+
struct buffer {
72+
int data[16];
73+
};
74+
""")
75+
```
76+
77+
## Nested Structs
78+
79+
Define multiple structs in a single definition:
80+
81+
```python
82+
t = definition_to_type("""
83+
struct point {
84+
int x;
85+
int y;
86+
};
87+
88+
struct rect {
89+
struct point origin;
90+
struct point size;
91+
};
92+
""")
93+
```
94+
95+
The last struct in the definition is returned. All previous structs are cached and available for forward references.
96+
97+
## Include Directives
98+
99+
The parser supports `#include` directives by running the C preprocessor:
100+
101+
```python
102+
t = definition_to_type("""
103+
#include <stdint.h>
104+
105+
struct packet {
106+
int type;
107+
unsigned long length;
108+
};
109+
""")
110+
```
111+
112+
!!! warning
113+
Include expansion requires a C preprocessor (`cpp`) to be available on your system.
114+
115+
## GCC Attributes
116+
117+
`__attribute__((...))` annotations are automatically stripped before parsing:
118+
119+
```python
120+
t = definition_to_type("""
121+
struct __attribute__((packed)) data {
122+
int x;
123+
int y;
124+
};
125+
""")
126+
```
127+
128+
## Caching
129+
130+
Parsed struct definitions are cached globally. Parsing the same struct name twice returns the cached version:
131+
132+
```python
133+
# First call parses
134+
t1 = definition_to_type("struct foo { int x; };")
135+
136+
# Second call with same name returns cached type
137+
t2 = definition_to_type("struct foo { int x; };")
138+
```

docs/advanced/forward_refs.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Forward References
2+
3+
Forward references allow structs to reference types that haven't been fully defined yet — most commonly, the struct itself. This is essential for recursive data structures like linked lists and trees.
4+
5+
## The `ptr["TypeName"]` Syntax
6+
7+
Use a string inside `ptr[...]` to reference a type by name:
8+
9+
```python
10+
from libdestruct import struct, c_int, ptr
11+
12+
class Node(struct):
13+
val: c_int
14+
next: ptr["Node"]
15+
```
16+
17+
At inflation time, the string `"Node"` is resolved to the actual `Node` class. This works because Python's `from __future__ import annotations` (used internally by libdestruct) defers annotation evaluation.
18+
19+
## The `ptr_to_self` Shortcut
20+
21+
For the common case of a pointer to the enclosing struct, use `ptr_to_self`:
22+
23+
```python
24+
from libdestruct import struct, c_int, ptr_to_self
25+
26+
class Node(struct):
27+
val: c_int
28+
next: ptr_to_self
29+
```
30+
31+
This is equivalent to `ptr["Node"]` but doesn't require you to spell out the type name.
32+
33+
## Linked List Example
34+
35+
```python
36+
from libdestruct import struct, c_int, ptr, inflater
37+
38+
class Node(struct):
39+
val: c_int
40+
next: ptr["Node"]
41+
42+
# Build a two-node list in memory
43+
# Node layout: c_int(4) + ptr(8) = 12 bytes
44+
memory = bytearray(24)
45+
46+
import struct as pystruct
47+
# Node 0 at offset 0
48+
memory[0:4] = pystruct.pack("<i", 10)
49+
memory[4:12] = pystruct.pack("<q", 12) # next -> offset 12
50+
51+
# Node 1 at offset 12
52+
memory[12:16] = pystruct.pack("<i", 20)
53+
memory[16:24] = pystruct.pack("<q", 0) # next -> null
54+
55+
lib = inflater(memory)
56+
head = lib.inflate(Node, 0)
57+
58+
print(head.val.value) # 10
59+
print(head.next.unwrap().val.value) # 20
60+
print(head.next.unwrap().next.try_unwrap()) # None
61+
```
62+
63+
## Tree Example
64+
65+
```python
66+
from libdestruct import struct, c_uint, ptr
67+
68+
class TreeNode(struct):
69+
data: c_uint
70+
left: ptr["TreeNode"]
71+
right: ptr["TreeNode"]
72+
```
73+
74+
## How It Works
75+
76+
When libdestruct encounters a `ptr["TypeName"]` annotation:
77+
78+
1. It stores the string reference during struct class creation
79+
2. At inflation time, it resolves the string against all known struct types
80+
3. The resolved type is used as the pointer's wrapper type
81+
82+
This means the referenced type must be defined before the struct is inflated, but not necessarily before it is declared.
83+
84+
!!! info
85+
Forward references are resolved through the `TypeRegistry` at inflation time. If the referenced type is not found, an error is raised.

docs/advanced/freeze_diff.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Freeze, Diff & Reset
2+
3+
libdestruct supports snapshotting values for change tracking. This is useful when you want to detect what changed in memory between two points in time.
4+
5+
## Freezing
6+
7+
Call `freeze()` to snapshot the current value:
8+
9+
```python
10+
from libdestruct import c_int, inflater
11+
12+
memory = bytearray(4)
13+
lib = inflater(memory)
14+
x = lib.inflate(c_int, 0)
15+
16+
x.value = 42
17+
x.freeze()
18+
```
19+
20+
Once frozen, the object remembers its value at the time of the freeze. Further reads still return the live value from memory, but writes are blocked:
21+
22+
```python
23+
# Writing to a frozen object raises ValueError
24+
try:
25+
x.value = 99
26+
except ValueError:
27+
print("Cannot write to frozen object")
28+
```
29+
30+
## Diffing
31+
32+
Use `diff()` to compare the frozen value with the current live value:
33+
34+
```python
35+
x.value = 42
36+
x.freeze()
37+
38+
# Something changes the underlying memory
39+
memory[0:4] = (100).to_bytes(4, "little")
40+
41+
frozen_val, current_val = x.diff()
42+
print(f"Was: {frozen_val}, Now: {current_val}")
43+
# Was: 42, Now: 100
44+
```
45+
46+
!!! note
47+
`diff()` only works on frozen objects. It returns a tuple of `(frozen_value, current_value)`.
48+
49+
## Resetting
50+
51+
Call `reset()` to restore the memory to the frozen value:
52+
53+
```python
54+
x.reset()
55+
print(x.value) # 42 (restored to frozen value)
56+
```
57+
58+
## Updating
59+
60+
Call `update()` to re-freeze with the current live value, discarding the old snapshot:
61+
62+
```python
63+
x.update()
64+
# The frozen value is now whatever is currently in memory
65+
```
66+
67+
## Freezing Structs
68+
69+
When you freeze a struct, all its members are frozen recursively:
70+
71+
```python
72+
from libdestruct import struct, c_int, inflater
73+
74+
class pair_t(struct):
75+
a: c_int
76+
b: c_int
77+
78+
memory = bytearray(8)
79+
lib = inflater(memory)
80+
pair = lib.inflate(pair_t, 0)
81+
82+
pair.a.value = 10
83+
pair.b.value = 20
84+
85+
pair.freeze()
86+
87+
# Both members are now frozen
88+
try:
89+
pair.a.value = 999
90+
except ValueError:
91+
print("Frozen!")
92+
```
93+
94+
## Workflow Example
95+
96+
A typical workflow for detecting changes:
97+
98+
```python
99+
# 1. Inflate the struct
100+
state = lib.inflate(game_state_t, addr)
101+
102+
# 2. Freeze the current state
103+
state.freeze()
104+
105+
# 3. Let the program run (memory changes externally)
106+
# ...
107+
108+
# 4. Check what changed
109+
for name in ["health", "score", "level"]:
110+
member = getattr(state, name)
111+
old, new = member.diff()
112+
if old != new:
113+
print(f"{name}: {old} -> {new}")
114+
115+
# 5. Optionally reset to the frozen state
116+
state.reset()
117+
```

0 commit comments

Comments
 (0)