diff --git a/Manifest.toml b/Manifest.toml index 6324733..ffa265f 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -28,15 +28,15 @@ version = "0.4.1" [[Clang_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "libLLVM_jll"] -git-tree-sha1 = "b0db0edbfd3388b23f9578ec2ee7ff814f646649" +git-tree-sha1 = "a5923c06de3178dd755f4b9411ea8922a7ae6fb8" uuid = "0ee61d77-7f21-5576-8119-9fcc46b10100" -version = "11.0.0+7" +version = "11.0.1+3" [[Compat]] deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "919c7f3151e79ff196add81d7f4e45d91bbf420b" +git-tree-sha1 = "ac4132ad78082518ec2037ae5770b6e796f7f956" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.25.0" +version = "3.27.0" [[DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] @@ -83,9 +83,9 @@ version = "1.2.0" [[LLVM]] deps = ["CEnum", "Libdl", "Printf", "Unicode"] -git-tree-sha1 = "000a737732aa4eb996414c4685368f6a74b41d14" +git-tree-sha1 = "b616937c31337576360cb9fb872ec7633af7b194" uuid = "929cbde3-209d-540e-8aea-75f648917ca0" -version = "3.4.0" +version = "3.6.0" [[LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] @@ -142,7 +142,7 @@ uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.4.0" [[Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs"] +deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [[Preferences]] @@ -250,3 +250,7 @@ deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "c417cbbce3efb49a309b2d478fea9df7cfe024c0" uuid = "c88a4935-d25e-5644-aacc-5db6f1b8ef79" version = "1.1.0+0" + +[[p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" diff --git a/Project.toml b/Project.toml index 5ce8959..f48f971 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "BPFnative" uuid = "b6338580-32ea-11e9-1791-33a79977d8c4" authors = ["Julian P Samaroo "] -version = "0.1.1" +version = "0.1.2" [deps] CBinding = "d43a6710-96b8-4a2d-833c-c424785e5374" @@ -15,7 +15,7 @@ Preferences = "21216c6a-2e73-6563-6e65-726566657250" [compat] CBinding = "1" GPUCompiler = "0.9.1" -LLVM = "= 3.4" +LLVM = "3.6" Libbpf_jll = "0.3" Preferences = "1" julia = "1.6" diff --git a/src/BPFnative.jl b/src/BPFnative.jl index 287818b..87eca12 100644 --- a/src/BPFnative.jl +++ b/src/BPFnative.jl @@ -53,15 +53,17 @@ end # Runtime API module RT import ..API -include("runtime_maps.jl") -include("misc.jl") +include("runtime/bpfcall.jl") +include("runtime/maps.jl") +include("runtime/buffers.jl") +include("runtime/helpers.jl") end # Host API module Host import ..API -include("syscall.jl") -include("host_maps.jl") +include("host/syscall.jl") +include("host/maps.jl") end # Compiler diff --git a/src/compiler.jl b/src/compiler.jl index 8c4eb9a..27cc482 100644 --- a/src/compiler.jl +++ b/src/compiler.jl @@ -39,7 +39,7 @@ const bpffunction_cache = Dict{UInt,Any}() # actual compilation function bpffunction_compile(source::FunctionSpec; format=:obj, license="", - prog_section="prog", btf=true, kwargs...) + prog_section="prog", btf=false, kwargs...) # compile to BPF target = BPFCompilerTarget(; license, prog_section) params = BPFCompilerParams() @@ -49,3 +49,96 @@ function bpffunction_compile(source::FunctionSpec; format=:obj, license="", return collect(codeunits(args[1])) end bpffunction_link(@nospecialize(source::FunctionSpec), exe; kwargs...) = exe + +function GPUCompiler.finish_module!(job::BPFCompilerJob, mod::LLVM.Module) + #= TODO: Fix upstream and re-enable if needed + invoke(GPUCompiler.finish_module!, + Tuple{CompilerJob{BPFCompilerTarget}, LLVM.Module}, + job, mod) + =# + + for func in LLVM.functions(mod) + if LLVM.name(func) == "gpu_signal_exception" + throw(GPUCompiler.KernelError(job, "eBPF does not support exceptions")) + end + # Set entry section for loaders like libbpf + LLVM.section!(func, job.target.prog_section) + end + + # Set license + license = job.target.license + if license != "" + ctx = LLVM.context(mod) + i8 = LLVM.Int8Type(ctx) + glob = GlobalVariable(mod, LLVM.ArrayType(i8, length(license)+1), "_license") + linkage!(glob, LLVM.API.LLVMExternalLinkage) + constant!(glob, true) + section!(glob, "license") + str = ConstantArray(Vector{UInt8}(license*'\0'), ctx) + @assert context(glob) == context(str) == ctx + initializer!(glob, str) + end + + # Set all map definitions as external linkage + for gv in filter(x->(section(x)=="maps")||(section(x)==".maps"), collect(LLVM.globals(mod))) + linkage!(gv, LLVM.API.LLVMExternalLinkage) + end + + ModulePassManager() do pm + if Base.JLOptions().debug_level > 1 + # Validate contexts, for my sanity + add!(pm, ModulePass("BPFValidateContexts", validate_contexts!)) + end + # Promote `@malloc` intrinsics + add!(pm, FunctionPass("BPFHeapToStack", heap_to_stack!)) + run!(pm, mod) + end +end + +"Validates LLVM contexts of all the things." +function validate_contexts!(mod::LLVM.Module) + ctx = LLVM.context(mod) + for fn in LLVM.functions(mod) + @assert context(fn) == ctx "Failed validation: $fn" + for bb in LLVM.blocks(fn) + for insn in LLVM.instructions(bb) + @assert context(insn) == ctx "Failed validation: $insn" + for op in LLVM.operands(insn) + @assert context(op) == ctx "Failed validation: $op" + end + end + end + end + for gv in LLVM.globals(mod) + @assert context(gv) == ctx "Failed validation: $gv" + end + false +end + +"Promotes `@malloc` intrinsics to allocas." +function heap_to_stack!(fn::LLVM.Function) + changed = false + ctx = LLVM.context(fn) + for bb in LLVM.blocks(fn) + for insn in LLVM.instructions(bb) + if insn isa LLVM.CallInst && LLVM.name(LLVM.called_value(insn)) == "malloc" + sz = convert(Int64, LLVM.operands(insn)[1]) + T_i8 = LLVM.Int8Type(ctx) + T_pi8 = LLVM.PointerType(T_i8) + T_buf = LLVM.ArrayType(T_i8, sz) + Builder(ctx) do builder + # Place alloca at beginning of entry + position!(builder, first(LLVM.instructions(first(LLVM.blocks(fn))))) + buf = alloca!(builder, T_buf) + # Replace malloc with bitcast'd alloca + position!(builder, insn) + new_insn = bitcast!(builder, buf, T_pi8) + replace_uses!(insn, new_insn) + unsafe_delete!(LLVM.parent(insn), insn) + end + changed = true + end + end + end + changed +end diff --git a/src/host_maps.jl b/src/host/maps.jl similarity index 98% rename from src/host_maps.jl rename to src/host/maps.jl index dcae5d6..3f67ce4 100644 --- a/src/host_maps.jl +++ b/src/host/maps.jl @@ -116,7 +116,7 @@ function Base.delete!(map::AbstractHashMap{K,V}, idx) where {K,V} map end -function Base.haskey(map::AbstractHashMap{K,V}, idx) where {K,V} +function Base.haskey(map::HostMap{K,V}, idx) where {K,V} key = Ref{K}(idx) value = Ref{V}() key_ptr = Base.unsafe_convert(Ptr{K}, key) diff --git a/src/syscall.jl b/src/host/syscall.jl similarity index 100% rename from src/syscall.jl rename to src/host/syscall.jl diff --git a/src/misc.jl b/src/misc.jl deleted file mode 100644 index 9be4d57..0000000 --- a/src/misc.jl +++ /dev/null @@ -1,22 +0,0 @@ -export bpf_get_current_pid_tgid - -@generated function bpf_get_current_pid_tgid() - JuliaContext() do ctx - T_u64 = LLVM.Int64Type(ctx) - llvm_f, _ = create_function(T_u64) - Builder(ctx) do builder - entry = BasicBlock(llvm_f, "entry", ctx) - position!(builder, entry) - ftp = LLVM.PointerType(LLVM.FunctionType(T_u64)) - f = inttoptr!(builder, ConstantInt(Int64(14), ctx), ftp) - value = call!(builder, f) - ret!(builder, value) - end - call_function(llvm_f, UInt64, Tuple{}, :(())) - end -end -function split_u64_u32(x::UInt64) - lower = Base.unsafe_trunc(UInt32, x) - upper = Base.unsafe_trunc(UInt32, (x & (UInt64(typemax(UInt32)) << 32)) >> 32) - return lower, upper -end diff --git a/src/probes.jl b/src/probes.jl index f6a7834..bec02ad 100644 --- a/src/probes.jl +++ b/src/probes.jl @@ -9,8 +9,8 @@ struct KProbe <: AbstractProbe kfunc::String retprobe::Bool end -function KProbe(f::Function, kfunc; retprobe=false) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; btf=false)) +function KProbe(f::Function, kfunc; retprobe=false, kwargs...) + obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; btf=false, kwargs...)) foreach(prog->API.set_kprobe!(prog), API.programs(obj)) KProbe(obj, kfunc, retprobe) end @@ -20,8 +20,8 @@ struct UProbe{F<:Function,T} <: AbstractProbe sig::T retprobe::Bool end -function UProbe(f::Function, method, sig; retprobe=false) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; btf=false)) +function UProbe(f::Function, method, sig; retprobe=false, kwargs...) + obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; btf=false, kwargs...)) #foreach(prog->API.set_uprobe!(prog), API.programs(obj)) foreach(prog->API.set_kprobe!(prog), API.programs(obj)) UProbe(obj, method, sig, retprobe) @@ -31,18 +31,18 @@ struct Tracepoint <: AbstractProbe category::String name::String end -function Tracepoint(f::Function, category, name) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; btf=false)) +function Tracepoint(f::Function, category, name; kwargs...) + obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; btf=false, kwargs...)) foreach(prog->API.set_tracepoint!(prog), API.programs(obj)) Tracepoint(obj, category, name) end Base.show(io::IO, p::KProbe) = - print(io, "KProbe ($(p.kfunc)") + print(io, "KProbe ($(p.kfunc))") Base.show(io::IO, p::UProbe) = print(io, "UProbe ($(p.func)($(p.sig)))") Base.show(io::IO, p::Tracepoint) = - print(io, "Tracepoint ($(p.category)/$(p.name)") + print(io, "Tracepoint ($(p.category)/$(p.name))") function API.load(p::KProbe) API.load(p.obj) diff --git a/src/reflection.jl b/src/reflection.jl index d596a5f..9b5ed94 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -14,7 +14,7 @@ for method in (:code_typed, :code_warntype, :code_llvm, :code_native) function $method(io::IO, @nospecialize(func), @nospecialize(types); kernel::Bool=false, kwargs...) source = FunctionSpec(func, Base.to_tuple_type(types), kernel) - target = BPFCompilerTarget(; dev_isa=default_isa(default_device())) + target = BPFCompilerTarget() params = BPFCompilerParams() job = CompilerJob(target, source, params) GPUCompiler.$method($(args...); kwargs...) diff --git a/src/runtime/bpfcall.jl b/src/runtime/bpfcall.jl new file mode 100644 index 0000000..73da580 --- /dev/null +++ b/src/runtime/bpfcall.jl @@ -0,0 +1,26 @@ +export bpfcall + +@generated function _bpfcall(::Val{cmd}, ::Type{rettype}, ::Type{argtypes}, args::Vararg{Any}) where {cmd,rettype,argtypes} + JuliaContext() do ctx + T_ret = convert(LLVMType, rettype, ctx) + T_args = map(x->convert(LLVMType, x, ctx), argtypes.parameters) + + llvm_f, _ = create_function(T_ret, LLVMType[T_args...]) + mod = LLVM.parent(llvm_f) + + Builder(ctx) do builder + entry = BasicBlock(llvm_f, "entry", ctx) + position!(builder, entry) + ft = LLVM.FunctionType(T_ret, LLVMType[T_args...]) + ftp = LLVM.PointerType(ft) + f = inttoptr!(builder, ConstantInt(cmd, ctx), ftp) + value = call!(builder, f, LLVM.Value[parameters(llvm_f)...]) + ret!(builder, value) + end + call_function(llvm_f, rettype, Base.to_tuple_type(args), :((args...,))) + end +end +@inline bpfcall(cmd::API.BPFHelper, RT, AT, args...) = + _bpfcall(Val(Int(cmd)), RT, AT, args...) +@inline bpfcall(cmd::API.BPFHelper, RT) = _bpfcall(Val(Int(cmd)), RT, Tuple{}) +@inline bpfcall(cmd::API.BPFHelper) = _bpfcall(Val(Int(cmd)), Cvoid, Tuple{}) diff --git a/src/runtime/buffers.jl b/src/runtime/buffers.jl new file mode 100644 index 0000000..4e66160 --- /dev/null +++ b/src/runtime/buffers.jl @@ -0,0 +1,96 @@ +# Buffers/strings + +abstract type AbstractBuffer end +abstract type AbstractSizedBuffer end +abstract type AbstractUnsizedBuffer end + +const BufPtr = Core.LLVMPtr{UInt8,0} + +@inline @generated function create_buffer(::Val{N}) where N + JuliaContext() do ctx + T_i8 = LLVM.Int8Type(ctx) + T_pi8 = LLVM.PointerType(T_i8) + T_i64 = LLVM.Int64Type(ctx) + T_buf = LLVM.ArrayType(T_i8, N) + llvm_f, _ = create_function(T_pi8) + mod = LLVM.parent(llvm_f) + Builder(ctx) do builder + entry = BasicBlock(llvm_f, "entry", ctx) + position!(builder, entry) + + # Allocate stack buffer + malloc_ft = LLVM.FunctionType(T_pi8, [T_i64]) + malloc_f = LLVM.Function(mod, "malloc", malloc_ft) + buf_ptr = call!(builder, malloc_f, [ConstantInt(T_i64, N)]) + + ret!(builder, buf_ptr) + end + call_function(llvm_f, BufPtr) + end +end + +""" + SizedBuffer <: AbstractSizedBuffer + +Represents a buffer/string with a known size. +""" +struct SizedBuffer <: AbstractSizedBuffer + ptr::BufPtr + length::UInt32 +end +SizedBuffer(buf::AbstractSizedBuffer) = SizedBuffer(pointer(buf), length(buf)) +Base.pointer(str::SizedBuffer) = str.ptr +Base.length(str::SizedBuffer) = str.length +@inline create_buffer(N::Int) = SizedBuffer(create_buffer(Val(N)), N) +macro create_string(str::String) + N = length(str)+1 + :(SizedBuffer(create_string($(Val(Symbol(str)))), $N)) +end + +@inline @generated function create_string(::Val{str}) where str + JuliaContext() do ctx + _str = String(str) + T_i8 = LLVM.Int8Type(ctx) + T_pi8 = LLVM.PointerType(T_i8) + T_i64 = LLVM.Int64Type(ctx) + T_i1 = LLVM.Int1Type(ctx) + T_buf = LLVM.ArrayType(T_i8, length(_str)+1) + llvm_f, _ = create_function(T_pi8) + mod = LLVM.parent(llvm_f) + Builder(ctx) do builder + entry = BasicBlock(llvm_f, "entry", ctx) + position!(builder, entry) + + # Allocate string + str_ptr = globalstring_ptr!(builder, _str) + gv = operands(str_ptr)[1] + #set_used!(mod, gv) + + # Allocate stack buffer + malloc_ft = LLVM.FunctionType(T_pi8, [T_i64]) + malloc_f = LLVM.Function(mod, "malloc", malloc_ft) + buf_ptr = call!(builder, malloc_f, [ConstantInt(T_i64, length(_str)+1)]) + + # Copy string into stack buffer + memcpy_ft = LLVM.FunctionType(LLVM.VoidType(ctx), [T_pi8, T_pi8, T_i64, T_i1]) + memcpy_f = LLVM.Function(mod, "llvm.memcpy.p0i8.p0i8.i64", memcpy_ft) + call!(builder, memcpy_f, [buf_ptr, str_ptr, ConstantInt(T_i64, length(_str)+1), ConstantInt(T_i1, 0)]) + + # Return stack buffer + ret!(builder, buf_ptr) + end + call_function(llvm_f, BufPtr) + end +end + +""" + UnsizedBuffer <: AbstractUnsizedBuffer + +Represents a buffer/string with an unknown size. +""" +struct UnsizedBuffer <: AbstractUnsizedBuffer + ptr::BufPtr +end +UnsizedBuffer(buf::AbstractBuffer) = UnsizedBuffer(pointer(buf)) +Base.pointer(str::UnsizedBuffer) = str.ptr +Base.length(str::UnsizedBuffer) = missing diff --git a/src/runtime/helpers.jl b/src/runtime/helpers.jl new file mode 100644 index 0000000..fa551bd --- /dev/null +++ b/src/runtime/helpers.jl @@ -0,0 +1,28 @@ +# BPF helpers + +bpfconvert(x) = x +bpfconvert(x::AbstractBuffer) = pointer(x) + +@inline probe_read(buf::AbstractSizedBuffer, addr::BufPtr) = + bpfcall(API.probe_read, Clong, Tuple{BufPtr, UInt32, BufPtr}, pointer(buf), length(buf), addr) +@inline ktime_get_ns() = bpfcall(API.ktime_get_ns, UInt64) +@inline trace_printk(fmt::AbstractSizedBuffer, x...) = # TODO: Allow trailing arguments + bpfcall(API.trace_printk, Clong, Tuple{BufPtr, UInt32, typeof(bpfconvert.(x))...}, pointer(fmt), length(fmt), map(bpfconvert, x)...) +@inline get_prandom_u32() = bpfcall(API.get_prandom_u32, UInt32) +@inline get_smp_processor_id() = bpfcall(API.get_smp_processor_id, UInt32) +# TODO: skb_store_bytes +# TODO: l3_csum_replace +# TODO: l4_csum_replace +# TODO: tail_call +# TODO: clone_redirect +@inline get_current_pid_tgid() = bpfcall(API.get_current_pid_tgid, UInt64) # TODO: Return Tuple{UInt32,UInt32} +@inline get_current_uid_gid() = bpfcall(API.get_current_uid_gid, UInt64) # TODO: Return Tuple{UInt32,UInt32} +@inline get_current_comm(buf::AbstractSizedBuffer) = + bpfcall(API.get_current_comm, Clong, Tuple{BufPtr, UInt32}, pointer(buf), length(buf)) +# TODO: The rest! + +function split_u64_u32(x::UInt64) + lower = Base.unsafe_trunc(UInt32, x) + upper = Base.unsafe_trunc(UInt32, (x & (UInt64(typemax(UInt32)) << 32)) >> 32) + return lower, upper +end diff --git a/src/runtime_maps.jl b/src/runtime/maps.jl similarity index 97% rename from src/runtime_maps.jl rename to src/runtime/maps.jl index 97f1886..013bb4f 100644 --- a/src/runtime_maps.jl +++ b/src/runtime/maps.jl @@ -39,15 +39,13 @@ function map_delete_elem(map::RTMap{Name,MT,K,V,ME,F}, key::K) where {Name,MT,K, end function _genmap!(mod::LLVM.Module, ::Type{<:RTMap{Name,MT,K,V,ME,F}}, ctx) where {Name,MT,K,V,ME,F} T_i32 = LLVM.Int32Type(ctx) - T_map = LLVM.StructType([T_i32, T_i32, T_i32, T_i32, T_i32]) + T_map = LLVM.StructType([T_i32, T_i32, T_i32, T_i32, T_i32], ctx) name = string(Name) gv = GlobalVariable(mod, T_map, name) section!(gv, "maps") alignment!(gv, 4) vec = Any[Int32(MT),Int32(sizeof(K)),Int32(sizeof(V)),Int32(ME),Int32(F)] - A_vec = [ConstantInt(v, ctx) for v in vec] - init = LLVM.API.LLVMConstStruct(A_vec, length(A_vec), 0) - init = ConstantStruct(init) + init = ConstantStruct([ConstantInt(v, ctx) for v in vec], ctx) initializer!(gv, init) linkage!(gv, LLVM.API.LLVMLinkOnceODRLinkage) return gv diff --git a/test/runtests.jl b/test/runtests.jl index 1e4057f..7531ad4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,7 +28,7 @@ using InteractiveUtils function kernel(x) return 0 end - asm = String(bpffunction(kernel, Tuple{Int}; format=:asm, license="abc", btf=false)) + asm = String(bpffunction(kernel, Tuple{Int}; format=:asm, license="abc")) @test occursin(".section\tlicense,", asm) @test occursin("_license,@object", asm) @test occursin(".asciz\t\"abc\"", asm) @@ -66,6 +66,35 @@ using InteractiveUtils end end end + @testset "buffers/strings" begin + @testset "buffer: simple" begin + function kernel(x) + buf = RT.create_buffer(4) + RT.trace_printk(buf) + end + asm = String(bpffunction(kernel, Tuple{Int}; format=:asm)) + @test !occursin("gpu_gc_pool_alloc", asm) + end + @testset "string: simple" begin + function kernel(x) + str = RT.@create_string("hello!") + RT.trace_printk(str) + end + asm = String(bpffunction(kernel, Tuple{Int}; format=:asm)) + @test !occursin("gpu_gc_pool_alloc", asm) + end + @testset "divergent execution" begin + function kernel(x) + if x > 1 + RT.trace_printk(RT.@create_string("Greater")) + else + RT.trace_printk(RT.@create_string("Lesser")) + end + end + asm = String(bpffunction(kernel, Tuple{Int}; format=:asm)) + # TODO: Test that allocas sit in top of first block + end + end end @testset "libbpf" begin function kernel(x) @@ -118,6 +147,7 @@ if run_root_tests API.unload(kp) end end + @test_skip "uprobe" #= FIXME @testset "uprobe" begin up = UProbe(+, Tuple{Int,Int}) do regs @@ -186,4 +216,72 @@ if run_root_tests @test hmap[1] == 42 end end + @testset "helpers" begin + # XXX: The below helper kernels are marked as GPL for the purpose of + # testing that the helper works as expected, however they are still + # licensed according to the MIT license. If you actually use GPL-only + # helpers in your kernels, make sure you adhere to the GPL license! + @test_skip "probe_read" + @testset "ktime_get_ns" begin + kp = KProbe("ksys_write"; license="GPL") do x + mymap = RT.RTMap(;name="mymap", maptype=API.BPF_MAP_TYPE_ARRAY, keytype=UInt32, valuetype=UInt64) + mymap[1] = RT.ktime_get_ns() + 0 + end + API.load(kp) do + map = first(API.maps(kp.obj)) + hmap = Host.hostmap(map; K=UInt32, V=UInt32) + run(`sh -c "echo 123 >/dev/null"`) + old = hmap[1] + run(`sh -c "echo 123 >/dev/null"`) + @test hmap[1] > old + end + end + @testset "trace_printk" begin + kp = KProbe("ksys_write"; license="GPL") do x + y = 1234 + z = RT.@create_string("1234") + RT.trace_printk(RT.@create_string("%d==%s"), y, z) + 0 + end + API.load(kp) do + run(`sh -c "echo 123 >/dev/null"`) + run(`grep -q -m 1 '1234==1234' /sys/kernel/debug/tracing/trace_pipe`) + end + end + @testset "get_prandom_u32" begin + kp = KProbe("ksys_write"; license="GPL") do x + mymap = RT.RTMap(;name="mymap", maptype=API.BPF_MAP_TYPE_ARRAY, keytype=UInt32, valuetype=UInt32) + mymap[1] = RT.get_prandom_u32() + 0 + end + API.load(kp) do + map = first(API.maps(kp.obj)) + hmap = Host.hostmap(map; K=UInt32, V=UInt32) + run(`sh -c "echo 123 >/dev/null"`) + old = hmap[1] + run(`sh -c "echo 123 >/dev/null"`) + @test hmap[1] != old + end + end + @testset "get_smp_processor_id" begin + @eval const CPU_THREADS = Sys.CPU_THREADS # Sys.CPU_THREADS is not const + kp = KProbe("ksys_write"; license="GPL") do x + mymap = RT.RTMap(;name="mymap", maptype=API.BPF_MAP_TYPE_ARRAY, keytype=UInt32, valuetype=UInt32, maxentries=CPU_THREADS+1) + mymap[RT.get_smp_processor_id()+1] = 1 + 0 + end + API.load(kp) do + map = first(API.maps(kp.obj)) + hmap = Host.hostmap(map; K=UInt32, V=UInt32) + for i in 1:100 + run(`sh -c "echo 123 >/dev/null"`) + end + for idx in 1:Sys.CPU_THREADS + @test hmap[idx] == 1 + end + @test !haskey(hmap, Sys.CPU_THREADS+1) + end + end + end end