Skip to content

Commit 06cd796

Browse files
committed
Add stack querying helpers
Add stack helpers, test get_stackid Expore BPF_MAP_GET_NEXT_KEY as nextkey() Add host map keys iteration Add Base.get for runtime map Improve bpfcall to handle maps
1 parent 5daf45d commit 06cd796

File tree

8 files changed

+177
-36
lines changed

8 files changed

+177
-36
lines changed

src/BPFnative.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ end
6666
# Runtime API
6767
module RT
6868
import ..API
69+
using ..LLVM
70+
using ..LLVM.Interop
71+
include("runtime/maps_core.jl")
6972
include("runtime/bpfcall.jl")
7073
include("runtime/maps.jl")
7174
include("runtime/buffers.jl")

src/common.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ end
255255
sk_assign
256256
end
257257

258+
const PERF_MAX_STACK_DEPTH = 127
259+
258260
# Kernel structures
259261

260262
if has_vmlinux

src/host/maps.jl

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct HashMap{K,V} <: AbstractHashMap{K,V}
1515
fd::Cint
1616
end
1717
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_HASH}) = HashMap
18+
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_STACK_TRACE}) = HashMap
1819
struct ArrayMap{K,V} <: AbstractArrayMap{K,V}
1920
fd::Cint
2021
end
@@ -134,4 +135,47 @@ function Base.haskey(map::HostMap{K,V}, idx) where {K,V}
134135
end
135136
end
136137

137-
# TODO: keys(map) using BPF_MAP_GET_NEXT_KEY
138+
function nextkey(map::HostMap{K,V}, idx) where {K,V}
139+
key = Ref{K}(idx)
140+
nkey = Ref{K}()
141+
key_ptr = Base.unsafe_convert(Ptr{K}, key)
142+
nkey_ptr = Base.unsafe_convert(Ptr{K}, nkey)
143+
attr = Ref(map_access_elem_attr(map.fd,
144+
key_ptr,
145+
nkey_ptr,
146+
0))
147+
ret = GC.@preserve key nkey begin
148+
bpf(API.BPF_MAP_GET_NEXT_KEY, attr)
149+
end
150+
if (ret == -1) && (Libc.errno() == Libc.ENOENT)
151+
return nothing
152+
end
153+
ret >= 0 || Base.systemerror(ret)
154+
nkey[]
155+
end
156+
157+
struct HostMapKeySet{K,V,H<:HostMap{K,V}}
158+
map::H
159+
end
160+
Base.keys(map::H) where {K,V,H<:HostMap{K,V}} = HostMapKeySet{K,V,H}(map)
161+
Base.IteratorSize(::Type{<:HostMapKeySet}) = Base.SizeUnknown()
162+
Base.eltype(::Type{HostMapKeySet{K,V,H}}) where {K,V,H} = K
163+
function Base.iterate(hmks::HostMapKeySet{K,V,H}) where {K,V,H}
164+
fakekey_ref = Ref{K}()
165+
iterate(hmks, fakekey_ref[])
166+
end
167+
function Base.iterate(hmks::HostMapKeySet{K,V,H}, key) where {K,V,H}
168+
nkey = nextkey(hmks.map, key)
169+
if nkey === nothing
170+
return nothing
171+
end
172+
return nkey, nkey
173+
end
174+
function Base.length(map::HostMap)
175+
# TODO: This sucks!
176+
ctr = 0
177+
for k in keys(map)
178+
ctr += 1
179+
end
180+
ctr
181+
end

src/runtime/bpfcall.jl

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,52 @@ export bpfcall
33
@generated function _bpfcall(::Val{cmd}, ::Type{rettype}, ::Type{argtypes}, args::Vararg{Any}) where {cmd,rettype,argtypes}
44
JuliaContext() do ctx
55
T_ret = convert(LLVMType, rettype, ctx)
6-
T_args = map(x->convert(LLVMType, x, ctx), argtypes.parameters)
6+
T_jlptr = convert(LLVMType, Ptr{Cvoid}, ctx)
7+
T_ptr_i8 = LLVM.PointerType(LLVM.Int8Type(ctx))
78

8-
llvm_f, _ = create_function(T_ret, LLVMType[T_args...])
9+
outer_args = filter(arg->!(arg <: RTMap), args)
10+
T_outer_args = LLVMType[convert(LLVMType, arg, ctx) for arg in outer_args]
11+
12+
llvm_f, _ = create_function(T_ret, T_outer_args)
913
mod = LLVM.parent(llvm_f)
1014

1115
Builder(ctx) do builder
1216
entry = BasicBlock(llvm_f, "entry", ctx)
1317
position!(builder, entry)
14-
ft = LLVM.FunctionType(T_ret, LLVMType[T_args...])
18+
19+
inner_args = LLVM.Value[]
20+
T_inner_args = LLVMType[]
21+
outer_args_ex = Expr[]
22+
jlidx = 1
23+
pidx = 1
24+
for (idx, arg) in enumerate(argtypes.parameters)
25+
if arg <: RTMap
26+
map_gv = _genmap!(mod, arg, ctx)
27+
push!(inner_args, map_gv)
28+
push!(T_inner_args, llvmtype(map_gv))
29+
else
30+
llvm_arg = parameters(llvm_f)[pidx]
31+
T_arg = T_outer_args[pidx]
32+
if arg <: Ptr
33+
llvm_arg = inttoptr!(builder, llvm_arg, T_ptr_i8)
34+
T_arg = llvmtype(llvm_arg)
35+
end
36+
push!(inner_args, llvm_arg)
37+
push!(T_inner_args, T_arg)
38+
push!(outer_args_ex, Expr(:ref, :args, jlidx))
39+
pidx += 1
40+
end
41+
jlidx += 1
42+
end
43+
44+
ft = LLVM.FunctionType(T_ret, T_inner_args)
1545
ftp = LLVM.PointerType(ft)
1646
f = inttoptr!(builder, ConstantInt(cmd, ctx), ftp)
17-
value = call!(builder, f, LLVM.Value[parameters(llvm_f)...])
47+
value = call!(builder, f, inner_args)
1848
ret!(builder, value)
1949
end
20-
call_function(llvm_f, rettype, Base.to_tuple_type(args), :((args...,)))
50+
outer_args_ex = Expr(:tuple, outer_args_ex...)
51+
call_function(llvm_f, rettype, Base.to_tuple_type(outer_args), outer_args_ex)
2152
end
2253
end
2354
@inline bpfcall(cmd::API.BPFHelper, RT, AT, args...) =

src/runtime/helpers.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# BPF helpers
22

33
const ptr_sk_buff = API.pointertype(API.sk_buff)
4+
const ptr_task_struct = Ptr{Cvoid} #API.pointertype(API.task_struct)
45

56
bpfconvert(x) = x
67
bpfconvert(x::AbstractBuffer) = pointer(x)
@@ -54,4 +55,19 @@ end
5455
@inline get_current_comm(buf::AbstractSizedBuffer) =
5556
bpfcall(API.get_current_comm, Clong, Tuple{BufPtr, UInt32}, pointer(buf), length(buf))
5657

58+
@inline get_stackid(ctx::T, map::M, flags::Integer) where {T,M<:RTMap} =
59+
bpfcall(API.get_stackid, Clong, Tuple{T, M, UInt64}, ctx, map, unsafe_trunc(UInt64,flags))
60+
@inline function get_current_task()
61+
res = bpfcall(API.get_current_task, UInt64)
62+
if res > 0
63+
unsafe_load(reinterpret(ptr_task_struct, res))
64+
else
65+
nothing
66+
end
67+
end
68+
@inline get_stack(ctx::T, buf::AbstractSizedBuffer, flags::UInt64) where {T} =
69+
bpfcall(API.get_stack, Clong, Tuple{T, BufPtr, UInt32, UInt64}, ctx, pointer(buf), length(buf), flags)
70+
@inline get_task_stack(ctx::ptr_task_struct, buf::AbstractSizedBuffer, flags::UInt64) where {T} =
71+
bpfcall(API.get_task_stack, Clong, Tuple{ptr_task_struct, BufPtr, UInt32, UInt64}, ctx, pointer(buf), length(buf), flags)
72+
5773
# TODO: The rest!

src/runtime/maps.jl

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
using ..LLVM
2-
using ..LLVM.Interop
3-
4-
abstract type RTMap{Name,MT,K,V,ME,F} end
5-
abstract type AbstractHashMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end
6-
abstract type AbstractArrayMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end
7-
8-
struct HashMap{Name,MT,K,V,ME,F} <: AbstractHashMap{Name,MT,K,V,ME,F} end
9-
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_HASH}) = HashMap
10-
struct ArrayMap{Name,MT,K,V,ME,F} <: AbstractArrayMap{Name,MT,K,V,ME,F} end
11-
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_ARRAY}) = ArrayMap
12-
13-
function RTMap(; name, maptype, keytype, valuetype, maxentries=1, flags=0)
14-
jltype = maptype_to_jltype(Val(maptype))
15-
jltype{Symbol(name), maptype, keytype, valuetype, maxentries, flags}()
16-
end
17-
181
function map_lookup_elem(map::RTMap{Name,MT,K,V,ME,F}, key::K) where {Name,MT,K,V,ME,F}
192
keyref = Ref{K}(key)
203
GC.@preserve keyref begin
@@ -37,19 +20,6 @@ function map_delete_elem(map::RTMap{Name,MT,K,V,ME,F}, key::K) where {Name,MT,K,
3720
_map_delete_elem(map, Base.unsafe_convert(Ptr{K}, keyref))
3821
end
3922
end
40-
function _genmap!(mod::LLVM.Module, ::Type{<:RTMap{Name,MT,K,V,ME,F}}, ctx) where {Name,MT,K,V,ME,F}
41-
T_i32 = LLVM.Int32Type(ctx)
42-
T_map = LLVM.StructType([T_i32, T_i32, T_i32, T_i32, T_i32], ctx)
43-
name = string(Name)
44-
gv = GlobalVariable(mod, T_map, name)
45-
section!(gv, "maps")
46-
alignment!(gv, 4)
47-
vec = Any[Int32(MT),Int32(sizeof(K)),Int32(sizeof(V)),Int32(ME),Int32(F)]
48-
init = ConstantStruct([ConstantInt(v, ctx) for v in vec], ctx)
49-
initializer!(gv, init)
50-
linkage!(gv, LLVM.API.LLVMLinkOnceODRLinkage)
51-
return gv
52-
end
5323
@generated function _map_lookup_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}) where {Name,MT,K,V,ME,F}
5424
JuliaContext() do ctx
5525
T_keyp = LLVM.PointerType(convert(LLVMType, K, ctx))
@@ -217,6 +187,17 @@ function Base.haskey(map::AbstractArrayMap{Name,MT,K,V,ME,F}, idx) where {Name,M
217187
end
218188
end
219189

190+
function Base.get(map::RTMap{Name,MT,K,V,ME,F}, k::K, v::V) where {Name,MT,K,V,ME,F}
191+
map_v = map[k]
192+
if map_v !== nothing
193+
return map_v
194+
else
195+
return v
196+
end
197+
end
198+
@inline Base.get(map::RTMap{Name,MT,K,V,ME,F}, k, v) where {Name,MT,K,V,ME,F} =
199+
get(map, bpfconvert(K, k), bpfconvert(V, v))
200+
220201
## Perf
221202

222203
#= TODO

src/runtime/maps_core.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
abstract type RTMap{Name,MT,K,V,ME,F} end
2+
abstract type AbstractHashMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end
3+
abstract type AbstractArrayMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end
4+
5+
struct HashMap{Name,MT,K,V,ME,F} <: AbstractHashMap{Name,MT,K,V,ME,F} end
6+
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_HASH}) = HashMap
7+
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_STACK_TRACE}) = HashMap
8+
struct ArrayMap{Name,MT,K,V,ME,F} <: AbstractArrayMap{Name,MT,K,V,ME,F} end
9+
maptype_to_jltype(::Val{API.BPF_MAP_TYPE_ARRAY}) = ArrayMap
10+
11+
function RTMap(; name, maptype, keytype, valuetype, maxentries=1, flags=0)
12+
jltype = maptype_to_jltype(Val(maptype))
13+
jltype{Symbol(name), maptype, keytype, valuetype, maxentries, flags}()
14+
end
15+
16+
function _genmap!(mod::LLVM.Module, ::Type{<:RTMap{Name,MT,K,V,ME,F}}, ctx) where {Name,MT,K,V,ME,F}
17+
T_i32 = LLVM.Int32Type(ctx)
18+
T_map = LLVM.StructType([T_i32, T_i32, T_i32, T_i32, T_i32], ctx)
19+
name = string(Name)
20+
gv = GlobalVariable(mod, T_map, name)
21+
section!(gv, "maps")
22+
alignment!(gv, 4)
23+
vec = Any[Int32(MT),Int32(sizeof(K)),Int32(sizeof(V)),Int32(ME),Int32(F)]
24+
init = ConstantStruct([ConstantInt(v, ctx) for v in vec], ctx)
25+
initializer!(gv, init)
26+
linkage!(gv, LLVM.API.LLVMLinkOnceODRLinkage)
27+
return gv
28+
end
29+

test/runtests.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,5 +452,40 @@ if run_root_tests
452452
@test haskey(hmap, 2)
453453
end
454454
end
455+
# TODO: get_current_comm
456+
@testset "get_stackid" begin
457+
# from BCC's stackcount
458+
# FIXME: { i64, i32 } causes verifier errors
459+
struct StackKey
460+
tgid::UInt64
461+
sid::Clong
462+
end
463+
kp = KProbe("ksys_write"; license="GPL") do x
464+
stacks = RT.RTMap(;name="stacks",maptype=API.BPF_MAP_TYPE_STACK_TRACE,keytype=UInt32,valuetype=NTuple{API.PERF_MAX_STACK_DEPTH,UInt64},maxentries=100)
465+
counts = RT.RTMap(;name="counts",maptype=API.BPF_MAP_TYPE_HASH,keytype=StackKey,valuetype=UInt32,maxentries=100)
466+
pid, tgid = RT.get_current_pid_tgid()
467+
sid = RT.get_stackid(x, stacks, 0)
468+
key = StackKey(tgid, sid)
469+
old_count = get(counts, key, 0)
470+
counts[key] = old_count + 1
471+
0
472+
end
473+
API.load(kp) do
474+
stacks = Host.hostmap(API.findmap(kp.obj, "stacks"); K=UInt32, V=NTuple{API.PERF_MAX_STACK_DEPTH,UInt64})
475+
counts = Host.hostmap(API.findmap(kp.obj, "counts"); K=StackKey, V=UInt32)
476+
write(test_io, "1"); flush(test_io)
477+
@test length(counts) > 0
478+
key = nothing
479+
for k in keys(counts)
480+
if k.tgid == getpid()
481+
key = k
482+
break
483+
end
484+
end
485+
@test key !== nothing
486+
@test haskey(counts, key)
487+
@test haskey(stacks, key.sid)
488+
end
489+
end
455490
end
456491
end

0 commit comments

Comments
 (0)