Skip to content

Commit c44c1d9

Browse files
viktormalikdanobi
authored andcommitted
Introduce builtin to get percpu kernel data
There exist "percpu" global variables in the kernel which contain a distinct value for each CPU. In BPF, access to these variables is done via the bpf_per_cpu_ptr and bpf_this_cpu_ptr helpers. Both accept a pointer to a percpu ksym and the former also accepts a CPU number. The ksym is an extern variable with a BTF entry matching the BTF of the corresponding kernel variable. Since we now use libbpf to do the loading, it is sufficient to emit a global variable declaration with a proper BTF and libbpf will take care of the rest. Introduce new bpftrace builtin percpu_kaddr to access the percpu data. The helper has two forms: percpu_kaddr("symbol_name") <- uses bpf_this_cpu_ptr percpu_kaddr("symbol_name", N) <- uses bpf_per_cpu_ptr where N is the CPU number. The former variant retrieves the value for the current CPU. A tricky part is that bpf_per_cpu_ptr may return NULL if the supplied CPU number is higher than the number of the CPUs. The BPF program should perform a NULL-check on the returned value, otherwise it is rejected by the verifier. In practice, this only happens if pointer arithmetics is used (i.e. a struct field is accessed). Since it is quite complex to detect a missing NULL-check in bpftrace, we instead let verifier do it and just display the potential verifier error in a nicer manner. The check if the global variable exists is done in semantic analyser to get better error highlighting. Therefore, for testing the new builtin in semantic analyser tests, we need to add a symbol (process_counts) into tests/data/data_source.c to get it into our mock BTF. The problem here is that pahole places only percpu variables into BTF (and none other) so the symbol must be in the ".data..percpu" section. To do that, we need to make data_source.o a relocatable file (using compiler's -c option), otherwise the linker would put the symbol back to ".data". This in turn breaks DWARF generation which seems to need a linked binary so we link data_source.o into data_source during the DWARF generation step.
1 parent fcd4d3b commit c44c1d9

19 files changed

+373
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ and this project adheres to
4848
- [#3547](https://github.com/bpftrace/bpftrace/pull/3547)
4949
- Add `symbol_source` config to source uprobe locations from either DWARF or the Symbol Table
5050
- [#3504](https://github.com/bpftrace/bpftrace/pull/3504/)
51+
- Introduce builtin to access percpu kernel data
52+
- [#3596](https://github.com/bpftrace/bpftrace/pull/3596/)
5153
#### Changed
5254
- Merge output into `stdout` when `-lv`
5355
- [#3383](https://github.com/bpftrace/bpftrace/pull/3383)

man/adoc/bpftrace.adoc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,10 @@ Tracing block I/O sizes > 0 bytes
13641364
| Return full path
13651365
| Sync
13661366

1367+
| <<functions-percpu-kaddr, `percpu_kaddr(const string name [, int cpu])`>>
1368+
| Resolve percpu kernel symbol name
1369+
| Sync
1370+
13671371
| <<functions-print, `print(...)`>>
13681372
| Print a non-map value with default formatting
13691373
| Async
@@ -1846,6 +1850,39 @@ the path will be clamped by `size` otherwise `BPFTRACE_MAX_STRLEN` is used.
18461850

18471851
This function can only be used by functions that are allowed to, these functions are contained in the `btf_allowlist_d_path` set in the kernel.
18481852

1853+
[#functions-percpu-kaddr]
1854+
=== percpu_kaddr
1855+
1856+
.variants
1857+
* `void *percpu_kaddr(const string name)`
1858+
* `void *percpu_kaddr(const string name, int cpu)`
1859+
1860+
*sync*
1861+
1862+
Get the address of the percpu kernel symbol `name` for CPU `cpu`. When `cpu` is
1863+
omitted, the current CPU is used.
1864+
1865+
----
1866+
interval:s:1 {
1867+
$proc_cnt = percpu_kaddr("process_counts");
1868+
printf("% processes are running on CPU %d\n", *$proc_cnt, cpu);
1869+
}
1870+
----
1871+
1872+
The second variant may return NULL if `cpu` is higher than the number of
1873+
available CPUs. Therefore, it is necessary to perform a NULL-check on the result
1874+
when accessing fields of the pointed structure, otherwise the BPF program will
1875+
be rejected.
1876+
1877+
----
1878+
interval:s:1 {
1879+
$runqueues = (struct rq *)percpu_kaddr("runqueues", 0);
1880+
if ($runqueues != 0) { // The check is mandatory here
1881+
print($runqueues->nr_running);
1882+
}
1883+
}
1884+
----
1885+
18491886
[#functions-print]
18501887
=== print
18511888

src/ast/dibuilderbpf.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,12 @@ DIType *DIBuilderBPF::GetType(const SizedType &stype, bool emit_codegen_types)
213213
{
214214
if (!emit_codegen_types && stype.IsRecordTy()) {
215215
std::string name = stype.GetName();
216-
if (name.find("struct ") == 0)
217-
name = name.substr(std::string("struct ").length());
218-
else if (name.find("union ") == 0)
219-
name = name.substr(std::string("union ").length());
216+
static constexpr std::string struct_prefix = "struct ";
217+
static constexpr std::string union_prefix = "union ";
218+
if (name.find(struct_prefix) == 0)
219+
name = name.substr(struct_prefix.length());
220+
else if (name.find(union_prefix) == 0)
221+
name = name.substr(union_prefix.length());
220222

221223
return createStructType(file,
222224
name,

src/ast/irbuilderbpf.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,6 +2141,39 @@ CallInst *IRBuilderBPF::CreateGetFuncIp(Value *ctx, const location &loc)
21412141
&loc);
21422142
}
21432143

2144+
CallInst *IRBuilderBPF::CreatePerCpuPtr(Value *var,
2145+
Value *cpu,
2146+
const location &loc)
2147+
{
2148+
// void *bpf_per_cpu_ptr(const void *percpu_ptr, u32 cpu)
2149+
// Return:
2150+
// A pointer pointing to the kernel percpu variable on
2151+
// cpu, or NULL, if cpu is invalid.
2152+
FunctionType *percpuptr_func_type = FunctionType::get(
2153+
GET_PTR_TY(), { GET_PTR_TY(), getInt64Ty() }, false);
2154+
return CreateHelperCall(libbpf::BPF_FUNC_per_cpu_ptr,
2155+
percpuptr_func_type,
2156+
{ var, cpu },
2157+
"per_cpu_ptr",
2158+
&loc);
2159+
}
2160+
2161+
CallInst *IRBuilderBPF::CreateThisCpuPtr(Value *var, const location &loc)
2162+
{
2163+
// void *bpf_per_cpu_ptr(const void *percpu_ptr)
2164+
// Return:
2165+
// A pointer pointing to the kernel percpu variable on
2166+
// this cpu. May never be NULL.
2167+
FunctionType *percpuptr_func_type = FunctionType::get(GET_PTR_TY(),
2168+
{ GET_PTR_TY() },
2169+
false);
2170+
return CreateHelperCall(libbpf::BPF_FUNC_this_cpu_ptr,
2171+
percpuptr_func_type,
2172+
{ var },
2173+
"this_cpu_ptr",
2174+
&loc);
2175+
}
2176+
21442177
void IRBuilderBPF::CreateGetCurrentComm(Value *ctx,
21452178
AllocaInst *buf,
21462179
size_t size,

src/ast/irbuilderbpf.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ class IRBuilderBPF : public IRBuilder<> {
161161
StackType stack_type,
162162
const location &loc);
163163
CallInst *CreateGetFuncIp(Value *ctx, const location &loc);
164+
CallInst *CreatePerCpuPtr(Value *var, Value *cpu, const location &loc);
165+
CallInst *CreateThisCpuPtr(Value *var, const location &loc);
164166
CallInst *CreateGetJoinMap(BasicBlock *failure_callback, const location &loc);
165167
CallInst *CreateGetStackScratchMap(StackType stack_type,
166168
BasicBlock *failure_callback,

src/ast/passes/codegen_llvm.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,18 @@ void CodegenLLVM::visit(Call &call)
927927
if (!addr)
928928
throw FatalUserException("Failed to resolve kernel symbol: " + name);
929929
expr_ = b_.getInt64(addr);
930+
} else if (call.func == "percpu_kaddr") {
931+
auto name = bpftrace_.get_string_literal(call.vargs.at(0));
932+
auto var = b_.CreatePointerCast(DeclareKernelVar(name), b_.GET_PTR_TY());
933+
Value *percpu_ptr;
934+
if (call.vargs.size() == 1) {
935+
percpu_ptr = b_.CreateThisCpuPtr(var, call.loc);
936+
} else {
937+
auto scoped_del = accept(call.vargs.at(1));
938+
Value *cpu = expr_;
939+
percpu_ptr = b_.CreatePerCpuPtr(var, cpu, call.loc);
940+
}
941+
expr_ = b_.CreatePtrToInt(percpu_ptr, b_.getInt64Ty());
930942
} else if (call.func == "uaddr") {
931943
auto name = bpftrace_.get_string_literal(call.vargs.at(0));
932944
struct symbol sym = {};
@@ -4697,4 +4709,34 @@ CallInst *CodegenLLVM::CreateKernelFuncCall(Kfunc kfunc,
46974709
return b_.createCall(func->getFunctionType(), func, args, name);
46984710
}
46994711

4712+
/// This should emit
4713+
///
4714+
/// declare !dbg !... extern ... @var_name(...) section ".ksyms"
4715+
///
4716+
/// with proper debug info entry.
4717+
///
4718+
/// The function type is retrieved from kernel BTF.
4719+
///
4720+
/// If the function declaration is already in the module, just return it.
4721+
///
4722+
GlobalVariable *CodegenLLVM::DeclareKernelVar(const std::string &var_name)
4723+
{
4724+
if (auto *sym = module_->getGlobalVariable(var_name))
4725+
return sym;
4726+
4727+
std::string err;
4728+
auto type = bpftrace_.btf_->get_var_type(var_name);
4729+
assert(!type.IsNoneTy()); // already checked in semantic analyser
4730+
4731+
auto var = llvm::dyn_cast<GlobalVariable>(
4732+
module_->getOrInsertGlobal(var_name, b_.GetType(type)));
4733+
var->setSection(".ksyms");
4734+
var->setLinkage(llvm::GlobalValue::ExternalLinkage);
4735+
4736+
auto var_debug = debug_.createGlobalVariable(var_name, type);
4737+
var->addDebugInfo(var_debug);
4738+
4739+
return var;
4740+
}
4741+
47004742
} // namespace bpftrace::ast

src/ast/passes/codegen_llvm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ class CodegenLLVM : public Visitor {
278278
ArrayRef<Value *> args,
279279
const Twine &name);
280280

281+
GlobalVariable *DeclareKernelVar(const std::string &name);
282+
281283
Node *root_ = nullptr;
282284

283285
BPFtrace &bpftrace_;

src/ast/passes/semantic_analyser.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,20 @@ void SemanticAnalyser::visit(Call &call)
11021102
}
11031103
call.type = CreateUInt64();
11041104
call.type.SetAS(AddrSpace::kernel);
1105+
} else if (call.func == "percpu_kaddr") {
1106+
if (check_varargs(call, 1, 2)) {
1107+
check_arg(call, Type::string, 0, true);
1108+
if (call.vargs.size() == 2)
1109+
check_arg(call, Type::integer, 1, false);
1110+
1111+
auto symbol = bpftrace_.get_string_literal(call.vargs.at(0));
1112+
if (bpftrace_.btf_->get_var_type(symbol).IsNoneTy()) {
1113+
LOG(ERROR, call.loc, err_)
1114+
<< "Could not resolve variable \"" << symbol << "\" from BTF";
1115+
}
1116+
}
1117+
call.type = CreateUInt64();
1118+
call.type.SetAS(AddrSpace::kernel);
11051119
} else if (call.func == "uaddr") {
11061120
auto probe = get_probe(call.loc, call.func);
11071121
if (probe == nullptr)

src/bpfbytecode.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,15 @@ void BpfBytecode::load_progs(const RequiredResources &resources,
220220
// failures when the verifier log is non-empty.
221221
std::string_view log(log_bufs[name].data());
222222
if (!log.empty()) {
223-
// This should be the only error that may occur here and does not imply
223+
// These should be the only errors that may occur here which do not imply
224224
// a bpftrace bug so throw immediately with a proper error message.
225225
maybe_throw_helper_verifier_error(log,
226226
"helper call is not allowed in probe",
227227
" not allowed in probe");
228+
maybe_throw_helper_verifier_error(
229+
log,
230+
"pointer arithmetic on ptr_or_null_ prohibited, null-check it first",
231+
": result needs to be null-checked before accessing fields");
228232

229233
std::stringstream errmsg;
230234
errmsg << "Error loading BPF program for " << name << ".";

src/btf.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,4 +875,17 @@ SizedType BTF::get_stype(const std::string &type_name)
875875
return CreateNone();
876876
}
877877

878+
SizedType BTF::get_var_type(const std::string &var_name)
879+
{
880+
auto var_id = find_id(var_name, BTF_KIND_VAR);
881+
if (!var_id.btf)
882+
return CreateNone();
883+
884+
const struct btf_type *t = btf__type_by_id(var_id.btf, var_id.id);
885+
if (!t)
886+
return CreateNone();
887+
888+
return get_stype(BTFId{ .btf = var_id.btf, .id = t->type });
889+
}
890+
878891
} // namespace bpftrace

0 commit comments

Comments
 (0)