Skip to content

Commit 8ec0865

Browse files
committed
Add new kprobe function syntax with BTF signature extraction support to SPEC.md
1 parent 2a1c3d1 commit 8ec0865

File tree

1 file changed

+87
-25
lines changed

1 file changed

+87
-25
lines changed

SPEC.md

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,55 @@ return_type = type_annotation
144144

145145
**Note:** eBPF programs are now simple attributed functions. All configuration is done through global named config blocks.
146146

147+
#### 3.1.1 Advanced Kprobe Functions with BTF Signature Extraction
148+
149+
KernelScript automatically extracts kernel function signatures from BTF (BPF Type Format) for kprobe functions, eliminating the need for `KprobeContext` and providing type-safe access to function parameters.
150+
151+
```kernelscript
152+
@kprobe("sys_read")
153+
fn new_style(fd: u32, buf: *u8, count: usize) -> i32 {
154+
// Direct access to function parameters with correct types
155+
// Compiler automatically extracts signature from BTF:
156+
// long sys_read(unsigned int fd, char __user *buf, size_t count)
157+
158+
print("Reading %d bytes from fd %d", count, fd)
159+
return 0
160+
}
161+
```
162+
163+
**Key Benefits:**
164+
- **Type Safety**: Parameters have correct types extracted from kernel BTF information
165+
- **No Magic Numbers**: Direct parameter access instead of `ctx.arg_*(index)`
166+
- **Self-Documenting**: Function signature matches the actual kernel function
167+
- **Compile-Time Validation**: Invalid parameter access caught at compile time
168+
169+
**BTF Signature Mapping:**
170+
```kernelscript
171+
// Kernel function: long sys_openat(int dfd, const char __user *filename, int flags, umode_t mode)
172+
@kprobe("sys_openat")
173+
fn trace_openat(dfd: i32, filename: *u8, flags: i32, mode: u16) -> i32 {
174+
// Parameters automatically mapped to PT_REGS_PARM1, PT_REGS_PARM2, etc.
175+
print("Opening file with flags %d", flags)
176+
return 0
177+
}
178+
179+
// Kernel function: long sys_write(unsigned int fd, const char __user *buf, size_t count)
180+
@kprobe("sys_write")
181+
fn trace_write(fd: u32, buf: *u8, count: usize) -> i32 {
182+
// Type-safe parameter access
183+
if (count > 1024) {
184+
print("Large write detected: %d bytes to fd %d", count, fd)
185+
}
186+
return 0
187+
}
188+
```
189+
190+
**Compiler Implementation:**
191+
- Automatically queries BTF information for the target kernel function
192+
- Generates parameter mappings to `PT_REGS_PARM*` macros
193+
- Validates parameter count (maximum 6 on x86_64)
194+
- Provides meaningful error messages for unknown functions
195+
147196
### 3.2 Named Configuration Blocks
148197
```kernelscript
149198
// Named configuration blocks - globally accessible
@@ -1702,21 +1751,20 @@ struct PersonInfo {
17021751
status: ShortString, // str(32)
17031752
}
17041753
1705-
// Kernel space usage
1706-
program user_monitor : kprobe("sys_open") {
1707-
fn main(ctx: KprobeContext) -> i32 {
1708-
var process_name: ProcessName = get_current_process_name()
1709-
var file_path: FilePath = get_file_path(ctx)
1710-
1711-
// String operations work the same in kernel space
1712-
if (process_name == "malware") {
1713-
var log_msg: LogMessage = "Blocked process: " + process_name
1714-
print(log_msg)
1715-
return -1
1716-
}
1717-
1718-
return 0
1754+
// Kernel space usage - kprobe with BTF-extracted function signature
1755+
@kprobe("sys_open")
1756+
fn user_monitor(dfd: i32, filename: *u8, flags: i32, mode: u16) -> i32 {
1757+
var process_name: ProcessName = get_current_process_name()
1758+
var file_path: FilePath = get_file_path_from_filename(filename)
1759+
1760+
// String operations work the same in kernel space
1761+
if (process_name == "malware") {
1762+
var log_msg: LogMessage = "Blocked process: " + process_name
1763+
print(log_msg)
1764+
return -1
17191765
}
1766+
1767+
return 0
17201768
}
17211769
17221770
// Userspace usage
@@ -2147,7 +2195,7 @@ pin map<u32, GlobalCounter> global_counters : Array(256)
21472195
pin map<Event> event_stream : RingBuffer(1024 * 1024)
21482196
21492197
@kprobe("sys_read")
2150-
fn producer(ctx: KprobeContext) -> i32 {
2198+
fn producer(fd: u32, buf: *u8, count: usize) -> i32 {
21512199
var pid = bpf_get_current_pid_tgid() as u32
21522200
21532201
// Update global counter (accessible by other programs)
@@ -2157,6 +2205,8 @@ fn producer(ctx: KprobeContext) -> i32 {
21572205
var event = Event {
21582206
pid: pid,
21592207
syscall: "read",
2208+
fd: fd,
2209+
bytes_requested: count,
21602210
timestamp: bpf_ktime_get_ns(),
21612211
}
21622212
event_stream.submit(event)
@@ -2165,14 +2215,14 @@ fn producer(ctx: KprobeContext) -> i32 {
21652215
}
21662216
21672217
@kprobe("sys_write")
2168-
fn consumer(ctx: KprobeContext) -> i32 {
2218+
fn consumer(fd: u32, buf: *u8, count: usize) -> i32 {
21692219
var pid = bpf_get_current_pid_tgid() as u32
21702220
21712221
// Access global counter (same map as producer program)
21722222
var read_count = global_counters[pid % 256]
21732223
2174-
// Process the read count data
2175-
process_read_count(read_count)
2224+
// Process the write count data with actual parameters
2225+
process_write_count(read_count, fd, count)
21762226
21772227
return 0
21782228
}
@@ -4075,45 +4125,57 @@ fn analyze_syscall_performance(pid: u32, syscall_nr: u32, bytes: u32, duration:
40754125
40764126
// Kernel-shared function for measuring write time
40774127
@helper
4078-
fn measure_write_time(ctx: KprobeContext) -> u64 {
4128+
fn measure_write_time() -> u64 {
40794129
return bpf_ktime_get_ns()
40804130
}
40814131
40824132
@kprobe("sys_read")
4083-
fn perf_monitor(ctx: KprobeContext) -> i32 {
4133+
fn perf_monitor(fd: u32, buf: *u8, count: usize) -> i32 {
40844134
var pid = bpf_get_current_pid_tgid() as u32
40854135
var call_info = CallInfo {
40864136
start_time: bpf_ktime_get_ns(),
4087-
bytes_requested: ctx.arg_u32(2),
4137+
bytes_requested: count, // Use actual parameter instead of ctx.arg_u32(2)
4138+
file_descriptor: fd, // Access file descriptor directly
40884139
}
40894140
40904141
active_calls[pid] = call_info
40914142
return 0
40924143
}
40934144
40944145
@kretprobe("sys_read")
4095-
fn perf_monitor_return(ctx: KretprobeContext) -> i32 {
4146+
fn perf_monitor_return(ret_value: isize) -> i32 {
40964147
var pid = bpf_get_current_pid_tgid() as u32
40974148
40984149
var call_info = active_calls[pid]
40994150
if (call_info != null) {
41004151
var duration = bpf_ktime_get_ns() - call_info.start_time
41014152
read_stats[pid % 1024] += duration
41024153
4103-
// Use kfunc for detailed analysis and logging
4154+
// Use actual return value and kfunc for detailed analysis and logging
41044155
analyze_syscall_performance(pid, __NR_read, call_info.bytes_requested, duration)
41054156
4157+
// Log if the syscall failed (negative return value)
4158+
if (ret_value < 0) {
4159+
print("sys_read failed with error: %d", ret_value)
4160+
}
4161+
41064162
delete active_calls[pid]
41074163
}
41084164
41094165
return 0
41104166
}
41114167
41124168
@kprobe("sys_write")
4113-
fn write_monitor(ctx: KprobeContext) -> i32 {
4169+
fn write_monitor(fd: u32, buf: *u8, count: usize) -> i32 {
41144170
var pid = bpf_get_current_pid_tgid() as u32
4115-
var duration = measure_write_time(ctx)
4171+
var duration = measure_write_time() // No context needed
41164172
write_stats[pid % 1024] += duration
4173+
4174+
// Log write operation with actual parameters
4175+
if (count > 0) {
4176+
print("Process %d writing %d bytes to fd %d", pid, count, fd)
4177+
}
4178+
41174179
return 0
41184180
}
41194181

0 commit comments

Comments
 (0)